C++ | 函数
一个典型的函数定义包含以下部分:返回类型、函数名字、由0个或多个形参组成的列表以及函数体。
函数基础
编写函数
例如:5的阶乘
1 | int fact(int val) |
调用函数
函数调用完成两项工作:
- 用实参初始化函数对应的形参
- 将控制权转移给被调用函数(此时主调函数的执行被暂时中断,被调函数开始执行)
1 | int main() |
形参与实参
实参是形参的初始值,且实参的类型必须与对应的形参类型匹配。
函数的形参列表
形参列表为空的表示方法:
1 | void f1() {} // 隐式 |
即使两个形参的类型一样,也必须把两个类型都写出来
1 | int f3(int v1, v2) {} // 错误 |
函数返回类型
函数的返回类型不能是数组类型或函数类型,但可以是指向数组或函数的指针。
局部对象
在C++语言中,名字有作用域,对象有生命周期。
- 名字的作用域是程序文本的一部分,名字在其中可见
- 对象的生命周期是程序执行过程中该对象存在的一段时间
函数体是-一个语句块。块构成- -个 新的作用域,我们可以在其中定义变量。形参和函数体内部定义的变量统称为局部变量( local variable)。它们对函数而言是“局部"的,仅在函数的作用域内可见,同时局部变量还会隐藏(hide)在外层作用域中同名的其他所有声明中。
自动对象
只存在于块执行期间的对象称为自动对象。当块的执行结束后,块中创建的自动对象的值就变成未定义的了。
形参是一种自动对象
局部静态对象
有些时候,我们需要令局部变量的生命周期贯彻函数调用及以后的时间,这时候便需要定义为static类型。局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,直到程序终止才会被销毁。
When a variable is declared as static, space for it gets allocated for the lifetime of the program.
例:
1 | size_t count_calls() |
函数声明
函数的名字必须在使用之前先声明。
函数的声明和函数的定义类似,唯一的区别是函数声明无需函数体,用一个分号替代即可。
例:
1 | void print(vector<int>::const_iterator beg, vector<int>::const_iterator end); |
函数的三要素(返回类型,函数名,形参类型)描述了函数的接口,说明了调用该函数所需的全部信息。函数声明也称作函数原型
参数传递
如果形参是引用类型,它将绑定到对应的形参上,否则,将实参的值拷贝后赋给形参。
指针形参
1 | void reset(int *ip) |
在C++中建议使用引用类型代替指针。
传引用参数
例:该函数接受一个int对象的引用,然后将对象的值置为0
1 | void reset(int &i) |
调用这一版本的reset函数时,我们直接传入对象而无须传递对象的地址。
使用引用避免拷贝,且如果函数无需改变引用形参的值,最好将其声明为常量引用。
const形参与实参
当用实参初始化形参时会忽略掉顶层的const。
1 | void fcn(const int i) {/* */} |
调用该函数时,既可以传入const int 也可以传入int。
把函数不会改变的形参定义成引用是一种比较常见的错误,这么做带给函数的调用者一张误导,即函数可以修改它的实参的值,
所以尽量使用常量引用
数组形参
数组两个性质为:
- 不允许拷贝
- 使用数组时会将其转换为指针
所以我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。
管理指针形参有三种常用的技术
- 使用标记指定数组长度
- 使用标准库规范
- 显式传递一个表示数组大小的形参
main:处理命令行选项
假设main函数位于可执行文件prog之内,我们可以向程序传递下面的选项
1 | prog -d -o ofile data0 |
这些命令行选项通过两个可选的形参传递给main函数
1 | int main(int argc, char *argv[]) {...} |
其中第一个形参argc表示数组中字符串的数量,第二个形参argv是一个数组
当使用argv中的实参时,一定要记得可选的实参从argv[1]开始;argv[0]保存进程的名字,而非用户输入。
例如:
1 |
|
含有可变形参的函数
为了编写能处理不同数量实参的函数,C++11新标准提供了两种主要的方法:
- 如果所有的实参类型相同,可以传递一个名为initializer_list的标准库类型
- 如果类型不同,可变参数模板
initializer_list形参
如果函数的实参数量未知但是知道全部实参的类型都相同,我们可以使用initializer_list类型的形参。
例如:
1 | void error_msg(initializer_list<strign> il) |
含有initializer_list形参的函数也可以同时拥有其他形参。
1 | void error_msg(ErrCode e, initializer_list<string> il) |
返回类型和return语句
无返回值的函数
也就是void
有返回值函数
return语句返回值的类型必须与函数的返回类型相同
值是如何被返回的
返回一个值的方式和初始化一个变量或形参的方式完全一样:返回的值用于初始化调用点的一个临时变量,该临时量就是函数调用的结果。
不要返回局部对象的引用或指针。
引用返回左值
函数的返回类型绝对函数调用是否是左值。调用一个返回引用的函数得到左值,其他返回类型得到右值。
1 | char &get_val(string &str, string::size_type ix) |
列表初始化返回值
c++11中,函数可以返回花括号包围的值的列表
1 | vector<string> process() |
主函数main的返回值
我们允许main函数没有return语句直接结束。
main函数的返回值可以看作是状态指示器。返回0表示执行成功,返回其他值表示执行失败,其中非0值的具体含义依机器而定。
为了使返回值与机器无关, 可以这样做
1 | int main() |
递归
如果一个函数调用了它本身,不管这种调用是直接的还是间接的,都称该函数为递归函数
例如 阶乘
1 | int factorial(int val) |
返回数组指针
因为数组不能被拷贝,所以函数不能返回数组,但是函数可以返回数组的指针或者引用。
使用类型别名,我们可以定义一个返回数组的指针或引用的函数
1 | typedef int arrT[10]; |
声明一个返回数组指针的函数
1 | int arr[10]; // arr是一个含有10个整数的数组 |
所以返回数组指针的函数形式如下所示:
1 | type (*function(parameter_list))[dimension] |
使用尾置返回类型
1 | // func接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组 |
使用decltype
1 | int odd[] = {1, 3, 5, 7, 9}; |
函数重载
如果同一作用域内的几个函数名字相同但形参列表不同,称之为重载函数
1 | void print(const char *cp); |
这些函数接受的形参类型不一样,但是执行的操作非常相似,当调用这些函数时,编译器会根据传递的实参类型推断想要的是哪个函数。
1 | int j[2] = {0, 1}; |
main函数不能重载
调用重载的函数
函数匹配是指一个过程,在这个过程中我们把函数调用与一组重载函数中的一个关联起来,函数匹配也叫做重载确定
当调用重载函数时有三种可能的结果
调用重载函数时有三种可能的结果:
- 编译器找到一个与实参最佳匹配的函数,并生成调用该函数的代码
- 找不到任何一个函数与调用的实参匹配,此时编译器发出无匹配的错误信息
- 有多于一个函数可以匹配,但是每一个都不是明显的最佳选择。此时也将发生错误,称为二义性调用
特殊用途语言特性
默认实参
1 | typedef string::size_type sz; |
如果我们想使用默认实参,只要在调用函数的时候省略该实参就可以。
需要注意的是函数调用时实参按其位置解析
1 | window = screen(, , '?'); // 错误:只能省略尾部的实参 |
所以当设计含有默认实参的函数时,其中一项任务是合理设置形参的顺序。
内联函数
内联函数可以避免函数调用的开销
将函数指定为内联函数,通常就是将它在每个调用点上"内联”展开。假设我们将shorterString函数定义为内联函数,则如下调用
1 | cout << shorterString(s1, s2) << endl; |
将在编译过程中展开成类似于下面的形式
1 | cout << (s1.size() < s2.size() ? s1 : s2) << endl; |
从而消除了shorterString函数的运行开销。
内联函数的声明
1 | inline const string & shorterString(const string &s1, const string &s2) |
内联说明只是向编译器发出一个请求,编译器可以选择忽略这个请求
函数指针
函数指针指向的是函数而非对象。
1 | bool lengthCompare(const string &, const string &); |
声明一个可以指向该函数的指针,只需要用指针替换函数名即可
1 | bool (*pf)(const string &, const string &); // 未初始化 |
pf前面有个*,因此pf是指针;右侧是形参列表,表示pf指向的函数;在观察左侧,发现函数的返回类型是布尔值,因此pf就是一个指向函数的指针。