[C++ Primer] 函数
函数
函数基础
-
函数是一个命名了的代码块,一个典型的函数定义包括以下部分:返回类型、函数名字、由0个或多个形参组成的列表以及函数体。
-
函数的调用完成两项工作:一是实参初始化形参,二是将控制权转移给被调用函数。
-
return语句也完成两项工作:一是返回return语句中的值,二是将控制权从被调函数转移回主调函数。
-
局部静态对象:将局部变量定义成static类型
在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。。
若局部静态变量没有显示的初始值,将执行值初始化,内置类型局部静态变量初始化为0。
-
分离式编译:允许将程序分割到几个文件中去,每个文件独立编译。若修改了其中一个源文件,只需重新编译改动的文件。大多数编译器提供了分离式编译每个文件的机制,这一过程通常会产生一个后缀名是.obj(Windows)或.o(UNIX)的文件,后缀名的含义是该文件包含对象代码(object code)。
参数传递
-
当形参是引用类型时,对应的实参被引用传递或者函数被传引用调用。当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。这样的实参被值传递或者函数被传值调用。
-
指针形参:执行指针拷贝操作时,拷贝的是指针的值。拷贝之后,两个指针是不同的指针。
-
使用引用避免拷贝:拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型(包括IO类型)不支持拷贝操作。
-
如果函数无须改变引用类型的值,最好将其声明为常量引用。
// 比较两个string对象的长度 bool isSHorter(const string &s1, const string &s2) { return s1.size() < s2.size(); }
-
使用引用形参返回额外信息
// 返回s中c第一次出现的位置索引 // 引用形参occurs负责统计c出现的总次数 string::size_type find_char(const string &s, char c, string::size_type &occurs) { // 具体实现 }
-
管理指针形参的三种常用技术:
-
使用标记指定数组长度
要求数组本身包含一个结束标记,典型示例是C风格字符串,C风格字符串存储在字符数组中,并且在最后一个字符后面跟着一个空字符。
-
使用标准库规范:传递指向数组首元素和尾后元素的指针
void print(const int *beg, const int *end) { // 输出beg到end之间(不含end)的所有元素 while(beg != end) cout << *beg++ << endl; // 输出当前元素并将指针向前移动一个位置 } int j[2] = {0,1}; print(begin(j),end(j));
-
显示传递一个表示数组大小的形参
// const int ia[] 等价于const int* ia // size表示数组的大小,将其显式地传给函数用于控制对ia元素的访问 void print(const int ia[], size_t size) { for(size_t i = 0; i != size; ++i){ cout << ia[i] << endl; } } int j[] = {0,1}; print(j,end(j) - begin(j));
-
-
main:处理命令行选项
假定main函数位于可执行文件prog之内,可以向程序传递下面的选项:prog -d -o ofile data0
这些命令行选项通过两个(可选的)形参传递给main函数:
int main(int argc, char *argv[])}{...}
第二个形参是一个数组,它的元素是指向C风格字符串的指针;第一个形参argc表示数组中字符串的数量。因为第二个形参是数组,所以main函数也可以写成:
int main(int argc, char **argv){...} // argv指向char*
实参传递给main函数之后,argv第一个元素指向程序的名字或者空字符串,接下来的元素依次传递命令行提供的实参,最后一个指针之后的元素值保证为0。以上面提供的命令行为例,argc应该等于5,argv应该包含如下的C风格字符串:
argv[0] = "prog"; // 或者argv[0]也可以指向一个空字符串"" argv[1] = "-d"; argv[2] = "-o"; argv[3] = "ofile"; argv[4] = "data0 "; argv[5] = 0;
使用argv实参时,记得可选的实参从argv[1]开始;argv[0]保存程序的名字,而非用户输入。
-
含有可变形参的函数
-
initializer_list标准库类型
函数实参数量未知但是类型相同,也是一种模板类型。initialize_list对象中的元素永远是常量值,无法改变对象中元素的值。
void error_msg(initializer_list<string> il) { for(auto beg = il.begin(); beg != il.end(); ++beg ) cout << *beg << " "; cout << endl; }
-
可变参数模板
-
省略符
使用了varargs的C标准库
-
返回类型和return语句
-
返回的值用于初始化函数调用点的临时量。
-
声明一个返回数组指针的函数
函数形式如下:
Type (*function(parameter_list))[deimension]
Type表示元素的类型,demision表示数组大小。
(*function(parameter_list))
两端的括号必须存在,如果没有这对括号,函数返回类型将是指针的数组。具体例子:
int (*func(int i))[10];
可以按照以下的顺序来逐层理解该声明的含义:
func(int i)
表示调用func函数时需要int类型实参。(*func(int i))
意味可以对函数调用的结果执行解引用操作。(*func(int i))[10]
表示解引用func的调用将得到一个大小为10的数组。int (*func(int i))[10]
表示数组中的元素是int类型。
-
尾置返回类型
任何函数的定义都能用尾置返回,这种形式对于返回类型比较复杂的函数最有效。尾置返回类型跟在形参列表后面并以一个->符号开头。为了表示函数真正的返回类型跟在形参列表之后,在本应该出现返回类型的地方放置一个auto:
// func接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组 auto func(int i) -> int(*)[10];
函数重载
特殊用途语言特性
默认实参
- 一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值。
- 当设计含有默认实参的函数时,合理设置形参顺序,尽量让不怎么使用默认值的形参出现在前面,让经常使用默认值的的形参出现在后面。
内联函数和constexpr函数
- 内联函数可避免函数调用的开销,在返回类型前加上关键字
inline
,就可以将其声明为内联函数。
inline const string &shorterString(const string &s1, const string &s2)
{
return s1.size()<=s2.size() ? s1 : s2;
}
内联函数只是向编译器发出一个内联展开的请求,编译器可以选择忽略这个请求。
-
constexpr函数
函数的返回类型及所有形参类型都得是字面值类型,函数体中必须有且仅有一条return语句。
调试帮助
-
assert预处理宏
assert(expr);
对expr求值,若表达式为假,assert输出信息并终止程序的执行。如果表达式为真,assert什么也不做。
-
NDEBUG预处理变量
assert的行为依赖于NDEBUG的预处理状态。若定义了NDEBUG,则assert什么也不做。
定义NDEBUG能避免检查各种条件所需的运行时开销,当然此时根本就不会执行运行时检查。
除了用于assert之外,也可以使用NDEBUG编写自己的条件调试代码。如果NDEBUG未定义,将执行
#ifndef
和#endif
之间的代码;如果定义了NDEBUG,这些代码将被忽略掉:void print(const int ia[], size_t size) { #ifndef NDEBUG // __func__是编译器定义的一个局部静态变量,用于存放函数的名字 cerr << __func__ << ": array size is " << size <<endl; #endif //... }
变量
__func__
输出当前调试的函数的名字。它是const char的静态数组。预处理器还定义了另外4个对于程序调试很有用的名字:
__FILE__ // 存放文件名的字符串字面值 __LINE__ // 存放当前行号的整型字面值 __TIME__ // 存放文件编译时间的字符串字面值 __DATE__ // 存放文件编译日期的字符串字面值
函数匹配
为了确定最佳匹配,编译器将实参类型到形参类型的转换分成几个等级,具体排序如下:
- 精确匹配,包括以下情况:
- 实参类型和形参类型相同
- 实参从数组类型或函数类型转换成对应的指针类型
- 向实参添加顶层const或者从实参中删除顶层const
- 通过const转换实现的匹配
- 通过类型提升实现的匹配
- 通过算术类型转换或指针转换实现的匹配
- 通过类类型转换实现的匹配
函数指针
-
函数指针指向的是函数而非对象。函数指针指向某种特定类型,函数的类型由它的返回类型和形参类型哦共同决定,与函数名无关。例如:
// 比较两个string对象的长度 bool lengthCompare(const string &, const string &);
该函数的类型是
bool(const string &, const string &)
,该函数的指针只需用指针替换函数名即可:// pf指向一个函数 bool (*pf)(const string &, const string &);