本章内容包括:
- 函数的基本知识
- 函数原型
- 按值传递函数参数
- 设计处理数组的函数
- 使用const指针参数
- 设计处理文本字符串的函数
- 设计处理结构的函数
- 设计处理string对象的函数
- 调用自身的函数(递归)
- 指向函数的指针
7.1 函数基本知识:
要使用C++函数,要完成工作:
1. 提供函数基本知识;
2. 提供函数原型;
3. 调用函数
库函数是已经定义和编译好的函数,同时可以使用标准库头文件提供其原型
例:标准头文件 cstring 中包含了 strlen() 和其它一些字符串相关的函数原型
定义函数的模板:
typename functionName(parameterList){ statements return value;}
对于返回值的函数,必须使用return语句返回。可以是常量、变量或者是表达式。其结果的类型只能为typename,若不是,会进行强制类型转换。
C++对返回值的类型一定的限制:不能使数组,但可以是其他任何类型——整数、浮点数、指针甚至可以是结构和对象!(数组可以作为结构或者对象的部分来返回)
通常,函数通过将返回值复制到指定的cpu寄存器或者内存单元来将其返回。随后,调用程序将查看这个内存单元。
函数在执行返回语句后结束,如果函数中包含多条返回语句,执行第一条后结束。
7.1.1函数原型 和函数调用
函数原型描述了函数到编译器的接口,将函数返回值的类型及参数类型和数量告诉编译器;
double cube ( double x ); or double cube ( double );
原型的功能:
编译器正确处理函数返回值;
编译器检查使用的参数数目是否正确;
编译器检查使用参数类型是否正确,如果不正确,转换为正确的类型(可能的话)
7.2函数参数和值传递
例:
double volume=cube(side);double cube(double x);
调用函数时,该函数将创建一个名为x的变量,并初始化为side的值。函数cube()接收的是side的副本而不是side。用于接收传递值的变量被称为形参。传递给函数的值被称为实参。处于简化的目的,C++使用参数来表示实参,使用参量来表示形参。
在函数中声明的变量(包括参数是该函数私有的,在函数被调用时,计算机将为这些变量分配内存,函数结束时,计算机将释放这些内存,这些变量被称为局部变量。
7.3 函数和数组
7.3.1 使用数组作为函数的参数:
例:int sum(int a[],int n)
注:在参数传递时,程序将数组的位置(指针传递给函数,并不是整个数组)。
C++和C语言一样,将数组名视为指针,C++将数组名解释为第一个元素的地址。
例外:
- 数组声明使用数组名来标记存储位置
- 对数组名使用sizeof()将得到整个数组的长度
- 将地址运算符&用于数组名时,将返回整个数组的地址。
函数传递数组时,将数组的位置(地址)、包含的元素种类(类型)以及元素的数目(n变量)提交给函数;
函数传递常规变量时,函数将使用变量的拷贝,但使用数组时,函数使用原来的数组;
可以在被传递函数中,数组参数使用 const 限定符,保证原始数组数据不被修改(只读传入)
int show_array ( const double arr[ ],int limit ); // 函数原型
7.3.2 自下而上的程序设计:
通过数据类型和设计适当的函数来处理数据,然后将这些函数组合成一个程序;
适合于OOP——它首先强调的数据表示和操纵
7.3.3 使用数组区间的函数:
对于处理数组的C++函数,必须将处理数组中的种类、数组的起始位置和数组中元素数量交给函数:
1. 将数组的起始处的指针作为一个参数,将数组的长度作为第二个参数;
2. 指定元素区间,通过传递两个指针完成,一个标识数组开头,一个标识数组尾部
eg: sum = sum_arr ( arr, arr+3 ); // 函数调用,实参传入地址区间 int sum ( const int* begin,const int* end );// 函数头,形参为两个指向数组类型的指针(int*)
7.3.4 指针和 const:
const用于指针一些很微妙的地方。可以使用两种方式将关键字const用于指针。第一种方法是让指针指向一个常量对象,这样能够防止使用该指针修改所指向的值,第二种方法是将指针变量本身声明为常量,这样能够防止指针指向的位置。
可以使用两种不同的方式将 const 关键字用于指针:
1. 让指针指向一个常量对象,防止使用该指针修改所指向的值;
2. 将指针本身声明为常量
声明一个指向常量的指针:
int age = 39;const int * pt = &age;
声明中 pt 指向一个 const int 因此不能使用 pt 修改这个值—— *pt 为常量不能修改
还可以将 const 变量的地址赋给指向const的指针,不能将 const 的地址赋给常规的指针
记住:如果数据类型不是指针,可以将 const 数据或非 const 数据的地址指向 const 指针
不能将 const 数据赋给非 const 指针
int * const finger = &sloth:
指针 finger 本身被声明为 const,使得 finger 只能指向 sloth,但允许使用 finger 来修改 sloth 的值
7.4 函数和二维数组
数组作为参数的函数,数组名被视为地址,相应的形参应为一个指针;
int data[3][4] = { {1,2,3,4}, {9,8,7,6}, {2,4,6,8} }; // 声明int tatal = sum(data, 3); // 调用
两种函数原型(形参的形式):
int sum (int (*ar2) [4],int size ); // 声明一个由4个指向 int 的指针组成的数组,括号不能省int sum (int ar2[ ][4],int size); // 可读性更强
在函数定义中使用二维数组,最简单的办法是将 ar2 看作一个二维数组的名称
ar2 实际上是一个指针,必须对 ar2 执行两次解除引用才能得到数据:
最简单的方式:ar2[r][c];ar2[r][c] = *(*(ar2 + r) + c); // same thing
7.5 函数和C-风格字符串
C-风格字符串由一系列字符组成,以空值字符结尾,作为参数时意味着传递地址。
表示字符串的方式有3种:
1. char 数组2. 用引括号起的字符串常量3. 被设置为字符串的地址的 char 指针
将字符串作为参数来传递,实际传递的是字符串的第一个字符的地址,形参声明应为 char*:
int c_in_str (const char * str,char ch); // 使用指针表示法int c_in_str (const char str[],char ch); // 也可以使用数组表示法
处理字符串中字符的标准方式:
while (*str) // until *stt == '0'{ statement; str++ // 将指针增加一个字节}
7.5.2 返回C-风格字符串的函数
函数声明:
char *buildstr(char c,int n);
调用:
char *ps=buildstr(ch,times); //字符串指针的初始化,ch='a',times=10;
函数:
char *buildstr(char c,int n){ char *pstr=new char [n+1]; pstr[n]='0'; while(n-->0) pstr[n]=c; return pstr;}
7.6 函数和结构
7.6.1 传递和返回结构
结构定义:
struct student{ char name[20]; int age;};
函数声明:student sum(const student s1,student s2);
函数调用:student s=sum(s1,s2); //初始化
函数:
student sum(const student s1,student s2){ student s; s.age=s1.age+s2.age; strcpy(s.name,s1.name); strcat(s.name,s2.name); return s;}
照上述操作,参数传递的是整个结构,如果结构很大,效率很低。
7.6.2传递结构的地址
如果要传递结构的地址的话,需要修改三个地方,以上述函数为例:
- 调用函数时,将结构的地址(&s而不是结构本身(s)传递给它
- 将形参声明为指向student的指针,即*student。
- 由于形参是指针而不是结构,因此使用间接成员运算符(->)而不是成员运算符(句点。
修改后的程序如下:
结构定义:
struct student{ char name[20]; int age;};
函数声明:student sum(const student *s1,const student *s2);
函数调用:student s=sum(s1,s2); //初始化
函数:
student sum(const student s1,const student s2){ student s; s->age=s1->age+s2->age; strcpy(s->name,s1->name); strcat(s->name,s2->name); return s;}
7.7函数和 string 对象:
C++如果需要多个字符串,可以声明一个 string 对象数组,而不是二维 char 数组;
以下例程声明了一个 string 对象数组,并将该数组传递给一个函数以显示其内容:
#include#includeusing namespace std;const int SIZE = 5;void display(const string list[], int n);int main(){ string list[SIZE]; // 声明 string 数组,每一个元素为一个 string 对象 cout << "Enter your " << SIZE << " favotite astronomical sights: "; for (int i = 0; i < SIZE; i++) { cout << i + 1 << ": "; getline(cin, list[i]); // 读取一个字符串 } cout << "Your list: "; display(list, SIZE); return 0;}void display(const string list[], int n){ for (int i = 0; i < n; i++) cout << list[i] << endl;}
该例程中,除了函数 getline() 外,程序像对待内置类型(int)一样对待 string 对象
7.8函数与array对象
函数声明:
void show(std::array da); //da是数组名。
或者:
void show(std::array *pa); //pa是指针
注意,第一种的参数传递是全部复制,第二种只是传递地址,效率较高。
7.9 递归
C++函数一个有趣的特点:可以调用自己(C++不允许main函数自己调用自己,C语言可行),这种功能被称为递归。
包含一个递归调用的递归
如下:
void recurs(argumentlist){ statements1 if (test) recurs(arguments) statements2}
test最终为false,程序断开。
如果recurs进行了5次递归调用,statements1将函数调用顺序执行5次,statements2将函数调用相反的顺序执行5次。
注意,每个递归调用都会创建自己的一套变量,所以程序在到达第五次调用的时候会有五个独立的变量,其中每个变量的值都不同。
7.9.1 包含多个递归调用的递归
递归方法时被称为分而治之策略。
现演示多个递归调用的递归的实例:
//ruler.cpp 使用递归来标定直尺的中点
#includeconst 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
7.10 函数指针
可以编写将另一个函数的地址作为参数的函数;
这种方法与调用函数相比,允许在不同的时间传递不同的函数地址;
7.10.1 函数指针基础知识:
获取函数地址;
声明函数的指针;
使用函数指针来调用函数
1. 获取函数地址
只要使用函数名即可,如果 think() 是一个函数,则 think 就是该函数的地址
要区分函数的地址和函数的返回值:
process ( think ); // 参数为函数地址,使得函数 process 能够在函数内部调用 think() 函数thought ( think() ); // 参数为函数的返回值,先调用 think() 函数,其返回值传给 thought
2. 声明函数指针
声明函数指针时,必须指定指针指向的函数类型:
double pam ( int ); // 原型double ( *pf ) ( int ); // 指针类型声明,将 pam 替换为了 (*pf),(*pf)是函数,pf 就是函数指针pf = pam; // 将相应的函数地址赋给指针
提示: 要声明指向特定类型的函数指针,可以先编写这种函数的原型,然后用(*pf)替换函数名
注意: 函数地址赋给函数指针时,特征标和返回返回类型必须相同
3. 使用指针来调用函数
void estimate ( int lines,double (*pf) (int) ); // 函数原型estimate ( 50, pam); // 让 estime() 使用 pam() 函数(*pf) 扮演的角色与函数名相同,使用(*pf)时,只需要将它看成函数名double pam ( int );double (*pf) (int);pf = pam; // 函数指针指向函数 pam()double x = pam (4); // 使用函数名调用函数pam()double y = (*pf) (5); // 使用函数指针调用函数pam()double y = pf(5); // C++允许像使用函数名那样使用 pf
7.10.2 深入探讨函数指针
例:声明一个函数指针:
const double *(*p1)(const double *,int);
也可在声明的同时进行初始化:
const double *(*p1)(const double *,int)=f1;
也可使用C++11的自动类型推断功能
auto p2=f2;
可完成函数指针p2的定义,代码大大简化,但是仅能用于单值,不能用于列表。
函数指针数组:
定义与初始化:
const double * (*pa[3])(const double *,int)={f1,f2,f3};
7.10.3 使用typedef进行简化
可使用typedef进行简化
typedef可创建类型别名
typedef double real; //创建double的别名real
typedef const double *(*p_fun)(const double *,int);
使用:p_fun p1=f1;