当前位置:   article > 正文

C++入门——Day6_C++函数编程模块_针对每种类型,要求给出函数(模块)的原型说明、函数功能说明和为何是这种类型的原

针对每种类型,要求给出函数(模块)的原型说明、函数功能说明和为何是这种类型的原

一、复习函数的基本知识

 

库函数

自定义函数

先写段程序,复习下自定义函数

  1. #include <iostream>
  2. void simple(void);
  3. using namespace std;
  4. int main(void)
  5. {
  6. cout << "main() will call the somple() function " << endl;
  7. simple();
  8. return 0;
  9. }
  10. void simple(void)
  11. {
  12. cout << "I'm simple but a function." << endl;
  13. }

自定义函数的三个部分:

函数定义,函数声明,函数调用,缺一不可

1:定义函数

函数分为有返回值和没有返回值的

没有返回值就是void函数,通用格式如下:

void functionName (parameterList)

{

        statement(s)

        return;   //可写可不写,没返回值一般不写

}

其中的paramenterList是指传递给函数的参数类型和数量,例如:
void cheers (int n)

{

         for(int i = 0; i <n ; i++)

                cout << "Cheers!";

        cout << endl;

}

这里的int意思就是,在调用cheers函数的时候,应该给它一个Int值作为参数传递给它

有返回值的函数将生成一个值,并将它返回给调用函数,通用格式如下:

typeName functionName(paramterList)

{

         statements;

        return value;

}

对于有返回值的函数,必须使用返回语句,以便把值返回给调用函数。

值本身可以是常量、变量、表达式,只是其结果的类型必须为typeName类型或者可以被转换为typeName(比如说我们返回类型是double,但是返回一个int表达式,int会被强转为double类型)

C++对于返回值的类有限制:不能是数组,但是可以是其他任何类型,比如结构对象等(但是可以把数组作为结构或对象组成部分来返回)

函数在执行返回语句后结束。如果函数包含多条返回语句,则函数在执行遇到第一条返回语句后结束。

int bigger (int a , int b)

{

        if(a>b)

                return a;

        else

                return b;

}

2:函数原型和函数调用

看如下程序,体会自定义有返回值和没有返回值的函数

  1. #include <iostream>
  2. using namespace std;
  3. void cheers(int n);//声明
  4. double cube(double x);
  5. int main(void)
  6. {
  7. cheers(5);//调用
  8. cout << "Give me a number: " << endl;
  9. double side;
  10. cin >> side;
  11. double volume = cube(side);
  12. cout << side << " side cube = " << volume << endl;
  13. cheers(cube(2)); //自定义函数调用自定义函数,并进行double转int的操作
  14. return 0;
  15. }
  16. void cheers(int n) //无返回值
  17. {
  18. for (int i = 0; i < n; i++)
  19. {
  20. cout << "Cheers!" << endl;
  21. }
  22. }
  23. double cube(double x) //有返回值
  24. {
  25. x = x * x * x;
  26. return x;
  27. }

结果:

Cheers!
Cheers!
Cheers!
Cheers!
Cheers!
Give me a number:
3
3 side cube = 27
Cheers!
Cheers!
Cheers!
Cheers!
Cheers!
Cheers!
Cheers!
Cheers!

*

1)自定义无返回值cheers,自定义有返回值cube

2)把输入作为cube的参数,返回double类型

3)自定义函数cheers引用自定义函数cube

1)为什么需要原型?
就是头文件下面为什么要再声明一下

函数原型描述了函数到编译器的结构,也就是它将函数的返回值类型及参数类型数量告诉编译器

cube()函数完成计算后,把返回值放到了指定位置——可能是寄存器,可能是内存,然后调用函数(main())从这个为之取得返回值

C++编译器并不会在文件中进一步查找,这样效率太低了,所以最好能提供原型

2)原型的语法

函数原型并不需要提供变量名,有类型列表有足够了。比如对于cheer()原型,我们只提供了参数类型:

void  cheers(int);

比方说我们去饭店吃饭,只需要打电话告诉饭店,我们有几个人,我们吃什么菜,我们有几个人,而不需要把每个人的名字告诉饭店一个道理

所以说比方说下面的原型:

double cube(double x)写成double cube (double) 一样是可以编译的!

3)原型的功能

要确保以下几点:

·编译器正确处理函数返回值;

·编译器检查使用的参数数目是否正确

·编译器检查使用的参数类型是否正确

但是如果是较大的数据类型转换为较小类型时,有些编译器会发出警告,指出数据可能丢失

比如把2.33E27转为int类型,这种值就不会被正确转换

二、函数参数和按值传递

C++通常按值传递参数,这意味着将数值参数传递给函数,而后者将其赋给新的变量

比如:double volume = cube  (side);

→   side是一个变量,前面定义为5

cube的函数头如下:double cube (double x)

→   函数被调用时,该函数创建一个新的名为x的double变量,把它初始化为5

→    这样cube()执行的操作将不会影响main()中的数据。

→    cube使用的是side的副本,而不是原来的数据

用于接收传递值得变量被称为形参,比如这里double 的x

传递给函数的值被称为实参,比如原来的side

1:多个参数

1)函数内定义两个变量,哪怕是相同类型,也要写全

void fifi (float a, float b)

2)与一个参数一样,原型中的变量名不必与定义中的变量名相同,甚至可以省略,但是最好写上,至少知道人家是干嘛用的

见如下程序:演示修改形参并不会影响调用函数中的数据

  1. #include <iostream>
  2. using namespace std;
  3. void n_chars(char c, int n);
  4. int main(void)
  5. {
  6. char ch;
  7. int times;
  8. cout << "Please enter a character: ";
  9. cin >> ch; //cin.get()也行
  10. while(ch != 'q')
  11. {
  12. cout << "Enter a integer: ";
  13. cin >> times;
  14. n_chars(ch,times);
  15. cout << endl;
  16. cout << "Enter another character or press q_key to quie: " << endl;
  17. cin >> ch;
  18. }
  19. return 0;
  20. }
  21. void n_chars(char c, int n)
  22. {
  23. while (n-- >0)
  24. {
  25. cout << c;
  26. }
  27. }

结果:

Please enter a character:

1
Enter a integer: 10
1111111111
Enter another character or press q_key to quie:
q

 

*

1)声明了ch和times分别作为n_chars的两个参数,然后通过函数返回内容

2:另外一个接收两个参数的函数

题目如下:

美国许多州都采用某种纸牌游戏来发行彩票,让参与者从卡片中选择一定数目的选项。

例如:从51个数字中选取6个,随后管理者随机抽取6个数。如果参与者选择的数字与这6个完全相同,赢得几百万美金。函数计算中奖概率

首先,必须从51个数中选取6个数,而中奖的概率为1/R,R的计算公式为:

R=\frac{51\times 50\times 49\times 48\times 47\times 46}{6\times 5\times 4\times 3\times 2\times 1}

选择6个数时,分母为6!,分子是连续6个整数的乘积,从51开始连续递减,可以采用for循环计算

long double result = 1.0;

for(n = numbers , p = picks ; p > 0 ; n-- , p--)(numbers是一共从多少个数中选择)

        result = result * n / p;(pick是选取数的个数)

程序:

  1. #include <iostream>
  2. using namespace std;
  3. long double probability(unsigned int numbers,unsigned int picks);
  4. int main(void)
  5. {
  6. unsigned int total,choices;
  7. cout << "Enter the total number of choices on the game card and the number of picks allowed: " << endl;
  8. while((cin >> total >> choices) && choices <= total)
  9. {
  10. cout << "You have one chance in " << probability(total,choices) << " of winning";
  11. cout << "Please enter next two number(q to quit)";
  12. }
  13. cout << "Bye";
  14. return 0;
  15. }
  16. long double probability(unsigned int numbers,unsigned int picks)
  17. {
  18. double n,p;
  19. long double result = 1.0;
  20. for(n = numbers, p = picks; p > 0; n--,p--)
  21. result = result * (n / p);
  22. return result;
  23. }

结果(输出是R,概率是1/R):

Enter the total number of choices on the game card and the number of picks allowed:
49
6
You have one chance in 1.39838e+07 of winningPlease enter next two number(q to quit)100
10
You have one chance in 1.73103e+13 of winningPlease enter next two number(q to quit)

*

1)这个程序就演示了可以在函数中使用两种局部变量。

首先是形参(numbers和picks);其次是其他局部变量(result、n、p)

形参和局部变量的主要区别是,形参从调用函数那里获得自己的值,而其他变量是从函数中获得自己的值

2)注意可以这样写:

while((cin >> total >> choices) && choices <= total),代表如果输入成功

三、函数和数组

如何把函数和数组结合起来?

假设使用一个数组来记录家庭野餐中每人吃了多少甜品,现在要统计总数,来使用函数实现

我们返回值部分不能为数组,但是参数部分可以是

比如以下声明:

int   sum_arr (  int arr[] , int n )

这里的arr看起来是一个数组,其实它是一个指针,但是函数编写时,可以把它看成是数组

下面程序将演示如同使用数组名那样使用指针

  1. #include <iostream>
  2. using namespace std;
  3. const int ArSize = 8;
  4. int sum_arr(int arr[],int n);
  5. int main(void)
  6. {
  7. int cookies[ArSize] = {1,2,4,8,16,32,64,128};//数组定义好了
  8. int sum = sum_arr(cookies,ArSize); //函数返回总数
  9. cout << "Total cookies eaten: " << sum << endl;
  10. return 0;
  11. }
  12. int sum_arr(int arr[],int n)//它用了两个参数,一个是数组,一个是数组数量
  13. {
  14. int total = 0;
  15. for(int i = 0; i < n; i++)//很明显,我们用数组的数量和求和
  16. {
  17. total += arr[i];
  18. }
  19. return total;
  20. }

结果:Total cookies eaten: 255

1:函数如何使用指针处理数组

大多数情况下,C++和C一样,也将数组名视为指针,它将数组名解释为第一个元素的地址:

cookies  ==  & cookies [0]

但是也有一些例外,比如

1)数组声明使用数组名来标记存储位置

2)对数组名使用sizeof将得到整个数组的长度(字节为单位)

3)&用于数组名时,返回整个数组的地址

再来看这个函数调用:int sum = sun_arr(cookies , ArSize)

cookies是数组名,而C++规定它是第一个元素的地址,因此函数传递的是地址。所以cookies类型必须是int指针,即int *。所以正确的函数头应该是:
in sun_arr ( int * arr , int n)

在函数头或函数原型中,int * arr和int arr[]含义相同,它们都意味着arr是一个int指针

arr[i] == *(ar + i)

&arr [i]  ==  ar +  i

将指针(数组名+1)意味着加上了一个与指针指向的类型的长度相等的值,就是指针偏移

2:将数组作为参数意味着什么?

sum_arr把cookies的第一个元素的地址和数组中的元素传递给了sum_arr函数。

sum_arr函数将cookies的地址赋给指针变量arr,将ArSize赋给int变量n

函数在传递数组的时候,使用的是原来的数组

看下面的函数,这回我们使用指针来演示上面的程序

  1. #include <iostream>
  2. using namespace std;
  3. const int ArSize = 8;
  4. int sum_arr(int arr[],int n);
  5. int main(void)
  6. {
  7. int cookies[ArSize] = {1,2,4,8,16,32,64,128};//数组定义好了
  8. cout << "array adress: " << &cookies << endl;
  9. cout << "size of cookies: " << sizeof(cookies) << endl; //32
  10. int sum = sum_arr(cookies,ArSize); //函数返回总数
  11. cout << "Total cookies eaten: " << sum << endl;
  12. sum = sum_arr(cookies,3);
  13. cout << "3 cookies eaten: " << sum << endl;
  14. return 0;
  15. }
  16. int sum_arr(int arr[],int n)//它用了两个参数,一个是数组,一个是数组数量
  17. {
  18. int total = 0;
  19. cout << "arr adress: " << arr << endl;
  20. cout << "size of arr: " << sizeof arr << endl;//这里返回的不是数组,而是指针,8个字节,int *占用8字节
  21. for(int i = 0; i < n; i++)//很明显,我们用数组的数量和求和
  22. {
  23. total += arr[i];
  24. }
  25. return total;
  26. }

结果:

array adress: 0x3cebbff7d0
size of cookies: 32
arr adress: 0x3cebbff7d0
size of arr: 8
Total cookies eaten: 255
arr adress: 0x3cebbff7d0
size of arr: 8
3 cookies eaten: 7

*

1)

cout << "size of cookies: " << sizeof(cookies) << endl; 这里打印的是数组的大小

2)cout << "arr adress: " << arr << endl;

我们sum_array的地址和cookies地址完全一样,所以我们知道sum_array其实就是使用了cookies的地址来进行运算

3)cout << "size of arr: " << sizeof arr << endl;//这里返回的不是数组,而是指针,8个字节,int *占用8字节

3:更多数组函数示例

先看一个案例:考虑对房地产数组执行的操作。两个基本操作是,将值读入到数组中和显示数组的内容。

另外添加另一个操作:重新评估每种房地产的值。(所有房地产都以相同的比率增加或减少)

  1. #include <iostream>
  2. using namespace std;
  3. const int Max = 5;
  4. int fill_array(double arr[],int limit);
  5. void show_array(const double arr[],int n);
  6. void revalue(double r,double arr[], int n);
  7. int main(void)
  8. {
  9. double properties[Max];//5个double类型数组
  10. int size = fill_array(properties,Max);//添加数组的方法
  11. show_array(properties,size);
  12. if(size > 0)
  13. {
  14. cout << "Enter revaluation factor: ";
  15. double factor;
  16. while(!(cin >> factor))
  17. {
  18. cin.clear();
  19. while ((cin.get() != '\n'))
  20. {
  21. continue;
  22. }
  23. cout << "Bad input : input process terminated." << endl;
  24. }
  25. revalue(factor,properties,size);
  26. show_array(properties,size);
  27. }
  28. cout << "Done!\n";
  29. cin.get();
  30. cin.get();
  31. return 0;
  32. }
  33. int fill_array(double arr[],int limit)//
  34. {
  35. double temp;
  36. int i;
  37. for (i = 0; i < Max; i++)
  38. {
  39. cout << "Enter value #" << i + 1 << ":";
  40. cin >> temp;
  41. if(!cin)
  42. {
  43. cin.clear();
  44. while (cin.get() != '\n')
  45. {
  46. continue;
  47. }
  48. cout << "Bad input : input process terminated." << endl;
  49. break;
  50. }
  51. else if(temp < 0)
  52. break;
  53. else
  54. arr[i] = temp;
  55. }
  56. return i;
  57. }
  58. void show_array(const double arr[],int n)//加上const以后,就不能通过指针来修改对象了
  59. {
  60. for (int i = 0; i < n; i++)
  61. {
  62. cout << "Property #" << i+1 << " : $";
  63. cout << arr[i] << endl;
  64. }
  65. }
  66. void revalue(double r,double arr[], int n)//r是比例系数
  67. {
  68. for(int i = 0; i < n ; i++)
  69. {
  70. arr[i] *= r;
  71. }
  72. }

结果:

输入:
Enter value #1:1000
Enter value #2:2000
Enter value #3:3000
Enter value #4:4000
Enter value #5:5000

显示:

Property #1 : $1000
Property #2 : $2000
Property #3 : $3000
Property #4 : $4000
Property #5 : $5000

修改因子:
Enter revaluation factor: 2
Property #1 : $2000
Property #2 : $4000
Property #3 : $6000
Property #4 : $8000
Property #5 : $10000
Done!

*程序说明:

1)填充数组

该函数指定两个参数,一个是数组名,另一个是指定读取的最大元素数,该函数返回实际读取的元素数,还要注意判断它输入是否正确和输入是否大于0这两个条件,然后使用for循环,创建临时值,挨个放进去

2)显示数组

这个就是循环+显示数组元素

3)修改数组

它需要给函数传递三个参数,分别是因子,数组和元素数目,没有返回值

4:使用数组区间和函数

除了上面例子中提供数组中的数据类型、数组的起始位置和数组中元素数量,还有另一种方法:

即指定元素区间(range),这可以通过传递两个指针来完成:一个指针标识数组开头,另一个指针标识数组的尾部,看下例(还是饼干):

  1. #include <iostream>
  2. using namespace std;
  3. const int ArSize = 8;
  4. int sum_arr(const int * begin, const int * end);
  5. int main(void)
  6. {
  7. int cookies[ArSize] = {1,2,4,8,16,32,64,128};
  8. int sum = sum_arr(cookies, cookies + ArSize);
  9. cout << "Total cookies eaten: " << sum << endl;
  10. return 0;
  11. }
  12. int sum_arr(const int * begin, const int * end)
  13. {
  14. int total = 0;
  15. const int * pt;
  16. for(pt = begin; pt != end; pt++)
  17. total += *pt;
  18. return total;
  19. }

结果:Total cookies eaten: 255

*

1)定义了指针的开头和指针的结尾,求和就用指针内容

2)调用函数的开头结尾位置采用cookies和cookies + ArSize

5:指针和const

三个const的用法

①const int *pt

②int *const pt;

③const int *const pt;

①const int *pt等效于  int const *pt

使用const,就不能通过指针修改数组了,比如如下程序:

  1. int main(void)
  2. {
  3. int n =10;
  4. int *pt = &n;
  5. cout << "1)n= " << n << endl;
  6. *pt = 20;//通过指针修改了n的对象
  7. cout << "2)n= " << n << endl;
  8. return 0;
  9. }

1)n= 10
2)n= 20

但是如果加上const,就不能通过指针来修改了

  1. int main(void)
  2. {
  3. int n =10;
  4. const int *pt = &n;
  5. cout << "1)n= " << n << endl;
  6. *pt = 20;//通过指针修改了n的对象
  7. cout << "2)n= " << n << endl;
  8. return 0;
  9. }

结果如下:

 报错说*pt是只读,所以不能通过const修改后的指针来修改原值

但是指针还是可以指向其他地方的,如下:

  1. #include <iostream>
  2. using namespace std;
  3. int main(void)
  4. {
  5. int n =10;
  6. int m = 100;
  7. const int *pt = &n;
  8. cout << "1)n= " << n << endl;
  9. //*pt = 20;//通过指针修改了n的对象
  10. pt = &m;
  11. cout << "*pt = " << *pt << endl;
  12. cout << "m = " << m << endl;
  13. return 0;
  14. }

还是可以指向其他位置,比如m

1)n= 10
*pt = 100
m = 100

②int * const pt;   

  1. #include <iostream>
  2. using namespace std;
  3. int main(void)
  4. {
  5. int n =10;
  6. int *const pt = &n;
  7. cout << "1)n= " << n << endl;
  8. *pt = 20;
  9. cout << "2)n = " << n << endl;
  10. return 0;
  11. }

这时候把*放到了const的前面,就可以通过pt指针修改原n值

结果:
1)n= 10
2)n = 20

再把pt指针指向m

  1. #include <iostream>
  2. using namespace std;
  3. int main(void)
  4. {
  5. int n =10;
  6. int *const pt = &n;
  7. cout << "1)n= " << n << endl;
  8. *pt = 20;
  9. cout << "2)n = " << n << endl;
  10. pt = &m;
  11. return 0;
  12. }

这时候就报错了,说明*const pt可以修改指向数值的值,但是只能指向一个值,不能指向任意变量

③const int *const pt;

这就说明了,你既不能通过指针修改内容,也不能指向其他位置

尽可能使用const

将指针参数声明为指向常量数据的指针有两条理由:

·这样可以避免由于无意间修改数据而导致的编程错误

·使用const使得函数能够处理const和非const实参,否则将只能接收非const数据

如果条件允许,则应将指针形参声明为指向const的指针

如果函数形参是const,那么不管函数是const或者非const,都能进行调用

如果函数形参是非const,那么如果函数是const,就不能进行调用

所以在使用的时候,最好给形参设置成const指针!

四、函数和二维数组

1:

跟一维数组类似,假设

int date[3] [4] = {{1,2,3,4},{5,6,7,8},{2,4,6,8}};

int total =sum(data , 3),sum函数就是数组名+数组中的元素

data是一个数组名,有三个元素,第一个元素本身是数组,有4个int组成,因此data类型是指向由4个int组成的数组的指针,正确的原型如下:

int sum(int(*ar2)[4], int size))(指针的数组)

这声明了将4个指向int的指针组成的数组

而不是由一个指向由4个int组成的数组的指针

如:int ar2[4],(这是数组的指针)

二维数组函数写成下面这样子,可读性更强;

int sum(int ar2[] [4] , int size);

2:二维数组遍历

因此二维数组的遍历就需要循环嵌套,外层访问行,内层访问列

  1. int sum(int are[][4],int size)
  2. {
  3. int total = ;
  4. for(int r = 0; r < size; r++)
  5. {
  6. for(int c = 0;c <4; c++)
  7. {
  8. total += ar2[r][c];
  9. }
  10. }
  11. return total;
  12. }

3:提取元素

可以用数组表示法的原因是:ar2指向数组的第一个元素,因此ar+r指向编号为r的元素,又因为元素本身又是数组,随意ar2[r]是由4个int组成的数组的名称,所以ar2[r][c]是4个int组成的数组中的一个元素(最简单的方法),必须对指针执行两次才能解除引用,如下:
 

ar2[r][c]  ==  *(*(ar2+r)+c)

先对行指针+r,利用*取出行元素,就是数组,然后对数组名+c,就是针对这个数组偏移,然后再*取出元素



五、函数和C风格字符串

1:表示字符串的三种方法

·char数组     char ghost[15] = "galloping";

·用引号括起来的字符串常量(字符串字面值)   

·被设置为字符串的地址的char指针       char * str = "galumping"

看如下例子:使用一个函数来计算特定的字符再字符串中出现的次数

  1. #include <iostream>
  2. using namespace std;
  3. unsigned int c_in_str(const char *str, char ch);
  4. int main(void)
  5. {
  6. char mmm[15] = "minimum";//第一种字符数组的方式
  7. const char * wail = "ululate"; //第二种字符指针方式 //char * wail = (char *"ululate")也行
  8. unsigned int ms = c_in_str(mmm,'m');
  9. unsigned int us = c_in_str(wail,'u');
  10. cout << ms << " m characters in " << mmm << endl;
  11. cout << us << " u characters in " << wail << endl;
  12. return 0;
  13. }
  14. unsigned int c_in_str(const char *str, char ch)
  15. {
  16. unsigned int count = 0;
  17. while ((*str))
  18. {
  19. if(*str == ch)
  20. count++;
  21. str++;
  22. }
  23. return count;
  24. }

3 m characters in minimum
2 u characters in ululate

2:返回C风格字符串的函数

函数无法返回字符串,但可以返回字符串的地址,这样效率更高

下面定义一个buildstr()函数,返回一个指针,接收两个参数,一个字符一个数字。函数使用new创建一个长度和数字参数相等的字符串,然后将每个元素都初始化为该数组

  1. #include <iostream>
  2. using namespace std;
  3. char * buildstr(char c, int n);
  4. int main(void)
  5. {
  6. char ch;
  7. cout << "Enter a character: ";
  8. cin >> ch;
  9. int times;
  10. cout << "Enter a integer: ";
  11. cin >> times;
  12. char *ps = buildstr(ch,times);
  13. cout << ps << endl;
  14. delete[] ps;
  15. ps = buildstr('+',20);
  16. cout << ps << "-Done-" << ps << endl;
  17. delete[]ps;
  18. return 0;
  19. }
  20. char * buildstr(char c, int n)
  21. {
  22. char * pstr = new char[n+1];//new创建数组
  23. pstr[n] = '\0';//最后一个留给空字符
  24. for(int i = 0 ; i < n; i++)
  25. {
  26. pstr[i] = c;
  27. }
  28. return pstr;
  29. }

结果:

Enter a character: V
Enter a integer: 20
VVVVVVVVVVVVVVVVVVVV
++++++++++++++++++++-Done-++++++++++++++++++++

*

1)要注意new字符串的时候多留一位给我们的空格

2)new完了要记得delete

六、函数和结构

结构变量的行为更接近基本的单值变量,在这种情况下,函数将使用原始结构的副本,另外,函数也可以返回结构

与数组名就是数组的第一个元素的地址不同,结构名只是结构的名称,要获得结构的地址,必须使用地址运算符&

如果结构非常大,则赋值结构将增加内存要求,所以许多C程序员进行函数传递和返回,更喜欢指针来访问结构体的内容

C++提供了第三种选择——按引用类型

1:传递和返回结构

看如下程序:

  1. #include <iostream>
  2. using namespace std;
  3. const int Mins_per_hr = 60;
  4. struct travel_time
  5. {
  6. int hours;
  7. int mins;
  8. };
  9. travel_time sum(travel_time t1, travel_time t2);
  10. void show_time(travel_time t);
  11. int main(void)
  12. {
  13. travel_time day1 = {5,45};
  14. travel_time day2 = {4,55};
  15. travel_time trip = sum(day1,day2);
  16. cout << "Two days :" << trip.hours << " hours , " << trip.mins << " mins." << endl;//这样写还是有些复杂,再定义一个shouwtime函数
  17. travel_time day3 = {4,32};
  18. cout << "Three days :" ;
  19. show_time(trip);
  20. return 0;
  21. }
  22. travel_time sum(travel_time t1, travel_time t2)
  23. {
  24. travel_time total;
  25. total.mins = (t1.mins + t2.mins) % Mins_per_hr;
  26. total.hours = t1.hours + t2.hours + (t1.mins + t2.mins) / Mins_per_hr; // (t1.mins + t2.mins) / Mins_per_hr会自动取整的
  27. return total;
  28. }
  29. void show_time(travel_time t)
  30. {
  31. cout << t.hours << " Hours, " << t.mins << "Minutes." << endl;//没返回值
  32. }

结果:

Two days :10 hours , 40 mins.
Three days :15 Hours, 12Minutes.

*这里所有的参数类型都是结构体

2:另一个处理结构的函数示例

定义两个结构,用于表示两种不同的描述位置的方法,然后开发一个函数,将一种格式转换为另一种格式,并显示结果

  1. #include <iostream>
  2. #include <cmath>
  3. using namespace std;
  4. struct polar
  5. {
  6. double distance;
  7. double angle;
  8. };
  9. struct rect
  10. {
  11. double x;
  12. double y;
  13. };
  14. polar rect_to_polar(rect xypos);
  15. void show_polar(polar dapos);
  16. int main(void)
  17. {
  18. rect rplace;
  19. polar pplace;
  20. cout << "Enter the x and y value: ";
  21. while (cin >> rplace.x >> rplace.y)
  22. {
  23. pplace = rect_to_polar(rplace);//将直角坐标系转为极坐标系,输入的一定是直角坐标系新建的
  24. show_polar(pplace);//要显示极坐标系,参数肯定是极坐标系
  25. cout << "Next two numbers(q to quit):";
  26. }
  27. return 0;
  28. }
  29. polar rect_to_polar(rect xypos)
  30. {
  31. polar answer;
  32. answer.distance = sqrt(xypos.x * xypos.x + xypos.y * xypos.y);
  33. answer.angle = atan2(xypos.y,xypos.x);//atan2返回弧度,还需要转化为角度
  34. return answer;
  35. }
  36. void show_polar(polar dapos)
  37. {
  38. const double Rad_to_deg = 57.29577951;
  39. cout << "Distance = " << dapos.distance << endl;
  40. cout << "Angel = " << dapos.angle * Rad_to_deg << "degree" << endl;
  41. }

结果:

Enter the x and y value: 30
40
Distance = 50
Angel = 53.1301degree
Next two numbers(q to quit):-100
100
Distance = 141.421
Angel = 135degree
Next two numbers(q to quit):q

*

1)函数引用结构体类型,结构体类型应该放在最前面,不然报错

2)while (cin >> rplace.x >> rplace.y),如果输入成功

3:传递结构的地址

可以对上述的程序进行修改

  1. #include <iostream>
  2. #include <cmath>
  3. using namespace std;
  4. struct polar
  5. {
  6. double distance;
  7. double angle;
  8. };
  9. struct rect
  10. {
  11. double x;
  12. double y;
  13. };
  14. void rect_to_polar(const rect *pxy , polar * pda);
  15. void show_polar(const polar *pda);
  16. int main(void)
  17. {
  18. rect rplace;
  19. polar pplace;
  20. cout << "Enter the x and y value: ";
  21. while (cin >> rplace.x >> rplace.y)
  22. {
  23. //pplace = rect_to_polar(rplace);
  24. rect_to_polar(&rplace,&pplace);//两个值,第一个是直角坐标系的指针,第二个是极坐标系的指针
  25. //show_polar(pplace);
  26. show_polar(&pplace);//直接用地址就行了,没有返回值
  27. cout << "Next two numbers(q to quit):";
  28. }
  29. return 0;
  30. }
  31. void rect_to_polar(const rect *pxy , polar *pda)//定义两个地址,只需要把地址换了就行
  32. {
  33. pda->distance = sqrt(pxy->x*pxy->x + pxy->y*pxy->y);
  34. pda->angle = atan2(pxy->y, pxy->x);//atan2返回弧度,还需要转化为角度
  35. }
  36. void show_polar(const polar *pda)
  37. {
  38. const double Rad_to_deg = 57.29577951;
  39. cout << "Distance = " << pda->distance << endl;
  40. cout << "Angel = " << pda->angle * Rad_to_deg << "degree" << endl;
  41. }

结果:

Enter the x and y value: 30
40
Distance = 50
Angel = 53.1301degree
Next two numbers(q to quit):-100
100
Distance = 141.421
Angel = 135degree
Next two numbers(q to quit):q

*

1)函数内部传递的是指针,参数是指针,没有返回值

七、函数和string对象

string对象和结构更为相似,例如,可以将一个结构赋给另一个结构,也可以将一个对象赋给另一个对象。可以将结构作为完整实体传递给函数

下面的程序声明了一个string对象数组,并将该数组传递给一个函数显示内容:

  1. #include <iostream>
  2. using namespace std;
  3. const int SIZE = 5;
  4. void display(const string sa[], int n);
  5. int main(void)
  6. {
  7. string list[SIZE];
  8. cout << "Enter " << SIZE << " favorite food: " << endl;
  9. for (int i = 0; i < SIZE; i++)
  10. {
  11. cout << i+1 << ": ";
  12. getline(cin,list[i]);
  13. }
  14. cout << "Your list" << endl;
  15. display(list,SIZE);
  16. return 0;
  17. }
  18. void display(const string sa[], int n)
  19. {
  20. for (int i = 0; i < n; i++)
  21. {
  22. cout << i+1 << ": " << sa[i] << endl;
  23. }
  24. }

结果:

Enter 5 favorite food:
1: break
2: milk
3: icecr
4: saled
5: cake
Your list
1: break
2: milk
3: icecr
4: saled
5: cake

八、函数与array对象

假设 要使用一个array对象存储一年四个季度的开支,看如下程序:

  1. #include <iostream>
  2. #include <string>
  3. #include <array>
  4. using namespace std;
  5. const int Seasons = 4;
  6. const array<string,Seasons> Snames = {"Spring","Summer","Fall","Winter"};
  7. void fill(array<double,Seasons> *pa);
  8. void show(array<double,Seasons> da);
  9. int main(void)
  10. {
  11. array<double,Seasons> expenses;
  12. fill(&expenses);//把指针填充到array里面
  13. show(expenses);
  14. return 0;
  15. }
  16. void fill(array<double,Seasons> *pa)
  17. {
  18. for (int i = 0; i < Seasons; i++)
  19. {
  20. cout << "Enter " << Snames[i] << " expensens: ";
  21. cin >> (*pa)[i];//pa是array对象的指针,需要转化成值再进行操作
  22. }
  23. }
  24. void show(array<double,Seasons> da)
  25. {
  26. double total = 0.0;
  27. cout << "EXPENSENS: " << endl;
  28. for (int i = 0; i < Seasons; i++)
  29. {
  30. cout << Snames[i] << ": $" << da[i] << endl;
  31. total += da[i];
  32. }
  33. cout << "Total expenses: " << total << endl;
  34. }

结果:

Enter Spring expensens: 212
Enter Summer expensens: 256
Enter Fall expensens: 208
Enter Winter expensens: 244
EXPENSENS:
Spring: $212
Summer: $256
Fall: $208
Winter: $244
Total expenses: 920
 

*cin >> (*pa)[i];//pa是array对象的指针,需要转化成值再进行操作

九、递归

C++函数又一个特点——可以自己调用自己(但是C++不允许main()调用自己),这种功能被称为递归。

1:包含一个递归调用的递归

如果递归函数调用自己,则被调用的函数也将调用自己,这将无限循环下去,除非代码又包含终止的内容,一般放在if语句中,如下:void类型的递归函数recurs()代码如下:

  1. void recurs(argumentlist)
  2. {
  3. statements1
  4. if(test)
  5. resurs(arguments)
  6. statements2
  7. }

看如下的例子:

  1. #include <iostream>
  2. using namespace std;
  3. void countdown(int n);
  4. int main(void)
  5. {
  6. countdown(4);
  7. return 0;
  8. }
  9. void countdown(int n)
  10. {
  11. cout << "counting down.... " << n << endl;
  12. if(n>0)//更新条件,递归退出条件
  13. {
  14. countdown(n-1);
  15. }
  16. cout << n << ": Kandom" << endl;
  17. }

结果:

counting down.... 4
counting down.... 3
counting down.... 2
counting down.... 1
counting down.... 0
0: Kandom
1: Kandom
2: Kandom
3: Kandom
4: Kandom

*

1)当n递减到0的时候才开始执行下面的代码:cout << n << ": Kandom" << endl;

2)第二个部分将于与函数调用相反的顺序执行5次!

3)每次调用递归都会创建自己的一套变量,因此当程序到第五次调用时,将有5个独立的n变量,其中每个变量的值都不同,对程序如下修改!

  1. #include <iostream>
  2. using namespace std;
  3. void countdown(int n);
  4. int main(void)
  5. {
  6. countdown(4);
  7. return 0;
  8. }
  9. void countdown(int n)
  10. {
  11. cout << "counting down.... " << n << "(n at adress: " << &n << ")" << endl;
  12. if(n>0)//更新条件,递归退出条件
  13. {
  14. countdown(n-1);
  15. }
  16. cout << n << ": Kandom" << "(n at adress: " << &n << ")" << endl;
  17. }

结果如下:

counting down.... 4(n at adress: 0x1d3adffc10)
counting down.... 3(n at adress: 0x1d3adffbe0)
counting down.... 2(n at adress: 0x1d3adffbb0)
counting down.... 1(n at adress: 0x1d3adffb80)
counting down.... 0(n at adress: 0x1d3adffb50)
0: Kandom(n at adress: 0x1d3adffb50)
1: Kandom(n at adress: 0x1d3adffb80)
2: Kandom(n at adress: 0x1d3adffbb0)
3: Kandom(n at adress: 0x1d3adffbe0)
4: Kandom(n at adress: 0x1d3adffc10)

2:包含多个递归调用的递归

在需要将一项工作不断分为两项较小、类似的工作,递归很有用。递归方法有时被称为分而治之策略,比如对标尺找到两端,找到终点并标记,然后同样的操作给标尺的左右两部分继续用。

见如下程序:

  1. #include <iostream>
  2. using namespace std;
  3. const int Len = 66;
  4. const int Divs = 6;
  5. void subdivide(char ar[], int low, int high,int levels);
  6. int main(void)
  7. {
  8. char ruler[Len];//字符数组
  9. for(int i = 0; i < Len; i++)
  10. ruler[i] = ' ';//清空数组
  11. int min = 0; //数组边界开始
  12. int max = Len - 2; //数组边界结束
  13. ruler[Len-1] = '\0';
  14. ruler[min] = ruler[max] = '|';
  15. for(int i = 1; i < Divs; i++)
  16. {
  17. subdivide(ruler,min,max,i);//这样使用递归好像就可以了
  18. cout << ruler << endl;
  19. }
  20. return 0;
  21. }
  22. void subdivide(char ar[], int low, int high,int levels)//levels是为了控制递归退出
  23. {
  24. if(levels == 0)
  25. return;
  26. int mid = (low + high) / 2;
  27. ar[mid] = '|';
  28. subdivide(ar,low,mid,levels-1);
  29. subdivide(ar,mid,high,levels-1);
  30. }

结果:

|                               |                               |
|               |               |               |               |
|       |       |       |       |       |       |       |       |
|   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
 

*

1)刚开始是两边两条斜杠,第二次变成三条,第三次变成5条,一直递归下去,

2)将递归次数写进了形参,然后形成判断条件

十、函数指针

与数据项相似,函数也有地址。函数的地址是存储机器语言代码的内存的开始地址。我们可以编写一个将另外一个函数的地址作为参数的函数,这样第一个函数能找到第二个函数并运行,特殊的是,它允许在不同的时间传递不同函数的地址,这意味着可以在不同的时间使用不同的函数

1:函数指针基础知识

比如我们要设计一个estimate()函数来估算指定行数的代码需要的时间,并希望不同的此恒许愿都将使用该函数。我们将程序员要使用的算法函数地址传递给esitmate(),为此必须:

·获取函数地址

·声明一个指针

·使用函数指针调用函数

1)获取函数地址

使用函数名即可,不用参数,比如说think()是函数,则think是该函数的地址

2)声明函数指针

比如:函数如下

double pan(int);则指针如下

double (*pf)(int);

写的时候直接把原型中的函数名用(*Name)替换了就行了

*一定要加(),不然就是函数名

double (*pf)(int);这时函数指针

double *pf(int),这就是一个函数了

                                                                

3)使用指针调用函数

现在(*pf)扮演的角色就与函数名相同,因此使用(*pf)时,可将它看作是函数名即可!

例如:
double pam(int);

double (*pf)(int);

pf = pam;

2:函数指针示例

下面的程序演示如何使用函数指针,两次调用estimate()函数,依次传递besty()函数的地址,另一次传递pam()函数的地址。如下
 

  1. #include <iostream>
  2. using namespace std;
  3. double Rick(int lines);
  4. double Jack(int lines);
  5. void estimate(int lines, double(*pf)(int));
  6. int main(void)
  7. {
  8. int code;
  9. cout << "How many lines of code do you need?\n";
  10. cin >> code;
  11. cout << "Here is Rick's estimate: " << endl;
  12. estimate(code,Rick);
  13. cout << "Here is Jack's estimate: " << endl;
  14. estimate(code,Jack);
  15. return 0;
  16. }
  17. double Rick(int lines)
  18. {
  19. return lines * 0.05;
  20. }
  21. double Jack(int lines)
  22. {
  23. return (0.03 * lines) + (0.0004 * lines * lines);
  24. }
  25. void estimate(int lines, double(*pf)(int))
  26. {
  27. cout << lines << " Lines code will take " << (*pf)(lines) << " hours." << endl;
  28. }

结果:

How many lines of code do you need?
100
Here is Rick's estimate:
100 Lines code will take 5 hours.
Here is Jack's estimate:
100 Lines code will take 7 hours.

*

可以看到在最后一行代码我们声明了和Rick,Jack类型相同的函数指针*pf,使用的时候直接用名字参数即可: (*pf)(lines) << " hours." << endl;

3:深入讨论函数指针

看下例:

  1. #include <iostream>
  2. using namespace std;
  3. const double *f1(const double *ar,int n);
  4. const double *f2(const double ar[],int n);
  5. const double *f3(const double ar[],int n);
  6. int main(void)
  7. {
  8. double av[3] = {1112.3,1542.6,2227.9};
  9. //part1: p1是指向函数的指针
  10. const double *(*p1)(const double *,int) = f1;//第一种方法,使用传统方法进行给指针赋值
  11. auto p2 = f2; //第二种方法:使用auto方法,让编译器自动判断p2的类型
  12. cout << "PART1:--------------------" << endl;
  13. cout << "Adress Value" << endl;
  14. cout << (*p1)(av,3) << ": " << *((*p1)(av,3)) << endl; //(*p1)(av,3) == f1(av,3),第二个是把该函数内容再显示出来
  15. cout << p2(av,3) << ": " << *p2(av,3) << endl;//第二种方法,直接把指针名称写出来就行了
  16. //part2: p2是由指针构成的数组
  17. const double *(*pa[3])(const double *,int) = {f1,f2,f3};//指针数组的内容是函数,注意它的声明方法
  18. auto pb = pa;//这样pb也是函数指针数组
  19. cout << "PART2:--------------------" << endl;
  20. cout << "Adress Value" << endl;
  21. for(int i = 0;i < 3; i++)
  22. cout << pa[i](av,3) << ": " << *pa[i](av,3) << endl;//pa[1]就是f1了,加*就是它里面的内容
  23. for (int i = 0; i < 3; i++)
  24. {
  25. cout << pb[i](av,3) << ": " << *pb[i](av,3) << endl;
  26. }
  27. //part3:
  28. //(pc)pd 是指针,指向了一个由函数指针构成的数组
  29. auto pc = &pa;//pc是指向由三个函数指针作为元素的数组pa的指针
  30. const double *(*(*pd)[3])(const double *,int) = &pa;//传统方法,首先pd是指针,就是*pd,然后*pd是3个元素数组指针,就是*(*pd)[3],最后每个元素都是函数指针,就有了这个
  31. cout << "PART3:--------------------" << endl;
  32. cout << "Adress Value" << endl;
  33. cout << (*pc)[0](av,3) << ": " << *(*pc)[0](av,3) << endl;
  34. const double *pdb = (*pd)[1](av,3);
  35. cout << pdb << ": " << *pdb << endl;
  36. cout << (*pd)[2](av,3) << ": " << *(*pd)[2](av,3) << endl;
  37. cout << (*(*pd)[2])(av,3) << ": " << *(*(*pd)[2])(av,3) << endl;//这两种写法其实是一样的
  38. return 0;
  39. }
  40. const double *f1(const double *ar,int n)
  41. {
  42. return ar;
  43. }
  44. const double *f2(const double ar[],int n)
  45. {
  46. return ar + 1;
  47. }
  48. const double *f3(const double ar[],int n)
  49. {
  50. return ar + 2;
  51. }

结果:
PART1:--------------------
Adress        Value
0xf0e7dff6a0: 1112.3
0xf0e7dff6a8: 1542.6
PART2:--------------------
Adress        Value
0xf0e7dff6a0: 1112.3
0xf0e7dff6a8: 1542.6
0xf0e7dff6b0: 2227.9
0xf0e7dff6a0: 1112.3
0xf0e7dff6a8: 1542.6
0xf0e7dff6b0: 2227.9
PART3:--------------------
Adress        Value
0xf0e7dff6a0: 1112.3
0xf0e7dff6a8: 1542.6
0xf0e7dff6b0: 2227.9
0xf0e7dff6b0: 2227.9

*总结一下

1)

(*pa[2])(av,3) ≠ *pa[2](av,3),括号优先级不一样

pa[2](av,3)来说,pa[2]是指针数组第三个元素,就是函数指针,最后*,就是第三个函数的值

(*pa[2])(av,3)来说,(*pa[2])就是第三个元素函数指针所指向的函数指针,然后赋值,出来的还是地址

2)

对于函数指针来说*pf和pf其实是等价的

3)

请注意pa和&pa的区别(pa是数组),pa是数组第一个元素的地址,即&pa[0],而&pa是整个数组(三个指针)的地址,从数字上说它们相同,但是它们类型不同。

比如pa+1是下一个元素的地址,而&pa+1是后面12个字节内存块的地址。

还有个差别,要得到第一个元素的值,只需要对pa解除即可,而&pa解除两次引用

**&pa == *pa == pa[0]

4)

用auto解放了复杂的声明过程

const double *(*pa[3])(const double *,int) = {f1,f2,f3};
    
auto pb = pa;//这样pb也是函数指针数组

5)

还可以用typedef进行简化(并没有定义新的东西,而是起了一个别名,第五章说过)

比如:typedef int n,这里就把n变成int类型了

比如:typedef double real;这里real就变成了double类型了,而不是变量

实例如下:

typedef const double *(*p_fun) (const double *,int),可以直接用p_fun类型了

p_fun p1 = f1;

p_fun pa[3] ={f1,f2,f3};

十一、复习题和编程练习

1:复习题:

1:使用函数的三个步骤?

定义,调用,声明

2:请创建与下面的描述匹配的函数原型

a:igor()没参数,没返回值

void igor(void);

b:tofu()接收一个int参数,且返回一个float

float tofu(int)

c:mpg()接收两个double,返回一个double

double mpg(double,double)

d:summation()将long数组名和数组长度作为参数,返回一个long

long arr[n];

long summation(long [],int)

e:doctor()接受一个字符串参数(不能修改),并返回double

double doctor(const char *str)

f:ofcourse()将boss结构作为参数,无返回值

void ofcourse(boss bs)  形参名可写可不写

g:plot将map结构的指针作为参数,返回字符串

char *plot(map *pt)

3:编写一个接受3个参数的函数,:int数组名、数组长度、和一个int值 ,并将数组所有元素都设置为该int值

void Set(int ar[], int n, int x)

{

     for(i = 0;i<n;i++)

                 {

                              ar[i] = x;

                }

}

4:编写一个接受三个参数的函数:指向数组区间中第一个元素的指针、指向数组区间最后一个元素后面的指针以及一个int值,并将数组中的每个元素都设置为该int值

  1. void Set(int *begin,int *end , int x)
  2. {
  3. for (int *pt = begin; pt != end ; pt ++ )
  4. {
  5. *pt = x;
  6. }
  7. }

*注意循环条件

5:编写一个double数组名和数组长度作为参数,并返回该数组中最大值的函数。该函数不应该修改数组内容。

  1. double Set(const double ar[],int len)
  2. {
  3. double max = arr[0];
  4. for(int i = 1; i < size; i++)
  5. {
  6. if(max < arr[i]);
  7. max = arr[i];
  8. }
  9. return max;
  10. }

6:为什么不对基本类型的函数参数使用const?

我们的C++一般是按值传递参数,而不像数组和结构这种,整体复制,修改副本其实对原来的值不影响,但是如果用指针了就必须加const

7:C++使用哪三种C风格的字符串

char数组。char ch[] = "Hello";

字符串。"hello;";

可以指向字符串首字符的指针。char * pt = "Hello.指的是地址

8:编写一个函数,原型如下:

该函数将字符串中所有的c1都替换为c2,并返回替换次数

int replace(char * str, char c1,char c2)

  1. int replace(char * str,char c1,char c2)
  2. {
  3. int count;
  4. while(*str)
  5. {
  6. if(*str == c1)
  7. {
  8. *str = c2;
  9. count++;
  10. }
  11. str++;
  12. }
  13. return count;
  14. }

9:表达式*"pizza"代表:字符串首地址,取出p

"taco[2]"呢,taco是数组,第三个元素,双引号就是首地址

10:C++允许按值传递结构,也允许传递结构的地址。如果glitz是一个结构变量,如何按值传递它?如何传递地址?两种方法有何利弊?

地址传递不能保护原始数据;但是按值传递内存消耗大

11:函数judge()返回类型是int,它将这样一个函数地址作为参数:将const char指针作为参数,并返回int值,写该函数原型

int judge(int (*pf) (const char));

12:结构如下:

struct applicant

{

        char name[30];

        int ratings[3];

}

a:编写一个显示该结构体内容的函数,参数就是结构体

  1. void show(applicant ap)
  2. {
  3. for (int i = 0; i < 2; i++)
  4. {
  5. cout << ap.name[i] << ap.ratings[i] << endl;
  6. }
  7. }

b.将该结构的地址作为参数,显示参数所指向结构的内容

  1. void show(applicant *ap)
  2. {
  3. for (int i = 0; i < 2; i++)
  4. {
  5. cout << ap->name << ap->ratings[i] << endl;
  6. }
  7. }

13:假设f1()和f2()原型如下:

void f1(applicant * a);

const char * f2(const applicant * a1,const applicant * a2);

1)请将p1和p2分别声明为指向f1和f2的指针;

2)将ap声明为数组,它包含5个类型与p1相同的指针

3)将pa声明为一个指针,它指向的数组包含10个类型与p2相同的指针

使用typedef方法来实现。

1)

  1. typedef void *(pf_1)(applicant * a);//使用typedef直接变成类型
  2. pf_1 p1 = f1;
  3. typedef char const char *(*p_f2)(const applicant *a1,const applicant *a2);
  4. p_f2 p2 = f2;

2)pf_1 ap[5];

3p_f2 (*pa) [10];

2:编程练习

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/780985
推荐阅读
相关标签
  

闽ICP备14008679号