第6章 函数(2026.5正在更新)

运行环境以Dev-C++、Visual Studio 2022、MacOS的命令行和Xcode为主

0.章节引言

  • 1.随着程序的膨胀和复杂化,将所有代码“堆积”到main()函数中不利于代码维护,把程序分解成不同的“功能段”会使工作简化、程序明晰,这些“功能段”即函数

  • 2.函数由实现一定任务的语句序列组成,是实现特定功能的一个模块,是构成 C 程序的基本单元。它可以减轻main()函数的负担,使程序结构简明清晰、便于维护,同时增强代码的复用性

// void:函数没有返回值
void display() {
    printf("hello!\n");
}    // 此函数执行结束后,回到main()函数

int main(int argc, const char * argv[]) {    // 先执行main函数
    display();    // 函数调用,数据流立即跳转到display()函数中
    return 0;
}
  • 3.一个C程序由一个或多个函数组成,函数之间可以相互调用,但不能调用main()函数

    • 标准函数,库函数:scanf() printf() getchar() putchar() strlen() strcpy()

    • 用户自定义函数:display() add() sum() getValue() my_strcat() my_strcmp()

1.函数的构成

  • 1.1 函数的定义

    • C 语言要求函数要先定义、后调用,函数定义时必须指定函数名、参数类型、要实现的功能等,编译系统在调用函数时会根据函数定义的功能来执行
    // 类型说明符:函数的返回值类型,若函数无返回值,返回值类型为 void
    // 形参列表:函数所需要的参数,在函数调用中传递数据。若函数不需要传递数据,可省略参数,但括号不能省略
    // {} 内部是函数体,即该函数具体要执行的功能
    类型说明符 函数名(形参列表) {
        声明部分;
        语句部分;
    }
    
    • 案例分析:定义一个函数,实现两个整数的求和运算
    // 方法1:无参数、无返回值的函数。关键字 void 表示函数没有返回值,函数名后方括号是空的,没有任何参数
    void add() {
        int a, b, ret;
        scanf("%d%d", &x, &y);
        ret = x + y;
        printf("%d", result);
    }
    
    // 方法2:有参数、无返回值的函数。调用该函数时,主调函数把实际参数的值传递给 a 和 b
    void add(int a, int b) {
        int ret;
        ret = x + y;
        printf("%d", result);
    }
    
    // 方法3:有参数、有返回值的函数。int 表示函数有返回值,且类型为整型
    int add(int a, int b) {
        int ret;
        ret = x + y;
        // return 语句的作用是将 ret 的值作为函数返回值,变量 ret 的类型必须与返回值的类型相同
        return ret;
    }
    
  • 1.2 函数的参数

    • 参数的分类

      • 形式参数:在函数定义时指定的参数,简称形参,如以下max()函数中的x 和 y。实际上,若定义了max()函数而不去调用的话,它的参数x 和 y只是形式上存在的,不会向内存申请空间(即没有真实存在),所以称为形式参数

      • 实际参数:在函数调用时传递的数据,简称实参,如以下main()函数的a 和 b。形式参数只有在函数被调用的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形参的实例化

    • 案例分析1:输出两个整数中的较大值

      • 函数max()被调用前,形参x 和 y并不占用内存空间

      • 函数max()main()函数调用时,形参x 和 y被分配内存空间,并被实参a 和 b分别赋值

      • 函数max()执行期间,由于形参x 和 y已有值,所以可执行x > y? x:y操作,并将结果返回

      • 函数max()执行结束后,形参x 和 y占用的内存空间被释放

      // 代码中实参传值给形参属于“值传递”
      #include <stdio.h>
      
      int max(int x, int y) {
          int z = 0;
          z = x > y? x:y;
      
          return z;
      }
      
      int main(int argc, const char * argv[]) {
          int a = 0, b = 0, c = 0;
          printf("input a、b:");
          scanf("%d %d", &a, &b);
      
          c = max(a, b);    // 函数调用,实参 a 的值传递给形参 x,实参 b 的值传递给形参 y,函数执行结束后的返回值传递给 c
          printf("较大值: %d", c);
      
          return 0;
      }
      

      image

    • 案例分析2:验证“值传递”方式中形参值的变化不影响实参

      • 实参num1的初值为10,实参num2的初值为20,调用函数swap()后,num1 和 num2的值并没有交换

      • 实参num1 和 num2分别传值给形参x 和 yx的值为 10,y的值为 20。当函数swap()执行结束后,形参x 和 y的值分别为 20 和 10,但是实参num1 和 num2的值仍未改变,说明此处函数调用对实参num1 和 num2未起到交换数值的作用

      • 代码中参数的传递方式为“值传递”(单向传递),当函数调用结束后,形参占用的内存空间被释放,所以形参不能传递值给实参,形参值的变化当然不会影响实参

      #include <stdio.h>
      
      void swap(int x, int y) {
          int temp;
          printf("函数内部  交换之前:x=%d, y=%d\n", x, y);
          temp = x;
          x = y;
          y = temp;
          printf("函数内部  交换之后:x=%d, y=%d\n", x, y);
      }
      
      int main(int argc, const char * argv[]) {
          int num1,num2;
          scanf("%d%d",&num1,&num2);
          
          printf("交换之前:num1=%d, num2=%d\n", num1, num2);
          swap(num1, num2);
          printf("交换之后:num1=%d, num2=%d\n", num1, num2);
          
          return 0;
      }
      
    • 注意事项

      • 1.函数的形参可以有多个,用,分隔,类型相同或不同均可。相同类型时不可随意简写,如int max(int x, int y)不可简写为int max(int x, y)

      • 2.形参必须是变量,实参可以是变量、常量或表达式,且必须要有确定的值

      • 3.实参类型必须与形参类型兼容,在符合数据类型转换规则的前提下,系统会按照赋值转换规则进行类型转换,如float转为intchar转为int

      • 4.函数进行普通“值传递”时,将实参值传递给形参,传递方向为单向。传递结束后,若在函数中修改形参的值,并不会反作用于实参,两者在内存中分别占用不同的内存单元

      • 5.参数的传递方式分为“值传递”和“地址传递”。“地址传递”方式中实参传递给形参的是地址,即实参和形参指向同一个内存空间

  • 1.3 函数的返回值

    • 返回值情型

      • 若函数没有返回值,函数定义时的返回类型为void,函数体中不能使用return语句

      • 若希望获取函数调用后的执行结果,并在main()函数中使用,则函数必须使用return语句将结果返回给主调函数。即若函数需要返回值,函数中必须有return语句,且返回值与函数定义时的函数类型一致

    • 一般格式:return (表达式); 或 return 表达式;

    • 注意事项

      • 1.一个函数可以有多条return语句,分别为指定条件返回各自的值。执行完第1个return语句后便终止函数的执行,将结果返回给主调函数并继续执行主调函数中其他语句

      • 2.函数默认的返回类型为int,可省略函数类型说明符,但不建议这么做

      • 3.若return的类型和函数类型不一致,以函数类型说明符为准

      • 4.若函数没有return语句,则返回一个不确定的值。建议需要函数返回值时就加上函数的类型,不需要返回值时定义函数类型为void

    • 案例分析:分析程序中函数的调用过程和运行结果

      • 函数add()中执行x + y操作后,return语句将变量result的值返回给主调函数main(),并且赋值给main()函数中的变量sum
      #include <stdio.h>
      
      int add(int x, int y) {
          int result;
          result = x + y;
      
          return result;
      }
      
      int main(int argc, const char * argv[]) {
          int sum;
          sum = add(5, 10);
          printf("%d\n", sum);
      
          return 0;
      }
      

      image

2.函数的调用与声明

  • 2.1 函数普通调用

    • 一般格式:无参函数的调用语法为函数名(),有参函数的调用语法为函数名(实参列表)

    • 调用位置与方式

      • 1.函数语句:将函数调用当做一条语句,即函数名();,最熟悉的调用方式
      // 函数有或没有返回值都可以这样调用
      max(a, b);
      calcu(n);
      
      // 该调用方法只执行函数中的语句,不使用返回值
      
      • 2.函数表达式:函数出现在表达式中,要求函数返回一个确定的值以参与表达式运算
      // 函数有返回值才可以这样调用,将函数的调用作为一个值参与表达式运算
      c = 2 * max(a, b) + 5;
      
      // void型函数不允许使用这种方式
      
      • 3.作为函数参数:把函数调用作为另外一个函数的参数
      // 函数有返回值才可以这样调用,将函数的调用作为函数实参传递给函数形参
      d = max(max(a, b), c);
      printf("处理的结果 %d\n", add(5, 10));
      
      // void型函数不允许使用这种方式
      
    • 注意事项

      • 1.()是函数调用操作符。若函数无参数,则括号内为空,但不能省略

      • 2.多个参数之间使用逗号分隔

      • 3.实参个数与形参相同,且类型赋值兼容

    • 案例分析

      • 1.无参函数调用流程分析
      // 程序从 main() 函数开始执行,输出第 1 行字符串“fun函数调用前”
      // main() 函数中第 2 行语句调用 fun() 函数,程序转至 fun() 函数
      // fun() 函数执行结束后,程序返回原来的调用点,即main() 函数中第 2 行,继续从第 3 行开始执行,直至 main() 函数结束
      #include <stdio.h>
      
      void fun() {
          printf("这是一个无参函数\n");
      }
      
      int main() {
          printf("fun函数调用前\n");
          fun();
          printf("fun函数调用后\n");
              	
          return 0;
      }
      

      image

      • 2.输入 n 的值,计算 1 ~ n 的整数和
      #include <stdio.h>
      
      int calcu(int x) {    // 求和函数
          int i = 0, total = 0;
          for(i = 1; i <= x; i++) {
              total += i;
          }
          return total;
      }
      
      int main(int argc, const char * argv[]) {
          int n = 0;
          scanf("%d", &n);
      
          printf("1+2+3+...+%d = %d", n, calcu(n));    // 传递n的值给形参x
          return 0;
      }
      
  • 2.2 函数的嵌套调用

    • 定义:函数的定义是独立的,在一个函数中不能定义另外一个函数。但函数可以嵌套调用,即可以在一个函数中调用另一个函数

    • 案例:函数嵌套调用程序的运行过程与结果分析

      • main()函数是程序的入口,一般不会像其他自定义函数显式调用,而是由编译器进行隐性调用。程序从main()函数开始执行,调用fun1()函数,执行fun1()函数中的代码

      • 调用fun2()函数,程序流程转向fun2()函数,执行fun2()函数中的代码,直至fun2()函数执行结束

      • 返回到fun1()函数中调用fun2()函数的位置,继续执行fun1()函数中的语句,直至fun1()函数执行结束

      • 返回到main()函数中调用fun1()函数的位置,继续执行main()函数,直至main()函数执行结束

      #include <stdio.h>
      
      void fun2() {
           printf("调用fun2()函数\n");
      }
      
      void fun1() {
           printf("调用fun1()函数\n");
           fun2();
      }
      
      int main() {
           fun1();
           
           return 0;
      }
      

      image

  • 2.3 函数的递归调用

    • 定义:在函数的嵌套调用中,如果函数嵌套调用的是自己,就是函数的递归调用

    • 特征

      • 1.递归调用必须要有终止条件,无限递归会导致程序崩溃,通常在函数定义时通过if语句控制

      • 2.实现递归算法的关键是找出递归公式(即递归关系和递归终止条件),根据公式找出下一项,直到得到结果

      • 3.递归算法容易理解,但递归有时会增加程序运行时间,消耗大量内存。用户应当权衡程序的运行时间与内存的占用情况,在程序的易读性和性能之间做出抉择

    • 案例1:函数递归调用程序的运行过程与结果分析。计算 1 ~ n 的整数之和

      • 递推:把大问题不断拆成更小的同类问题,一直拆到最简单的已知结果。要计算1 ~ n的整数之和,需要计算1 ~ n - 1的整数之和,要计算1 ~ n - 1的整数之和,需要计算1 ~ n - 2的整数之和,以此类推,直到递推至 n = 1时的和为 1

      • 回归:从最小问题的结果出发,反向代入,一步步算出上层问题的结果。由n = 1的和得到1、2之和(1 + 2 = 3),然后用这个结果加 3,得到 1 ~ 3 的整数之和(1 + 2 + 3 = 6),以此类推,直到得到1 ~ n - 1的整数之和。最后将该结果加 n,得到1 ~ n的整数之和(1 + 2 + 3 + ... + n - 1 + n)。递归公式为:
        image

      #include <stdio.h>
      
      int fun(int n) {
          if(n == 1) {
              return 1;
          }
          else {
              return n + fun(n - 1);
          }
      }
      
      int main() {
          int sum;
          sum = fun(4);
          printf("1~ 4 的整数和是 %d\n", sum);
          return 0;
      }
      

      image

    • 案例2:输入正整数 n 的值,求n!

      • 阶乘的数学定义:n! = 1 * 2 * 3 * ... * (n - 1) * n,规定0! = 1,1! = 1
        image

      • 递推:以求4!为例

        • 根据公式,需要先求出3!,即f(4) = 4 * f(3)

        • 3!,需要先求出2!,即f(3) = 3 * f(2)

        • 2!,需要先求出1!,即f(2) = 2 * f(1)

        • 1!,触发递归终止条件,直接得到结果f(1) = 1

      • 回归

        • 已知f(1) = 1,回代计算f(2) = 2 * 1 = 2

        • f(2) = 2回代计算f(3) = 3 * 2 = 6

        • f(3) = 6回代计算f(4) = 4 * 6 = 24,最终得到4! = 24

      #include <stdio.h>
      
      float fac(int n) {
          float f = 0.0f;
          if(n <= 1) {    // 避免无限递归
              f = 1;
          }
          else {
             f = n * fac(n - 1);
          }
          return f;
      }
      
      int main(int argc, const char * argv[]){
          int n = 0;
          float result = 0.0f;
          printf("input an integer:");
          scanf("%d", &n);
      
          result = fac(n);
          printf("result=%.2f\n", result);
      
          return 0;
      }
      

    • 案例3:输入正整数 n,求斐波那契数列第 n 项的值

      • 斐波那契数列,又称黄金分割数列,数列的第一个数是 1,第二个数是 1,后续每个数都是前面两个数的和
      • 随着 n 值的增加,递归函数调用的次数将大幅度增加。例如当n = 10时,调用f() 函数109次,而当n = 20时,调用f() 函数达到13529次
        image
      #include <stdio.h>
      
      int cnt = 0;    // 全局变量,用于统计递归函数调用的次数
      int f(int n) {
          cnt++;
          if(n == 1 || n == 2) {
              return 1;
          }
          else {
              return f(n - 1) + f(n - 2);
          }
      }
      
      int main(int argc, const char * argv[]){
          int n = 0, result = 0;
          scanf("%d", &n);
          result = f(n);
      
          printf("fib(%d) = %d\n", n, result);
          printf("递归次数=%d\n", cnt);
      
          return 0;
      }
      

      image

    • 案例4:递归经典问题之汉诺塔,有 A、B、C 三根柱子,A 柱上有 n 个大小不同的盘子,大盘在下、小盘在上。要求把所有盘子从 A 移到 C,每次只能移动 1 个盘子,大盘不能放在小盘上面

      • 递归思想:把 n 个盘子的问题,拆解成n - 1个盘子的子问题。终止条件为当n = 1时,直接把盘子从 A 移动到 C,程序结束

        • 1.把 A 上的n ‑ 1个盘子,借助 C,移到 B

        • 2.把 A 剩下的最大 1 个盘子,直接移到 C

        • 3.把 B 上的n ‑ 1个盘子,借助 A,移到 C

      #include <stdio.h>
      
      int hanoi(int n, char x, char y, char z){  // n:塔的数量 a、b、c位柱子的名称
          if(n > 0){
              hanoi(n-1, x, z, y);
              printf("moving from %c to %c\n", x, z);
              hanoi(n-1, y, x, z);
          }
          return 0;
      }
      
      int main(int argc, const char * argv[]){
          int num = 0;
          char a, b, c;
          printf("请输入塔的数量和三个柱子的名称:\n");
          scanf("%d %c %c %c", &num, &a, &b, &c);
      
          hanoi(num, a, b, c);
      
          return 0;
      }
      
  • 2.4 函数声明

    • 一般格式:类型说明符 函数名(形参列表);,函数定义时的首行称为函数原型,函数声明时只需要在函数的原型后面加上;

    • 作用:把函数的信息(函数名、函数类型、参数的个数和类型)提前告知编译系统,在编译时对函数调用检查合法性,非法函数调用会编译报错

    • 声明位置

      • 1.函数声明在主调函数的外部,此时函数可以被声明之后出现的所有函数调用
      #include <stdio.h>
      int add(int, int);
      
      int main(int argc, const char * argv[]) {
          int sum;
          sum = add(5, 10);
          printf("%d\n", sum);
      
          return 0;
      }
      
      int add(int x, int y) {
          int result;
          result = x + y;
      
          return result;
      }
      
      • 2.函数声明在主调函数的内部,此时函数只能被主函数调用
      #include <stdio.h>
      
      int main(int argc, const char * argv[]) {
          int add(int, int);
          int sum;
          sum = add(5, 10);
          printf("%d\n", sum);
      
          return 0;
      }
      
      int add(int x, int y) {
          int result;
          result = x + y;
      
          return result;
      }
      
    • 注意事项

      • 1.为提高程序可读性,建议把函数原型写在main()函数前,函数定义写在main()函数后。若函数定义在main()函数前,可省略函数原型

      • 2.函数声明也称为函数原型,可写在主调函数中

      • 3.函数声明中可省略形参名称,只写形参类型,如int max(int, int);编译系统不检查形参名

3.数组作为函数参数

  • 3.1 数组元素作为函数参数

    • 值传递:实参和形参占用不同的存储单元,只是存储的值相同。数组元素作为函数参数传递,本质上传的是元素数值,与普通变量作为参数的行为类似,函数内部修改参数不会影响原数组的元素值

    • 案例分析

      • 1.找出数组中的最大值
      #include <stdio.h>
      
      int max(int x, int y) {    // 计算两数较大值的函数
          return x > y? x : y;
      }
      
      int main(int argc, const char * argv[]) {
          int a[10] = {0};
          int i = 0, m = 0;
      
          printf("input 10 integers:\n");
          for (i = 0; i < 10; i++) {
              scanf("%d", &a[i]);
          }
      
          m = a[0];  // 假设下标为 0 的元素为最大值,后续遍历下标为 1 ~ N - 1 的元素
          for (i = 1; i < 10; i++) {
              m = max(m, a[i]);  // 调用函数
          }
          printf("max is %d", m);
      
          return 0;
      }
      
  • 3.2 数组名作为函数参数

    • 地址传递

      • 因为数组名表示数组的首地址,所以实参传递地址给形参,形参和实参所代表的数组共享同一段内存单元

      • 若在函数中改变数组元素值,则修改了内存单元中存储的数据,实参数组对应元素值也会被修改

      • 编译器只关心数组的首地址,不关心数组长度,因此会加一个函数形参来接收数组长度

    • 案例分析

      • 1.编写一个函数显示数组的值
      #include <stdio.h>
      
      void display(int b[], int n) {
          int i = 0;
          for (i = 0; i < n; i++) {
              printf("%4d", b[i]);
          }
      }
      
      int main(int argc, const char * argv[]) {
          int a[5] = {10, 20, 30, 40, 50};
      
          display(a, 5);  // 数组名作为实参
          return 0;
      }
      
      • 2.某同学一学期有 5 门课程,根据每门课程的得分情况,找出其最高分和最低分
      // 定义一个函数 min 用来求最低分,定义一个函数 max 用来求最高分,如 int min(int array[5])
      // main()函数调用 min 函数 min(array),其中实参 score 是一个整型数组名
      #include <stdio.h>
      
      int main(int array[5]) {    
      
      }
      
  • 3.3 多维数组作为函数参数

    • 注意事项

      • 数组名作为函数参数传递时传递的是数组的首地址

      • 函数形参若是二维数组,可省略第一维的大小,不能省略第二维的大小,否则函数无法确定所传数组的行列数

      • 实参数组可以与形参数组的第一维大小不同,在函数形参中常添加一个整型参数表示二维数组第一维的大小

    • 案例分析

      • 1.将一个 4 * 4 的矩阵转置
      // 分析:矩阵转置就是第i行第j列的元素与第j行第i列的元素交换
      
      #include <stdio.h>
      
      void reverse(int b[][4], int n) {    // 实现矩阵转置
          int i = 0, j = 0, temp = 0;
      
          for (i = 0; i < n; i++) {
              for (j = 0; j <= i; j++) {    // 只遍历数组主对角线及左下角的元素
                  temp = b[i][j];
                  bi[][j] = b[j][i];
                  b[j][i] = temp;
              }
          }
      }
      
      void printfarr(int a[][4], int n) {    // 实现打印数组元素
          int i = 0, j = 0;
      
          for(i = 0; i < 4; i++) {
              for(j = 0; j < 4; j++) {
                  printf("%4d", a[i][j]);
              }
              printf("\n");
          }
      }
      
      int main(int argc, const char * argv[]) {
          int a[4][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}};
      
          printf("输出转置前的矩阵:\n");
          printfarr(a, 4);
      
          reverse(a, 4);
      
          printf("\n输出转置后的矩阵:\n");
          printfarr(a, 4);
      
          return 0;
      }
      

4.变量的作用域

  • 4.1 局部变量

    • 定义:在函数内部或代码块中({}内部)声明的变量,包括函数的形参

    • 作用域:变量所在的局部范围仅在包含该变量声明的函数中才起作用,在函数外不能使用

    • 注意事项

      • 1.局部变量的生命周期为从定义开始,到函数或代码块结束

      • 2.在程序的各个部分允许使用同名变量,实参和形参名也允许同名,如案例分析3

      • 3.main()函数中的变量也是局部变量

      • 4.允许在复合语句(分程序或程序块)中定义变量,它仅在复合语句内有效,如案例分析1

    • 案例分析

      • 1.分析以下代码的执行情况
       #include <stdio.h>
      
      int main(int argc, const char * argv[]) {
          int i = 0;    // i 的作用域是整个 main() 函数
          for (i = 0; i < 3; i++) {
              ;
          }
          printf("%d\n", i);
      
          return 0;
      }
      
      // 可正常编译运行
      
      #include <stdio.h>
      
      int main(int argc, const char * argv[]) {
          for (int i = 0; i < 3; i++) {
              ;
          }
          printf("%d\n", i);    // 超出了i的作用域
      
          return 0;
      }
      
      // 编译报错,提示变量 i 未声明
      // 变量 i 在for循环中声明,因此其生效范围仅在 for 循环中
      

      image

      • 2.分析以下代码的执行情况
      #include <stdio.h>
      
      void local_value() {
          int a = 1, b = 2;    // a、b的作用域仅限于local_value函数中
          printf("%d %d", a, b);
      }
      
      int main(int argc, const char * argv[]) {
          printf("%d %d", a, b);
      
          return 0;
      }
      
      // 编译报错,main() 函数中并未定义变量 a、b
      

      • 3.分析以下代码的执行情况
      #include <stdio.h>
      
      void local_value() {
          int a = 1, b = 2;    // a、b的作用域仅限于local_value函数中
          printf("%d %d", a, b);
      }
      
      int main(int argc, const char * argv[]) {
          int a = 5;    // a、b的作用域仅限于main()函数中
          float b = 2.5;
          printf("%d %f\n", a, b);
      
          return 0;
      }
      // 可正常编译运行
      
    • 函数的地址

      • 定义:函数名本质上是指向函数代码起始地址的常量指针,可通过打印函数名或使用函数指针来获取函数的地址

      • 与变量作用域的关系:正是因为不同函数在内存中的地址不同,即占用的内存空间不同,所以才允许在不同函数中使用相同的变量名

      • 案例分析

      #include <stdio.h>
      
      void local_value() {
          int a = 1, b = 2;
          printf("%d %d", a, b);
      }
      
      int main(int argc, const char * argv[]) {
          int a = 5;
          float b = 2.5;
          printf("main函数的地址为: %p\n", (void *)main);    // 将函数指针强制转换为void*类型以匹配%p格式说明符
          printf("local_value函数的地址为: %p\n", (void *)local_value);
      
          return 0;
      }
      

  • 4.2 全局变量

    • 定义:在函数外部({}外部)定义的变量,也称外部变量或全程变量

    • 作用域:整个工程;从定义位置到文件结束,在这个范围的任何地方都可使用,包括函数内

    • 注意事项

      • 1.全局变量的生命周期即整个程序的生命周期

      • 2.全局变量的作用域使得其值容易修改,降低了程序的可读性,容易产生逻辑错误

        • 模块之间高耦合度导致某个模块的修改牵涉到其他模块修改,增加了代码的脆弱性和复杂性
      • 3.允许局部变量和全局变量重名但不建议这么做,在局部变量作用域内全局变量被"屏蔽"

      • 4.程序设计要满足"高内聚,低耦合",全局变量破坏了这一要求

    • 案例分析

      • 1.分析以下代码的执行情况
      #include <stdio.h>
      int a = 5, b = 10;    // 全局变量a、b
      
      void modify() {
          int a = 5, b = 10;    // 局部变量a、b,此时全局变量a、b被屏蔽
          a++;
          b--;
          printf("pos1: a=%d, b=%d\n", a, b);    // a的值为 6,b的值为 9
      }
      
      int main(int argc, const char * argv[]) {
          modify();
          printf("pos2: a=%d, b=%d\n", a, b);    // a的值为 5,b的值为 10
      
          return 0;
      }
      

5.变量的存储类别

  • 5.1 存储方式

    • 时空分析

      • 时间角度:生存期,静态存储方式和动态存储方式

      • 空间角度:作用域,局部变量和全局变量

    • 内存划分

      • 栈区

        • 存放局部变量和函数调用时的上下文信息,如现场保护、返回地址等

        • 容量有限,递归过深或局部变量过大都可能会导致栈溢出

      • 堆区

        • 程序运行时动态分配的内存(通过 malloc/calloc/realloc 申请)

        • 适合存储生命周期不确定的数据,如链表、动态数组

        • 程序员负责申请和释放(free)内存,忘记释放会导致内存泄露

      • 静态区

        • 存放静态变量和全局变量

        • 程序执行期间始终占据固定存储单元

    • 变量属性

      • 数据类型:定义变量的取值范围和允许的操作

      • 存储类别:数据在内存中存储分配和释放的时机以及存储结构

  • 5.2 自动变量

    • 定义:函数中的局部变量未加static声明就属于自动变量,包括函数内的局部变量、函数形参和复合语句中的变量

    • 注意事项

      • 1.调用函数时为自动变量分配内存,调用结束后释放内存

      • 2.声明关键字为auto,如auto int z;,可省略auto

      • 3.C 标准(如 C89、C99、C11)明确规定,函数形参列表中不能显式使用存储类别说明符,如auto

  • 5.3 静态变量

    • 1.静态局部变量

      • 定义:使用static关键字修饰的局部变量

      • 特征:属于静态存储类型,在静态存储区分配内存;程序运行之初就分配内存,而非函数调用时,运行结束后释放

      • 适用场合

        • 1.保留调用后的变量值

        • 2.确定某函数的调用次数

      • 注意事项

        • 1.未初始化的静态局部变量系统自动为其初始化为0

        • 2.静态局部变量的初始化在编译时进行,可通过汇编代码查看,静态局部变量的部分没有汇编代码。也可以分步调试查看,调试期间会自动跳过静态变量所属语句

        • 3.静态局部变量在程序运行期间都存在,作用域仍为函数内部

        • 4.静态局部变量长期占用内存,应尽量少用

      • 案例分析

        • 1.分析以下代码的执行情况
        #include <stdio.h>
        
        int i = 0;
        
        void sub() {
            int a = 0;
            static int b = 0;    // 静态局部变量b,保留上一次函数调用后的值
            a++;
            b++;
            printf("%d: a=%d, b=%d\n", i, a, b);
        }
        
        int main(int argc, const char * argv[]) {
            for(i = 1; i <= 3; i++) {
                sub();
            }
        
            return 0;
        }
        // 1: a=1, b=1
        // 2: a=1, b=2
        // 3: a=1, b=3
        
    • 2.静态全局变量

      • 全局变量的外部链接属性变成了内部链接属性

      • 其他.c源文件不能再使用该变量,使用时感觉作用域变小

    • 3.静态函数

      • 定义:用static关键字修饰的函数

      • 特征:只能在定义它的源文件内部被调用,其他源文件无法对其调用,即便其他文件使用extern声明也不行

      • 注意事项

        • 不同源文件有同名的静态函数并不会引发冲突,因为它们的作用域相互独立

        • 静态函数具有内部链接属性,它不会被链接器导出,不会和其他文件中同名的函数产生冲突

  • 5.4 寄存器变量

    • 定义:存储在CPU的寄存器中的变量,用register修饰

    • 作用:访问寄存器的速度远高于访问内存的速度,因此能够提高程序运行效率

    • 注意事项

      • 1.只有局部变量和形式参数才允许声明为寄存器变量,通过不断分配与释放寄存器可提高寄存器利用率

      • 2.寄存器变量的数量受到计算机寄存器数目的限制

      • 3.多数系统只允许定义int、char和指针类型变量为寄存器变量;多数系统将register作为自动变量处理,存储在内存中

      • 4.若部分未定义为寄存器类别的变量使用频繁,编译系统自动将之存放在寄存器中

  • 5.5 外部变量

6.函数应用实例

  • 1.将序列的最大值和第 1 个数交换,最小值和最后一个数交换,并输出新序列
// 分析:遍历数组,定位最大值和最小值的下标,完成交换
// 注意1:代码通过最值的下标定位到最值,而不是直接记录最值的数值,因为数组中对元素的引用借助下标实现
// 注意2:第1个数恰好是最小值的特殊情况

#include <stdio.h>

void exchange(int b[], int n) {    // 完成交换的核心代码
    // max 为最大值下标,min 为最小值下标,初值均为0,即假设下标为 0 的数为最大/小值
    int max = 0, min = 0;
    int i = 0, temp = 0;
    
    for (i = 1; i < n; i++) {
        if (b[i] > b[max]) {
            max = i;
        }
        if (b[i] < b[min]) {
            min = i;
        }
    }
    printf("最大值为 %d, 最小值为 %d\n", b[max], b[min]);

    // 最大值和下标为 0 的数交换
    temp = b[0];
    b[0] = b[max];
    b[max] = temp;
    
    // 若下标为 0 的位置恰好是最小值,它被替换到了下标为 max 的位置
    if (0 == min) {
        min = max;
    }
    
    // 最小值和下标为 n - 1 的数交换
    temp = b[n - 1];
    b[n - 1] = b[min];
    b[min] = temp;
}

void print_arr(int a[], int n) {    // 完成打印数组元素的核心代码
    int i = 0;
    while (i < n) {
        printf("%4d", a[i]);
        i++;
    }
    printf("\n");
}

int main(int argc, const char * argv[]) {
    int a[10] = {-5, 3, 8, 9, -1, -3, 5, 6, 0, 4};
    int i = 0;
    
    printf("交换前: ");
    print_arr(a, 10);
    exchange(a, 10);
    printf("\n交换后: ");
    print_arr(a, 10);
}
  • 2.编写一个函数,在一个整数序列中查找某个整数,若存在,返回该整数在序列中的位置;否则返回 -1 。要求在主函数中调用,输出结果
// 分析:遍历数组,逐个对比当前数组元素与目标值看是否相等
// 本题可延伸至二分查找,具体参考案例分析4

#include <stdio.h>

int search(int a[], int n, int target) {    // 实现查找的核心代码
    int i = 0;

    for (i = 0; i < n; i++) {
        if (a[i] == target) {
            return i;    // 找到目标,返回下标
        }
    }
    return -1;    // 未找到目标
}

int main(int argc, const char * argv[]) {
    int arr[] = {10, 20, 30, 40, 50};
    int size = sizeof(arr) / sizeof(arr[0]);
    int target = 30;

    int result = search(arr, size, target);
    
    if (result != -1) {
        printf("目标整数 %d 在数组中的位置是: %d\n", target, result);
    }
    else {
        printf("目标整数 %d 不在数组中\n", target);
    }

    return 0;
}
  • 3.编写一个函数,将字符串转换成相应整数。转换时,遇到非数字字符停止转换。要求在主函数中调用,输出结果。例如:"123"——>123;"12a3"——>12;"a123"——>0
#include <stdio.h>
#include <ctype.h>

int convert(char s[]) {
    int result = 0;
    int i = 0;
    
    // 跳过前导空格
    while (s[i] == ' ') {
        i++;
    }
    
    // 处理正负号(默认为正数)
    int sign = 1;
    if (s[i] == '-') {
        sign = -1;
        i++;
    }
    else if (str[i] == '+') {
        i++;
    }
    
    // 转换数字字符,遇到非数字则停止
    while (isdigit(s[i])) {
        result = result * 10 + (s[i] - '0');
        i++;
    }
    
    return sign * result;
}

int main(int argc, const char * argv[]) {
    char test1[] = "123";
    char test2[] = "12a3";
    char test3[] = "a123";
    char test4[] = "   -456xyz";
    char test5[] = "   +789";
    
    printf("%s -> %d\n", test1, convert(test1));
    printf("%s -> %d\n", test2, convert(test2));
    printf("%s -> %d\n", test3, convert(test3));
    printf("%s -> %d\n", test4, convert(test4));
    printf("%s -> %d\n", test5, convert(test5));
    
    return 0;
}
  • 4.现有一按升序排列的数组 {3, 9, 10, 13, 16, 27, 30},输入正整数x,使用二分查找的方式查询它是否在数组中
//  代码1
#include <stdio.h>

int main(int argc, const char * argv[]) {
    // insert code here...
    int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int k = 17, sz = sizeof(arr) / sizeof(arr[0]);
    int left = 0, right = sz - 1;
    
    while (left <= right) {     // 中间有元素被查找,要带等于
//      int mid = (left + right) / 2;    // mid的求取在循环内
        int mid = left + (right - left) / 2;    // 避免left + right越界溢出,将right比left多的部分的一半再加到left上,这样两者就一样了
        if (arr[mid] < k) {
            left = mid + 1;
        }
        else if (arr[mid] > k) {
            right = mid - 1;
        }
        else {
            printf("找到了, 下标是: %d\n", mid);
            break;
        }
    }
    if (left > right) {
        printf("找不到\n");
    }
    return 0;
}

// 代码2
#include <stdio.h>

int main(int argc, const char * argv[]){
    int a[7] = {3, 9, 10, 13, 16, 27, 30};
    int left = 0, right = 6;
    int mid = 0, x = 0;

    printf("请输入要查找的数:\n");
    scanf("%d", &x);

    while(left <= right){
        mid = (left + right) / 2;
        if(a[mid] > x){
            right = mid - 1;
        }
        else if(a[mid] < x){
            left = mid + 1;
        }
        else{
            printf("查找的数据在第%d个\n", mid + 1);
            return mid;
        }
    }
    printf("查找的数据不存在!\n");
    return 0;
}

// 代码3
#include <stdio.h>

int binary_sort(int s[], int n, int x){
    int left = 0, right = n - 1, mid;

    while(left <= right){
        mid = (left + right) / 2;
        if(s[mid] > x){
            right = mid - 1;
        }
        else if(s[mid] < x){
            left = mid + 1;
        }
        else{
            return mid;
        }
    }
    return -1;
}

int main(int argc, const char * argv[]){
    int a[7] = {3, 9, 10, 13, 16, 27, 30};
    int index = 0, x = 0;

    printf("请输入要查找的数:\n");
    scanf("%d", &x);

    index = binary_sort(a, 7, x);
    if(index >= 0){
        printf("要查找的 %d 下标为 %d\n", x, index);
    }
    else {
        printf("查找的数据不存在!\n");
    }
    return 0;
}
posted @ 2025-03-06 09:32  pycoder_666  阅读(131)  评论(0)    收藏  举报