C++函数高阶

一.函数重载

在C++中,同一作用域下,同一个函数名是可以定义多次的,前提是形参列表不同,这种名字相同但形参列表不同的函数叫做“重载函数”

1.定义重载函数

#include"iostream"
using namespace std;

// 函数重载
// 使用指针和长度作为形参
void printArray(const int* arr, int size) {
    for (int i = 0; i < size; i++) {
        cout << arr[i] << "\t";
    }
    cout << endl;
}

// 使用数组引用作为形参
void printArray(const int(&arr)[6]) {
    for (int num : arr) {
        cout << num << "\t";
    }
    cout << endl;
}

int main(){
    int arr[6] = {1, 2, 3, 4, 5, 6,};
    printArray(arr, 6);
    printArray(arr);
};

这里需要注意的是:

①:重载的函数应该在形参的数量或者类型上有所不同

②:形参的名称在类型中可以省略,所以只有形参名不同的函数是一样的

③:调用函数时,编译器会根据传递的实参个数和类型自动推断使用哪个函数

④:主函数不能重载

二.有const形参时的重载

当形参有const修饰时,要区分它对于实参的要求到底是什么,是否要进行值的拷贝,如果是传值参数,传入实参时会发生值的拷贝,那么实参是变量还是常量其实是没有区别的

void fun(int x);
void fun(const int x); // int常量做形参,和不加const等价
void function(int* p);
void function(int* const p); // 指针常量做形参,也跟不加const等价
// 也就是说,这里的两对函数其实是冲突的,会在编译器里报错

这种情况下,const不会影响传入函数的实参类型,所以跟不加const的定义是一样的,这叫做“顶层const”,这时两个函数相同,无法进行函数重载

另一种情况则不同,那就是传引用参数,这时如果有const修饰,就成了“常量的引用”,对于一个常量,只能用常量引用来绑定,而不能使用普通引用。

类似地,对于一个常量的地址,只能由“指向常量的指针”来指向它,而不能用普通指针

void fun(int &x);
void fun(const int &x); // 形参类型是常量引用,这是一个新函数
void function(int* p);
void function(const int* p); // 形参类型是指向常量的指针,这是一个新函数

这种情况下,const限制了间接访问的数据对象是常量,这也叫做“底层const”,当实参是常量时,不能对不带const的引用进行初始化,所以只能调用常量引用做形参的函数,而如果实参是变量,就会优先匹配不带const的普通引用,这就实现了函数重载

也就是说,如果你想要实现函数的重载,就必须要做到在相同的函数名之后跟上能让编译器识别出来的不同的形参,否则编译器根本识别不出参数间的差别,会直接报错

三.函数匹配

如果传入的实参跟形参类型不同,只要能通过隐式类型转换变成我们所需要的类型,函数同样可以正确调用,那假如有好几个不同的重载函数,它们的形参类型可以进行自动转换,这时传入实参应该调用哪个函数呢?

#include"iostream"
using namespace std;

void fun();
void fun(int x);
void fun(int x, int y);
void fun(double x, double y = 10.0);

int main(){
    // 此时这样调用函数,会调用哪个函数呢?
    fun(3.14);
};

像这样,确定到底调用哪个函数的过程就叫做“函数匹配”

1.候选函数

函数匹配的第一步就是确定“候选函数”,也就是先找到对应的重载函数集,候选函数有两个要求:

①:与调用的函数同名

②:函数的声明在函数的调用点是可见的

2.可行函数

在确定了候选函数之后就需要从候选函数中选出跟传入的实参匹配的函数,这些函数叫做“可行函数”,可行函数也有两个要求:

①:形参个数与调用传入的实参数量相等

②:每个实参的类型与对应形参的类型相同,或者可以转换成形参的类型

在上面的例子中,传入的实参只有一个,是一个double类型的字面值常量,所以可以排除fun()和fun(int, int),而剩下的fun(int)和fun(double, double)都是匹配的,所以有着两个可行函数

3.寻找最佳匹配

最后就是要在可行函数中选择最佳匹配,简单来说,实参类型与形参类型越接近,他们就匹配地越好,所以能不进行转换就实际匹配的,要优于需要转换的

在上面的例子中,fun(int)必须要将double类型的实参转换成int类型,而fun(double, double)不需要,所以后者是最佳匹配,最终调用的就是它,第二个参数会由默认实参10.0来填补

4.多参数的最佳匹配

如果实参的数量不止一个,那么就需要逐个比较每个参数,同样,类型能够精确匹配的要由于需要转换的,这时寻找最佳匹配的原则如下:

①:如果可行函数的所有形参都能精确匹配实参,那么它就是最佳匹配

②:如果没有全部精确匹配,那么当一个可行函数所有参数的匹配都不比别的可行函数差,并且至少有一个参数更优,那它就是最佳匹配

5.二叉性调用

如果检查所有实参之后,有多个可行函数不分优劣,无法找到一个最佳匹配,那么编译器会报错,这被称为“二叉性调用”

四.重载与作用域

重载是否生效,跟作用域是有关系的,如果是在内层、外层作用域分别声明了同名的函数,那么内层作用域中的函数会覆盖外层的同名实体,将他隐藏起来

不同的作用域中无法重载函数名

#include"iostream"
using namespace std;

void fun(int x);
void fun(double x);
void fun(string s);

int main(){
    // 在这里做了函数声明之后,就会将上方的三个重载函数全部覆盖掉,
    // 也就是说现在的fun函数只能传递int类型的数据了,传递无法强转为int类型的数据的话就会报错
    void fun(int x);
};

五.函数指针

这是一类特殊的指针,指向的不是数据对象而是函数

1.声明函数指针

函数指针的本质还是指针,它的类型和所指向的对象类型有关,现在指向的是函数,函数的类型是由它的返回类型和形参类型共同确定的,跟函数名、形参名都没有关系

就如下方一般定义一个函数:

void fun(int x, int y);

这个函数的类型就是void(int, int)

如果要声明一个指向函数的指针,只要把原先函数名的位置填上指针就可以了

#include"iostream"
using namespace std;

// 函数指针的定义
void fun(int x, int y);

int main(){
    void(*p) (int, int) = nullptr;
};

2.使用函数指针

当一个函数名后面跟调用操作符(也就是小括号),表示函数调用,而单独使用函数名作为一个值时,函数会自动转换成指针,这一点跟数组名类似

所以我们可以直接使用函数名给函数指针赋值

p = fun; // 直接将函数名作为指针赋给p
p = &fun; // 取地址符是可选项,就作用来说和上面没有区别

复制之后就可以通过函数指针调用函数了,直接对函数指针名做解引用就可以得到函数,同样,这里的解引用符也是可选项,不适用解引用符同样可以直接表示函数

cout << p(int x, int y) << endl;
cout << *p(int x, int y) << endl; // 这两者从作用上来说和fun(int x, int y)没有任何区别

也就是说,函数指针完全可以当作函数来使用,同时在我们对函数指针赋值时,函数的类型必须要精确匹配,当然,作为指针,函数指针也可以先赋值nullptr

3.函数指针作为形参

有了指向函数的指针,就给函数带来了更加丰富灵活的用法,比如我们可以将函数指针作为形参定义在另一个函数中,也就是说,我们可以定一个函数,它以另一个函数类型作为形参,同时函数本身不能作为形参,不过函数指针填补了这个空缺,在这一点上,函数跟数组非常类似

// 在这个函数中,传入了另一个函数作为这个函数的参数
void fun(int x, int y, void(*p)(int m, int n));

那么你有没有觉得函数类型和函数指针类型的定义太过复杂了呢,因此我们有必要使用typedef做一个类型别名的声明

// 类型别名
typedef void fun(int, int); // 函数类型
typedef void (*p)(int, int); // 函数指针类型

也就是说,在你定义了类型别名之后,你就可以这样使用这两个函数指针了:

// 类型别名
typedef void fun(int, int); // 函数类型
typedef void (*p)(int, int); // 函数指针类型

void function(int, int, fun); // 调用了上方函数类型的类型别名
void function(int, int, *p); // 调用了上方函数指针类型的类型别名

在C++11中还提供了一个更加方便易懂的函数decltype直接获取类型

// 定义一个示例函数
void fun(int x, int y);

typedef decltype(fun) function; // 为fun函数起一个function别名
typedef decltype(fun) *p; // 函数指针类型

4.函数指针作为返回值

函数不能直接返回另一个函数,但是可以返回函数指针,所以可以将函数指针作为另一个函数的返回值

需要注意的是,这时函数的返回类型必须是函数指针,而不能是函数类型

// 函数指针作为返回值
p fun(int, int);
*p fun(int, int);
// function fun(int, int); // 像这样的写法是错误的,因为function是函数类型的别名,我们在使用函数指针作为返回值时不能直接返回函数

尾置返回类型则更加便捷:

// 尾置返回类型
auto fun(int, int) -> p; 
// 这里的auto是自动推断类型,也就是说会根据之后的函数指针的类型自动适应
// 注意->指向的一定要是一个函数指针

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值