介绍
C++Primer第五版第六章学习笔记
一、函数基础
形参和实参是对应的,但是当有多个形参时,求值顺序没有规定,因此最好是没有关联的实参。
函数体内部的对象的生命周期取决于对象类型,自动对象的生命周期从变量声明开始,函数末尾结束。而静态局部变量(static)从声明开始,到程序结束才结束,函数第一次被调用时初始化,若未初始化时默认初始化为0,第二次调用时仍使用第一次调用后的值。
二、参数传递
不同于C中通过指针传递实参的地址,C++通过引用,将实参引用传递,使用函数时直接将变量作为参数即可,但仍可以用指针间接改变指针所指对象的值,使用函数时将变量地址作为参数。
需要注意的是:即使不需要改变实参的值,可以考虑使用常量引用,目的是避免拷贝操作,节省空间。
关于const形参的一些总结:
可以将普通变量赋值给指向常量的指针或者引用,同理,可以将普通变量作为实参调用形参为顶层const的函数,因为调用时会忽略顶层const。因此,在重载函数时,形参的顶层const会被忽略。
void fct(int a);
void fct(const int a);//两个函数等价,不是重载
三、返回类型和return语句
1.void类型函数
可以通过return提前结束函数,当然,也可以通过return 调用另一个类型为void的函数
2.有返回值类型函数
对于非引用和指针类型的返回值,实际为一个局部变量,当函数调用结束会被释放,因此,不要返回局部对象的引用或者指针。
另外,返回值允许是一个列表,当列表非空时,会根据函数的返回类型进行列表初始化。列表为空时,执行值初始化。
由于数组不能被拷贝,因此数组不能作为函数返回类型,但可以返回指向数组的指针或者引用。
typedef int arr1[10]
using arr2 = int [10];
arr1 *fct(int a);
arr2 *fct(int a);
int (* fct(int a))[10];//fct是一个返回类型为指向十个整型数组的指针的函数
auto fct(int a) -> int (*)[10];//c++11的新方式,尾置返回类型
int a[10];
decltype(a) *fct(int a);
//decltype不会将数组名看作指针,而是数组本身,因此函数声明时要附加一个*
四、函数重载
允许形参列表不同的函数重名,注意顶层const不算不同,但底层const有区分。
由于非常量实参可以传递给常量形参,因此对于同时存在非常量形参和常量形参时,非常量实参会优先选择非常量形参。
调用重载函数时,通过函数匹配(重载确定)选择对应函数。可能会发生二义性匹配。
五、特殊用途语言特性
1.默认实参
使用默认实参时,设定默认实参的形参后面的所有形参都必须是默认。调用时,默认实参可以省略,但是只能省略尾部的,不能省略中间的部分默认实参。
允许多次声明同一个函数,但是不能修改已有的默认实参,且右侧的都必须是默认实参
string screen(sz,sz,char = ' ');
string screen(sz,sz,char = '*');//错误
string screen(sz = 24,sz = 80,char);//正确
局部变量不能作为默认实参,表达式的类型能转换成形参的类型时是可以做默认实参的。用作默认实参的名字在函数声明所在的作用域内解析,但真正求值发生在调用时。
sz wd = 80;
char ch = ' ';
sz ht();
string fct(sz = wd,sz = ht(),char = ch);
int main(){
ch = '*';//改变默认实参
sz wd = 60;//屏蔽了全局变量,但未改变默认实参
fct();//等价于fct(80,ht(),‘*’)
}
2.内联函数
内联函数的作用是在调用函数处直接展开函数,避免函数调用产生的开销,因此对于一些简单函数可以定义为内联函数,相当于一个请求,编译器可以忽略,仍当作普通函数。大多数编译器都不支持内联递归函数。
在函数返回类型前加上 关键字: inline
3.constexpr函数
指能用于常量表达式的函数,前提是函数的返回类型以及所有形参的类型都是字面值类型,而且必须有且只有一条retrun语句,且constexpr函数的声明和定义都必须在调用它之前。constexpr是在运行前就能得到值。
但是constexpr函数的返回值并不一定都是常量表达式,该函数是可以作为普通函数调用的,前提是该函数返回值的作用。
constexpr int fct(int x){
return x;
}
int main(){
int x = 1;
int z = fct(x);//z是int类型,因此不需要常量表达式也可初始化
return 0;
}
int main(){
int x = 1;
constexpr int z = fct(x);//错误,因为x并非常量表达式,所以编译错误。
return 0;
}
内联函数和constexpr函数通常定义在头文件中。
六、函数匹配
1.确定候选函数——同名、声明在前
2.确定可行函数——形参与调用实参数量相等、类型相同
二义性调用:每个可行函数各自在相同数量实参上实现了更好的匹配
类型转换排序:
1.精确匹配(顶层const也属于)
2.const底层转换
3.类型提升
4.算术类型转换或者指针转换
5.类类型转换
七、函数指针
函数的类型由函数的返回类型和形参类型共同决定,用指针代替函数名即可。
类似数组名可以看作指针,函数名同样可以看作指针
不同与直接使用重载函数,通过指针指向重载函数时,指针类型与重载函数类型必须精确匹配。
将地址赋值给指针时,可以直接使用函数名,也可以通过取地址符,二者等价
pf = lengthCompare;//lengthCompare是函数名
pf = &lengthCompare;//同上
函数指针做形参时,有多种写法
void fuc(const string &s1,const string &s2,string pf(const string &,const string &));
void fuc(const string &s1,const string &s2,string (*pf)(const string &,const string &));
//同样可以用using 、typedef decltype()+*或 尾置返回等简写
typedef decltype(lengthCompare) *Fuc1;
void fuc(const string &s1,const string &s2,Fuc1);
typedef bool (*Fuc2)(const string&,const string&);
void fuc(const string &s1,const string &s2,Fuc2);
函数指针做返回类型时,简写最好
using F = int(*)(char,double);//F是函数指针类型
F f(string);//f的返回类型是函数指针
using G = int(char,double);
G *f(string);
auto f1(string) -> int (*)(char,double);
int (*f(string))(char,double);//等价
最后,当函数名前出现*,且*不与函数名直接在一个括号里,表示返回类型时指针。
总结
对函数的部分,主要是参数传递时的const的使用、重载时const的使用、默认实参等之前没有注意到,函数指针再次学习了。