一、函数重载
如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数。
1、定义重载函数
对于重载的函数来说,它们应该在形参数量或形参类型上有所不同。
2、重载和const形参
一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来:
1 int func(int x); 2 int func(const int x); // 重复声明了func(int x) 3 4 int func2(int *); 5 int func2(int *const); // 重复声明了func2(int *)
如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const是底层的:
1 int func(int &); 2 int func(const int &); // 新函数,作用于常量引用 3 4 int func2(int *); 5 int func2(const int *); // 新函数,作用于指向常量的指针
3、const_cast和重载
const_cast在重载函数的情景中最有用。
1 #include <iostream> 2 #include <string> 3 4 const std::string &shortString(const std::string &s1, const std::string &s2) 5 { 6 std::cout << "const" << std::endl; 7 return s1.size() < s2.size() ? s1 : s2; 8 } 9 std::string shortString(std::string &s1, std::string s2) 10 { 11 std::cout << "not const" << std::endl; 12 auto &r = shortString(const_cast<const std::string &>(s1), const_cast<const std::string &>(s2)); 13 return const_cast<std::string &>(r); 14 } 15 int main() 16 { 17 const std::string s1 = "abc", s2 = "ABC"; 18 std::cout << shortString(s1, s2) << std::endl; 19 std::cout << "-----------------------" << std::endl; 20 std::string s3 = "abc", s4 = "ABC"; 21 std::cout << shortString(s3, s4) << std::endl; 22 return 0; 23 }
4、调用重载的函数
定义了一组重载函数后,我们需要以合理的实参调用它们。函数匹配是指一个过程,在这个过程中我们把函数调用与一组重载函数中的某一个关联起来,函数匹配也叫重载确定。编译器首先将调用的实参与重载集合中每一个函数的形参进行比较,然后根据比较的结果决定到底调用哪个函数。
当个调用重载函数时有三种可能的结果:
a、编译器找到一个与实参最佳匹配的函数,并生成调用该函数的代码。
b、找不到任何一个函数与调用的实参匹配,此时编译器发出无匹配的错误信息。
c、有多于一个函数可以匹配,但是每一个都不是明显的最佳选择。此时也将发生错误信息,称为二义性调用。
5、重载与作用域
重载对作用域的一般性质并没有什么改变:如果我们在内层作用域中声明了一个函数名字,它将隐藏外层作用域中声明的同名实体。
二、特殊用途语言特性
1、默认实参
我们可以为一个或多个形参定义默认值,不过需要注意的是,一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值。
在给定的作用域中一个形参只能被赋予一次默认实参。换句话说,函数的后续声明只能为之前那些没有默认值的形参添加默认值,而且该形参右侧的所有形参必须都有默认值。
局部变量不能作为默认实参。除此之外,只要表达式的类型能转换成形参所需的类型,该表达式就能作为默认实参。用作默认实参的名字在函数声明所在的作用域解析,而这些名字的求值过程发生在函数调用时。
2、内联函数和constexpr函数
在大多数机器上,一次函数调用其实包含着一系列工作:调用前要先保存寄存器,并在返回时恢复;可能需要拷贝实参;程序转向一个新的位置继续执行。
1)内联函数可避免函数调用的开销
声明内联函数只要在函数的返回类型前面加上关键字inline就行了。
2)constexpr函数
constexpr函数是指能用于常量表达式的函数。定义constexpr函数的方法与其他函数类似,不过要遵循几项规定:函数的返回类型及所有形参的类型都是字面值类型,而且函数体中必须有且只有一条return语句。
1 #include <iostream> 2 #include <string> 3 4 constexpr int func() { 5 return 1024; 6 } 7 int main() 8 { 9 constexpr int x = func(); 10 return 0; 11 }
执行初始化任务时,编译器把对constexpr函数的调用替换成其结果值。为了能在编译过程中随时展开,constexpr函数被隐式地指定为内联函数。
constexpr函数体内也可以包含其他语句,只要这些语句在运行时不执行任何操作就行。
constexpr函数不一定返回常量表达式。当把constexpr函数用在需要常量表达式的上下文中时,由编译器负责检查函数的结果是否符合要求。如果结果恰好不是常量表达式。编译器将发出错误信息。
3、调试帮助
1、assert预处理宏
assert宏定义在cassert头文件中。assert是一种预处理宏。所谓预处理宏其实是一个预处理变量,它的行为有点类似于内联函数。assert宏使用一个表达式作为它的条件:
assert(expr);
首先对expr求值,如果表达式为假(即为0),assert输出信息并终止程序的执行。如果表达式为真(即非0),assert什么也不做。
2、NDEBUG预处理变量
assert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NDEBUG,则assert什么也不做。默认状态下没有定义NDEBUG,此时assert将执行运行时检查。
预处理器定义了几个对调试很有用的名字,如下表所示:
名字 | 说明 |
__FUNCTION__ | 存放函数名的字符串字面值 |
__FILE__ | 存放文件名的字符串字面值 |
__LINE__ | 存放当前行号的整型字面值 |
__TIME__ | 存放文件编译时间的字符串字面值 |
__DATE__ | 存放文件编译日期的字符串字面值 |
三、函数匹配
1、实参类型转换
为了确定最佳匹配,编译器将实参类型到形参类型的转换划分成几个等级,具体排序如下所示:
1)精确匹配,包括以下情况:
a、实参类型和形参类型相同。
b、实参从数组类型或函数类型转换成对应的指针类型。
c、向实参添加顶层const或者从实参中删除顶层const。
2)通过const转换实现的匹配。
3)通过类型提升实现的匹配。
4)通过算术类型转换(所有算术类型转换的级别都一样)或指针转换实现的匹配。
5)通过类类型转换实现的匹配。
四、函数指针
函数指针指向的是函数而非对象。和其他指针一样,函数指针指向某种特定类型。函数的类型由它的返回类型和形参类型共同决定,与函数名无关。
1 bool lengthCompare(const std::string &s1, const std::string &s2) 2 { 3 return s1.size() > s2.size(); 4 }
该函数的类型是bool(const std::string &, const std::string &)。要想声明一个可以指向该函数的指针,只需要用指针替换函数名即可。
1 //pf指向一个函数,该函数的参数是两个const string的引用,返回值是bool类型 2 bool (*pf)(const std::string &, const std::string &); // 未初始化,*pf两端的括号必不可少
1、使用函数指针
1 // 当我们把函数名作为一个值使用时,该函数自动地转换成指针 2 pf = lengthCompare; 3 pf = &lengthCompare; // 等价的赋值语句:取地址符是可选的 4 // 此外,我们还能直接使用指向函数的指针调用该函数,无须提前解引用指针 5 bool b1 = pf("hello", "world"); 6 bool b2 = (*pf)("hello", "world"); 7 bool b3 = lengthCompare("hello", "world"); // 三条调用语句等价
在指向不同函数类型的指针间不存在转换规则。但是,我们可以为函数指针赋一个nullptr或者值为0的整型表达式,表示该指针没有指向任何一个函数。
2、函数指针形参
和数组类似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针。此时,形参看起来是函数类型,实际上却是当成指针使用。
1 // 参数是函数类型,它会自动地转换成指向函数的指针 2 void use(bool pf(const std::string &, const std::string &)); 3 // 等价的声明:显示地将形参定义成指向函数的指针 4 void use(bool (*pf)(const std::string &, const std::string &));
可以直接把函数作为实参使用,此时它会自动转换成指针。
use(lengthCompare);
类型别名可以简化使用了函数指针的代码:
1 // Func和Func2是函数类型 2 typedef bool Func(const std::string &, const std::string &); 3 typedef decltype(lengthCompare) Func2; // 等价的类型 4 // FuncP和FuncP2是指向函数的指针 5 typedef bool(*FuncP)(const std::string &, const std::string &); 6 typedef decltype(lengthCompare) *FuncP2; // 等价的类型
3、返回指向函数的指针
和数组类似,虽然不能返回一个函数,但是能返回指向函数类型的指针。然而,必须显示地将返回类型写成指针形式,编译器不会自动地将函数返回类型当成对应的指针类型处理。
4、将auto和decltype用于函数指针类型
当将decltype作用于某个函数时,它返回函数类型而非指针类型。因此,我们必须显示地加上*以表明我们需要返回指针。