函数小结 :
函数是有名字的计算单元,对程序(就算是小程序)的结构化至关重要。函数的定义由返回类型、函数名、形参表(可能为空)以及函数体组成。函数体是调用函数时执行的语句块。在调用函数时,传递给函数的实参必须与相应的形参类型兼容。
给函数传递实参遵循变量初始化的规则。非引用类型的形参以相应实参的副本初始化。对(非引用)形参的任何修改仅作用于局部副本,并不影响实参本身。 复制庞大而复杂的值有昂贵的开销。为了避免传递副本的开销,可将形参指定为引用类型。对引用形参的任何修改会直接影响实参本身。应将不需要修改相应实参的引用形参定义为const 引用。
在 C++ 中,函数可以重载。只要函数中形参的个数或类型不同,则同一个函数名可用于定义不同的函数。编译器将根据函数调用时的实参确定调用哪一个函数。在重载函数集合中选择适合的函数的过程称为函数匹配。
C++ 提供了两种特殊的函数:内联函数和成员函数。将函数指定为内联是建议编译器在调用点直接把函数代码展开。内联函数避免了调用函数的代价。成员函数则是身为类成员的函数。
1. 函数不能返回另一个函数或者内置数组类型,但可以返回指向函数的指针,或指向数组元素的指针的指针:
// ok: pointer tofirst element of the array int *foo_bar() { /*... */ } //在定义或声明函数时,没有显式指定返回类型是不合法的: // error: missingreturn type test(double v1,double v2) { /* ... */
2.函数形参表
函数形参表可以为空,但不能省略。没有任何形参的函数可以用空形参表或含有单个关键字void 的形参表来表示。例如,下面关于process 的声明是等价的:
void process() { /*... */ } // implicit void parameter list void process(void){/* ... */ } // equivalent declaration
形参表由一系列用逗号分隔的参数类型和(可选的)参数名组成。如果两个参数
具有相同的类型,则其类型必须重复声明:
int manip(int v1, v2){ /* ... */ } // error int manip(int v1, intv2) { /* ... */ } // ok
参数表中不能出现同名的参数。类似地,局部于函数的变量也不能使用与函数的任意参数相同的名字。
参数名是可选的,但在函数定义中,通常所有参数都要命名。参数必须在命名后才能使用。
3. 参数传递
每次调用函数时,都会重新创建该函数所有的形参,此时所传递的实参将会初始化对应的形参。
形参的初始化与变量的初始化一样:如果形参具有非引用类型,
则复制实参的值,如果形参为引用类型,则它只是实参的别名。
4. 复制实参的局限性
复制实参并不是在所有的情况下都适合,不适宜复制实参的情况包括:
? 当需要在函数中修改实参的值时。
? 当需要以大型对象作为实参传递时。对实际的应用而言,复制对象所付出的时间和存储空间代价往往过在。
? 当没有办法实现对象的复制时。
5. 更灵活的指向const 的引用
如果函数具有普通的非const 引用形参,则显然不能通过const 对象进行调用。
毕竟,此时函数可以修改传递进来的对象,这样就违背了实参的const 特性。
但比较容易忽略的是,调用这样的函数时,传递一个右值或具有需要转换的类型的对象同样是不允许的:
// function takes anon-const reference parameter int incr(int &val) { return ++val; } int main() { short v1 = 0; const int v2 = 42; int v3 = incr(v1); // error: v1 is not an int v3 = incr(v2); // error: v2 is const v3 = incr(0); // error: literals arenot lvalues v3 = incr(v1 + v2); // error: addition doesn't yield anlvalue int v4 = incr(v3); // ok: v3 is a non const objecttype int }
6. 传递指向指针的引用 –指针的交换
假设我们想编写一个与前面交换两个整数的swap 类似的函数,实现两个指
针的交换。已知需用* 定义指针,用& 定义引用。现在,问题在于如何将这两
个操作符结合起来以获得指向指针的引用。这里给出一个例子:
// swap values of twopointers to int void ptrswap(int*&v1, int *&v2) { int *tmp = v2; v2 = v1; v1 = tmp; }
形参 int *&v1的定义应从右至左理解:v1是一个引用,与指向int 型对象的指针相关联。
也就是说,v1只是传递进ptrswap 函数的任意指针的别名。
重写第7.2.2 节的main 函数,调用ptrswap 交换分别指向值10 和20 的指针:
int main() { int i = 10; int j = 20; int *pi = &i; //pi points to i int *pj = &j; //pj points to j cout <<"Before ptrswap():\t*pi: " << *pi <<"\t*pj: " << *pj << endl; ptrswap(pi, pj); //now pi points to j; pj points to i cout <<"After ptrswap():\t*pi: "<< *pi << "\t*pj: " << *pj<< endl; return 0; } // 编译并执行后,该程序产生如下结果: Before ptrswap():*pi: 10 *pj: 20 After ptrswap(): *pi:20 *pj: 10
7. 千万不要返回局部对象的引用
理解返回引用至关重要的是:千万不能返回局部变量的引用。当函数执行完毕时,将释放分配给局部对象的存储空间。此时,对局部对象的引用就会指向不确定的内存。考虑下面的程序:
// Disaster: Functionreturns a reference to a local object const string&manip(const string& s) { string ret = s; // transform ret insome way return ret; // Wrong:Returning reference to a local object! }
这个函数会在运行时出错,因为它返回了局部对象的引用。当函数执行完毕,
字符串ret 占用的储存空间被释放,函数返回值指向了对于这个程序来说不再有效的内存空间。
8. 千万不要返回指向局部对象的指针
函数的返回类型可以是大多数类型。特别地,函数也可以返回指针类型。和返回局部对象的引用一样,返回指向局部对象的指针也是错误的。一旦函数结束,局部对象被释放,返回的指针就变成了指向不再存在的对象的悬垂指针.
9. 内联函数
调用函数比求解等价表达式要慢得多。在大多数的机器上,调用函数都要做很多工作;调用前要先保存寄存器,并在返回时恢复;复制实参;程序还必须转向一个新位置执行。
将函数指定为inline 函数,(通常)就是将它在程序中每个调用点上“内联地”展开。
假设我们将shorterString 定义为内联函数,则调用:
cout <<shorterString(s1, s2) << endl;
在编译时将展开为:
cout <<(s1.size() < s2.size() ? s1 : s2) << endl;
从而消除了把shorterString 写成函数的额外执行开销。
从而消除了把shorterString 写成函数的额外执行开销。
// inline version:find longer of two strings inline conststring & shorterString(conststring &s1, const string &s2) { return s1.size() <s2.size() ? s1 : s2; }
inline 说明对于编译器来说只是一个建议,编译器可以选择忽略这个。
一般来说,内联机制适用于优化小的、只有几行的而且经常被调用的函数。
大多数的编译器都不支持递归函数的内联。一个1200 行的函数也不太可能在调用点内联展开。
内联函数应该在头文件中定义,这一点不同于其他函数。
10. 重载与作用域
一般的作用域规则同样适用于重载函数名。如果局部地声明一个函数,则该函数
将屏蔽而不是重载在外层作用域中声明的同名函数。由此推论,每一个版本的重载函数都应在同一个作用域中声明。
一般来说,局部地声明函数是一种不明智的选择。函数的声明应放在头文件中。
/* Program for illustration purposes only: * It is bad style fora function to define a local variable * with the same nameas a global name it wants to use */ string init(); // the name init hasglobal scope void fcn() { int init = 0; // init is local andhides global init string s = init(); // error: global init is hidden } … void print(const string &); void print(double); // overloads the print function void fooBar(int ival) { void print(int); // new scope: hides previousinstances ofprint print("Value:"); // error:print(const string &) is hidden print(ival); //ok: print(int) is visible print(3.14); //ok: calls print(int); print(double) is hidden }
11. 候选函数
调用所考虑的重载函数集合,该集合中的函数称为候选函数。候选函数是与被调函数同名的函数,
可行函数
从候选函数中选择一个或多个函数,它们能够用该调用中指定的实参来调用。因此,选出来的函数称为可行函数.
12. 重载和const 形参
可基于函数的引用形参是指向const 对象还是指向非const 对象,实现函数重载。将引用形参定义为const 来重载函数是合法的,因为编译器可以根据实参是否为const 确定调用哪一个函数:
Recordlookup(Account&); Record lookup(constAccount&); // new function const Account a(0); Account b; lookup(a); // calls lookup(const Account&) lookup(b); // calls lookup(Account&)
如果形参是普通的引用,则不能将const 对象传递给这个形参。如果传递了const 对象,则只有带const 引用形参的版本才是该调用的可行函数。
13. 指向函数的指针
函数指针是指指向函数而非指向对象的指针。
14.函数指针
函数指针类型相当地冗长。使用typedef 为指针类型定义同义词,可将函数指针的使用大大简化:
typedef bool(*cmpFcn)(const string &, const string &);
在引用函数名但又没有调用该函数时,函数名将被自动解释为指向函数的指针。假设有函数:
// compares lengthsof two strings boollengthCompare(const string &, const string &);
除了用作函数调用的左操作数以外,对lengthCompare 的任何使用都被解释为如下类型的指针:
bool (*)(const string&, const string &); // 可使用函数名对函数指针做初始化或赋值: cmpFcn pf1 = 0; // ok: unboundpointer to function cmpFcn pf2 =lengthCompare; // ok: pointer typematches function's type pf1 = lengthCompare; // ok: pointer type matchesfunction's type pf2 = pf1; //ok: pointer types match // 此时,直接引用函数名等效于在函数名上应用取地址操作符: cmpFcn pf1 =lengthCompare; cmpFcn pf2 =&lengthCompare;
函数指针只能通过同类型的函数或函数指针或0 值常量表达
式进行初始化或赋值。
将函数指针初始化为0,表示该指针不指向任何函数。
指向不同函数类型的指针之间不存在转换:
string::size_typesumLength(const string&, const string&); boolcstringCompare(char*, char*); // pointer tofunction returning bool taking two const string& cmpFcn pf; pf = sumLength; // error: returntype differs pf = cstringCompare; // error: parameter typesdiffer pf = lengthCompare; // ok: function and pointertypes match exactly
15. 通过指针调用函数
指向函数的指针可用于调用它所指向的函数。可以不需要使用解引用操作
符,直接通过指针调用函数:
cmpFcn pf =lengthCompare; lengthCompare("hi","bye"); // direct call pf("hi","bye"); //equivalent call: pf1 implicitly dereferenced (*pf)("hi","bye"); //equivalent call: pf1 explicitly dereferenced
如果指向函数的指针没有初始化,或者具有0 值,则该指针不
能在函数调用中使用。只有当指针已经初始化,或被赋值为指
向某个函数,方能安全地用来调用函数。
16. 指向重载函数的指针
C++ 语言允许使用函数指针指向重载的函数:
extern voidff(vector<double>); extern voidff(unsigned int); // which functiondoes pf1 refer to? void (*pf1)(unsignedint) = &ff; // ff(unsigned) //指针的类型必须与重载函数的一个版本精确匹配。如果没有精确匹配的函数,则对该指针的初始化或赋值都将导致编译错误: // error: no match:invalid parameter list void (*pf2)(int) =&ff; // error: no match:invalid return type double(*pf3)(vector<double>); pf3 = &ff;