待定
本章知识点:
函数基本知识
函数原型
按值传递函数参数
设计处理数组的函数
使用const指针参数
设计处理文本字符串的函数
设计处理结构的函数
设计处理string对象的函数
递归
7.1 复习函数的基本知识
使用c++函数,必须完成以下步骤:
函数定义;
提供函数原型
调用函数
库函数是已经定义和编译好的函数,可使用标准库头文件提供其原型,因此只需正确地调用这种函数即可。
7.1.1 定义函数
函数可分为两类:无返回值的函数和有返回值的函数。
无返回值的函数(void函数)
语法格式:
void functionname(parameterlist)
{
statement(s)
return; //optional
}
parameterlist指定传递给函数的参数类型和数量;return标记了函数的结尾,return为可选项,否则函数将在右花括号处结束。
有返回值的函数
语法格式:
typename functionname(parameterlist)
{
statement(s)
return value; //value is type cast to type typename
}
对于有返回值的函数,必须使用返回语句,以便将值返回给调用函数。
值可以为常量、变量,也可以是表达式,但其结果的类型必须为typename类型或可被转换为typename(返回类型为double,而函数返回一个int表达式,则该int值被强制转换为double类型)。
c++对返回值的类型的限制:不能为数组,可以是其他任何返回类型——整数、浮点数、指针,结构和对象(因为c++不支持将一个数组直接赋值给另一个数组)。
7.1.2 函数原型和函数调用
1.为什么需要原型
原型描述了函数到编译器的接口,它将函数返回值的类型(如果有)以及参数类型和数量告知编译器。
2. 原型的语法
函数原型为一条语句,必须以分号结束。复制函数定义中的函数头,添加分号即可。另外,函数原型不要求提供变量名,有类型列表即可。
void cheers(int);
void cheers(int n);
以上两种方式都正确。
7.2 函数参数和按值传递
形参(parameter):用于接收传递值的变量;
实参(argument):传递给函数的值。
7.2.1 多个参数
函数可有多个参数,在声明/调用时使用逗号将其分开即可。
void n_chars(char c, int n);
n_chars(‘r’, 25);
当函数的两个参数类型相同时,必须分别指定每个参数的类型,不能像声明常规变量那样组合在一起:
void fifi(float a, float b) //valid,declare each variable separately
void fufu(float a, b) //invalid
7.3 函数和数组
返回一个长度为n的数组sum_arr的函数头:
int sum_arr(int arr[], int n)
这里的arr实际并非数组,而是一个指针。但是在编写函数时,可以将其看做是数组。
7.3.1 函数如何使用指针来处理数组
大多数情况下,c++和c语言一样,也将数组名视为指针。
sum_arr == &sum_arr[0]
7.3中的函数执行调用时:
int sum = sum_arr(cookies, arsize);
cookies是数组名,根据c++规则,cookies是其第一个元素的地址,因此函数传递的是地址。而数组的元素类型为int,因此cookies的类型必须是int指针,即int*。这说明,正确的函数头应是如下:
int sum_arr(int* arr, int n)
上式用int* arr替换了int arr[]。两个函数头都是正确的,因为在c++中,当且仅当用于函数头或函数原型中,int* arr和int arr[]的含义是相同,都意味着arr是一个int指针。
注意:数组表示法int arr[],arr不仅指向int,还指向int数组的第一个int;在其他上下文中,int* arr和int arr[]的并不相同
当指针指向数组的第一个元素时,(本书)使用数组表示法;当指针指向一个独立的值时,使用指针表示法。
记住以下两个恒等式:
arr[i] == *(ar + i) //values in two notations
&arr[i] = ar + i //address in two notations
7.3.2 将数组作为参数
传递常规变量时,函数将使用该变量的拷贝;但传递数组时,函数将使用原来的数组。
函数调用sum_arr(cookies, arsize)时将cookies数组第一个元素的地址和数组中的元素数目传递给sum_arr()函数,有了这些信息后,函数便可以使用原来的数组。
数组名和指针对应的好处和坏处。
好处:节省了赋值整个数组所需的时间和内存。
坏处:增加了原始数据被破坏的风险。
int cookies[8] = {1, 2, 3, 4, 5, 6, 7, 8};
sizeof(cookies) = 32;
一个int类型占4个字节,4*8 = 32;
int sum_arr(int arr[], int n)
{
int size = sizeof(arr); //size = 4(32位)/8(64位)
}
这意味着arr并非一个数组,而是一个指针,即int*,所以返回的是整型指针(int*)的大小。arr是一个指针,标识的是数组在内存中的首地址,也知道每个元素所占内存空,但只知道开头不知结尾,所以必须包含n,才能知道数组arr的所有内容。
注意:务必使用两个不同的参数来传递数组类型和元素数量给数组处理函数,而非使用方括号。
void fillarray(int arr[], int size): //valid
void fillarray(int arr[size]); //invalid
7.3.3 更多数组函数示例
对于不允许函数修改原始数组的情况,可以使用关键字const修饰该数组,以防止函数无意中修改数组的内容。
void show_array(const double ar[], int n)
7.3.4 使用数组区间的函数
另外:定义函数void function(int *a),在调用时可传递数组function(b)(int b[2] = {1, 2}),因为在c++中数组名即为数组的首地址,而指针指向的也是地址,所以此处可以直接传递数组名。
# include<iostream>
using namespace std;
const int arsize = 4;
int sum_arr(const int *begin, const int *end);
int main(void)
{
int cookies [arsize] = {1, 2, 3, 4};
int sum = sum_arr(cookies, cookies + arsize);
}
7.3.5 指针和const
可以用两种方式将const关键字用于指针。
- 让指针指向一个常量对象,这样可以防止使用该指针来修改所指向的值。
- 将指针本身声明为常量,这样可以防止改变指针指向的位置。
指向常量的指针(1)——const int *pt == int const *pt
int n = 10;
int *pt = &n;
* pt = 20; //此时n=20,*pt可以修改
//
int n = 10;
const int *pt = &n; //指针指向的内容,不能通过指针修改
* pt = 20; //此时n=10,*pt为可读,不能通过指针来修改
//
int n = 10;
int m = 100;
const int *pt = &n; //指针指向的内容,不能通过指针修改
pt = &m; //此时*pt = 100 (pt可以指向任何内容)
指针本身为常量(2)
int n = 10;
int m = 100;
int *const pt= &n; //此时,pt只能指向n
*pt = 20; //此时,n = 20,可以通过修改指针来修改指向的内容
int n = 10;
int m = 100;
int *const pt= &n;
pt = &m; //invalid,当前pt可以通过*pt指向对象(n)的值,但不能改变//它指向的对象(不能改为指向m)
总结:只需要记住const在“”的前面还是后面,在“”前边,对应(1),表示不能修改其指向对象的值,但可以修改通过pt修改它指向位置;在“”后边,对应(2),表示当前指针不能再指向其他位置,但可以通过pt修改它指向对象的值。
特殊:const int *const pt
int n = 10;
const int *const pt = &n;
此时pt指针只能指向一个变量n,而且不能通过stick来修改n的值,即pt和*pt都是const。
尽可能使用const
将指针参数声明为指向常量数据的指针有两个理由:
可以避免由于无意间修改数据而导致的编程错误;
使用const使得函数能够处理const和非const实参,否则将只能接受非const数据。
如果条件允许,则应将指针声明为指向const的指针。
将指针作为函数参数传递时,可以使用const的指针保护数据。如:
void show_array(const double ar[], int n);
const关键字表示show_array()不能修改传递给它的数组中的值。只要只有一层间接关系,就可以使用这种技术。这里的数组元素时基本类型,所以可以使用这种技术,但如果是指针或指向指针的指针则不能使用const。
7.4 函数和二维数组
将二维数组作为函数的参数,数组名被视为其地址,因此,其相应的形参也是一个指针(同一维数组)。
例如:
int data[3][4] = {{1, 2, 3, 4}, {3, 5, 7, 8}, {1, 1, 4, 7}};
int total = sum(data, 3);
//此处参数为行数3,因为data数组有3个元素,每个元素是一个由4个int值组成的数组
因此,data的类型是指向由4个int组成的数组的指针,正确原型如下:
int sum(int (*ar2)[4], int size); //含义与 int sum(int ar2[][4], int size)完全相同,可读性更强
//红色括号()必不可少,区别如下声明:
int *ar2[4];
//此处声明一个由4个指向int的指针组成的数组,而非一个指向由4个int组成的数组的指针。
注意:函数参数不能是数组。
7.5 函数和c-风格字符串
7.5.1 将c-风格字符串作为参数的函数
将字符串作为参数传递给函数,表示方式有三种:
char数组
引号括起来的字符串常量
被设置为字符串的地址的char指针
以上三种都是char指针(char*),实际传递的是字符串第一个字符的地址。也就是说字符串函数原型应将其表示字符串的形参声明为char*类型。
7.5.2 返回c-风格字符串的函数
同数组,函数无法返回一个字符串,但可以返回字符串的地址,且返回地址效率更高。