C++Primer笔记 第七章 函数

来源:互联网 发布:php模块开发教程 编辑:程序博客网 时间:2024/05/21 12:07
7.1. 函数的定义
函数由函数名以及一组操作数类型唯一地表示。
函数的操作数,也即形参,在一对圆括号中声明,形参与形参之间以逗号分隔。
函数执行的运算在一个称为函数体的块语句中定义。
每一个函数都有一个相关联的返回类型。


1. 函数的调用
C++ 语言使用调用操作符(即一对圆括号)实现函数的调用。
正如其他操作符一样,调用操作符需要操作数并产生一个结果。
调用操作符的操作数是函数名和一组(有可能是空的)由逗号分隔的实参。
函数调用的结果类型就是函数返回值的类型,该运算的结果本身就是函数的返回值


2. 函数体是一个作用域
函数体是一个语句块,定义了函数的具体操作。
通常,这个块语句包含在一对花括号中,形成了一个新的作用域。
和其他的块语句一样,在函数体中可以定义变量。
在函数体内定义的变量只在该函数中才可以访问。
这种变量称为局部变量,它们相对于定义它们的函数而言是“局部”的,其名字只能在该函数的作用域中可见。
这种变量只在函数运行时存在


当执行到 return 语句时,函数调用结束。
被调用的函数完成时,将产生一个在return 语句中指定的结果值。
执行 return 语句后,被挂起的主调函数在调用处恢复执行,并将函数的返回值用作求解调用操作符的结果,
继续处理在执行调用的语句中所剩余的工作。


3. 形参和实参
类似于局部变量,函数的形参为函数提供了已命名的局部存储空间。
它们之间的差别在于形参是在函数的形参表中定义的,并由调用函数时传递函数的实参初始化。


实参则是一个表达式。
它可以是变量或字面值常量,甚至是包含一个或几个操作符的表达式。
在调用函数时,所传递的实参个数必须与函数的形参个数完全相同。


7.1.1. 函数返回类型
函数的返回类型可以是内置类型(如 int 或者 double)、类类型或复合类型(如int& 或 string*),
还可以是 void 类型,表示该函数不返回任何值。


1. 函数必须指定返回类型
在定义或声明函数时,没有显式指定返回类型是不合法的


7.1.2. 函数形参表
函数形参表可以为空,但不能省略。
没有任何形参的函数可以用空形参表或含有单个关键字 void 的形参表来表示


1. 参数类型检查
C++ 是一种静态强类型语句,对于每一次的函数调用,编译时都会检查其实参。


7.2. 参数传递
每次调用函数时,都会重新创建该函数所有的形参,此时所传递的实参将会初始化对应的形参


7.2.1. 非引用形参
普通的非引用类型的参数通过复制对应的实参实现初始化。
当用实参副本初始化形参时,函数并没有访问调用所传递的实参本身,因此不会修改实参的值。


1. 指针形参
函数的形参可以是指针(第 4.2 节),此时将复制实参指针。
与其他非引用类型的形参一样,该类形参的任何改变也仅作用于局部副本。
如果函数将新指针赋给形参,主调函数使用的实参指针的值没有改变。


2. const 形参
在调用函数时,如果该函数使用非引用的非 const 形参,
则既可给该函数传递 const 实参也可传递非 const 的实参。


例如,可以传递两个 int 型 const对象调用 gcd:
const int i = 3, j = 6;
int k = rgcd(3, 6); // ok: k initialized to 3


这种行为源于 const 对象的标准初始化规则。
因为初始化复制了初始化式的值,所以可用 const 对象初始化非 const 对象,反之亦然。


3. 复制实参的局限性
复制实参并不是在所有的情况下都适合,不适宜复制实参的情况包括:
• 当需要在函数中修改实参的值时。
• 当需要以大型对象作为实参传递时。对实际的应用而言,复制对象所付出的时间和存储空间代价往往过在。
• 当没有办法实现对象的复制时。


7.2.2. 引用形参
1. 使用引用形参返回额外的信息


2. 利用 const 引用避免复制


3. 更灵活的指向 const 的引用
如果函数具有普通的非 const 引用形参,则显然不能通过 const 对象进行调用


4. 传递指向指针的引用


7.2.3. vector 和其他容器类型的形参
通常,函数不应该有 vector 或其他标准库容器类型的形参。
调用含有普通的非引用 vector 形参的函数将会复制 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;
}


这个函数将输出从 beg 指向的元素开始到 end 指向的元素(不含)为止的范围内所有的元素。
除了最后一个元素外,每个元素后面都输出一个空格。


7.2.4. 数组形参
数组有两个特殊的性质,影响我们定义和使用作用在数组上的函数:
一是不能复制数组;
二是使用数组名字时,数组名会自动转化为指向其第一个元素的指针。


因为数组不能复制,所以无法编写使用数组类型形参的函数。
因为数组会被自动转化为指针,所以处理数组的函数通常通过操纵指向数组指向数组中的元素的指针来处理数组。
数组形参的定义如果要编写一个函数,输出 int 型数组的内容,


可用下面三种方式指定数组形参:
// three equivalent definitions of printValues
void printValues(int*) { /* ... */ }
void printValues(int[]) { /* ... */ }
void printValues(int[10]) { /* ... */ }


虽然不能直接传递数组,但是函数的形参可以写成数组的形式。
虽然形参表示方式不同,但可将使用数组语法定义的形参看作指向数组元素类型的指针。
上面的三种定义是等价的,形参类型都是 int*。


1. 通过引用传递数组
和其他类型一样,数组形参可声明为数组的引用。如果形参是数组的引用,
编译器不会将数组实参转化为指针,而是传递数组的引用本身。
在这种情况下,数组大小成为形参和实参类型的一部分


2. 多维数组的传递
回顾前面我们说过在 C++ 中没有多维数组。所谓多维数组实际是指数组的数组。


7.2.7. 含有可变形参的函数
C++ 中的省略符形参是为了编译使用了 varargs 的 C 语言程序。
关于如何使用 varargs,请查阅所用 C 语言编译器的文档。
对于 C++ 程序,只能将简单数据类型传递给含有省略符形参的函数。
实际上,当需要传递给省略符形参时,大多数类类型对象都不能正确地复制。


7.3. return 语句
return 语句用于结束当前正在执行的函数,并将控制权返回给调用此函数的函数。


return 语句有两种形式:
return;
return expression;


7.3.1. 没有返回值的函数
不带返回值的 return 语句只能用于返回类型为 void 的函数。
在返回类型为 void 的函数中,return 返回语句不是必需的,
隐式的 return 发生在函数的最后一个语句完成时。


7.3.2. 具有返回值的函数
return 语句的第二种形式提供了函数的结果。
任何返回类型不是 void 的函数必须返回一个值,而且这个返回值的类型必须和函数的返回类型相同,
或者能隐式转化为函数的返回类型。


1. 主函数 main 的返回值
返回类型不是 void 的函数必须返回一个值,但此规则有一个例外情况:
允许主函数 main 没有返回值就可结束。
如果程序控制执行到主函数 main 的最后一个语句都还没有返回,那么编译器会隐式地插入返回 0 的语句。


7.3.3. 递归
直接或间接调用自己的函数称为递归函数。


7.4. 函数声明
正如变量必须先声明后使用一样,函数也必须在被调用之前先声明。
与变量的定义类似,函数的声明也可以和函数的定义分离;
一个函数只能定义一次,但是可声明多次。


函数声明由函数返回类型、函数名和形参列表组成。
形参列表必须包括形参类型,但是不必对形参命名。
这三个元素被称为函数原型,函数原型描述了函数的接口。
函数原型为定义函数的程序员和使用函数的程序员之间提供了接口。
在使用函数时,程序员只对函数原型编程即可。
函数声明中的形参名会被忽略,如果在声明中给出了形参的名字,它应该用作辅助文档:


void print(int *array, int size);


1. 在头文件中提供函数声明
回顾前面章节,变量可在头文件中声明(第 2.9 节),而在源文件中定义。
同理,函数也应当在头文件中声明,并在源文件中定义


7.4.1. 默认实参
默认实参是一种虽然并不普遍、但在多数情况下仍然适用的实参值。调用函数时,
可以省略有默认值的实参。编译器会为我们省略的实参提供默认值。


7.5. 局部对象
在 C++ 语言中,每个名字都有作用域,而每个对象都有生命期。
要弄清楚函数是怎么运行的,理解这两个概念十分重要。
名字的作用域指的是知道该名字的程序文本区。
对象的生命期则是在程序执行过程中对象存在的时间。


7.5.1. 自动对象
默认情况下,局部变量的生命期局限于所在函数的每次执行期间。
只有当定义它的函数被调用时才存在的对象称为自动对象。
自动对象在每次调用函数时创建和撤销。


7.6. 内联函数


7.7. 类的成员函数
成员函数的定义与普通函数的定义类似。和任何函数一样,成员函数也包含下面四个部分:
• 函数返回类型。
• 函数名。
• 用逗号隔开的形参表(也可能是空的)。
• 包含在一对花括号里面的函数体。


1. this 指针的引入
每个成员函数(除了在第 12.6 节介绍的 static 成员函数外)都有一个额外的、隐含的形参 this。
在调用成员函数时,形参 this 初始化为调用函数的对象的地址。


2. this 指针的使用
在成员函数中,不必显式地使用 this 指针来访问被调用函数所属对象的成员。
对这个类的成员的任何没有前缀的引用,都被假定为通过指针 this 实现的引
用:


bool same_isbn(const Sales_item &rhs) const
{ return isbn == rhs.isbn; }


在这个函数中 isbn 的用法与 this->units_sold 或 this->revenue 的用法一
样。
由于 this 指针是隐式定义的,因此不需要在函数的形参表中包含 this 指针,
实际上,这样做也是非法的。但是,在函数体中可以显式地使用 this 指针。如
下定义函数 same_isbn 尽管没有必要,但是却是合法的:


bool same_isbn(const Sales_item &rhs) const
{ return this->isbn == rhs.isbn; }


7.8. 重载函数
出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数。


7.9. 指向函数的指针
函数指针是指指向函数而非指向对象的指针。像其他指针一样,函数指针也
指向某个特定的类型。函数类型由其返回类型以及形参表确定,而与函数名无关:
// pf points to function returning bool that takes two const string references
bool (*pf)(const string &, const string &);
这个语句将 pf 声明为指向函数的指针,
它所指向的函数带有两个 const string& 类型的形参和 bool 类型的返回值。



原创粉丝点击