C++—函数
一、函数的基本知识
要使用C++函数,必须完成一下工作:
(1)提供函数定义;
(2)提供函数原型;
(3)调用函数。
1、定义函数
可以将函数分为两类,有返回值的函数和没有返回值的函数。没有返回值的函数称为void函数,其通用格式如下:
void functionName(parameterList){
statement(s);
return;//可以使用不带任何返回值的return,返回语句也可以省略
}
有返回值的函数的通用格式如下:
typeName functionName(parameterList){
statement(s);
return value; //其中value的类型必须和声明中函数的返回值类型typeName一致;并且返回语句不能省略
}
C++对于返回类型有一定的限制:不能是数组,但可以使其他任何类型——整数、浮点数、指针,甚至可以使结构和对象(虽然C++函数不能直接返回数组,但是将数组作为结构和对象的组成部分来返回)。
函数在执行返回语句后结束。如果函数包含多条返回语句,则函数在执行遇到的第一条返回语句后结束。
2、函数原型和函数调用
(1)为什么需要原型?
原型描述了函数到编译器的接口,即原型将函数返回值的类型以及参数的类型和数量告诉编译器。
(2)原型的语法
函数原型是一条语句,所以必须以分号结束。并且,函数原型不要求必须提供参数名,有类型列表就足够了。
(3)原型的功能
具体来说,原型确保一下几点:
1)编译器正确处理返回值;
2)编译器检查使用的参数数目是否正确;
3)编译器检查使用的参数类型是否正确。如果不正确就转换成正确的类型(如果可能的话)。
二、函数参数和按值传递
C++通常按值传递参数,这意味着将数值参数传递给函数,而后者将其赋给一个新的变量。
在函数中声明的变量(包括参数)是该函数私有的。在函数调用时,计算机将为这些变量分配内存;在函数结束时,计算机将释放这些变量占用的内存。这样的变量称为局部变量,因为他们被限制在函数内部。函数可以有多个参数。
1 补充:局部变量、全局变量、静态全局变量和静态局部变量之间的区别 2 3 (1)局部变量,指的是在代码块中(大括号之间)声明的变量,在执行这些块的时候,计算机将为这些变量分配内存;在函数结束的时候,计算机将释放这些变量占用的内存。如果局部变量的名称和全局变量的名称一致,在包含该局部变量的代码块中,声明局部变量之后的代码中局部变量优先;如过要在声明局部变量后面的代码中要使用相同名字的全局变量,需要在变量前加上::。 4 5 (2)全局变量,指的是在代码块(包括函数)之外声明的变量,全局变量能够被该文件中的所有函数反问并修改,并且在其他文件中使用了extern声明的也可以访问该全局变量。 6 7 (3)静态局部变量,静态局部变量和局部变量的区别在于其生命周期。局部变量的声明周期时包含该局部变量的块的声明周期,而静态局部变量的生命周期时整个程序。 8 9 (4)静态全局变量,静态全局变量和全局变量的区别在于其作用域。全局变量的作用域是声明该变量的文件以及其他使用了extern声明的文件;而静态全局变量的作用域被限制在声明该变量的文件中,其他文件无权访问。
三、 函数和数组
1、
1 (1)、 2 对静态数组名进行sizeof运算时,结果是整个数组占用空间的大小; 3 因此可以用sizeof(数组名)/sizeof(*数组名)来获取数组的长度。 4 int a[5]; 则sizeof(a)=20,sizeof(*a)=4.因为整个数组共占20字节,首个元素(int型)占4字节。 5 int *a=new int[4];则sizeof(a)=sizeof(*a)=4,因为地址位数为4字节,int型也占4字节。 6 (2)、 7 静态数组作为函数参数时,在函数内对数组名进行sizeof运算,结果为4,因为此时数组名代表的指针即一个地址,占用4个字节的内存(因为在传递数组名的参数时,编译器对数组的长度不做检查)。对动态数组的函数名,无论何时进行sizeof运算,得到的结果都是4. 8 (3)、 9 new还需要你delete,是在堆分配空间,效率较低;而静态数组直接在栈上分配,会自动释放,效率高,但是栈空间有限。 10 (4)、通过函数返回一个数组的问题 11 函数声明的静态数组不可能通过函数返回,因为生存期的问题,函数调用完其内部变量占用的内存就被释放了。如果想通过函数返回一个数组,可以在函数中用new动态创建该数组,然后返回其首地址。 12 其原因可以这样理解,因为静态数组是在栈中申请的,而函数中的局部变量也是在栈中的,而new动态数组是在堆中的分配的,所以函数返回后,栈中的东西被自动释放,而堆中的东西如果没有delete不会自动释放。
1 #include <iostream> 2 3 using namespace std; 4 5 int *getStaticArr(); 6 int *getActivityArr(); 7 int main(int argc, const char * argv[]) { 8 9 int *arr = getStaticArr(); 10 for (int i = 0; i < 4; i++) { 11 cout <<"arr["<< i << "] is "<< arr[i]<< " at " << &arr[i] << endl; 12 } 13 cout << endl << endl; 14 arr = getActivityArr(); 15 for (int i = 0; i < 4; i++) { 16 cout <<"arr["<< i << "] is "<< arr[i]<< " at " << &arr[i] << endl; 17 } 18 delete [] arr; 19 return 0; 20 } 21 22 int *getStaticArr(){ 23 int arrn[4] {1,2,3,4}; 24 for (int i = 0; i < 4; i ++) { 25 cout << "arrn[" <<i <<"] is " << arrn[i] << " at " << &arrn[i] << endl; 26 } 27 return arrn; 28 } 29 int *getActivityArr(){ 30 int *arrm = new int[4]{2,3,4,5}; 31 for (int i = 0; i < 4; i++) { 32 cout << "arrm[" <<i <<"] is " << arrm[i] << " at " << &arrm[i] << endl; 33 } 34 return arrm; 35 } 36 37 输出结果: 38 arrn[0] is 1 at 0x7fff5fbff750 39 arrn[1] is 2 at 0x7fff5fbff754 40 arrn[2] is 3 at 0x7fff5fbff758 41 arrn[3] is 4 at 0x7fff5fbff75c 42 arr[0] is 1958675192 at 0x7fff5fbff750 43 arr[1] is 32767 at 0x7fff5fbff754 44 arr[2] is 7977 at 0x7fff5fbff758 45 arr[3] is 1 at 0x7fff5fbff75c 46 47 48 arrm[0] is 2 at 0x100200000 49 arrm[1] is 3 at 0x100200004 50 arrm[2] is 4 at 0x100200008 51 arrm[3] is 5 at 0x10020000c 52 arr[0] is 2 at 0x100200000 53 arr[1] is 3 at 0x100200004 54 arr[2] is 4 at 0x100200008 55 arr[3] is 5 at 0x10020000c
说明:
(1)在函数中,不能将静态数组作为返回值进行返回,因为在调用完函数之后静态数组将被释放;但是动态数组被存储在堆区,在函数调用完后,如果没有用delete进行释放,动态数组将不会被释放。
(2)但是,我们可以利用静态数组和动态数组的这一特性,在创建返回从外部输入得到的字符串的函数时,用来节省内存空间。因为在读取输入的时候,并不知道需要创建多大的数组来存储输入字符串,因此需要创建一个比较大的静态数组;读取完输入以后,可以根据静态数组中字符的数量来创建一个刚好能够存储输入的动态数组来保存输入数据,再将保存有输入数据的动态数组返回,再该函数调用结束后,静态数组将会被释放,而动态数组只要还没有被delete就依然存在,一次来达到节省内存空间的目的,代码如下:
1 #include <iostream> 2 3 using namespace std; 4 5 char *getEnter(); 6 int main(int argc, const char * argv[]) { 7 char *str = getEnter();//返回的得到的是一个动态数组的第一个元素的地址 8 cout << str << endl; 9 delete [] str;//使用完后,需要将动态数组释放掉,避免内存泄漏 10 return 0; 11 } 12 char *getEnter(){ 13 char enter[100]; 14 cout << "请输入一段字符:\n"; 15 cin >> enter; 16 char *str = new char[strlen(enter) + 1];//根据输入的长度来创建一个刚好能够保存输入数据的动态数组,+1是为了保存字符串结尾的空字符。 17 strcpy(str, enter); 18 return str; 19 } 20 21 输出结果: 22 请输入一段字符: 23 WhatIsYourMother'sName?//用户输入 24 WhatIsYourMother'sName?
(3)在编写数组函数时,如果把数组作为参数传给函数,并且在函数中直接对接受数组的形式参数进行操作,那么等同于对原数组进行操作。如下:
1 #include <iostream> 2 3 using namespace std; 4 5 int getEnter(int *arr,int max); 6 int main(int argc, const char * argv[]) { 7 int arr[5]{0}; 8 int m = getEnter(arr,sizeof(arr)/sizeof(*arr)); 9 cout << "m :" << m << endl; 10 for (int i = 0; i < sizeof(arr)/sizeof(*arr); i++) { 11 cout << "arr[" << i << "] is " << arr[i] << endl; 12 } 13 return 0; 14 } 15 int getEnter(int* arr, int max){ 16 double num; 17 int i; 18 for (i = 0 ; i < max; i++) { 19 cout << "Enter value #" << (i + 1) << ":\n"; 20 cin >> num; 21 if (!cin) { 22 cin.clear(); 23 while (cin.get() != '\n') 24 continue; 25 cout << "Bad input;input process terminated.\n"; 26 break; 27 } 28 else if(num < 0){ 29 break; 30 } 31 arr[i] = num; 32 } 33 return i ; 34 } 35 36 输出结果: 37 Enter value #1: 38 90 39 Enter value #2: 40 45 41 Enter value #3: 42 123 43 Enter value #4: 44 221 45 Enter value #5: 46 1001 47 m :5 48 arr[0] is 90 49 arr[1] is 45 50 arr[2] is 123 51 arr[3] is 221 52 arr[4] is 1001
(3)显示数组和使用const关键字保护数组
显示数组很简单,只需要把数组名和填充的元素数目传递给函数就可以了。一般来说,给函数传递参数时,函数都是使用参数副本,不会对实际参数产生影响;但是在传递数组名的时候,由于传递的是指针,形式参数和实际参数都指向同一个地址,对形式参数进行某些操作时可能会影响数组本身;为防止函数无意中修改数组的内容,可在声明形参时使用const关键字,这样该函数中就不能修改数组的内容。
2、使用数组区间的函数
对于处理数组的C++函数需要将数组的起始位置、数组元素类型和数组元素的数目传递给函数;另一种给函数提供所需信息的方法是指定元素区间,这可以通过传递两个指针来完成:一个指针标识数组的开头,另一个指针标识数组的结尾。C++标准库(STL)使用“超尾”概念来指定区间。对于数组而言,标识数组结尾的参数将是指向最后一个元素后面的指针。例如:
1 #include <iostream> 2 3 using namespace std; 4 5 void showArr(const int *begin, const int *end); 6 int main(int argc, const char * argv[]) { 7 int arr[6] = {16,21,45,23,80,100}; 8 showArr(arr, arr+sizeof(arr)/sizeof(*arr)); 9 return 0; 10 } 11 void showArr(const int *begin, const int *end){ 12 const int *ps;//可以将一个常量赋给一个变量,但是不能将一个指向常量的指针赋给一个指向变量的指针;const int *begin表示begin是一个指向常量的指针。 13 for (ps = begin; ps != end; ps++) {//循环条件 14 cout << "The value at " << ps << " is " << *ps << endl; 15 } 16 } 17 18 输出结果: 19 The value at 0x7fff5fbff7f0 is 16 20 The value at 0x7fff5fbff7f4 is 21 21 The value at 0x7fff5fbff7f8 is 45 22 The value at 0x7fff5fbff7fc is 23 23 The value at 0x7fff5fbff800 is 80 24 The value at 0x7fff5fbff804 is 100
3、指针和const关键字
指针使用CONST
(1)指针本身是常量不可变 (char*) const pContent; const (char*) pContent; (2)指针所指向的内容是常量不可变 const (char) *pContent; (char) const *pContent; (3)两者都不可变 const char* const pContent; (4)还有其中区别方法,沿着*号划一条线:
如果const位于*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量; 如果const位于*的右侧,const就是修饰指针本身,即指针本身是常量。在不带小括号的情况下,关键在于const修饰的是指针指向的值(即带*号的指针,const在*号前,指针指向的值不可变),还是直接修饰指针本身(即不带*号的指针,const在*后面,指针本身不可变)。 (5)指向常量的指针,并不意味着指针指向的一定就是常量,只是对于这个指针而言,这个值是常量。 (6)不能将常量的地址赋给一个指向变量的指针,这是自相矛盾的;只能将常量的地址赋给指向常量的指针。注意常量指针和指向常量的指针的区别,常量指针是指指针本身不能变,指向常量的指针指的是指针指向的内容不可变。
1 #include <iostream> 2 3 using namespace std; 4 5 void showArr(const int *begin, const int *end); 6 int main(int argc, const char * argv[]) { 7 int m = 10; 8 const int n = 15; 9 const int * ptr = &m; 10 //int *pts = &n;不将常量的地址赋给一个指向变量的指针,这样是矛盾的;只能将常量的地址赋给一个指向常量的指针 11 const int *pts = &n; 12 //*ptr = 19; 指向常量的指针,并不意味着指向的一定就是一个常量,只是对于这个指针而言指向的是一个常量,即不能通过该指针来修改指向的内容 13 cout << "修改变量m前*ptr的值:" << *ptr << endl; 14 m = 19; 15 cout << "修改变量m后*ptr的值:"<< *ptr << endl; 16 17 return 0; 18 } 19 20 输出结果: 21 修改变量m前*ptr的值:10 22 修改变量m后*ptr的值:19
四、函数和二维数组
1 #include <iostream> 2 3 using namespace std; 4 5 int sum(int (*arr)[4], int size); 6 int main(int argc, const char * argv[]) { 7 int num[3][4] = {{12,22,32,10},{11,45,34,18},{89,21,42,16}}; 8 cout << sum(num, 3) << endl; 9 10 return 0; 11 } 12 int sum(int (*arr)[4], int size ){ 13 int sum = 0; 14 for (int i = 0; i < size; i ++) { 15 for (int m = 0 ; m < 4; m ++) { 16 sum += arr[i][m]; 17 } 18 } 19 return sum; 20 } 21 22 输出结果: 23 352
五、函数和C—风格字符串
C-风格字符串有一些列字符组成,以空字符结尾。大部分有关数组的函数的知识也适用于字符串函数。将字符串作为参数,意味着传递的是地址。
1、将C-风格字符串作为参数的函数
假设将字符串作为参数传递给函数,则表示字符串的方式有三种:
(1)char数组;
(2)用引号括起来的字符串常量(也称为字符串字面值);
(3)被设置成字符串地址的char指针。
将字符串作为参数来传递,实际传递的是字符串第一个字符的地址。意味着字符串函数原型声明应将其表示字符串的形参声明为char*类型。
C-风格字符串与常规char数组的一个重要区别是,字符串有内置的结束空字符(包含字符,但是不是以空字符结尾的char数组只是数组,而不是字符串)。因此,不必将字符串长度作为参数传递给字符串函数,而函数可以使用循环一次检查字符串中的每个字节,直到遇到结尾的空字符为止。
1 #include <iostream> 2 3 using namespace std; 4 5 unsigned int c_in_str(const char *str, char ch);//由于不需要函数对字符串做出修改,所以将指针声明为const 6 int main(int argc, const char * argv[]) { 7 char str[] = "aidfionifeoiaosdf"; 8 cout << "在字符串str中有"<< c_in_str(str, 'f')<<"个字符f." << endl; 9 return 0; 10 } 11 unsigned int c_in_str(const char *str, char ch){ 12 unsigned int num = 0; 13 while (*str) {//通过检查字符是不是空字符来作为字符串结尾条件 14 if (*str == ch) { 15 num ++; 16 } 17 str +=1; 18 } 19 return num; 20 } 21 22 输出结果: 23 在字符串str中有3个字符f.
2、返回C-风格字符串的函数
1 #include <iostream> 2 3 using namespace std; 4 5 char *getStr(); 6 int main(int argc, const char * argv[]) { 7 char *str = getStr(); 8 cout << "获得的字符串:"<< str << endl; 9 delete [] str; 10 return 0; 11 } 12 char *getStr(){ 13 char str[100]; 14 cout << "请输入一个字符串:\n"; 15 cin >> str; 16 char *str1 = new char[strlen(str) + 1]; 17 strcpy(str1, str); 18 return str1;//不能返回字符数组,因为常规数组保存在栈区,为自动变量 19 } 20 21 输出结果: 22 请输入一个字符串: 23 adfadse 24 获得的字符串:adfadse
六、函数和结构
可以将一个结构赋给另一个结构,可以按值传递结构(函数将使用结构的副本),函数也可以返回结构。
1、传递和返回结构
1 #include <iostream> 2 3 using namespace std; 4 5 typedef struct{//定义一个结构体 6 int hours; 7 int mins; 8 } Travel_time; 9 10 Travel_time sum(Travel_time t1,Travel_time t2); 11 12 int main(int argc, const char * argv[]) { 13 Travel_time time1{2,50}; 14 Travel_time time2{1,45}; 15 Travel_time timeSum = sum(time1, time2); 16 cout << "time1 + time2 = " << timeSum.hours << ":" << timeSum.mins << endl; 17 18 return 0; 19 } 20 21 Travel_time sum(Travel_time t1, Travel_time t2){//函数将使用结构体的副本,就像使用简单变量一样简单 22 Travel_time time; 23 time.mins = (t1.mins + t2.mins)%60; 24 time.hours = t1.hours + t2.hours + (t1.mins + t2.mins)/60; 25 return time;//可以将结构体返回 26 } 27 28 输出结果: 29 time1 + time2 = 4:35
注意:直接把结构题作为参数传递给函数以及函数直接将结构体作为返回值,适合于结构体比较小的情况。当结构体比较大的时候,由于复制结构体将增加内存,降低运行的速度,该方法并不适合。
2、另一个处理结构体的示例
1 #include <iostream> 2 #include <cmath> 3 4 using namespace std; 5 //数学中,描述坐标的方式通常有两种:一种是直角坐标系,用点在x方向上相对于原点的偏移量和y方向上相对于原点的偏移量来描述点的位置;另一种是极坐标系,用点到原点的距离和点与原点连线跟水平直线的夹角(逆时针为正,顺时针为负)来描述点的位置 6 typedef struct{ 7 double x; 8 double y; 9 } Rect; 10 typedef struct{ 11 double distance;//表示到原点的距离 12 double angle; //表示角度,转换成度数 13 } Polar; 14 15 void showPolar(Polar dapos);//打印极坐标 16 Polar rect_to_polar(Rect rect);//将直角坐标系转换成极坐标 17 18 int main(int argc, const char * argv[]) { 19 20 Rect rect; 21 Polar polar; 22 cout << "请依次输入x和y的值:\n"; 23 while (cin>>rect.x>>rect.y) { 24 polar = rect_to_polar(rect); 25 showPolar(polar); 26 cout << "请继续输入x和y的值(输入q退出):\n"; 27 } 28 cout << "完成\n"; 29 return 0; 30 } 31 32 void showPolar(Polar dapos){ 33 const double Rad_to_deg = 57.29577951;//该值为180/π 34 cout << "distance = " << dapos.distance; 35 cout << ", angle = " << dapos.angle * Rad_to_deg << endl; 36 37 } 38 39 Polar rect_to_polar(Rect rect){ 40 Polar point; 41 double x = rect.x; 42 double y = rect.y; 43 point.distance = sqrt(x*x + y*y); 44 point.angle = atan2(y, x); 45 return point; 46 47 } 48 49 输出结果: 50 请依次输入x和y的值: 51 69 //用户输入 52 69 //用户输入 53 distance = 97.5807, angle = 45 54 请继续输入x和y的值(输入q退出): 55 90 //用户输入 56 30 //用户输入 57 distance = 94.8683, angle = 18.4349 58 请继续输入x和y的值(输入q退出): 59 q //用户输入 60 完成
3、传递结构的地址
为了节省空间,又是后我们不希望直接传递结构而改为传递结构的地址。在此,我们仅将上例做出修改,需要修改三个地方:
(1)调用函数时,应该将结构的地址而不是结构本身传递给他;
(2)将形参声明为指向结构的指针。由于函数不应该修改结构,因此需要使用const修饰符;
(3)由于是指针而不是结构,这里应该使用间接成员运算符(->),而不是成员运算符。
1 #include <iostream> 2 #include <cmath> 3 4 using namespace std; 5 //数学中,描述坐标的方式通常有两种:一种是直角坐标系,用点在x方向上相对于原点的偏移量和y方向上相对于原点的偏移量来描述点的位置;另一种是极坐标系,用点到原点的距离和点与原点连线跟水平直线的夹角(逆时针为正,顺时针为负)来描述点的位置 6 typedef struct{ 7 double x; 8 double y; 9 } Rect; 10 typedef struct{ 11 double distance;//表示到原点的距离 12 double angle; //表示角度,转换成度数 13 } Polar; 14 15 void showPolar(const Polar *dapos);//打印极坐标 16 Polar *rect_to_polar(const Rect* rect);//将直角坐标系转换成极坐标 17 18 int main(int argc, const char * argv[]) { 19 20 Rect *rect = new Rect; 21 Polar *polar = NULL ; 22 cout << "请依次输入x和y的值:\n"; 23 while (cin>>rect->x>>rect->y) { 24 polar = rect_to_polar(rect); 25 showPolar(polar); 26 cout << "请继续输入x和y的值(输入q退出):\n"; 27 } 28 cout << "完成\n"; 29 delete rect;//最后需要用delete来释放内存 30 rect = NULL; 31 if (!polar) {// 32 delete polar; 33 polar = NULL; 34 } 35 return 0; 36 } 37 38 void showPolar(const Polar* dapos){//不需要函数对结构的内容进行修改,需要在声明形参时加上const修饰符 39 const double Rad_to_deg = 57.29577951;//该值为180/π 40 cout << "distance = " << dapos->distance; 41 cout << ", angle = " << dapos->angle * Rad_to_deg << endl; 42 43 } 44 45 Polar* rect_to_polar(const Rect* rect){ 46 Polar* point = new Polar; //在这里,声明了指针以后,还需要让指针指向一个用于保存Polar类型数据的内存块,不然后面的操作将会崩溃 47 double x = rect->x; 48 double y = rect->y; 49 point->distance = sqrt(x*x + y*y); 50 point->angle = atan2(y, x); 51 return point; 52 53 } 54 55 输出结果: 56 请依次输入x和y的值: 57 48 58 48 59 distance = 67.8823, angle = 45 60 请继续输入x和y的值(输入q退出): 61 90 62 89 63 distance = 126.574, angle = 44.6799 64 请继续输入x和y的值(输入q退出): 65 q 66 完成
七、函数和string对象
可以将对象赋给另一个对象,可以将对象作为实体进行传递,如果需要多个string对象,可以声明string数组,而不是二维char数组。
1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 #define SIZE 5 6 7 void showString(const string str[], int size); 8 int main(int argc, const char * argv[]) { 9 10 string state[SIZE]; 11 cout << "请输入" << SIZE << "条语句:\n"; 12 for (int i = 0; i < SIZE; i ++) { 13 cout << "第" << i + 1 << "条:"; 14 getline(cin, state[i]); 15 } 16 showString(state, 5); 17 return 0; 18 } 19 void showString(const string str[], int size){ 20 cout << "您输入的语句有:\n"; 21 for (int i = 0; i < size ; i++) { 22 cout << "第" << i+1 << "条语句:" << str[i] << endl; 23 } 24 } 25 26 输出结果: 27 请输入5条语句: 28 第1条:ni hao! 29 第2条:ni hao! 30 第3条:chi fan le mei? 31 第4条:chi le, ni chi le ma? 32 第5条:ye chi le. 33 您输入的语句有: 34 第1条语句:ni hao! 35 第2条语句:ni hao! 36 第3条语句:chi fan le mei? 37 第4条语句:chi le, ni chi le ma? 38 第5条语句:ye chi le.
八、函数与array对象
类对象是基于结构的,因此结构方面的考虑因素也适用于类。可以给函数传递指向对象的指针,让函数直接能够操作原始的对象,在某些时候这种方法可以达到节省内存和运行时间的目的。
1 #include <iostream> 2 #include <array> 3 4 using namespace std; 5 //一个记录一年四季开支的程序 6 7 void show(array<double, 4> da); 8 void fill(array<double, 4> *pa); 9 10 int main(int argc, const char * argv[]) { 11 array<double, 4> expenese; 12 fill(&expenese);//把对象当作常规变量来使用 13 show(expenese); 14 15 return 0; 16 } 17 18 void show(array<double, 4> da){ 19 for (int i = 0; i < 4; i ++) { 20 cout << "第" << i+1 << "季度开支:" << da[i] << ";\n"; 21 } 22 } 23 void fill(array<double, 4> * pa){ 24 for (int i = 0; i < 4; i++) { 25 cout << "请输入第" << i+1 << "季度的开支:"; 26 cin >> (*pa)[i]; 27 } 28 } 29 30 输出结果: 31 请输入第1季度的开支:15000.00 32 请输入第2季度的开支:23089.20 33 请输入第3季度的开支:58932.30 34 请输入第4季度的开支:23134.10 35 第1季度开支:15000; 36 第2季度开支:23089.2; 37 第3季度开支:58932.3; 38 第4季度开支:23134.1;
九、递归
C++函数可以自己调用自己,这种功能称为递归。
1、包含一个递归调用的递归
如果递归函数调用自己,则被调用的函数也将调用自己,这将无限循环下去,除非代码中包含终止调用链的内容。通常的方法将递归调用放在if语句中。例如:void类型的递归函数如下:
void func(argumentlist){
statements1
if(test)
func(arguments)
statements2
}
test最终将为false,调用将会断开。
只要if语句为true,每个func都将执行statements1,然后再调用func(),而不会执行statements2 。当if语句为false时,当前调用将执行statements2 。当前调用结束后,程序将控制权返回给调用他的func(),而该func()将执行其statements2部分,然后结束,并将控制权返回给前一个调用,以此类推。因此,如果func()进行了n次递归调用,则第一个statements1部分将按函数调用的顺序执行n次。然后,statements2部分将以与函数调用相反的顺序执行n次。进入n层递归后,函数将沿进入的路径返回。
1 #include <iostream> 2 3 using namespace std; 4 5 void show(unsigned int num); 6 7 int main(int argc, const char * argv[]) { 8 show(10); 9 10 return 0; 11 } 12 void show(unsigned int num){ 13 for (int i = 0; i < num; i ++) { 14 cout << "+" ; 15 } 16 cout << endl; 17 //上面部分为statements1 18 if (num > 0) { 19 show(num-1); 20 } 21 //下面部分为statements2 22 for (int i = 0; i < num; i ++) { 23 cout << "+"; 24 } 25 cout << endl; 26 } 27 28 输出结果: 29 ++++++++++ 30 +++++++++ 31 ++++++++ 32 +++++++ 33 ++++++ 34 +++++ 35 ++++ 36 +++ 37 ++ 38 + 39 40 41 + 42 ++ 43 +++ 44 ++++ 45 +++++ 46 ++++++ 47 +++++++ 48 ++++++++ 49 +++++++++ 50 ++++++++++
注意:每个递归调用都创建自己的一套变量。因此,递归是相当消耗内存的。
2、包含多个递归调用的递归
在需要将一项工作不断分为较小的、类似的工作时,递归非常有用。
1 #include <iostream> 2 3 using namespace std; 4 5 const int Len = 66; 6 const int Divs = 6; 7 8 void subdivide(char ar[],int low, int high, int level); 9 10 int main(int argc, const char * argv[]) { 11 char ruler[Len]; 12 int i; 13 for (i = 1; i < Len - 2; i ++) { 14 ruler[i] = ' '; 15 } 16 ruler[Len-1] = '\0'; 17 int max = Len - 2; 18 int min = 0; 19 ruler[min] = ruler[max] = '|'; 20 cout << ruler << endl; 21 for (i = 1; i <= Divs; i++) { 22 subdivide(ruler, min, max, i); 23 cout << ruler << endl; 24 for (int j = 1; j < Len - 2; j ++) { 25 ruler[j] = ' '; 26 } 27 } 28 29 return 0; 30 } 31 32 void subdivide(char ar[],int low, int high, int level){ 33 if (level == 0) { 34 return; 35 } 36 int mid = (high + low)/2; 37 ar[mid] = '|'; 38 subdivide(ar, low, mid, level-1); //针对左半部分 39 subdivide(ar, mid, high, level-1);//针对右半部分 40 } 41 42 输出结果: 43 | | 44 | | | 45 | | | | | 46 | | | | | | | | | 47 | | | | | | | | | | | | | | | | | 48 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 49 |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
十、函数指针
与数据项相似,函数也有地址。函数的地址是存储其机器语言代码的内存的开始地址。
1、函数指针的基础知识
要使用函数指针,需要完成下面的工作:
*获取函数的地址;
*声明一个函数指针;
*使用函数指针来调用函数。
(1)获取函数的地址
获取函数的地址:只要使用函数名(后面不跟参数)即可,即函数func(),那么func就是函数func()的地址。要将函数作为参数进行传递,必须传递函数名。一定要区分传递的是函数的地址还是函数的返回值,例如,假设有函数think():
sum(think); //把函数think的地址传给函数sum()
sum(think());//把函数think()的返回值传给函数sum()
(2)声明函数指针
声明指向函数的指针时,必须指定指针指向的函数类型。这意味这声明应指定函数的返回类型以及函数的特征标(参数列表)。也就是说,声明应像函数原型那样指出有关函数的信息。常规声明方式如下:
typeName (*pf) (argumentlist);
说明:其中小括号必不可少,如果么有小括号,即typeName *pf(argumentlist)表示声明了一个参数为argumentlist,返回值类型为typeName*的函数。正确的理解方式:(*pf)是一个参数为argumentlist,返回值类型为typeName的函数;由于*表明(*pf)是对pf的间接取值,那么pf就只能是一个指向该函数原型的指针,即函数指针。例如:
int sum(int );
int (*pf)(int);
pf = sum;//pf现在指向函数sum(),sum()函数的特征标(参数列表)和返回值必须与pf相同;否则,编译器拒绝这种赋值。
(3)使用函数指针调用函数
函数指针与函数名扮演着相同的角色,都是指向函数地址;因此,使用函数指针时,把指针当作函数名即可。
1 #include <iostream> 2 3 using namespace std; 4 //比如我们可以使用函数指针来调用原型相同、功能不同的函数来计算用户输入的数字 5 int sum(int , int );//加法计算 6 int mult(int , int );//乘法计算 7 int sub(int , int );//减法计算 8 void planTheEnter(int (*pf)(int ,int ));//获取用户输入,并计算结果 9 10 int main(int argc, const char * argv[]) { 11 12 planTheEnter(sum);//把函数名作为参数传给函数 13 14 planTheEnter(mult); 15 16 planTheEnter(sub); 17 18 return 0; 19 } 20 int sum(int a, int b){ 21 return a + b; 22 } 23 int mult(int a, int b){ 24 return a * b; 25 } 26 int sub(int a, int b){ 27 return a - b; 28 } 29 void planTheEnter(int (*pf) (int a,int b)){ 30 int x; 31 int y; 32 33 if (pf == sum) { 34 cout << "现在计算两个数的和,请输入两个整数:\n"; 35 cin >> x >> y; 36 cout << "这两个数相加的和为:" << pf(x,y) <<endl; 37 } 38 else if(pf == mult){ 39 cout << "现在计算两个数的积,请输入两个整数:\n"; 40 cin >> x >> y; 41 cout << "这两个数相乘的积为:" << pf(x,y) << endl; 42 } 43 else{ 44 cout << "现在计算两个数的差,请输入两个整数:\n"; 45 cin >> x >> y; 46 cout << "这两个数相减的差为:" << pf(x,y) << endl; 47 } 48 } 49 50 输出结果: 51 现在计算两个数的和,请输入两个整数: 52 12 53 67 54 这两个数相加的和为:79 55 现在计算两个数的积,请输入两个整数: 56 90 57 221 58 这两个数相乘的积为:19890 59 现在计算两个数的差,请输入两个整数: 60 87 61 100 62 这两个数相减的差为:-13
2、深入讨论函数指针
函数指针数组:
有三个函数,他们的函数原型分别为:
const double* f1 (const double arr[], int n);
const double* f2 (const double*, int);
const double* f3 (const double [],int);
以上三个函数的特征标看似不同,但是实际上是一样的。在函数原型中,参数列表const double []与const double *ar的含义完全一样。其次,在函数原型中,可以省略标识符,因此上面三个函数的特征标完全相同。但是,在函数定义中,必须提供表示符。
对于以上三个函数,我们可以声明一个函数指针数组,声明方式如下:
const double *(*pf[3])(const double*, int);
说明:pf[3]说明pf是一个包含三个元素的数组,而声明的其他部分说明了数组中元素的类型。在这里,[]的结合性要高于* 。声明的其他部分说明pf中的元素的类型是函数指针。
1 #include <iostream> 2 3 using namespace std; 4 //比如我们可以使用函数指针来调用原型相同、功能不同的函数来计算用户输入的数字 5 int sum(int , int );//加法计算 6 int mult(int , int );//乘法计算 7 int sub(int , int );//减法计算 8 void planTheEnter(int (*pf)(int ,int ));//获取用户输入,并计算结果 9 10 int main(int argc, const char * argv[]) { 11 int (*pf[3])(int,int){sum,mult,sub}; 12 for (int i = 0; i < 3; i ++) { 13 planTheEnter(pf[i]); 14 } 15 16 17 return 0; 18 } 19 int sum(int a, int b){ 20 return a + b; 21 } 22 int mult(int a, int b){ 23 return a * b; 24 } 25 int sub(int a, int b){ 26 return a - b; 27 } 28 void planTheEnter(int (*pf) (int a,int b)){ 29 int x; 30 int y; 31 32 if (pf == sum) { 33 cout << "现在计算两个数的和,请输入两个整数:\n"; 34 cin >> x >> y; 35 cout << "这两个数相加的和为:" << pf(x,y) <<endl; 36 } 37 else if(pf == mult){ 38 cout << "现在计算两个数的积,请输入两个整数:\n"; 39 cin >> x >> y; 40 cout << "这两个数相乘的积为:" << pf(x,y) << endl; 41 } 42 else{ 43 cout << "现在计算两个数的差,请输入两个整数:\n"; 44 cin >> x >> y; 45 cout << "这两个数相减的差为:" << pf(x,y) << endl; 46 } 47 } 48 49 输出结果: 50 现在计算两个数的和,请输入两个整数: 51 421 52 23 53 这两个数相加的和为:444 54 现在计算两个数的积,请输入两个整数: 55 12 56 32 57 这两个数相乘的积为:384 58 现在计算两个数的差,请输入两个整数: 59 12 60 1 61 这两个数相减的差为:11