C++ Primer第六章⑥
C++ Primer
第六章 函数
函数匹配
在大多数情况下,我们都能很容易地找出该选用哪个重载函数(虽然这是编译器的工作,但是是我们来决定调用哪个函数),例如:
void f();
void f(int);
void f(double, double = 3.14);
void f(int);
f(5.6); //调用的是最后一个函数
这是怎么判断的呢?书上说了很多,我觉得就一句话,尽量不损失信息,调用最后一个完全不损失信息,所以用它。如果还有一个函数是void f(double)编译就会报错,因为两个一样,调用有二义性。 含有多个形参的函数匹配也是一样,只要你在不损失信息的情况下挑选的那个函数就是被调用的,但如果有两个以上匹配在调用的时候编译就会报错了,例如:
void f(int, double);
void f(double, int);
int main()
{
f(1,1); //调用有二义性
return 0;
}
实参类型转换
为了确定最佳匹配,编译器将实参类型到形参类型的转换划分了五个等级,具体如下,不要慌,有些我们都碰到过了:
1:精确匹配:
- 实参类型和形参类型相同
- 实参从数组类型或函数类型转换为对应的指针类型,数组类型转换为指针我们之前就学过,函数类型转换为指针我接下来也会讲,不要急
-
向实参添加顶层const或者从实参中删除const,因为const可以赋值给非const
2:通过const转换实现的匹配
int i = 0; //假定为实参 const int &j = i;; //非常量转换为常量引用 //等价于下面的函数调用过程 void f(const int &j){} int i = 0; f(i);
3:通过类型提升实现的匹配
void f(double i){} int j = 0; f(j);
4:通过算术类型转换或指针转换
char a = 'a'; void f(int a){} f(a); //char->int //指针转换主要指void指针可以和任意类型的指针互相转换
5:通过类类型转换实现的匹配(以后介绍)
介绍了这么多,连我自己都烦了,其实大家多看些例子应该能很容易的去识别实参类型转换,如果有完全一样的那最好,如果要转换的话,说白了就是你这个实参这么传过去会不会有问题,跟变量赋值差不多的。 我们来看个例子:
void f(int &); void f(const int &); int a = 1; const int b = 0; f(a); //调用f(int &) f(b); //调用f(const int &) //其实f(a)也可以调用const,但是人家有完全匹配的啊
函数指针
函数指针指向的是函数,不是对象哦。和其他指针一样,函数指针指向某种特定类型,函数的类型由它的返回类型和形参类型决定,与函数名无关。 来看一个函数作为示例:
//比较两个string对象的长度 bool lengthCompare(const string &, const string &);
根据函数类型的定义,该函数的类型是bool(const string &, const string &)。要想声明一个可以指向该函数的指针,只需用指针替换函数名即可:
//pf指向一个函数,该函数的形参是两个const string的引用,返回类型是bool bool (*pf)(const string &, const string &); //没有初始化的指针
直接看这个pf,怎么看出它是什么类型呢: 1. pf前面有个,表示它是个指针 2. 右侧是形参列表,表示pf指向的是函数 3. 再看左边,发现函数的返回类型是bool 综上所述:pf是一个指向函数的指针,这个函数的参数是两个const string的引用,返回类型是bool ++pf两边的括号不能少,如果少了的话,bool pf(const string &, const string &);就是一个返回值为bool 的函数++
使用函数指针
当我们把函数名作为一个值使用时,该函数自动转换为指针(是不是和数组很相似啊):
pf = lengthCompare; //pf指向名为lengthCompare的函数 pf = &lengthCompare; //等价的
我们可以通过指针来访问函数,也就是说,我们可以借助pf来调用lengthCompare函数:
bool b1 = (*pf)("hello", "Jay"); //普通青年这么写 bool b2 = pf("hello", "Jay"); //二比青年这么写,也对 bool b3 = lengthCompare("hello", "Jay"); //这么写最传统
指向不同函数类型的指针间不存在转换规则~ 不过,我们可以为任意类型的函数指针赋值一个nullptr或0来表示该指针没有指向任何一个函数:
pf = 0;
重载函数的指针
老规矩,编译器来决定调用哪个,但是因为函数指针不存在转换规则,所以,必须要有一个精确匹配的函数
void f(int *); void f(int); void (*pf1)(int) = f; //pf指向void f(int)
函数指针作为形参
这个高级了,看起来非常六:
void f(bool (*pf)(int));
怎么样,看起来是不是很吊,感觉像写错了,其实,这里声明了一个f函数,后面那一堆就是f函数的形参,只不过形参比较复杂,是一个函数指针,这个指针指向的函数类型是形参为一个int返回类型是bool。 装逼的人也可以这么写,和上面是等价的:
void f(bool pf(int)); //pf会自动转换为指向函数的指针
我们也可以直接把函数作为实参使用,因为我们在使用函数名的时候,编译器会帮我们转换为指针:
void useBigger(const string &, const string &, bool (*pf)(const string &, const string &)); //先声明函数 useBigger(s1, s2, lengthCompare); //使用函数,用函数名调用
是不是觉得声明函数很长很烦呢,我们可以用神器typedef:
typedef decltype(lengthCompare) *Func; //先用decltype把函数lengthCompare的类型返回,然后让Func与这个类型的指针等价, //也就是说Func指向和lengthCompare一样的函数类型 //现在我们就可以简单声明了 void useBigger(const string &, const string &, Func);
函数指针作为返回值
说完了函数指针作为形参,现在来说作为返回值。和数组类似,我们不能返回函数,但是可以返回函数指针。这里我们就要老老实实把返回类型写成指针,不能用前面的装逼写法了(
其实一直不装逼比较好):using F = int(int*, int); //F是函数类型 using PF = int(*)(int*, int); //PF才是函数指针类型
为了方便,我直接使用了类型别名,应该好理解的。 根据返回类型来判断几个表达式:
PF f1(int); //对 F f2(int); //错 F *f3(int); //对
好判断把,只要记住只能返回函数指针不能返回函数就好,为什么我们要用类型别名呢?来,看一个不用类型别名的:
int (*f1(int)(int*, int)); //等价的f1
1. f1有形参列表(int),f1是个函数 2. f1前面有,返回的是指针 3. 指针类型本身也包含形参列表(int, int),所以指针指向函数 4. 该函数的返回类型是int,形参列表为(int*, int) 所以啊,还是用类型别名吧,程序员何苦为难程序员 当然,返回类型复杂的时候我们可以使用尾置返回类型:
auto f1(int) -> int (*)(int*, int);
个人推荐尾置和类型别名。