前言
该系列是《C++Primer第五版》的笔记,包含本人认为值得记录和整理的主要的知识点,并不是全部内容,也不是具体的内容。
该系列文章的作用应该是作为复习或预习的参考,有哪些知识点忘记或想学,可以大致浏览下该文章,然后再去书中寻找详细解答。(本系列文章基本是按书本顺序罗列的知识点,便于大家去书中寻找)
所以看该文章前,需要有一定的C++基础,否则阅读起来可能有困难。
本文大致整理了第六章的知识点,涉及到C++函数相关的知识,内容比较多,有几个难点有一定难度。
链接目录
- 第二章:变量与基本类型
- 第三章:字符串、向量和数组
- 第四章:表达式
- 第五章:语句
- 第六章:函数
- 第七章:类
- 第八章:IO库
- 第九章:顺序容器
- 第十章:泛型算法
- 第十一章:关联容器
- 第十二章:动态内存
- 第十三章:拷贝控制
- 第十四章:重载运算与类型转换
- 第十五章:面向对象程序设计
- 第十六章:模板与泛型编程
- 第十七章:标准库特殊设施
- 第十八章:用于大型程序的工具
- 第十九章:特殊工具与技术
函数基础
函数调用方式:
- 通过括号调用
- 通过指向函数的指针调用
函数参数:
- 形参:形式参数,用来接收传递进来的数据
- 实参:用来初始化形参,将数据传给形参
局部对象:形参和函数体内部定义的变量
局部静态对象:将局部变量定义成static,第一次调用时初始化,到程序终止才会被销毁
函数可以多次声明,但只能定义一次,对函数声明时,可以不要形参的名字
例如:void function(int, double);
函数应该在头文件中声明,在源文件中定义。
参数传递
参数传递类型:
- 引用传递:形参是引用类型,形参是实参的别名
- 值传递:形参不是引用类型,形参是实参的一份拷贝
初始化一个非引用类型的形参,不会影响实参。但是可以通过指针形参修改指针指向对象的值,但无法修改实参指针本身的指向。即指针作为参数,实际上会将指针复制一份再传入。
void fun(int* p2){
*p2 = 2;//可以通过指针修改对象
p2 = nullptr;//不影响函数外的指针p
}
int main(){
int i = 1;
int *p = &i;
fun(p);//传入p的一个副本
cout << *p << endl;//函数内的操作不影响p
return 0;
}
传引用参数:
引用形参可以修改实参的值,形参和实参进行绑定
- 使用引用可以避免拷贝,对于大的类型对象可以减少开销
- 使用引用形参返回额外信息,通过引用传递某些额外信息
把形参定义成普通引用会限制函数所能接受的实参类型(不能把const对象、字面值或者需要类型转换的对象传递给普通引用)
void find_char(string &s, char c);
find_char("hello", 'l');//错误,常量不能传递给普通引用
数组形参:
传递数组时,实际上传递的是指向数组首元素的指针。另外函数并不知道数组的大小,可以通过显式传递大小参数来进行相关操作。
即void print(const int*)
等价于void print(const int[])
如果传递多维数组,数组第二维及后面的维度大小不能省略。
含有可变形参数量的函数
传递参数数量不确定,该方法通常只用于与C函数交互的程序接口。
initializer_list
:标准库类型;实参数量未知,但类型全部相同。
void printList(initializer_list<string> il){
for(auto beg = il.begin(); beg != il.end(); ++beg)
cout << *beg << " ";
cout << endl;
}
int main(){
printList({"hello", "world"});
return 0;
}
返回类型和return语句
无返回值函数:void
函数,可以不要return
语句,会隐式地执行return
有返回值函数:
值返回过程:在调用时会初始化一个临时量,在函数结束时将结果拷贝至调用点,所以返回的是一个副本或未命名的临时对象。如果返回的是引用类型,则不需要进行拷贝的操作。
不要返回局部对象的引用或指针!函数完成后,局部对象的内存会被释放,则该引用或指针不再有效,是一个未定义的值。
const string &fun(){
string ret;
return ret;//错误,返回局部对象的引用
return "Empty";//错误,返回局部临时量
}
//如果函数返回类型是string则没有问题,会对结果进行拷贝
//但这里返回的是string的引用,所以错误
当返回类型是一个左值时,可以将函数放在表达式左边
char &get_val(char &c){
return c;
}
int main(){
char c = 'a';
get_val(c) = 'A';//函数返回结果作为左值,此时c的值为'A'
return 0;
}
主函数main
的返回值:main
中返回0代表成功,其他值的含义取决于机器。为了使返回值与机器无关,cstdlib头文件定义了两个预处理变量,不能加上std::
,也不能在using
声明中出现。
int main(){
if(success)
return EXIT_SUCCESS;
else
return EXIT_FAILURE;
}
返回数组指针
因为数组不能被拷贝,所以函数不能返回数组,只能返回数组的指针或引用。
声明一个返回数组指针的函数:
//形如
Type(*function(parameter_list))[dimension];//最外层括号不可省略
//例如
int(*func(int i))[10];
//使用尾置返回类型(C++11特性)
auto func(int i) -> int(*)[10];
函数重载
函数名字相同,但形参列表不同,称为函数的重载,但不允许只有返回类型不同的重载。
重载和const:
//一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开
Record lookup(Phone);
Record lookup(const Phone);//重复声明
//对于引用和指针则有区别
Record lookup(Phone&);
Record lookup(const Phone&);
Record lookup(Phone*);
Record lookup(const Phone*);
重载确定:
- 编译器找到一个与实参最佳匹配的函数
- 找不到任何一个函数与实参匹配,发出无匹配错误
- 多于一个函数可以匹配,但每个都不是明显的最佳选择,发生二义性调用
重载与作用域:
string read();
void print(int i);
void print(string s);
void fooBar(int ival){
bool read = false;//新作用域,隐藏了外层的read
string s = read();//错误
void print(double);//新作用域,隐藏了外层的print
print("Value");//错误,外层的print被隐藏了
print(3.14);//正确,满足内层的print函数
}
特殊用途语言特性
- 默认实参
- 内联函数
constexpr
函数
默认实参:为某个形参提供默认值,它后面所有的形参都必须有默认值,有默认值的形参必须在参数表的末尾。
默认实参的例子:
int func(int i, int j = 0, int k = 1);
//调用时如果想使用默认实参,省略该参数即可
func(4);
func(4, 5);
func(4,,5);//无法使用该种方法只调用中间的默认实参
默认实参声明:一个函数可以多次声明,但一个函数的后续声明只能为之前没有默认值的形参添加默认实参,而该形参右侧的所有形参都必须有默认值。
string screen(int, int, char = ' ');
string screen(int, int, char = '*');//错误
string screen(int i = 24, int j = 80, char);//正确
默认实参初始值:局部变量不能作为默认实参,其他类型只要能转换成形参所需的类型,都可以作为默认实参。例如可能出现这样的实参初始值:int func(int i = getN(), int j = HEIGHT);
内联函数:内联函数实际上会在所有调用到的地方进行展开操作。内联函数相比于外部的函数,可以避免函数调用的开销,不需要保存寄存器、恢复现场、拷贝实参等操作。通常内联机制用于优化规模小、流程直接、频繁调用的函数。
内联函数的例子:
inline int fun(int i, int j){
i += j;
return i;
}
constexpr
函数:能用于常量表达式的函数
- 函数的返回类型及所有形参必须都是字面值类型
- 函数体内有且只有一条
return
语句
//当scale的实参是常量表达式时,它的返回值也是常量表达式,反之则不然
constexpr int scale(int cnt){
return 5 * cnt;
}
int arr[scale(2)];//正确
int i = 2;
int arr2[scale(i)];//错误,i不是常量表达式
//但是此处发生的错误不是因为constexpr函数不能使用非常量表达式,而是数组维度的声明参数必须是常量表达式
int j = scale(i);//正确,此时scale函数返回的不是常量表达式
可以把内联函数和constexpr
函数放在头文件内,内联函数和constexpr
函数可以在程序中多次定义,但是多个定义必须完全一致,所以内联函数和constexpr
函数通常定义在头文件中。
调试帮助
assert
预处理宏:定义在cassert
头文件中。
//表达式为假,输出信息并终止运行,表达式为真不做任何操作
assert(expr);
assert(word.size() > threshold);
NDEBUG
预处理变量:在assert
头文件前加入#define NDEBUG
可关闭调试状态,使assert
函数失效,大量的assert
函数会造成一定的性能损失,应该在不调试时终止。
C++内置预定义宏:
__func__
:当前函数的名字__FILE__
:文件名的字符串字面值__LINE__
:当前行号的整型字面值__TIME__
:文件编译事件的字符串字面值__DATE__
:文件编译日期的字符串字面值
cout << "Error:" << __FILE__
<< ": in function:" << __func__
<< "at line:" << __LINE__
<< ...
函数匹配
某些重载函数形参数量相等且形参类可以由同一类型转换时。
确定候选函数和可行函数:
void f(int);
void f(int, int);
void f(double, double = 3.14);
f(5.6);//f(int)和f(double, double)都是满足的
寻找最佳匹配:上述代码最终匹配的是f(double, double)
实参类型与形参最接近
含有多个形参的函数匹配:如果只有一个函数满足条件,则成功,否则产生二义性。例如调用f(14, 2.5)
将产生二义性,可以通过类型转换指定实参类型。
实参类型转换
匹配等级:
- 精确匹配:
- 实参类型和形参类型相同
- 实参从数组类型或函数类型转换成对应的指针类型
- 向实参添加顶层const或从实参中删除顶层const
- 通过const转换实现的匹配
- 通过类型提升实现的匹配
- 通过算术类型转换或指针转换实现的匹配
- 通过类类型转换实现的匹配
类型提升:
void f(int);
void f(short);
f('a');//char提升成int
f(1);//默认整型字面值为int
函数指针
使用函数指针,不同函数类型的指针不存在转换
auto pf = lengthCompare;//pf指向名为lengthCompare的函数
auto pf = &lengthCompare;//等价
bool b1 = pf("hello", "world");//函数指针可以不解引用
bool b2 = (*pf)("hello", "world");//等价
bool b3 = lengthCompare("hello", "world");//等价
重载函数的指针:必须精确匹配
void f(int *);
void f(unsigned int);
void (*pf1)(unsigned int) = f;//pf1指向f(unsigned)
函数指针形参:
void fun(int i, bool pf(int j, int k));//自动转换成指向函数的指针
//等价,第二个参数为指向函数的指针
void fun(int i, bool (*pf)(int j, int k));
//fun2为形参列表匹配的函数的函数名,自动转换成指向该函数的指针
fun(1, fun2);
返回指向函数的指针,我们不能返回一个函数,但是可以返回指向函数类型的指针。可以使用类型别名简化。
using F = int(int*, int);//F是函数类型,不是指针
using PF = int(*)(int*, int);//PF是指针类型
//返回类型不能自动转换成指针
PF f1(int);//正确,f1返回指向函数的指针
F f1(int);//错误,f1不能返回一个函数
F *f1(int);//正确,显式地指定返回类型是函数指针
//等价于直接声明
int (*f1(int))(int*, int);//返回一个指向返回类型是int的函数的指针
//也可以使用尾置返回类型
auto f1(int) -> int(*)(int*, int);
如果返回类型过于复杂,建议使用decltype
将auto和decltype用于函数指针类型:
int sumLength(const string&, const string&);
//getFcn返回一个指向sumLength函数的指针
decltype(sumLength) *getFcn(const string&);
//另外需要注意,使用decltype时,它返回的是函数类型而非指针
//所以需要显式的加上解引用符来表明返回指针