C++primer第七章 函数
本章将介绍函数的定义和声明。然后具体分析三类特殊的函数:内联(inline)函数、类成员函数和重载函数。
7.1. 函数的定义
函数由函数名以及一组操作数类型唯一地表示。函数的操作数,也即形参,在一对圆括号中声明,形参与形参之间以逗号分隔。函数执行的运算在一个称为函数体的块语句中定义。每一个函数都有一个相关联的返回类型。
函数的调用
C++ 语言使用调用操作符(即一对圆括号)实现函数的调用。正如其他操作符一样,调用操作符需要操作数并产生一个结果。调用操作符的操作数是函数名和一组(有可能是空的)由逗号分隔的实参。
函数体是一个作用域
函数体是一个语句块,定义了函数的具体操作。
通常,这个块语句包含在一对花括号中,形成了一个新的作用域。和其他的块语句一样,在函数体中可以定义变量。在函数体内定义的变量只在该函数中才可以访问。这种变量称为局部变量。
当执行到 return 语句时,函数调用结束。被调用的函数完成时,将产生一个在return 语句中指定的结果值。执行 return 语句后,被挂起的主调函数在调用处恢复执行,并将函数的返回值用作求解调用操作符的结果,继续处理在执行调用的语句中所剩余的工作。
形参和实参
形参是在函数的形参表中定义的,并由调用函数时传递函数的实参初始化。
实参则是一个表达式。它可以是变量或字面值常量,甚至是包含一个或几个操作符的表达式。在调用函数时,所传递的实参个数必须与函数的形参个数完全相同。
7.1.1. 函数返回类型
函数的返回类型可以是内置类型(如 int 或者 double)、类类型或复合类型(如int& 或 string*),还可以是 void 类型,表示该函数不返回任何值。
bool is_present(int *, int); // returns bool int count(const string &, char); // returns int Date &calendar(const char*); // returns reference to Date void process(); // process does not return a value
函数必须指定返回类型
在定义或声明函数时,没有显式指定返回类型是不合法的:
// error: missing return type test(double v1, double v2) { /* ... */ }
7.1.2. 函数形参表
函数形参表可以为空,但不能省略。没有任何形参的函数可以用空形参表或含有单个关键字 void 的形参表来表示。
如果两个参数具有相同的类型,则其类型必须重复声明:
int manip(int v1, v2) { /* ... */ } // error int manip(int v1, int v2) { /* ... */ } // ok
参数表中不能出现同名的参数。类似地,局部于函数的变量也不能使用与函数的任意参数相同的名字。
7.2. 参数传递
每次调用函数时,都会重新创建该函数所有的形参,此时所传递的实参将会初始化对应的形参。
形参的初始化与变量的初始化一样:如果形参具有非引用类型,则复制实参的值,如果形参为引用类型(第 2.5 节),则它只是实参的别名。
7.2.1. 非引用形参
普通的非引用类型的参数通过复制对应的实参实现初始化。当用实参副本初始化形参时,函数并没有访问调用所传递的实参本身,因此不会修改实参的值。
指针形参
函数的形参可以是指针(第 4.2 节),此时将复制实参指针。与其他非引用类型的形参一样,该类形参的任何改变也仅作用于局部副本。如果函数将新指针赋给形参,主调函数使用的实参指针的值没有改变。
被复制的指针只影响对指针的赋值。
const 形参
在调用函数时,如果该函数使用非引用的非 const 形参,则既可给该函数传递 const 实参也可传递非 const 的实参。
这种行为源于 const 对象的标准初始化规则(第 2.4 节)。因为初始化复制了初始化式的值,所以可用 const 对象初始化非 const 对象,反之亦然
复制实参的局限性
• 当需要在函数中修改实参的值时。
• 当需要以大型对象作为实参传递时。对实际的应用而言,复制对象所付出的时间和存储空间代价往往过在。
• 当没有办法实现对象的复制时。
7.2.2. 引用形参
// incorrect version of swap: The arguments are not changed! void swap(int v1, int v2) { int tmp = v2; v2 = v1; // assigns new value to local copy of the argument v1 = tmp; } // local objects v1 and v2 no longer exist
// ok: swap acts on references to its arguments void swap(int &v1, int &v2) { int tmp = v2; v2 = v1; v1 = tmp; }
使用引用形参返回额外的信息
在一个整型 vector 对象的元素中搜索某个特定值。如果找到满足要求的元素,则返回指向该元素的迭代器;否则返回一个迭代器,指向该 vector 对象的 end 操作返回的元素。
// 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 }
利用const 引用避免复制
在向函数传递大型对象时,需要使用引用形参,这是引用形参适用的另一种情况。虽然复制实参对于内置数据类型的对象或者规模较小的类类型对象来说没有什么问题,但是对于大部分的类类型或者大型数组,它的效率(通常)太低了;此外,我们将在第十三章学习到,某些类类型是无法复制的。使用引用形参,函数可以直接访问实参对象,而无须复制它。
更灵活的指向 const 的引用
// function takes a non-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 are not lvalues v3 = incr(v1 + v2); // error: addition doesn‘t yield an lvalue int v4 = incr(v3); // ok: v3 is a non const object type int }
7.2.3. vector 和其他容器类型的形参
通常,函数不应该有 vector 或其他标准库容器类型的形参。调用含有普通的非引用 vector 形参的函数将会复制 vector 的每一个元素。
// 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; }
7.2.4. 数组形参
数组有两个特殊的性质,影响我们定义和使用作用在数组上的函数:一是不能复制数组(第 4.1.1 节);二是使用数组名字时,数组名会自动转化为指向其第一个元素的指针(第 4.2.4 节)
数组形参的定义
如果要编写一个函数,输出 int 型数组的内容,可用下面三种方式指定数组形参:
// three equivalent definitions of printValues void printValues(int*) { /* ... */ } void printValues(int[]) { /* ... */ } void printValues(int[10]) { /* ... */ }
形参的长度会引起误解
// parameter treated as const int*, size of array is ignored void printValues(const int ia[10]) { // this code assumes array has 10 elements; // disaster if argument has fewer than 10 elements! for (size_t i = 0; i != 10; ++i) { cout << ia[i] << endl; } }
int main() { int i = 0, j[2] = {0, 1}; printValues(&i); // ok: &i is int*; probable run-time error printValues(j); // ok: j is converted to pointer to 0th // element; argument has type int*; // probable run-time error return 0; }
当编译器检查数组形参关联的实参时,它只会检查实参是不是指针、指针的类型和数组元素的类型时是否匹配,而不会检查数组的长度。
数组实参
在传递数组时,实参是指向数组第一个元素的指针,形参复制的是这个指针的值,而不是数组元素本身
通过引用传递数组
数组大小成为形参和实参类型的一部分。编译器检查数组的实参的大小与形参的大小是否匹配:
// ok: parameter is a reference to an array; size of array is fixed void printValues(int (&arr)[10]) { /* ... */ } int main() { int i = 0, j[2] = {0, 1}; int k[10] = {0,1,2,3,4,5,6,7,8,9}; printValues(&i); // error: argument is not an array of 10 ints printValues(j); // error: argument is not an array of 10 ints printValues(k); // ok: argument is an array of 10 ints return 0; }
7.2.6. main: 处理命令行选项
int main(int argc, char *argv[]) { ... }
第二个形参 argv 是一个 C 风格字符串数组。第一个形参 argc 则用于传递该数组中字符串的个数。由于第二个参数是一个数组,主函数 main 也可以这样定义:
int main(int argc, char **argv) { ... }
7.3. return 语句
7.3.1. 没有返回值的函数
不带返回值的 return 语句只能用于返回类型为 void 的函数。在返回类型为 void 的函数中,return 返回语句不是必需的,隐式的 return 发生在函数的最后一个语句完成时。
7.3.2. 具有返回值的函数
return 语句的第二种形式提供了函数的结果。任何返回类型不是 void 的函数必须返回一个值,而且这个返回值的类型必须和函数的返回类型相同,或者能隐式转化为函数的返回类型。
7.3.3. 递归
直接或间接调用自己的函数称为递归函数。
// calculate val!, which is 1*2 *3 ... * val int factorial(int val) { if (val > 1) return factorial(val-1) * val; return 1; }
7.4. 函数声明
正如变量必须先声明后使用一样,函数也必须在被调用之前先声明。与变量的定义(第 2.3.5 节)类似,函数的声明也可以和函数的定义分离;一个函数只能定义一次,但是可声明多次。
函数声明由函数返回类型、函数名和形参列表组成。形参列表必须包括形参类型,但是不必对形参命名。这三个元素被称为函数原型,函数原型描述了函数的接口。
7.4.1. 默认实参
下面的函数创建并初始化了一个 string 对象,用于模拟窗口屏幕。此函数为窗口屏幕的高、宽和背景字符提供了默认实参:
string screenInit(string::size_type height = 24, string::size_type width = 80, char background = ‘ ‘ );
调用包含默认实参的函数时,可以为该形参提供实参,也可以不提供。如果提供了实参,则它将覆盖默认的实参值;否则,函数将使用默认实参值。下面的函数screenInit 的调用都是正确的:
string screen; screen = screenInit(); // equivalent to screenInit (24,80,‘‘) screen = screenInit(66); // equivalent to screenInit (66,80,‘‘) screen = screenInit(66, 256); // screenInit(66,256,‘ ‘) screen = screenInit(66, 256, ‘#‘);
函数调用的实参按位置解析,默认实参只能用来替换函数调用缺少的尾部实参。例如,如果要给 background 提供实参,那么也必须给 height 和 width 提供实参:
screen = screenInit(, , ‘?‘); // error, can omit only trailing arguments screen = screenInit( ‘?‘); // calls screenInit(‘?‘,80,‘ ‘)
7.5. 局部对象
在 C++ 语言中,每个名字都有作用域,而每个对象都有生命期。要弄清楚函数是怎么运行的,理解这两个概念十分重要。名字的作用域指的是知道该名字的程序文本区。对象的生命期则是在程序执行过程中对象存在的时间。
7.6. 内联函数
将函数指定为 inline 函数,(通常)就是将它在程序中每个调用点上“内联地”展开。
// inline version: find longer of two strings inline const string & shorterString(const string &s1, const string &s2) { return s1.size() < s2.size() ? s1 : s2; }
7.7. 类的成员函数
成员函数的定义与普通函数的定义类似。和任何函数一样,成员函数也包含下面四个部分
• 函数返回类型。
• 函数名。
• 用逗号隔开的形参表(也可能是空的)。
• 包含在一对花括号里面的函数体。
7.7.1. 定义成员函数的函数体
this 指针的引入
每个成员函数(除了在第 12.6 节介绍的 static 成员函数外)都有一个额外的、隐含的形参 this。
7.7.2. 在类外定义成员函数
在类的定义外面定义成员函数必须指明它们是类的成员:
double Sales_item::avg_price() const { if (units_sold) return revenue/units_sold; else return 0; }
7.8. 重载函数
出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数。
7.9. 指向函数的指针
函数指针是指指向函数而非指向对象的指针。像其他指针一样,函数指针也
指向某个特定的类型。函数类型由其返回类型以及形参表确定,而与函数名无关:
// pf points to function returning bool that takes two const stringreferences bool (*pf)(const string &, const string &);
用 typedef 简化函数指针的定义
typedef bool (*cmpFcn)(const string &, const string &);
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。