函数
1 函数基础
自动对象:只存在于块执行期间的对象,当块的执行结束后,块中创建的自动对象的值就变成未定义的。
局部静态对象:在程序的执行路径第一次经过对象定义语句初始化后,直到程序终止才被销毁,对象所在的函数结束执行也不会影响该变量。将局部变量定义成static类型即可变成局部静态对象
2 参数传递
引用传递:形参是引用类型
值传递:实参的值被拷贝给形参时
2.1 传引用参数
示例:
//该函数接受一个int对象的引用,然后将对象置0
void reset(int &i)
{
i = 0;
}
此时i绑定的是传给函数的int对象,改变i也就是改变i所引对象的值。
tip:使用引用可以避免拷贝;如果函数无须改变引用形参的值,最好将其声明为常量引用
2.2 数组形参
当将一个数组作为参数传递给一个函数时,实际上传递的是指向数组首元素的指针
类似下面的例子,这三个print函数是等价的:每个函数的唯一参数都const int*
void print(const int*);
void print(const int[]);
void print(const int[10]);
由于数组是以指针的形式传递给函数,因此刚开始数组的大小是未知的。管理指针形参有三种常有的的方法:
- 使用标记指定数组的长度,即判断指针的最后一个是否是空字符,典型示例是C风格的字符串
- 使用标准库规范,即传递指向数组首元素和尾元素的指针
- 传递一个表示数组大小的形参
数组引用形参:形参可以是数组的引用,此时引用形参绑定到对应的形参上,也就是绑定到数组上:
void print(int (&arr)[10])
{
for(auto elem : arr)
cout << elem << endl;
}
&arr两端的括号必不可少:
f(int &arr[10]); //错误:将arr声明成了引用的数组
f(int (&arr)[10]); //正确:arr是具有10个整数的整型数组的引用
传递多维数组:
//matrix指向数组的首元素,该数组的元素是由10个整数构成的数组
void print(int (*matrix)[10], int rowSize)
强调:matrix
两端的括号必不可少:
f(int *matrix[10]); //10个指针构成的数组
f(int (*matrix)[10]); //指向含有10个整数的数组的指针
main处理命令行选项
int main(int argc, char *argv[]){...}
第二个形参argv是一个数组,其元素是指向C风格字符串的指针;
第一个形参argc表示数组中字符串的数量
例如输入下面命令: prog -d -o ofile data0
argv[0] = "prog";
argv[1] = "-d";
argv[2] = "-o";
argv[3] = "ofile";
argv[4] = "data0";
argv[5] = 0;
其中argc = 5
,当使用argv中的实参时,可选的实参是从argv[1]开始,argv[0]保存程序的名字,而非用户输入
含有可变参数的函数
若所有的实参类型相同,可以传递initializer_list类型的形参,initializer_list可用于表示某种特定类型的数组。
操作 | 描述 |
---|---|
initializer_list<T> list; | 默认初始化;T类型元素的空列表 |
initializer_list<T> list{a,b,c...}; | lst 的元素数量和初始值一样,是对应初始值的副本;列表中的元素是const |
lst2(lst) | 拷贝lst ,原始列表和副本共享元素 |
lst2 = lst | 赋值lst ,原始列表和副本共享元素 |
lst.size() | 列表中元素的数量 |
lst.begin() | 返回指向lst 中首元素的指针 |
lst.end() | 返回指向lst 中尾元素下一位置的指针 |
示例
void error_msg(initializer_list<string> il)
{
for(auto beg = il.begin(); beg != il.end(); ++beg)
{
cout << *beg << " ";
}
cout << endl;
}
与vector不同,initializer_list对象中的元素永远是常量值;如果想向initializer_list形参中传递一个值的系列,则必须把序列放在一对花括号内
error({"abcd", excepted, actual});
error({"abcd", "okey"});
省略符形参,省略符形参只能出现在形参列表的最后一个位置;其形式如下:
void foo(parm_list,...);
void foo(...);
3 返回类型和return语句
不要返回局部对象的引用或指针,原因在于函数完成后,其所占用的存储空间也随之被释放掉,因此函数终止意味着局部变量的引用将指向不在有效的内存区域
引用返回左值,函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值,其他返回类型得到右值
char &get_val(string &str, string::size_type ix)
{
return str[ix];
}
//调用上述函数如下,将s[0]的值改为A:
get_val(s,0) = 'A';
返回数组指针,声明一个返回数组指针的函数形式如下所示:
Type (*function(parameter_list)) [dimension]
type
表示元素的类型
dimension
表示数组的大小
示例:
int (*func(int i))[10];
对上述示例的理解如下:
func(int i)
表示调用fun函数时需要一个int 类型的实参;(*func(int i))
意味着可以对函数调用的结果执行解引用操作;(*func(int i))[10]
表示解引用func的调用将得到一个大小是10的数组;int (*func(int i))[10]
表示数组中的元素是int类型。
使用尾置返回类型
示例:
//func接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组
auto func(int i) -> int(*)[10];
使用decltype,若事先知道函数返回的指针将指向哪个数组,就可以使用decltype关键字声明返回类型。
4 函数重载
函数重载:若同一作用域内的几个函数名字相同但形参列表不同的函数
如下:
void print(const char *cp);
void print(const int *beg, const int *end);
void print(const int ia[], size_t size);
Tips:main函数不能重载;不允许两个函数除了返回类型外其他所有的要素都相同,即返回类型要相同
重载和const形参
顶层const不影响传入函数的对象,但底层const会影响。如果形参是某种类型的指针或者引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,这里是通过底层const实现的
例如:
int func(int&); //函数作用于int的引用
int func(const int&); //新函数,作用于常量的引用
int func(int*); //新函数,作用于指向int的指针
int func(const int*); //新函数,作用于指向常量的指针
5 特殊用途语言特性
5.1 默认实参
默认实参:在函数的很多次调用中,形参都被赋予一个相同的值,这个反复出现的值称为默认实参。
示例如下:
typedef string::size_type sz;
string screen(sz ht = 24, sz wid = 80, char backgrnd = '');
这里默认窗口的高度为24,宽度为80。调用的时候,可以省略该实参,但是函数调用时实参是按其位置解析的,默认实参负责填补函数调用缺少的尾部参数(靠右侧的位置),即只能省略尾部的实参。
5.2 内联函数和constexpr函数
将函数指定为内联函数(inline),可避免函数调用的开销。在函数的返回类型前加上关键字inline
,即可将函数声明为内联函数。示例如下:
//函数原型
const string & shorterString(const string &s1, const string &s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}
若声明为以下形式(内联函数):
//函数原型
inline const string & shorterString(const string &s1, const string &s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}
则在函数调用会变成以下过程:
//函数调用
cout << shorterString(s1, s2) << endl;
编译过程中会展开成如下形式:
//函数调用
cout << (s1.size() <= s2.size() ? s1 : s2) << endl;
constexpr函数是指能用于常量表达式的函数,示例如下:
constexpr int new_sz() {return 43;}
constexpr int foo = new_sz();
- 此函数的返回类型及所有形参的类型都必须是字面值类型
- 函数体中必须有且只有一条return语句
Tip:由于内联函数和constexpr函数可以在程序中多次定义,内联函数和constexpr函数通常定义在头文件中
5.3 调试帮助
两种预处理功能:assert
和NDEBUG
assert
属于预处理宏,定义在cassert头文件中,常用于检查“不能发生”的条件,示例如下:
assert(expr);
首先对expr
求值,如果表达式为假(即0),assert
输出信息并终止程序的执行。
NDEBUG
预处理变量
可以使用NDEBUG
编写自己的条件调试代码,如果NDEBUG
未定义,将执行#ifndef
和#endef
之间的代码;若有定义,则上述代码将被忽略掉。示例如下:
void print(const int ia[],size_t size)
{
#ifndef NDEBUG
cerr << _ _func_ _ << ": array szie is " << size << endl;
#endif
}
此处使用变量_ _func_ _
输出当前调试的函数的名字
类似的变量还有如下:
_ _FILE_ _
存放文件名的字符串字面量值
_ _LINE_ _
存放当前行号的整型面量值
_ _TIME_ _
存放文件编译时间的字符串字面量值
_ _DATE_ _
存放文件编译日期的字符串字面量值
6 函数指针
函数指针指向的是函数,而非对象,函数的类型由它的返回类型和形参类型共同决定,与函数名无关。示例如下:
//比较两个string对象的长度
bool lengthCompare(const string &,const string &);
声明一个可以指向该函数的指针如下,只需要用指针替换函数名即可,*pf
两端的括号必不可少:
//pf指向一个函数
bool (*pf)(const string &,const string &); //未初始化
使用函数指针,示例如下:
pf = lengthCompare; //pf指向名为lengthCompare的函数
pf = &lengthCompare; //等价的赋值语句