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