本章内容包括:
- 函数基本知识
- 函数原型
- 按值传递函数参数
- 设计处理数组的函数
- 使用const指针参数
- 设计处理文本字符串的函数
- 设计处理结构的函数
- 设计处理string对象的函数
- 调用自身的函数(递归)
- 指向函数的指针
7.1 复习函数的基本知识
- 要使用C++函数,必须完成如下工作:
- 提供函数定义
- 提供函数原型
- 调用函数
- 库函数是已经定义和编译号的函数,同时可以使用标准库头文件提供其原型,因此只需正确地调用这种函数即可.
- 程序清单7.1 calling.cpp
7.1.1 定义函数
- C++对于返回值的类型有一定的限制:不能是数组,但可以是其他任何类型—整数,浮点数,指针,甚至可以是结构和对象(有趣的是,虽然C++函数不能直接返回数组,但可以将数组作为结构或对象组成部分来返回).
- 通常,函数通过将返回值复制到制定的CPU寄存器或内存单元中来将其返回.随后,调用程序将查看该内存单元.返回函数和调用函数必须就该内存单元中存储的数据的类型达成一致.
- 函数原型将返回值类型告知调用程序,而函数定义命令被调用函数应返回什么类型的数据.
7.1.2 函数原型和函数调用
- 程序清单7.2 protos.cpp
- 程序说明
- 为什么需要原型
- 原型描述了函数到编译器的接口,也就是说,它将函数返回值的类型(如果有的话)以及参数的类型和数量告诉编译器.
- C++的编程风格是将main()方在最前面,因为它通常提供了程序的整体结构.
- 原型的语法
- 函数原型是一条语句,因此必须以分号结束.
- 函数原型不要求提供变量名,有类型列表就足够了,但在原型的参数列表中,可以包括变量名,也可以不包括.原型中的变量名相当于占位符,因此不必与函数定义中的变量名相同.
- C++原型与ANSI原型:ANSI C借鉴了C++中的原型,但这两种语言还是有区别的.其中最重要的区别是,为与基本C兼容,ANSI C中的原型是可选的,但在C++中,原型是必不可少的.
- 原型的功能
- 具体来说,原型确保以下几点:
- 编译器正确处理函数返回值
- 编译器检查使用的参数数目是否正确
- 编译器检查使用的参数类型是否正确.如果不正确,则转换为正确的类型(如果可能的话).
- 在编译阶段进行的原型化被称为静态类型检查.可以看出,静态类型检查可捕获许多在运行阶段非常难以捕获的错误.
- 具体来说,原型确保以下几点:
- 为什么需要原型
7.2 函数参数和按值传递
- C++通常按值传递参数,这意味着将数值参数传递给函数,而后者将其赋给一个新的变量.
- 出于简化的目的,C++标准使用参数来表示实参,使用参量来表示形参.
7.2.1 多个参数
- 程序清单7.3 twoarg.cpp
7.2.2 另外一个接受两个参数的函数
- 注意:有些C++实现不支持long double类型,如果所用的C++实现是这样的,请使用double类型.
- 程序清单7.4 lotto.cpp
7.3 函数和数组
- 程序清单7.5 arrfun1.cpp
7.3.1 函数如何使用指针来处理数组
- 当指针指向数组的第一个元素时,本书使用数组表示法;而当指针指向一个独立的值时,使用指针表示法.例如:在C++中,当(且仅当)用于函数头或函数原型中,int *arr和int arr[]的含义才是相同的.
- 记住,将指针(包括数组名)加1,实际上是加上了一个与指针指向的类型的长度(以字节为单位)相等的值.对于遍历数组而言,使用指针假发和数组下标是等效的.
7.3.2 将数组作为参数意味着什么
- 传递常规变量时,函数将使用该变量的拷贝;但传递数组时,函数将使用原来的数组.实际上,这种却别并不违反C++按值传递的方法,sum_arr()函数仍传递了一个值,这个值被赋给一个新变量,但这个值是一个地址,而不是数组的内容.
- 程序清单7.6 arrfun2.cpp
- 注意:为将数组类别和元素数量高速数组处理函数,请通过两个不同的参数来传递它们.
7.3.3 更多数组函数示例
- 填充数组
- 显示数组及用const保护数组
- 为防止函数无意中修改数组的内容,可在声明形参时使用关键字const
- 修改数组
- 将上述代码组合起来
- 程序清单7.7 arrfun3.cpp
- 程序说明
- 这种被称为自下而上的程序设计,因为设计过程从组件到整体进行.这种方法非常适合于OOP—它首先强调的是数据表示和曹总.而传统的过程性编程倾向于从上而下的程序设计,首先制定模块化设计方案,然后再研究细节.这两种方法都很有用,最终的产品都是模块化程序.
- 数组处理函数的常用编写方式
7.3.4 使用数组区间的函数
- STL方法使用”超尾”概念来制定区间.也就是说,对于数组而言,标识数组结尾的参数将是指向最后一个元素后面的指针.
- 程序清单7.8 arrfun4.cpp
7.3.5 指针和const
- 可以用两种不同的方式将const关键字用于指针.第一种方法是让指针指向一个常量对象,这样可以防止使用该指针来修改所指向的值,第二种方法是将指针本身声明为常量,这样可以防止改变指针指向的位置.
- 注意:如果数据类型本身并不是指针,则可以将const数据或非const数据的地址赋给指向const的指针,但只能将非const数据的地址赋给非const指针.
- 尽可能使用const:将指针参数声明为指向常量数据的指针有两条理由
- 这样可以避免由于无意间修改数据而导致的编程错误.
- 使用const使得函数能够处理const和非const实参,否则将只能接受非const数据.
- 如果条件允许,则应将指针形参声明为指向const的指针
int sloth = 3;
const int * ps = &sloth;//a pointer to const int
int * const finger = &sloth;//a const pointer to int
- 在最后一个声明中,关键字const的位置与以前不同.这种声明格式使得finger只能指向sloth,但允许使用finger来修改sloth的值.中间的声明不允许使用ps来修改sloth的值,但允许将ps指向另一个位置.简而言之,finger和*ps都是const,而*finger和ps不是.
7.4 函数和二维数组
7.5 函数和C风格字符串
7.5.1 将C风格字符串作为参数的函数
- 假设要将字符串作为参数传递给函数,则表示字符串的方式有三种:
- char数组
- 用引号括起的字符串常量(也称字符串字面值)
- 被设置为字符串的地址的char指针
- 但上述3种选择的类型都是char指针(准确的说是char*).可以说是将字符串作为参数来传递,但实际传递的是字符串第一个字符的地址.这意味着字符串函数原型应将其表示字符串的形参声明为char*类型.
- 程序清单7.9 strgfun.cpp
7.5.2 返回C风格字符串的函数
- 程序清单7.10 strgback.cpp
7.6 函数和结构
- 可以将一个结构赋给另外一个结构.同样,也可以按值传递结构,就像普通变量那样.在这种情况下,函数将只用原始结构的副本.另外,函数也可以返回结构.
- 在C语言和C++中,都使用符号&来表示地址运算符.
- 按值传递结构有一个缺点.如果结构非常大,则赋值结构将增加内存要求,降低系统运行的速度.处于这些原因(同时由于最初C语言不允许按值传递结构),许多C程序员倾向于床底结构的地址,然后使用指针来访问结构的内容.C++提供了第三种选择—按应用传递.
7.6.1 传递和返回结构
- 程序清单7.11 travel.cpp
7.6.2 另一个处理结构的函数示例
- 程序清单7.12 atrctfun.cpp
- 注意:有些编译器仅当被明确指示后,才会搜索数学库.
7.6.3 传递结构的地址
- 程序清单7.13 strctptr.cpp
7.7 函数和string对象
- 程序清单7.14 topfive.cpp
7.8 函数与array对象
- 在C++中,类对象是基于结构的,因此结构编程方面的有些考虑因素也适用于类.
- 程序清单7.15 arrobj.cpp
7.9 递归
- C++函数有一种有趣的特点—可以调用自己(然而,与C语言不同的是,C++不允许main()调用自己),这种功能被称为递归.
7.9.1 包含一个递归调用的递归
- 程序清单7.16 recur.cpp
7.9.2 包含多个递归调用的递归
- 在需要将一项工作不断分为两项较小的类似的工作时,递归非常有用.
递归方法有时被称为分而治之策略. - 程序清单7.17 ruler.cpp
// ruler.cpp -- using recursion to subdivide a ruler
#include <iostream>
const int Len = 66;
const int Divs = 6;
void subdivide(char ar[], int low, int high, int level);
int main()
{
char ruler[Len];
int i;
for (i = 1; i < Len - 2; i++)
ruler[i] = ' ';
ruler[Len - 1] = '