1. 语句
1.1. 空语句
;//空语句
1.2. 复合语句
花括号括起来的语句和声明序列,也称为块
1.3. 范围for
不能通过范围for向vector等对象增加元素,因为预存了end的值,可能失效。
for (auto &r : v)
r *= 2;
//等价于
for(auto beg = v.begin(), end=v.end(); beg!= end; ++beg){
auto &r = *begin;
r *= 2;
}
1.4. try语句
try{
program-statements
} catch (exception-declaration){
handler-statements
} catch (exception-declaration){
handler-statements
} //..
2. 函数
形参(parameter)
实参(argument):形参的初始值,实参求值顺序不规定。
调用运算符(call operator): ()
函数调用完成两项工作
- 用实参初始化形参(隐式定义并初始化
- 将控制权转交给被调用函数(主函数此时被暂时中断)。
return语句结束函数执行过程,完成两个工作:
- 返回return语句中的值(若有)
- 将控制权从被调函数转回主调函数。不能返回数组、函数,但可以返回指向数组或者函数的指针
2.1. 局部对象与生命周期
名字有作用域(在其中可见)
生命周期是该对象存在的一段时间
局部对象:形参、函数体内部定义的变量,为局部对象(local variable)
- 自动对象:函数控制路径经过变量定义语句时创建,块尾部销毁。只存在于块控制期间的对象为自动对象。形参为实参初始化的自动对象,对于自动对象若无初始值则默认初始化。
- 局部静态对象(Local static object):生命周期贯穿函数调用及之后的时间。在执行路径第一次经过对象定义语句时初始化,程序结束时销毁。
2.2. 分离式编译
- 修改了其中一个源文件,只需要重新编译那个改动了的文件。产生后缀为.obj(Win)或.o(UNIX)的文件,含义为该文件包含对象代码(object code)。
- 编译器将对象文件链接成可执行文件
2.3. 内联函数
在每个调用点上“内联地”展开,避免调用开销。
#向编译器发出请求,编译器可以拒绝
2.4. constexpr函数
能作用于常量表达式的函数
约束:
- 返回类型和所有形参的类型都得是字面值类型;
- 有且只有一条return语句
#编译器把对constexpr函数的调用替换成结果值,为了能在编译过程中展开,constexpr函数被隐式地指定为内联函数
允许constexpr函数返回值并非常量。(例,写为,用非常量表达式调用返回非常量表达式,用常量表达式调用返回常量表达式)
内联函数和constexpr函数可以多次定义,必须完全一致(展开函数需要其定义)。通常定义在头文件中。
3. 函数参数传递
实参对形参初始化。
形参为引用类型,则绑定到对应实参(引用传递);否则值拷贝后赋值给形参(值传递)。
3.1. const形参和实参
实参初始化形参时忽略顶层const。形参为const时,传给它常量或非常量都可以。
void fcn(const int i) {}
void fcn(int i){} //错误,重复定义func(int)
虽然函数可以重载,但是形参列表应该有区别。上述两个函数参数可以完全一样,因此第二个定义有错。(guess:顶层const无法重载.?)
3.2. 指针或引用形参与const
- 可以用非常量初始化底层const,反之不行。
- 只有引用传递和指针传递可以用是否加const来重载。
3.3. 尽量使用常量引用
- 传入参数限制:普通引用无法传入const对象。
- 适应性:其他函数正确定义const引用,但非常量引用的函数无法在其中调用
3.4. 数组形参
数组特点:不允许拷贝数组、使用时通常转换为指针
无法值传递,传递时数组会被转换为指针,数组大小无影响。
形参可写为:
void print(const int*);
void print(const int[]);
void print(const int[10]); //表示期望数组的元素数目,但实不一定如此
上述三个函数等价,形参都为 const int*,传入时只检查参数是否为const int *。
3.4.1. 指定数组大小
用标记指定数组结束,如字符数组的
'\0',读到表示数组结束使用标准库规范:传入数组首元素、尾后元素的指针
显式传递数组大小
3.4.2. 数组引用形参和const
变量(形参)可定义为数组的引用
//正确:形参是数组的引用,维度是类型的一部分
void print(int (&arr)[10]){
for (auto elem : arr)
cout<< elem << endl;
}
//&arr两端括号不可省略
f(int &arr[10]); //错误,arr声明为引用的数组
f(int (&arr)[10]);
数组大小是构成数组类型的一部分,不超过维度都可在函数体内使用。
当不需要对数组元素执行写操作时,形参应该为指向const的指针。
3.4.3. 多维数组
C++没有真正意义的多维数组,是数组的数组。
传递多维数组时,传递的是指向数组首元素的指针。
//matrix指向数组的首元素,该数组的元素是由10个整数构成的数组。matrix括号不可忽略!
void print(int (*matrix)[10], int rowSize){};
也可以使用数组语法定义,第一个维度会被忽略因此不适宜放在形参列表中
//等价定义
void print(int matrix[][10], int rowSize){};
3.5. 可变形参的函数
- 使用initializer_list标准库类型
- 特殊形参类型:省略符。一般只用于与C函数交互的接口
3.6. 默认实参
可以为形参提供默认实参,作为初始值出现在形参列表中。
一旦某个形参被赋予默认值,它后面所有形参必须有默认值。
默认实参声明
一个函数可以声明多次,但给定作用域中,一个形参只能被赋予一次默认实参。
同一个函数的后续声明,只能为之前没有被赋予默认值的形参添加默认实参,且形参右侧的所有形参必须有默认值。
局部变量不能作为默认实参(即,全局变量可以)。表达式的类型能转换为形参所需类型也可以。
默认实参名字在函数声明所在的作用域内解析,求值过程发生在函数调用时。(即,默认实参为某个全局变量,调用时求全局变量值(可能会在运行时发生改变))
4. 返回类型与return语句
返回值的方式和初始化形参、变量的方式一样:返回值用于初始化调用点的一个临时量,临时量就是函数调用的结果。
- 返回引用,引用为所引对象的别名。因此,不返回局部对象的引用或指针。若返回引用得到左值,其他返回类型得到右值。
const string &manip(){
//局部变量
string ret;
//某些处理ret
if(!ret.empty())
return ret; //错误:返回局部对象引用
else
return "Empty"; //错误:"Empty"是一个局部临时量
}
- 列表初始化返回,用花括号初始化。
vector<string> process(){
// ...
return {"functionX","Okay};
}
返回数组指针
- 数组特点:不可拷贝,因此可以返回数组指针或引用。
- 声明一个返回数组指针的函数:
int arr[10]; int *p1[10]; //含有10个指针的数组 int (*p2)[10] = &arr; //指向含有10个整型的数组的指针若要返回一个数组指针,数组维度必须跟在函数名字之后。形参也跟在函数名前且先于数组维度。
Type (*function(parameter list)) [dimension]; int (*func(int i))[10]; //使用类型别名代替 typedef int arrT[10]; using arrT = int[10]; arrT* func(int i);- 使用尾置返回类型(C++11)【也可使用decltype】
auto func(int i) -> int(*)[10]
主函数main
- 可以没有
return语句,编译器隐式插入返回0的return语句。 - 不可调用自己
递归:直接、间接调用自己
5. 函数重载
同一个 作用域内,几个函数名字相同但形参不同,称之为重载函数
#main函数不能重载
#不允许两个函数除了返回类型外,其他要素相同
5.1. 重载与const形参
如上所述,顶层const不影响传入函数的对象。一个有顶层const的形参无法与另一个无顶层const的形参区分。
int func(int);
int func(const int); //重复声明
int func_(int*);
int func_(int* const); //顶层const,重复声明
底层const可以实现重载
int func(int &);
int func(const int &); //合法重载,常量引用
int func_(int*);
int func_(const int*);
底层const只能传给const。由于非常量可以转换为const,非常量引用、指向非常量的指针可以作用于以上四个函数,在调用时,编译器将非常量的参数优先调用非常量的重载函数。
5.2. const_cast与重载
//常量引用版本
const string &shorter_string(const string &s1, const string &s2){
return s1.size()<=s2.size()?s1:s2;
}
//非常量引用版,需要一个普通引用返回
//可以使用const_cast
string &shorter_string(string &s1, string &s2){
auto &r = shorter_string(const_cast<const string&>(s1),
const_cast<const string&>(s2));
return const_cast<string&>(r);
}
5.3. 调用重载的函数
函数匹配(function matching),又叫重载确定(overload resolution)
把函数调用与一组重载函数中的某一个关联起来。将调用的实参与重载集合中的每一个函数的形参进行比较,再根据比较结果决定调用哪个:
- 找到一个与实参最佳匹配的(best mtach)的函数,生成调用的代码。
- 找不到任何一个匹配,编译器发出无匹配(no match)的错误。
- 有多于一个的函数可以匹配,但每一个都不是明显更好的选择。发生错误,二义性调用(ambiguous call)
5.4. 重载与作用域
如果在内层作用域声明名字,将隐藏外层作用域中的同名实体。不同作用域中无法重载。
#C++中,名字查找发生在类型检查之前。
6. 函数匹配
- 选定候选函数:与被调用函数同名,且在调用点函数声明可见
- 考察本次调用的实参,选出能被这组实参调用的可行函数:1.形参数量与本次调用提供的实参数目相同;2.每个实参的类型与形参的类型相同,或者能转换为形参的类型。【如果有提供默认参数,那么实参数目可以少于形参数;若找不到可行参数则编译器报错】
- 试图从可行函数中寻找最佳匹配,寻找形参形式与实参类型最匹配的可行函数。若有多个形参,最佳匹配需要满足
- 每个实参都不劣于其他可行函数需要的匹配
- 至少有一个实参匹配优于其他可行函数提供的匹配
- 若不满足,则存在二义性调用(ambiguous call)
例
//声明
void f(int a, int b);
void f(double a, double b);
//二义性调用,无最佳匹配
f(2, 3.14);
类型提升:小类型往往被提升到
int,参数是小类型反而可能导致类型转换而不成为优先匹配算数类型转换的匹配:所有算数类型转换级别一致,对于double转为float或long,优先级一致
7. 函数指针
指向函数而非对象,类型由返回值和参数共同决定。
bool (*pf)(const string &, const string &); //必须有圆括号(*pf)
使用方法,定义或赋值时可不加&;使用也可以不解引用*
pf = lengthCompare;
pf = &lengthCompare; //两种都可以
//
bool b1 = pf("hello", "byebye");
bool b2 = (*pf)("hello", "goodbye");
不同类型的函数指针不存在转换规则。