委员长 发表于 2025-1-3 22:28:19

C++Primer学习笔记06-2.函数重载

### 函数重载

如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载(overloaded)函数

```cpp
#include<iostream>

void print(const char *cp) {}
void print(const int *beg, const int *end) {}
// 错误,和上面的 const int *beg, const int *end 本质上是一样的
void print(const int ia[], const int ib[]) {}
```

<span style="color:red">注意:main 函数不能重载。</span>

> <b>区分重载的正确性</b>

```cpp
void lookup(const int&); // 可以省略形参
bool lookup(const int&); // 错误,与上一个函数只有返回值不一样,不是重载
```

```cpp
void lookup(const int&); // 可以省略形参
void lookup(const int& a); // 错误,只是省略的形参,不是重载
```

```cpp
// 重载和 const 形参
void lookup(int a);
// 错误,一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来
void lookup(const int a);
```

> <b>const_cast 和重载</b>

假定有以下代码

```cpp
const string &shortcmp(const string &s1, const string &s2){
    return s1.size()<=s2.size()? s1:s2;
}
// 将普通引用转为 const 引用是安全的
string &shorter(string &s1, string &s2){
    auto &r = shortcmp(const_cast<const string&>(s1),
                     const_cast<const string&>(s2));
    // 原本就是可修改的,因此从 const 转为 非 const 也是安全的
    return const_cast<string&>(r);
}
```

> <b>重载匹配问题</b>

先匹配最合适的,没有在向上找。

> <b>默认参数</b>

```cpp
#include<iostream>

using std::cout;
using std::endl;

void f(double a, double b = 100) {
    cout << "double,double\t";
    cout << "a+b=" << a + b;
}

int main() {
    f(1);
    return 0;
}
```

### 函数匹配

查找最佳匹配的函数。

```cpp
#include<iostream>

using std::cout;
using std::endl;

void f(int a, int b) { cout << "int,int"; }

void f(double a, double b) { cout << "double,double"; }

int main() {
    // 因为二义性而拒绝调用请求,报错。
    f(1, 2.2);
    return 0;
}
```

其他的和 Java 类似。const 的话,重载的时候顶层 const 会被忽略。

### 函数指针

- 函数指针指向的是函数。指针函数是返回值为指针的函数,`类型名 *函数名(函数参数列表)`,其中 \* 的优先级低于 ()。所以 `int *pfun(int,int)` 等同于 `int *(pfun(int,int))`,声明了一个函数 pfun,函数形参为 int、int,返回值为整型指针。
- 数组指针指向的是数组。指针数组,指针元素的数组,数组里面存的元素是指针;

和其他指针一样,函数指针指向某种特定类型。函数的类型由它的返回类型和形参类型共同决定,与函数名无关。

```cpp
// pf就是一个指向函数的指针,其中该函数的参数是两个const string的引用
// 返回值是bool类型。
bool (*pf)(const string &, const string &);
// 如果不写括号,则pf是一个返回值为 bool 指针的函数
bool *pf(const string &, const string &);
```

> <b>使用函数指针</b>

函数指针是一种<b>特殊的指针,指向函数,函数返回一个类型</b>。

例如 `int (*add)(int,int)` 定义了一个<b>函数指针 add</b>,指向一个函数,函数形参是 int/int,返回值是 int。

例如 `int (*pfun)(int,int)`,意味着 pfun 是一个指针,指向一个函数,函数的返回值是 int。

```cpp
#include<iostream>

using std::cout;
using std::endl;
using std::string;

void lengthCompare() {
    cout << "compare" << endl;
}

int func2(int n) {
    return n + 1;
}

int main() {
    int (*pf)(int) = func2;
    void (*pf2)() = lengthCompare;
    cout << pf(2) << endl;
    pf2();
    return EXIT_SUCCESS;
}
// 3
// compare
```

调用函数的话也不需要解引用,可以直接调用

```cpp
int main() {
    void (*pf2)() = lengthCompare;
    void (*pf3)() = &lengthCompare;
    (*pf2)();
    pf3();
    return 0;
}
```

在指向不同函数类型的指针间不存在转换规则,可以指向参数类型匹配的任意函数。

```cpp
#include<iostream>

using std::cout;
using std::endl;
using std::string;

int func2(int n) {
    return n + 1;
}

int func3(int n) {
    return n * n;
}

int main() {
    int (*pf)(int) = func2;
    pf = func3;
    // 100
    cout << pf(10) << endl;
    return EXIT_SUCCESS;
}
```

> <b>重载函数的指针</b>

当我们使用重载函数时,上下文必须清晰地界定到底应该选用那个函数,通过指定参数类型来界定使用那个函数。

```cpp
#include<iostream>

using std::cout;
using std::endl;

void f(int *n) {
    cout << "point" << endl;
}

void f(int n) {
    cout << "int" << endl;
}

int main() {
    void (*pf)(int) = f;
    void (*pff)(int *) = f;
    pf(1);
    int n = 10;
    pff(&n);
    return EXIT_SUCCESS;
}
```

> <b>函数指针形参</b>

和数组类似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针。此时,形参看起来是函数类型,实际上却是当成指针使用。

```mermaid
graph LR
指针-->|指向|函数
```

```cpp
#include<iostream>

using std::cout;
using std::endl;
using std::string;

bool pf(const string &s1, const string &s2) {
    return s1.size() >= s2.size();
}

void useBigger(const string &s1,
               const string &s2,
               bool pf(const string &, const string &)) {
    cout << pf(s1, s2) << "" << endl;
}
// 等价声明
void useBigger(const string &s1,
               const string &s2,
               bool (*pf)(const string &, const string &)) {
    cout << pf(s1, s2) << "" << endl;
}

int main() {
    string s1 = "hello";
    string s2 = "world";
    useBigger(s1, s2, pf);
    return EXIT_SUCCESS;
}
```

也可以使用形式参数

```cpp
#include<iostream>

using std::cout;
using std::endl;
using std::string;

// pfT 和 pfT2 是函数类型
typedef bool pfT(const string &s1, const string &s2);
typedef decltype(pf) pfT2; // 等价得类型

// FuncP 和 FuncP2 是指向函数的指针
typedef bool(*FuncP)(const string &, const string&);
typedef decltype(pf) *FuncP2 // 等价的类型

bool pf(const string &s1, const string &s2) {
    return s1.size() >= s2.size();
}
void useBigger2(const string &s1,
                const string &s2,
                pfT pf) {
    cout << pf(s1, s2) << "" << endl;
}

int main() {
    string s1 = "hello";
    string s2 = "world";
    useBigger2(s1, s2, pf);
    return EXIT_SUCCESS;
}
```

> <b>返回指向函数的指针</b>

虽然不能返回一个函数,但是能返回指向函数类型的指针。然而,我们必须把返回类型写成指针形式,编译器不会自动地将函数返回类型当成对应的指针类型处理。

```cpp
using F = int(int*, int); // F 是函数类型,不是指针
using PF = int(*)(int*, int);; // PF 是指针类型

// f1是指向函数的指针
int (*f1(int))(int*, int);
auto f1(int)->int(*)(int*, int);
```

一个例子

```cpp
#include<iostream>

using std::cout;
using std::endl;
using std::string;

int func2(int n) {
    cout << "f2" << endl;
    return n + 1;
}

using FP = int(int n);

FP *f1(int n) {
    return func2;
}

int main() {
    auto f = f1;
    // f1 的返回值是一个指针,这个指针指向函数
    FP *(*f2)(int) = f1;
    // 先解引用拿到方法f1执行,然后f1的返回值是一个函数,再执行
    // 也可以不解引用,直接调用,一样的(前面说了)
    cout << (*f2)(19)(1) << endl;
    cout << f2(19)(1) << endl;
    return EXIT_SUCCESS;
}
```

auto 和 decltype 同样可以用于函数指针类型。

> <b>练习</b>

编写函数的声明,令其接受两个 int 形参并且返回类型也是 int;然后声明一个 vector 对象,令其元素是指向该函数的指针。

```cpp
#include<iostream>
#include<vector>

using std::cout;
using std::endl;
using std::string;
using std::vector;

using fun = int(int, int);

int f1(int a, int b) {
    return a + b;
}
// 产生函数的函数
fun *createF1() {
    return f1;
}

int main() {
    vector<fun *> fvec;
    for (int i = 0; i < 5; ++i) {
      fvec.push_back(createF1());
    }
    for (int i = 0; i < fvec.size(); ++i) {
      auto temp = fvec.at(i);
      cout << temp(i, i) << "\t";
    }
    return EXIT_SUCCESS;
}
```

### 头文件

假定我们自己编写了一个 Log 函数,很多 `.cpp` 文件都需要使用这个函数,这些文件是如何知道 Log 函数确实是存在的,只是定义在别处呢?=\=\>函数声明。函数声明放在哪里呢?=\=\>头文件。

我们使用 #include 预处理器将这些头文件复制粘贴带其他文件,告诉它们,嘿,有 Log 函数,你放心用,链接的时候会找到对应的函数体的。

<b>解决重复引入头文件带来的问题</b>

#include 预处理器会将内容复制粘贴到其他文件中,如果多次引入头文件(有时候文件之间的都包含某个头文件,然后又相互引用,会导致多次引入重复的头文件)这会带来一些意想不到的问题。

```cpp
// 头文件 Log.h
void Logger(const char* message);

struct data {
        char* name;
        int age;
} mydata;

// Logger 的定义省略
```

```cpp
// Math.cpp 多次引入 Log.h, 编译时会发生错误, data 被重复定义。
#include<iostream>
#include"Log.h"
#include"Log.h"

static int Multiply(int a, int b) {
        //int result = a * b;
        //return result;
        Logger("Print Log");
        return a * b;
}

int main() {
        Multiply(1, 2);
        std::cout << sizeof(bool)<< std::endl;
}
```

为了避免多次引入头文件,可以在头文件的开头加上 `#pragma once` 解决这类问题。

```cpp
#pragma once
```

除了 `pragma once` 外还有其他方式可以解决这类问题 =\=\> `#ifndef`

```shell
#ifndef _LOG_H                // 如果没有定义 _LOG_H 就在编译中包含以下代码。 如果被定义了,则 #define 和 #endif 中的都不会被包含。
#define _LOG_H                // 定义 _LOG_H

void Log(const char* msg);

#endif                                // 结束
```

#pragma once 被很多主流的编译器所支持。

<b>引入头文件时 `<>` 和 `""` 的区别</b>

- `<>` 搜索 include 路径文件夹下的文件,即只用于编译器 `include` 路径。
- `""` 包含相对于当前文件的文件,`../xx.h, ../../../oo.h` 都可,`""`可以做一切,但是通常只用它在相对路径。

### 调试技巧

VS 在调试过程中鼠标右击『转到反汇编』可以逐汇编代码调试。如果我们在源代码中无法找到错误原因,此时只能求助于调试 CPU 指令。
页: [1]
查看完整版本: C++Primer学习笔记06-2.函数重载