7.1 函数的定义
函数调用做了两件事情:用对应的实参初始化函数的形参,并将控制权转移给被调函数。
// return the greatest common divisor int gcd(int v1, int v2) { while (v2) { int temp = v2; v2 = v1 % v2; v1 = temp; } return v1; }
7.2 参数传递
形参的初始化与变量的初始化一样,如果形参具有非引用类型,则复制实参的值;如果形参为引用类型,则它只是实参的别名。
非引用形参
实参副本初始化形参,函数并未访问实参本身,因此不会修改实参的值。
1)指针形参
如需保护指针指向的值,形参需定义为指向const对象的指针;
2)const形参
若形参是普通形参,因为初始化复制了初始化式的值,可以传递const实参;
若形参是const形参,则在函数中不可改变实参的局部副本。
//What may be surprising, is that although the parameter is a const inside the function, //the compiler otherwise treats the definition of fcn as if we had defined the parameter as a plain int: void fcn(const int i) { /* fcn can read but not write to i */ } void fcn(int i) { /* ... */ } // error: redefines fcn(int)
3)不宜复制实参的情况
需要修改实参值;大型对象作为实参传递;无法实现对象复制
引用形参
引用形参直接关联到其所绑定的对象,而非这些对象的副本。
1)引用形参返回额外信息
// returns an iterator that refers to the first occurrence of value // the reference parameter occurs contains a second return value vector<int>::const_iterator find_val( vector<int>::const_iterator beg, // first element vector<int>::const_iterator end, // one past last element int value, // the value we want vector<int>::size_type &occurs) // number of times it occurs { // res_iter will hold first occurrence, if any vector<int>::const_iterator res_iter = end; occurs = 0; // set occurrence count parameter for ( ; beg != end; ++beg) if (*beg == value) { // remember first occurrence of value if (res_iter == end) res_iter = beg; ++occurs; // increment occurrence count } return res_iter; // count returned implicitly in occurs }
2)利用const引用避免复制
避免复制实参且不对实参做修改,应将形参定义为const引用。
应该将不需要修改的引用形参定义为const引用。普通的非const引用形参在使用时不太灵活。这样的形参既不能用const对象初始化,也不能用字面值或产生右值的表达式实参初始化。
vector和其它容器类型的形参
从避免复制vector的角度出发,应考虑将形参声明为引用类型。事实上,C++倾向于通过传递指向容器中元素的迭代器来传递容器。
// pass iterators to the first and one past the last element to print void print(vector<int>::const_iterator beg, vector<int>::const_iterator end) { while (beg != end) { cout << *beg++; if (beg != end) cout << " "; // no space after last element } cout << endl; }
数组形参
数组的两个特殊性质:1)不能直接复制数组;2)使用数组名时,数组名会自动转化为指向其第一个元素的指针。
指定数组形参
// three equivalent definitions of printValues void printValues(int*) { /* ... */ } void printValues(int[]) { /* ... */ } void printValues(int[10]) { /* ... */ }
编译器检查数组形参关联的实参时,不会检查数组的长度。
通过引用传递数组
数组形参可声明为数组的引用。如果形参是数组的引用,编译器不会将实参转化为指针,而是传递数组的引用本身。编译器检查数组实参大小和形参大小是否一致。
其它
任何处理数组的程序都要确保程序停留在数组的边界内。
main:命令行处理选项
int main( int argc, char *argv[ ] ) {}
//argv的第一个字符串是程序名,传递给主函数的参数从argv[1]开始计
含有可变形参的函数
void foo( parm_list, … ); void foo( … );
7.3 return语句
具有返回值的函数
在含有return语句的循环后没有提供return语句很危险,因为大部分编译器不能检测出这个漏洞,运行时会出现什么问题是不确定的。
返回非引用类型
返回类型不是引用时,在调用函数的地方会将函数返回值复制给临时对象。返回值既可以是局部对象,也可以是求解表达式的结果。
两个千万
千万不能返回局部变量的引用。函数执行完毕时,将释放分配给局部对象的存储空间。此时,局部对象的引用就会指向不确定的内存。
千万不能返回指向局部对象的指针。
递归
// recursive version greatest common divisor program int rgcd(int v1, int v2) { if (v2 != 0) // we're done once v2 gets to zero return rgcd(v2, v1%v2); // recurse, reducing v2 on each call return v1; }
7.4 函数声明
函数声明由返回类型、函数名和形参列表组成,这三个元素被称为函数原型(function prototype);函数声明中的形参名会被忽略。
默认实参
A default argument can be any expression of an appropriate type:
string::size_type screenHeight(); string::size_type screenWidth(string::size_type); char screenDefault(char = ' '); string screenInit( string::size_type height = screenHeight(), string::size_type width = screenWidth(screenHeight()), char background = screenDefault());
在一个文件中,只能为一个形参指定默认实参一次。应该在函数声明中指定默认实参。如果在函数定义的形参表中提供默认实参,那么只有在包含该函数定义的源文件中调用该函数,默认实参才是有效的。
7.5 局部对象
名字的作用域指的是知道该名字的程序文本区;对象的生命期指的是在程序执行过程中对象存在的时间。
自动对象
默认情况下,局部变量的生命期局限于所在函数的每次执行期间。只有当定义它的函被调用时才存在的对象称为自动对象。自动对象在每次调用函数时创建和撤销。
静态局部对象
一个变量位于函数作用域,生命期却跨域了这个函数的多次调用,应定义为static。
static局部对象确保不迟于在程序执行流程第一次经过该对象的定义语句时进行初始化。这种对象一旦被创建,在程序结束前都不会被撤销。
7.6 内联函数
背景:调用函数比求解等价表达式要慢得多
调用函数要做很多工作,1)调用前要先保存寄存器,并在返回时恢复;2)复制实参;3)程序还必须转向一个新位置执行。
内联函数(inline)避免调用函数的开销
将函数指定为内联函数,就是将它在程序中每个调用点上“内联地”展开。内联说明对编译器来说只是一个建议。
内联函数在头文件中定义
内联函数的定义对编译器而言必须是可见的,以便编译器能够在调用点内联展开该函数的代码。此时,仅有函数原型是不够的。
7.7 类的成员函数
成员函数
编译器隐式地将在类内定义的成员函数当做内联函数;
在调用非static成员函数时,隐含形参 this 初始化为调用函数的对象的地址。
const成员函数中const改变了this形参的类型,使this成为指向const对象的指针,因而不能修改该对象的数据成员。
const对象、指向const对象的指针或引用只能用于调用其 const 成员函数。
构造函数
默认构造函数说明当定义对象却没有为它提供显式初始化式时应该怎么办
构造函数是public成员
我们希望使用类的代码可以定义和初始化类的对象,如果将构造函数定义为private,则不能定义类的对象。(只有在public部分定义的成员才可被使用该类型的所有代码访问)
合成的默认构造函数
如果没有为一个类显示定义任何构造函数,编译器将自动为这个类生成默认构造函数,即synthesized default constructor,它将根据变量初始化规则来初始化类中所有成员。
合成默认构造函数一般适用于仅包含类类型成员的类。而对于含有内置类型或复合类型成员的类,则通常应该定义他们自己的默认构造函数初始化这些成员。
7.8 重载函数
重载的一些概念
准确定义
相同作用域中的函数,名字相同而形参表不同。
与重复声明的区别
Two parameter lists can be identical, even if they don't look the same:
// each pair declares the same function Record lookup(const Account &acct); Record lookup(const Account&); // parameter names are ignored typedef Phone Telno; Record lookup(const Phone&); Record lookup(const Telno&); // Telno and Phone are the same type Record lookup(const Phone&, const Name&); // default argument doesn't change the number of parameters Record lookup(const Phone&, const Name& = ""); // const is irrelevent for nonreference parameters Record lookup(Phone); Record lookup(const Phone); // redeclaration
复制形参时并不考虑形参是否为const,函数操纵的只是副本,函数无法修改实参。当形参以副本传递时,不能基于形参是否为const来实现重载
实参类型转换
1)类型提升或转换的匹配
较小的整型提升为int型
void ff(int); void ff(short); ff('a'); // char promotes to int, so matches f(int)
通过类型提升实现的转换优于其他标准转换,如char到int优于double,char到double优于unsigned char
2)参数匹配和枚举类型
枚举值传递给整型形参时,枚举值被提升为int型或更大的整型。
3)重载和const形参
仅当形参是引用或指针时,形参是否为const才有影响。
7.9 指向函数的指针
指向指针的函数称为函数指针,函数指针指向特定的类型,函数类型由其返回类型以及形参表确定,而与函数名无关。
用typedef简化函数指针定义
typedef bool (*cmpFcn)(const string &, const string &);
函数名与函数指针
直接引用函数名等效于在函数名上应用取地址操作符;
函数指针只能通过同类型的函数或函数指针或0值常量表达式进行初始化及赋值;
指向不同函数类型的指针之间不存在转换;
通过指针调用函数
cmpFcn pf = lengthCompare; lengthCompare("hi", "bye"); // direct call pf("hi", "bye"); // equivalent call: pf1 implicitly dereferenced (*pf)("hi", "bye"); // equivalent call: pf1 explicitly dereferenced
函数的形参可以是指向函数的指针
/* useBigger function's third parameter is a pointer to function * that function returns a bool and takes two const string references * two ways to specify that parameter: */ // third parameter is a function type and is automatically treated as a pointer to function void useBigger(const string &, const string &, bool(const string &, const string &)); // equivalent declaration: explicitly define the parameter as a pointer to function void useBigger(const string &, const string &, bool (*)(const string &, const string &));
允许将形参定义为函数类型(实参将自动转换为函数指针),但函数的返回类型则必须是指向函数的指针,而不能是函数。
// func is a function type, not a pointer to function! typedef int func(int*, int); void f1(func); // ok: f1 has a parameter of function type func f2(int); // error: f2 has a return type of function type func *f3(int); // ok: f3 returns a pointer to function typ
指向重载函数的指针
C++语言中,函数指针指向重载的函数时,指针的类型必须与重载函数的一个版本精确匹配。