C++ primer 5th .Chapter 6 函数(二) 读书笔记
6.4 函数重载
如果同一作用域内的几个函数名字相同但是形参列表不同,我们称之为重载函数。在调用时,编译器会根据传递的实参类型腿短想要的是哪个函数。
Tips:main函数无法重载
判断两个形参类型是否相异
int look(const int &);
int look(const int &num);
第二个给形参取了名字,但是不影响形参列表的内容。
重载和const形参
顶层const不影响传入函数的对象,一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。
int look(int);
int look(const int);//重复声明了int look(int)
int look(int*);
int look(int* const);//重复声明了int look(int*)
对于接受引用或指针的函数来说,对象是常量还是非常量对应的形参不同,此时const是底层的:
int look(int &); //函数作用于int对象的引用
int look(const int&);//新函数,作用于常量引用
int look(int*);//新函数作用于指向int的指针
int look(const int*);//作用于指向const int的指针
const_cast和重载
const string &shorterString(const &s1, const &s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}
这个函数的参数和返回值都是const string的引用。我们可以对两个非常量的实参调用这个函数但是返回值依然是const string引用。
可以使用const_cast让这个函数的实参不是常量的时候,得到的结果是一个普通的引用。
string &shorterString(string &s1, string &s2)
{
auto &r = shorterString(const_cast<const string&>(s1),
const_cast<const string&>(s2));
return const_cast<string&>(r)
}
这个函数首先将实参转换为对const的引用,然后调用了shorterString函数的const版本。const版本返回对const string的引用,这个引用事实上绑定在了某个初始的非常量实参上,所以可以安全的转回string&。
调用重载的函数
函数匹配是一个过程,在这个过程中我们把函数调用与一组重载函数中的某一个关联起来,函数匹配也叫作重载确定。
有三种情况:1)最佳匹配 。2)无匹配。 3)二义性调用。
6.4.1 重载与作用域
如果我们在内层的作用域中声明名字,他将隐藏外层作用域中的同名实体。在不同的作用域中无法重载函数名。
Tips:在C++语言中,名字查找发生在类型检查之前。
6.5 特殊用途语言特性
6.5.1 默认实参
一旦某个形参被赋予了默认值后,他后面的所有形参都必须有默认值。
//声明时设定默认值
string screen(size_t ht = 24, size_t wid = 80, char backgrnd = ' ' );
//在调用时只能省略尾部的实参
window = screen(, , '?'); //错误
window = screen('?'); //调用screen('?', 80, ' ');
Tips:尽量让不怎么使用默认值的形参出现在前面,而让那些经常使用默认值的形参出现在后面。
默认实参声明
string screen(size_t, size_t, char = ' ');
string screen(size_t, size_t, char = '*');//错误:重复声明
string screen(size_t = 24, size_t = 80, char );//正确:添加默认实参
默认实参初始值
局部变量不能作为默认实参。此外,只要表达式的类型能够转换成形参需要的类型,该表达式就能作为默认实参。
用作默认实参的名字在函数声明的作用域内解析,而这些名字的求值过程发生在函数调用时;
sz wd = 80;
char def = ' ' ;
sz ht();
string screen(size_t = ht(), size_t = wd, char = def);
void f2()
{
def = '*'; //改变默认实参的值
sz wd = 100; //隐藏了外层定义的wd,但是没有改变默认值
window = screen(); //调用了screen(ht(), 80, ' ');
}
6.5.2 内联函数和constexpr函数
内联函数可以避免函数调用的开销,但是内联只是向编译器发送的一个请求,编译器可以选择忽略这个请求(如果编译器认为这个函数比较复杂)。
constexpr函数是指能用于常量表达式的函数。定义该函数的方法和其他函数类似,但是函数的返回类型以及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return语句。
constexpr int new_sz() {return 42;}
constexpr int foo = new_sz(); //正确:foo是一个常量表达式
constexpr函数被隐式的指定为内联函数。
Tips:constexpr函数不一定返回常量表达式。内联函数和constexpr函数通常定义在头文件中。
6.6函数匹配
函数匹配的第一步:是选定本次调用对应的重载函数集,集合中的函数称为候选函数。候选函数需要与被调用的函数同名,声明在调用点可见。
第二步考察本次调用提供的实参,选出能被这组实参调用的函数,称为可行函数。可行函数需要参数个数匹配,参数类型匹配,或者类型转换后能匹配。
第三步选取最佳匹配。
Tips:调用重载函数时应该尽量避免强制类型转换。
6.7 函数指针
函数指针指向的是函数而非对象,和其他指针一样,函数指针指向某种特定类型,函数的类型由他的返回类型和形参共同决定,与函数名无关。
bool lengthCompare(const string&, const string&);
该函数的类型是bool(const string&, const string&)。想要声明一个可以指向该函数的指针,只需要用指针替换函数名单即可:
//fp指向一个函数
bool (*fp)(const string&, const string&);//未初始化的函数指针
如果漏掉了*fp两端的括号
//声明一个名为fp的函数,该函数返回bool*
bool *fp(const string&, const string&);
使用函数指针
当把函数名作为一个值使用时,该函数自动转化为指针。
fp = lengthCompare;//fp指向lengthCompare函数
fp = &lengthCompare;//等价的赋值语句,取地址符是可选的
还可以用指向函数的指针来调用该函数,无需提前解引用指针:
bool b1 = fp("hello","good");//调用lengthCompare函数
bool b2 = (*fp)("hello","good");//一个等价调用
bool b1 = lengthCompare("hello","good");//另一个等价调用
在指向不同函数类型的指针之间不存在转换规则,但是可以为指针赋一个nullptr或者值为0的整型常量表达式,表示该指针没有指向任何函数。
重载函数的指针
使用重载函数指针时,编译器根据指针类型来选择哪个函数,指针类型必须与重载函数精确匹配。
函数指针形参
和数组类似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针。
//第三个形参是函数类型,他会自动转换成指向函数的指针
void useBigger(const string &s1, const string &s2,
bool fp(const string&, const string&));
//等价声明:显示的将形参定义成指向函数的指针
void useBigger(const string &s1, const string &s2,
bool (*fp)(const string&, const string&));
返回指向函数的指针
在返回指向函数的指针时必须把返回类型写成指针形式,编译器不会自动将函数返回类型当成对应的指针类型处理。
想要声明一个返回函数指针的函数,最简单的办法是使用类型别名:
using F = int(int*, int);//F是函数类型
using PF = int(*)(int*, int); //PF是指针类型
PF f1(int);//正确:PF是指向函数的指针,f1返回指向函数的指针
F f1(int); //错误:F是函数类型,f1不能返回函数
F *f1(int);//正确:显示的指定返回类型是指向函数的指针
int (*f1(int))(int*, int);//直接声明f1
尾置类型写法: auto f1(int) -> int(*)(int*, int);
也可以使用auto和decltype:
size_t sumLength(const string&, const string&);
//根据形参的取值,getFcn函数返回指向sumLength的指针
decltype(sumLength) *getFcn(const string&);
使用decltype需要注意,他返回的是函数类型而不是指针,所以需要显示的加上*号。