当前位置:   article > 正文

嵌入式学习之路 11(C语言基础学习——函数)

嵌入式学习之路 11(C语言基础学习——函数)

        函数的思想:从上到下,逐步求解问题的过程。将一个大的问题拆开成一个个小问题,每一个小问题都有与之对应的解决方案,这个解决方案就是“函数”。

函数的定义:(表示把功能实现出来)

1.main函数之前:

2.main函数之后:需要在调用函数之前声明该函数,函数头+分号 就是函数声明

表示形式:

  1. 类型标识符 函数名(形式参数)
  2. 函数体代码;

类型标识符:表示函数要带出的结果的类型,既返回值的数据类型。

注意:

1、数组类型也是数据类型,但这里不能用数组类型来表示返回值的类型。

2、如果函数不需要带出什么结果,此时返回结果的类型说明符一般设计为void。如果类型为void,一般不写return。

3、如果返回结果的类型与类型说明符不一致,以类型说明符为准,最终结果的类型都会转为类型说明符表示的类型。

4、类型说明符如果不写,默认是int型。

函数名:函数的入口地址,命名规则符合标识符的命名规则。(由字母、数字、下划线组成组成,不能以数字开头)

形式参数:表示该函数要用到的数据,表明将来使用时需要用到的实际参数该怎么写。

注意:

1、每一个形参变量都必须明确指定类型,不能写成(int a,b)。

2、实参和形参对应的关系:类型匹配,个数相同,顺序一一对应。

3、函数传参,传递的是实际参数的数值(值传递)。

4、如果不需要接收实际参数,形参一般设计为void(表示空类型)。

函数体代码:这是用来实现函数具体功能的代码。

eg:写一个函数,实现两数求和。

1.先确定函数名

2.考虑函数需要用到哪些数据 ---- 形参

  形参的写法:
  (数据类型 形参变量名1,数据类型 形参变量名2 ...)

3.处理数据---- 函数体的具体实现 

4.考虑需不要带出结果 --- (返回值对应)类型说明符

  1. #include <stdio.h>
  2. //函数的定义
  3. int add(int num1,int num2)
  4. {
  5. int sum;
  6. sum = num1 + num2;
  7. return sum;
  8. }
  9. int main()
  10. {
  11. int ret;
  12. //函数的调用
  13. ret = add(1,2);
  14. printf("ret = %d\n",ret);
  15. return 0;
  16. }

函数的调用

1、函数语句

把函数调用作为一个语句。如"printstar(); "这时不要求函数带回值, 只要求函数完成一定的操作。

2、函数表达式

        函数出现在一个表达式中,这种表达式称为函数表达式。这时要求函数带回一个确定的值以参加表达式的运算。例如: c=2*max(a,b);函数 max 是表达式的一部分,它的值乘2再赋给c。

3、函数参数

函数调用作为一个函数的实参。

例如: m=max(a,max(b,c));

其中 max(b, c) 是一次函数调用,它的值作为 max 另一次调用的实参。 m的值是a,b,c三者中的最大者。

又如: printf ("%d\n", max (a,b));

也是把 max(a, b) 作为 printf 函数的一个参数。

函数调用作为函数的参数,实质上也是函数表达式形式调用的一种,因为函数的参数本来就要求是表达式形式。

在函数里又调用另一个函数,叫做函数的嵌套调用。

注意:函数不支持嵌套定义(在定义的函数里又定义新的函数),但是可以嵌套调用。

函数调用的关系:

调用者与被调用者 

  1. int main(void)
  2. {
  3. printf("%d\n",getMonthDays());
  4. return 0;
  5. }

这个个示例的代码里面:
main 为调用者  ---main函数是整个程序的入口,只能作为调用者 
getMonthDays --- 在此处是被调用者

函数调用的本质

实现函数调用的本质,实际上是利用了栈的结构,先入后出,保证了函数可以层层调用。

栈:实际上是一种数据结构,数据结构表示数据的一种组织形式。栈的特点是:先进后出(first in last out)FILO。

从c语言角度的栈,本质上是一块内存空间,只是按照栈这种数据结构来处理和使用的。

栈还可以放局部变量,变量的空间自动申请,自动释放。

c语言程序把内存划分了五个区域,栈只是其中的一个区域,栈(主要用来存放自动变量或函数调用的数据),堆(空间大,堆上的空间使用需要手动申请,手动释放),字符串常量区(只读),静态区,也叫全局区(用来存放全局变量和静态变量),代码区(只读)。

在 Linux 系统中,默认情况下栈的大小通常为 8MB,这是一个常见的默认设置。 然而,这个默认值是可以修改的。修改栈大小的方式可能因不同的情况和需求而有所不同。 一种常见的方法是通过编译器的选项来设置。例如,在使用 GCC 编译器时,可以使用 `-Wl,--stack=<size>` 选项来指定栈的大小,其中 `<size>` 是以字节为单位的栈大小值。 另外,在一些特定的环境或应用场景中,可能还可以通过系统配置文件或相关的内核参数来进行修改。 需要注意的是,修改栈大小应该谨慎进行。如果将栈大小设置得过小,可能会导致程序在运行时出现栈溢出的错误。相反,如果设置得过大,可能会浪费系统资源。 例如,如果一个程序需要处理大量的递归操作,可能需要适当增加栈的大小以避免栈溢出。但如果是一个简单的小型程序,过大的栈大小可能是不必要的。

特殊嵌套调用——递归

        递归就是函数自己调用自己,递归有两种形式,一是直接递归,二是间接递归。递归类似于循环——递归是一种特殊的循环。

下面是递归的几个简单的示例:

  1. #include <stdio.h> // 包含标准输入输出头文件
  2. /**
  3. * 计算斐波那契数列的第 n 项
  4. * @param n 项数
  5. * @return 斐波那契数列第 n 项的值
  6. */
  7. int pbnq(int n)
  8. {
  9. if(n==1 || n==2) // 如果是第 1 项或第 2 项
  10. {
  11. return 1; // 返回 1
  12. }
  13. else // 否则
  14. {
  15. return pbnq(n-1)+pbnq(n-2); // 递归计算,前两项之和
  16. }
  17. }
  18. /**
  19. * 计算 1 到 n 的累加和
  20. * @param n 上限
  21. * @return 1 到 n 的累加和
  22. */
  23. int sum(int n)
  24. {
  25. if (n==1) // 如果 n 为 1
  26. {
  27. return 1; // 返回 1
  28. }
  29. else // 否则
  30. {
  31. return sum(n-1)+n; // 递归计算,前 n - 1 项的和加上 n
  32. }
  33. }
  34. /**
  35. * 计算 n 的阶乘
  36. * @param n 数字
  37. * @return n 的阶乘
  38. */
  39. int jieCheng(int n)
  40. {
  41. if (n==1) // 如果 n 为 1
  42. {
  43. return 1; // 返回 1
  44. }
  45. else // 否则
  46. {
  47. return jieCheng(n-1)*n; // 递归计算,前 n - 1 项的阶乘乘以 n
  48. }
  49. }
  50. /**
  51. * 主函数
  52. */
  53. int main()
  54. {
  55. int n; // 定义输入的数字
  56. scanf("%d",&n); // 从用户输入获取数字
  57. //printf("%d\n",sum(100));
  58. //printf("%d\n",jieCheng(n));
  59. printf("%d\n",pbnq(n)); // 输出斐波那契数列的第 n 项
  60. return 0;
  61. }

经典的汉诺塔问题:

汉诺塔(Tower of Hanoi)是一个经典的数学问题和递归算法的示例。

 

问题描述:
有三根柱子 A、B、C ,在 A 柱上有 n 个圆盘,圆盘大小不等,大的在下,小的在上。要把这 n 个圆盘从 A 柱移动到 C 柱,在移动过程中始终保持大盘在下,小盘在上。每次只能移动一个圆盘,并且只能在三根柱子之间移动。

 

解决思路:
通过递归的方式来解决。
当只有一个圆盘时,直接将其从 A 柱移动到 C 柱。
当有多个圆盘时,把上面 n - 1 个圆盘看成一个整体,先将这 n - 1 个圆盘从 A 柱借助 C 柱移动到 B 柱,然后把最大的圆盘从 A 柱移动到 C 柱,最后再把 B 柱上的 n - 1 个圆盘借助 A 柱移动到 C 柱。

  1. #include <stdio.h> // 包含标准输入输出头文件
  2. /**
  3. * 移动函数,用于打印移动的起始位置和结束位置
  4. * @param befor 起始位置
  5. * @param after 结束位置
  6. */
  7. void move(int befor, int after)
  8. {
  9. printf("%c ---> %c \n",befor,after); // 打印移动的起始位置和结束位置
  10. }
  11. /**
  12. * 汉诺塔问题的递归函数
  13. * @param n 盘子的数量
  14. * @param begin 起始柱子
  15. * @param mid 中间柱子
  16. * @param end 目标柱子
  17. * @return 无
  18. */
  19. int hannuo(int n, int begin, int mid, int end)
  20. {
  21. if(n == 1) // 如果只有一个盘子
  22. {
  23. move(begin,end); // 直接从起始柱子移动到目标柱子
  24. }
  25. else // 如果盘子数量大于 1
  26. {
  27. hannuo(n-1,begin,end,mid); // 先将上面 n - 1 个盘子从起始柱子借助目标柱子移动到中间柱子
  28. move(begin,end); // 将最底下的盘子从起始柱子移动到目标柱子
  29. hannuo(n-1,mid,begin,end); // 再将中间柱子上的 n - 1 个盘子借助起始柱子移动到目标柱子
  30. }
  31. }
  32. /**
  33. * 主函数
  34. */
  35. int main()
  36. {
  37. int n; // 定义盘子数量变量
  38. scanf("%d",&n); // 从用户输入获取盘子数量
  39. hannuo(n,'A','B','C'); // 调用汉诺塔函数进行计算
  40. return 0;
  41. }

数组作为函数参数

        普通变量可以作为函数参数,数组也可以作为函数参数。 

        数组作为函数参数, 传递的是数组首元素的地址。---数组名可以做形参,也可以做实参。

1、数组元素作为函数参数

        由于实参可以是表达式,而数组元素可以是表达式的组成部分,因此数组元素当然可作为函数的实参,与用变量作实参一样,是单向传递,即“值传送”方式。

2、数组本身作为函数参数

总结:一维整形数组做函数的参数

做形参:写成数组形式,还需要传数组长度

做实参:传数组名,数组长度

  1. eg:
  2. printArrray(int a[],int len) //函数头,数组作为形参
  3. //printArrray (int *a,int len) ---编译器最终理解的形式
  4. printArray(a,len); //调用函数

练习: 

  1. #include <stdio.h> // 包含标准输入输出头文件
  2. /**
  3. * 打印数组函数
  4. * @param a 数组
  5. * @param len 数组长度
  6. */
  7. void printfArray(int a[],int len)
  8. {
  9. int i; // 循环变量
  10. for ( i=0; i<len; i++ ) // 遍历数组
  11. {
  12. printf("a[%d] = %d\n",i,a[i]); // 打印数组元素
  13. }
  14. }
  15. /**
  16. * 数组逆序函数
  17. * @param a 数组
  18. * @param len 数组长度
  19. */
  20. void nixu(int a[], int len)
  21. {
  22. int i=0; // 起始索引
  23. int j=len-1; // 结束索引
  24. int temp; // 临时变量用于交换
  25. while(i<j) // 当起始索引小于结束索引时
  26. {
  27. temp = a[i]; // 交换元素
  28. a[i] = a[j];
  29. a[j] = temp;
  30. i++; // 起始索引递增
  31. j--; // 结束索引递减
  32. }
  33. return ;
  34. }
  35. /**
  36. * 插入排序函数
  37. * @param a 数组
  38. * @param len 数组长度
  39. */
  40. void crpaixu(int a[], int len)
  41. {
  42. int i=0, j=0; // 循环变量
  43. int temp; // 临时变量用于存储待插入元素
  44. for( i=1; i<len; i++ ) // 从第二个元素开始
  45. {
  46. temp = a[i]; // 保存当前待插入元素
  47. j = i;
  48. while(j>0 && temp<a[i-1]) // 寻找插入位置
  49. {
  50. a[i] = a[i-1]; // 元素后移
  51. j--;
  52. }
  53. a[j] = temp; // 插入元素
  54. }
  55. return ;
  56. }
  57. /**
  58. * 二分查找函数
  59. * @param a 数组
  60. * @param len 数组长度
  61. * @param n 要查找的数字
  62. * @return 找到返回索引,未找到返回 -1
  63. */
  64. int chazhao(int a[], int len, int n)
  65. {
  66. int begin, mid, end; // 起始、中间、结束索引
  67. begin = 0;
  68. end = len - 1;
  69. while ( begin <= end ) // 当查找范围有效
  70. {
  71. mid = (begin+end)/2; // 计算中间索引
  72. if( n < a[mid] ) // 如果要查找的数字小于中间元素
  73. {
  74. end = mid - 1; // 缩小查找范围到前半部分
  75. }
  76. else if ( a[mid] < n ) // 如果中间元素小于要查找的数字
  77. {
  78. begin = mid + 1; // 缩小查找范围到后半部分
  79. }
  80. else
  81. {
  82. break; // 找到
  83. }
  84. }
  85. if (begin <= end) // 如果找到了
  86. {
  87. return mid; // 返回索引
  88. }
  89. else
  90. {
  91. return -1; // 未找到返回 -1
  92. }
  93. }
  94. /**
  95. * 主函数
  96. */
  97. int main()
  98. {
  99. int a[] = {1,2,3,4,5,6,7,8,9}; // 定义并初始化数组
  100. int len = sizeof(a)/sizeof(a[0]); // 计算数组长度
  101. /*printfArray(a,len);
  102. printf("-------------------\n");
  103. nixu(a,len);
  104. printfArray(a,len);
  105. printf("-------------------\n");
  106. crpaixu(a,len);
  107. printfArray(a,len);
  108. printf("-------------------\n");
  109. */
  110. int n; // 要查找的数字
  111. printf("input n : "); // 提示输入
  112. scanf("%d",&n); // 读取输入
  113. chazhao(a,len,n); // 执行查找
  114. if(chazhao(a,len,n)>=0) // 如果找到
  115. printf("found!\n"); // 打印找到
  116. else
  117. printf("no found!\n"); // 打印未找到
  118. return 0;
  119. }

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

闽ICP备14008679号