值传递和引用传递
与值传递相比,引用传递的优势主要体现在三个方面:一是可以直接操作引用形参所引的对象;二是使用引用形参可以避免拷贝大的类类型对象或容器类型对象;三是使用引用形参可以帮助我们从函数中返回多个值。
数组形参
数组有两个性质: 不允许数组拷贝,使用数组时通常会被转换为指针
数组以指针的形式传给函数,所以一开始函数并不知道数组的确切尺寸,要解决这个问题有三种常用的技术:
- 使用标记指定数组长度,函数在处理 C 风格字符串时遇到空字符停止,但是这种方法只适合字符这种有结束标记,想 int 这种所有取值都合理的不适用。
- 使用标准库范围
示例代码:1
2int j[2] = {0, 1};
print(begin(j), end(j)); - 显示传递一个表示数组大小的形参数组引用形参,维度是类型的一部分,
1
2
3
4
5void print(const int ia[], size_t size){
for(size_t i = 0; i != size; ++i){
cout <<ia[i] << endl;
}
}void print(int (&arr)[10])
,&arr 两端的括号必不可少,没有括号的话就变成了引用的数组。但是这样只能将函数作用于大小为 10 的数组。
当我们想把数组作为函数的形参时,有三种可供选择的方式:一是声明为指针,二是声明为不限维度的数组,三是声明为维度确定的数组。
可变形参
- initializer_list
和 vector 一样,initializer_list 也是一种模板类型
和 vector 不一样的是,initializer_list 对象中的元素永远是常量值,我们无法改变 initializer_list 对象中元素的值。
因为 initializer_list 对象的元素永远是常量值,所以我们不可能通过设定引用类型来更改循环控制变量的内容。只有当 initializer_list 对象的元素类型是类类型或容器类型(比如 string)时,才有必要把范围 for 循环的循环控制变量设为引用类型。 - 省略符形参
省略符形参应该仅仅用于 C 和 C++ 通用的类型。特别应该注意的是,大多数类类型的对象在传递给省略符形参时都无法正确拷贝。
省略符形参只能出现在形参列表的最后一个位置,它的形式无外乎以下两种:1
2void foo(parm_list, ...); // , 可以省略,省略符形参所对应的实参无须类型检查
void foo(...);
返回数组指针
分四种情况,比较复杂:
类型别名
1
2
3typedef int arrT[10]; // arrT 是一个类型别名
using arrT = int[lO]; // 等价于上一行
arrT* func(int i);声明一个返回数组指针的函数
1
int (*func(int i))[10];
func(int i)表示调用 func 函数时需要一个 int 类型的实参。
(*func(int i))意味着我们可以对函数调用的结果执行解引用操作。
(*func(int i))[10] 表示解引用 func 的调用将得到一个大小是 10 的数组。
int (*func(int i))[10] 表示数组中的元素是 int 类型。使用尾置返回类型
1
auto func(int i) —> int(*)[10];
使用 decltype
1
2
3
4
5
6
7int odd[] = {1,3,5,7,9};
int even[] = {O,2,4,6,8};
decltype(odd) *arrPtr(int i)
{
return (i % 2) ? &odd : &even;
}decltype 并不负责把数组类型转换成对应的指针,所以 decltype 的结果是个数组,要想表示 arrPtr 返回指针还必须在函数声明时加一个*符号。
函数重载
顶层不可重载,一个拥有顶层 const 的形参无法和另一个没有顶层 const 的形参区分开来
1 | Record lookup(Phone); |
底层可以重载,另一方面,如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的 const 是底层的:
1 | Record lookup(Account&); |
函数调用的底层实现
在大多数机器上,一次函数调用其实包含着一系列工作:调用前要先保存寄存器,并在返回时恢复;可能需要拷贝实参;程序转向一个新的位置继续执行。
内联函数
将函数指定为内联函数(inline),通常就是将它在每个调用点上 “内联地” 展开。
1 | cout <<shorterString(sl, 52) << endl; |
声明内联函数
1 | inline const string & |
constexpr 函数
constexpr 函数(constexpr function)是指能用于常量表达式的函数。
函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条 return 语句:
1 | constexpr int new_sz() { return 42;} |
执行该初始化任务时,编译器把对 constexpr 函数的调用替换成其结果值。为了能在编译过程中随时展开,constexpr 函数被隐式地指定为内联函数。
头文件保护技术
所谓预处理宏其实是一个预处理变量,它的行为有点类似于内联函数。
1 | assert(expr); |
首先对 expr 求值,如果表达式为假(即 0),assert 输出信息并终止程序的执行。如果表达式为真(即非 0),assert 什么也不做。
assert 的行为依赖于一个名为 NDEBUG 的预处理变量的状态。如果定义了 NDEBUG,则 assert 什么也不做。默认状态下没有定义 NDEBUG,此时 assert 将执行运行时检查。
打开和关闭调试
1 | CC -D NDEBUG main.C it use /D with the Microsoft compiler |
这条命令的作用等价于在 main.c 文件的一开始写 #define NDEBUG。
类型提升
即使实参是一个很小的整数值,也会直接将它提升成 int 类型
1 | void ff(int); |
所有算术类型的转换级别都一样
1 | void manip(long); |
类型匹配
1 | int calc(char*, char*); |
函数指针
1 | bool lengthCompare(const string &, const string &); |
使用函数指针
1 | bool bl = pf("hello", "goodbye"); |
可以为函数指针赋一个 nullptr 或者值为 0 的整型常量表达式,表示该指针没有指向任何一个函数。
重载函数 的指针,指针类型必须与重载函数中的某一个精确匹配。
1 | void ff(int*); |
decltype 返回函数类型,此时不会将函数类型自动转换成指针类型。因为 decltype 的结果是函数类型,所以只有在结果前面加上*才能得到指针。
1 | typedef decltype(lengthCompare)* FuncPZ; // * 号位置可变 |
函数类型的形参会自动转换成指针,返回类型不会。
返回指向函数的指针
1 | int (*fl(int))(int*, int); |
按照由内向外的顺序阅读这条声明语句:我们看到 f1 有形参列表,所以 f1 是个函数;f1 前面有 *,所以 f1 返回一个指针;进一步观察发现,指针的类型本身也包含形参列表,因此指针指向函数,该函数的返回类型是 int。
示例
编写函数的声明,令其接受两个 int 形参并且返回类型也是 int;然后声明一个 vector 对象,令其元素是指向该函数的指针。
1 | int func(int, int); |
const 函数
C++ 中的 const 函数指的是在类中声明的成员函数,该函数承诺不会修改类的成员变量,因此可以在函数声明和定义中使用 const 关键字来表示这一点。使用 const 修饰的成员函数被称为 const 成员函数。
const 成员函数可以被声明为常量对象调用,这意味着该对象的成员变量的值不能被修改。如果试图在 const 成员函数中修改成员变量的值,则会导致编译错误。
1 | class MyClass { |