本章介绍

  • 关键字:return

  • 运算符: *(一元), &(一元)

  • 函数及其定义方式

  • 如何使用参数和返回值

  • 如何把指针变量用作函数参数

  • 函数类型

  • ANSI C原型

  • 递归

章末小结

9.1.2 分析程序

函数原型指明函数的返回值类型和函数可以接受的参数类型。这些信息统称为函数的签名(signature)

  1. #include <stdio.h>
  2. #define NAME "GIGATHINK, INC."
  3. #define ADDRESS "101 Megabuck Plaza"
  4. #define PLACE "Megapolis, CA 94904"
  5. #define WIDTH 40
  6. void starbar(void); // ANSI C 函数原型
  7. int main(void)
  8. {
  9. starbar(); // 该函数的作用就是打印四十个星号,调用函数
  10. printf("%s\n", NAME);
  11. printf("%s\n", ADDRESS);
  12. printf("%s\n", PLACE);
  13. starbar(); // 该函数的作用就是打印四十个星号,调用函数
  14. return 0;
  15. }
  16. void starbar(void) // 函数准确的定义
  17. {
  18. int count;
  19. for (count = 1; count <= WIDTH; count++)
  20. putchar('*');
  21. putchar('\n');
  22. }

image.png

9.1.13 函数参数

函数参数的书写格式

  1. void dibs(int x, y, z)     /* 无效的函数头 */
  2. void dubs(int x, int y, int z) /* 有效的函数头 */
  3. void dibs(x, y, z)
  4. int x, y, z;   /* 有效 */
  1. /* lethead2.c*/
  2. /*20210315 20:11*/
  3. #include <stdio.h>
  4. #include <string.h>
  5. #define NAME "GIGATHINK, INC."
  6. #define ADDRESS "101 Megabuck Plaza"
  7. #define PLACE "Megapolis, CA 94904"
  8. #define WIDTH 40
  9. #define SPACE ' '
  10. void show_n_char(char ch, int num);
  11. int main(void)
  12. {
  13. int spaces;
  14. show_n_char('*', WIDTH); // 第一行
  15. putchar('\n'); // 手动换行
  16. show_n_char(SPACE, 12); // 12个空格
  17. printf("%s\n", NAME); //打印NAME并且换行
  18. spaces = (WIDTH - strlen(ADDRESS)) / 2;
  19. show_n_char(SPACE, spaces);
  20. printf("%s\n", ADDRESS); // 打印第二行的文字: ADDRESS
  21. show_n_char(SPACE, (WIDTH - strlen(PLACE)) / 2);
  22. printf("%s\n", PLACE);
  23. show_n_char('*', WIDTH);
  24. putchar('\n'); //换行
  25. return 0;
  26. }
  27. void show_n_char(char ch, int num)
  28. {
  29. int count;
  30. for (count = 1; count <= num; count++)
  31. putchar(ch);
  32. }

image.png

9.1.8 return返回

返回值不仅可以赋值给变量,还可以用作表达式的一部分;

返回值不一定是变量的值,也可以是任意表达式;

return语句可以融入花括号里面;

  1. #include <stdio.h>
  2. int imin(int, int);
  3. int main(void)
  4. {
  5. int evill, evil2;
  6. printf("Enter a pair of integers(q to quit): \n");
  7. while (scanf_s("%d %d", &evill, &evil2) == 2)
  8. {
  9. printf("The lesser of %d and %d is %d.\n", evill, evil2, imin(evill, evil2));
  10. printf("Enter a pair of integers (q to quit):\n");
  11. }
  12. printf("Bye.\n");
  13. return 0;
  14. }
  15. int imin(int n, int m)
  16. {
  17. int min;
  18. if (n < m)
  19. min = n;
  20. else
  21. min = m;
  22. return min;
  23. }

image.png

9.2ANSI 函数原型

  1. /* misuse.c -- 错误使用函数*/
  2. // 20210315 23:15
  3. #include <stdio.h>
  4. int imax();
  5. int main(void)
  6. {
  7. printf("The maximum of %d and %d is %d\n", 3, 5, imax(3));
  8. printf("The maximum of %d and %d is %d\n", 3, 5, imax(3.0, 5.0));
  9. return 0;
  10. }
  11. int imax(n, m)
  12. int n, m;
  13. {
  14. return (n > m ? n : m);
  15. }

image.png

9.2.2 ANSI 的解决方案

由代码可见,函数原型中,个数对应的形式参数数据类型是必须的,而变量是选配;

函数原型可以去掉,直接把自定义函数放在主调函数之前,完整声明,然后在主调函数中调用即可;

printf()的参数是不确定的,可以是多个。

  1. /* proto.c -- 使用函数原型*/
  2. // 20210315 23:43
  3. #include <stdio.h>
  4. int imax(int, int);
  5. int main(void)
  6. {
  7. printf("The maximum of %d and %d is %d\n", 3, 5, imax(3, 5));
  8. printf("The maximum of %d and %d is %d\n", 3, 5, imax(3.0, 5.0));
  9. return 0;
  10. }
  11. int imax(int n, int m)
  12. {
  13. return (n > m ? n : m);
  14. }

image.png

9.3 递归

代码解析:但n的值小于四的时候进入循环,资质进入第四层,因为每一层都只执行到第一句打印语句;

所以但第四层循环退出之后,首先进行的是重复当前循环的打印语句,也就是第二个打印语句,然后逐次退出第三层,第二层和第一层;

递归和循环,条件下可以相互转换;

可在调试模式下观察代码执行过程。

  1. /*recur.c -- 演示递归*/
  2. // 20210315 23:59
  3. #include <stdio.h>
  4. void up_and_down(int);
  5. int main(void)
  6. {
  7. up_and_down(1);
  8. return 0;
  9. }
  10. void up_and_down(int n)
  11. {
  12. printf("Level %d: n location %p\n", n, &n); // #1
  13. if (n < 4)
  14. up_and_down(n + 1);
  15. printf("LEVEL %d: location %p\n", n, &n); // #2
  16. }

image.png

9.3.3尾递归

把递归调用置于函数末尾,return语句之前,相当于循环;

每次递归都会创造一组变量,所以占用的内存更多;

递归调用的数量受限于内存空间;

  1. // factor.c -- 使用循环和递归计算阶乘
  2. // 20210317 14:13
  3. #include <stdio.h>
  4. long fact(int n);
  5. long rfact(int n);
  6. int main(void)
  7. {
  8. int num;
  9. printf("This program calculates factorials.\n");
  10. printf("Enter a value in the range 0-12(q to quit):\n");
  11. while (scanf_s("%d", &num) == 1)
  12. {
  13. if (num < 0)
  14. printf("No negative numbers, please.\n");
  15. else if (num > 12)
  16. printf("Keep input under 13.\n");
  17. else
  18. {
  19. printf("loop: %d factorial = %ld\n", num, fact(num));
  20. printf("recursion: %d factorial = %ld\n", num, rfact(num));
  21. }
  22. printf("Enter a value in the range 0-12 (q to quit):\n");
  23. }
  24. printf("Bye!\n");
  25. return 0;
  26. }
  27. long fact(int n)
  28. {
  29. long ans;
  30. for (ans = 1; n > 1; n--)
  31. ans *= n;
  32. return ans;
  33. }
  34. long rfact(int n)
  35. {
  36. long ans;
  37. if (n > 0)
  38. ans = n * rfact(n - 1);
  39. else
  40. ans = 1;
  41. return ans;
  42. }

image.png

9.3.5 递归的优缺点

为编程问题提供了简单的解决方案,但会快速消耗计算机内存;

递归通过创造指数级变量快速占有大量内存空间,类似于fork()函数创建多线程;

image.png

9.4.5 使用头文件(构建文件项目)

1,文件结构

image.png

2,hotel.h

  1. /*hotel.h -- 符号常量和hotel.c中的所有函数原型*/
  2. //20210319 08:42
  3. #include <stdio.h>
  4. #define QUIT 5
  5. #define HOTEL1 180.00
  6. #define HOTEL2 225.00
  7. #define HOTEL3 255.00
  8. #define HOTEL4 355.00
  9. #define DISCOUNT 0.95
  10. #define STARS "****************************"
  11. //显示选择列表
  12. int menu(void);
  13. //返回预定天数
  14. int getnights(void);
  15. //根据费率,入住天数计算费用
  16. //并显示结果
  17. void showprice(double rate, int nights);

3 hotel.c

  1. /*hotel.c -- 酒店管理函数*/
  2. //20210319 0851
  3. #include <stdio.h>
  4. #include "hotel.h"
  5. int menu(void)
  6. {
  7. int code, status;
  8. printf("\n%s%s\n", STARS, STARS);
  9. printf("Enter the number of the desired hotel:\n");
  10. printf("1) Fairfield Arms 2) Hotel Olympic\n");
  11. printf("3) Cherworthy Plaza 4) The Stockon\n");
  12. printf("5) quit\n");
  13. printf("%s%s\n", STARS, STARS);
  14. while ((status = scanf_s("%d", &code) != 1) || (code < 1 || code > 5))
  15. {
  16. if (status != 1)
  17. scanf_s("%*s");
  18. printf("Enter an integer form 1 to 5, please.\n");
  19. }
  20. return code;
  21. }
  22. int getnights(void)
  23. {
  24. int nights;
  25. printf("How many nights are needed?");
  26. while (scanf_s("%d", &nights) != 1)
  27. {
  28. scanf_s("%*s");
  29. printf("Please enter an integer, such as 2.\n");
  30. }
  31. return nights;
  32. }
  33. void showprice(double rate, int nights)
  34. {
  35. int n;
  36. double total = 0.0;
  37. double factor = 1.0;
  38. for (n = 1; n <= nights; n++, factor *= DISCOUNT)
  39. total += rate * factor;
  40. printf("The total cost will be $%0.2f.\n", total);
  41. }

3 main.c

  1. /*usehotel.c -- 房间汇率程序*/
  2. /*与程序清单9.10一起编译*/
  3. //20210319 09:23
  4. #include <stdio.h>
  5. #include "hotel.h"
  6. int main(void)
  7. {
  8. int nights;
  9. double hotel_rate;
  10. int code;
  11. while ((code = menu()) != QUIT)
  12. {
  13. switch (code)
  14. {
  15. case 1:
  16. hotel_rate = HOTEL1;
  17. break;
  18. case 2:
  19. hotel_rate = HOTEL2;
  20. break;
  21. case 3:
  22. hotel_rate = HOTEL3;
  23. break;
  24. case 4:
  25. hotel_rate = HOTEL4;
  26. break;
  27. default:
  28. hotel_rate = 0.0;
  29. printf("Oops!\n");
  30. break;
  31. }
  32. nights = getnights();
  33. showprice(hotel_rate, nights);
  34. }
  35. printf("Goodbye and welcome next time.\n");
  36. return 0;
  37. }

4,测试输出

image.png

9.5 查找地址:&运算符

取地址运算符也是一元运算符;

子函数共享主调函数的变量;

  1. /*locheck.c -- 查看变量被存储在何处*/
  2. // 20210320 19:31
  3. #include <stdio.h>
  4. void mikado(int);
  5. int main(void)
  6. {
  7. int pooh = 2, bah = 5;
  8. printf("In main(), pooh = %d and &pooh = %p\n", pooh, &pooh);
  9. printf("In main(), bah = %d and &bah = %p\n", bah, &bah);
  10. mikado(pooh);
  11. return 0;
  12. }
  13. void mikado(int bah)
  14. {
  15. int pooh = 10;
  16. printf("In mikado(), pooh = %d and &pooh = %p\n", pooh, &pooh);
  17. printf("In mikado(), bah = %d and &bah = %p\n", bah, &bah);
  18. }

image.png

9.6更改主调函数的变量

失败截图

  1. /*swap1.c -- 第一个版本的交换函数*/
  2. // 20210320 19:53
  3. #include <stdio.h>
  4. void interchange(int u, int y);
  5. int main(void)
  6. {
  7. int x = 5, y = 10;
  8. printf("Originally x = %d and y = %d.\n", x, y);
  9. interchange(x, y);
  10. printf("Now x = %d and y = %d.\n", x, y);
  11. return 0;
  12. }
  13. void interchange(int u, int v)
  14. {
  15. int temp;
  16. temp = u;
  17. u = v;
  18. v = temp;
  19. }

image.png

对代码的改进

使用printf()语句,检查程序运行过程

  1. /*swap1.c -- 第一个版本的交换函数*/
  2. // 20210320 20:19
  3. #include <stdio.h>
  4. void interchange(int u, int y);
  5. int main(void)
  6. {
  7. int x = 5, y = 10;
  8. printf("Originally x = %d and y = %d.\n", x, y);
  9. interchange(x, y);
  10. printf("Now x = %d and y = %d.\n", x, y);
  11. return 0;
  12. }
  13. void interchange(int u, int v)
  14. {
  15. int temp;
  16. printf("Originally u = %d and v = %d.\n", u, v);
  17. temp = u;
  18. u = v;
  19. v = temp;
  20. printf("Now u = %d and v = %d.\n", u, v);
  21. }

image.png

修改函数调用方式,传递参数返回主调函数,交换子函数的值会传递给主调函数,但缺点也是明显的。

首先需要修改函数原型声明,其次只能返回一个值

  1. /*swap1.c -- 第一个版本的交换函数*/
  2. // 20210320 20:19
  3. #include <stdio.h>
  4. int interchange(int u, int y);
  5. int main(void)
  6. {
  7. int x = 5, y = 10;
  8. printf("Originally x = %d and y = %d.\n", x, y);
  9. x = interchange(x, y);
  10. printf("Now x = %d and y = %d.\n", x, y);
  11. return 0;
  12. }
  13. int interchange(int u, int v)
  14. {
  15. int temp;
  16. printf("Originally u = %d and v = %d.\n", u, v);
  17. temp = u;
  18. u = v;
  19. v = temp;
  20. printf("Now u = %d and v = %d.\n", u, v);
  21. return (u);
  22. }

image.png

9.7 指针简介

使用指针也需要声明指针的数据类型;

&将变量指向该变量的内存地址, *解析该内存地址存储的值;

  1. val = *ptr;
  2. ptr = &bah;
  3. val = *ptr;
  4. // 等价于 val = bah

9.7.2 声明指针

必须指明指针指向变量的数据类型;

程序员在声明指针数据类型时在* 和数据类型之间保留一个空格,但是解引用变量时忽略空格;

指针指向的是一个新的数据类型,不要对它做整数的一些操作;

%p用于printf()语句中指针的转义声明;

9.7.3 使用指针在函数间通信

传入的是指向该变量的内存地址,但是函数内部完成交换是通过解析引用运算符

  1. // 第一种声明方式,形式参数必须是一个与调入值数据类型相同
  2. void interchange(int u, int v);
  3. // 第二种声明方式, 形式参数必须是一个指向正确类型的指针
  4. void interchange(int * u, int * v);
  1. /* swap3.c 使用指针解决交换函数失败(实际是一种调用关系)*/
  2. // 20210320 20:43
  3. #include <stdio.h>
  4. void interchange(int * u, int * v);
  5. int main(void)
  6. {
  7. int x = 5, y = 10;
  8. printf("Orinignally x = %d and y = %d.\n", x, y);
  9. interchange(&x, &y);
  10. printf("Now x = %d and y = %d.\n", x, y);
  11. return 0;
  12. }
  13. void interchange(int * u, int * v)
  14. {
  15. int temp;
  16. temp = *u;
  17. *u = *v;
  18. *v = temp;
  19. }

image.png