局部静态对象
有些时候,我们需要保留一些对象在函数体结束后依然使用,定义时使用static将其声明为静态类型,直到程序终止才销毁。
虽然在函数外没有销毁,但是其仍然是局部变量,外部不可访问,只不过其内存也不释放。
局部静态变量如果没有显式的初始化值,将执行值初始化,内置类型的值初始化为0。
一个简单的例子,这个程序会输出 1~10 的数字。
1 | size_t count_calls() |
指针变量传递
指针变量作为参数和普通变量作为参数一样,都是值传递。即形参和实参是两个不同的指针变量,两个指针自己的地址不一样,但是它们的值一样——即它们指向的地址是一样的。因此可以通过传递指针修改实参。
const 参数
实参传给形参会忽略掉顶层const,即从函数外部看函数,我们不知道这个函数的参数究竟有没有顶层const,也因此不能通过顶层const来重载函数。例如:
1 | void fcn1(const int i){···} //忽略顶层 const |
顶层可以理解为离变量最近的一层,顶层 const 修饰指针本身的值,即指向哪一个地址不能被修改,底层 const 修饰指针指向的对象的值不能被修改。
形参初始化和变量初始化一样,关于 const
的要求都是可降格兼容,不能升格。即如上int可用于初始化const int变量,const int不能初始化int变量。引用和指针层面也是类似,cont int引用可绑定到int对象,int引用不可绑定到const int对象。
尽量使用常量引用。使用常量引用可以避免拷贝数据,同时阻止非必要的修改操作。另一方面由于外部的 const 类型不能升格传参给非 const 形参,容易发生类型匹配错误。而外部的非 const 类型可以传给 const 形参,因此使用 const 引用,也可以有效减少这种类型错误的情况。
数组形参
数组有两个特殊性质,影响我们对数组的传参操作:
- 数组不能进行拷贝
- 使用数组时相当于使用指针
所以我们不能值传递数组,但是我们可以通过指针进行传递,如下三个等价的传递数组的函数,看起来不一样,其实都是形参都是 const int*,导致重复定义:
1 | //会互相造成重复定义错误 |
但是正如示例所述,只传递指针的话,我们不知道传进来的数组有多大,因此可以通过别的途径传递长度信息:
- 加一个length变量
void fcn(const int a[],int length);//用另一个变量标记长度 - 取得尾后迭代器
void fcn(const int *begin,const int * end)//标准库规范做法,传递尾后元素,进行尾后检测
多维数组
我们知道多维数组其实就是一维数组,只不过数组元素是新数组指针而已。多维数组即看作指针的指针进行传递。如下,首先明确传参的只是一个指针,指向首元素,这个指针的类型是首元素类型。
1 | void fcn(int (* matrix)[10]); //matrix 是一个指针,指向首元素类型为 int[10] |
注意,虽然上述传递一维数组会忽略数组大小,但是第二维及以上的数组大小算作一种数据类型,需要严格对应:
1 | void fcn(int a[][2]) // == int (*a) [2] , != int *a[2] |
另外注意 int ** 和 int (*) [10] 不是同一个类型。
变长形参
有时候我们不确定要传递几个实参给函数,因此 C++提供了两种方法。
Initializer_list
Initializer_list
是一个很简单的列表容器,有点像python的*args。
- 形参使用
initializer_list<T>来接受列表参数 - 实参使用初始化列表传参
{T1,T2,T3} - 函数体内则和其他容器一样访问。
1 | void error_msg(int count,initializer_list<string> msg) |
省略符
省略符是为了兼容 C 而设置的。通常不建议在 C 之外使用。
1 | void foo(parm_list,...); |
返回值
返回数组指针
函数返回一个普通指针很简单,返回值 type *
即可,但是返回一个指向数组的指针呢?正常声明数组指针变量是type (*var)[size]
: var是一个指针,指向一个大小为size的数组,数组元素类型是type。
那么函数返回同理:
1 | type (* func(param...)) [size] |
decltype返回
如果已知返回的是什么变量的类型,可以使用decltype来推断返回类型:
1 | decltype(somevar) * func(int i) |
尾置返回
C++11更方便的尾置返回,在返回复杂的东西时显得很方便,例如返回数组指针:
1 | auto func(int i) -> int(*) [size] |
函数指针
和普通类型的指针type *一样,把函数看作一种类型,可以定义函数指针如下,并且可以将函数名赋值给该指针,然后当做函数一样使用:
1 | bool func (string,string);//原函数 |
拥有了指针,自然也能将函数指针作为 函数参数 和 函数返回值 使用。
1 | //两种形参方式等价,自动将函数类型转为函数指针 |
显然上述的函数指针形式很繁琐,因此可以利用typedef和decltype等改名工具来简化:
1 | //两种改名方式等价 |
类型别名 不是 变量名,上述代码没有创建一个f3变量或者一个p3变量,应当看作类型名去使用。
参考资料
C++ Primer 5 edition 中文版