局部静态对象
有些时候,我们需要保留一些对象在函数体结束后依然使用,定义时使用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 中文版