C语言:函数后续

C语言基础知识:函数的调用 程序调用自身的行为就是递归。可以直接或间接的调用,本质是把复杂的问题转化为一个规模小的问题。递归一般只需少量的代码就可描绘出多次重复计算。其主要思考方式在于大事化小。优点是为具有某些特征的编程问题提供了最简单的策略,缺点是层层调用,算法的复杂度可能过高,以致于快速耗干了计算机的内存资源,不方便阅读和维护等。 阅读详情

函数的嵌套调用

定义

函数不允许嵌套定义,但是允许嵌套调用。

  • 正确示例:函数嵌套调用

     
    void a()
     {
         ...
     }
     // 函数的嵌套调用
     void b()
     {
         a();
     }
     ​
     int main()
     {
         printf(...);
     }

  • 错误示例:函数嵌套定义

     // 函数的嵌套定义
     void a()
     {
         void b()
         {
            ... 
         }
     }

嵌套调用:在被调函数内又主动去调用其他函数,这样的函数调用形式,称之为嵌套调用。

案例
案例1
  • 需求:编写一个函数,判断给定的3~100的正整数是否是素数,若是返回1,否则返回0

  • 分析:

    • 素数:又被称作质数,素数是大于1的自然数,除了1和它本身以外,不能被其他数整除。

  • 代码:

     /*************************************************************************
       > File Name:    demo01.c
       > Author:       三道渊
       > Description:  
       > Created Time: 2025年07月23日 星期三 09时31分01秒
      ************************************************************************/
     ​
     #include <stdio.h>
     ​
     /**
      * 定义一个函数,判断一个自然数是否是素数
      * @param n  待判断的自然数
      * @return   1-素数,0-非素数
      */
     int is_prime(int n)
     {
         // 定义一个标记,默认是素数
         int flag = 1;
         // 循环变量  
         int i;
     ​
         // 生成测试数据 2~n/2
         for (i = 2; i <= n/2; i++)
         {
             // 校验
             if (n % i == 0)
             {
                 flag = 0;
                 break;
             }
         }
     ​
         return flag;
     }
     ​
     int main(int argc,char *argv[])
     {
         // 生存3~100之间的自然数
         for (int i = 3; i <= 100; i++)
         {
             // 如果是自然数,就打印,否则不打印
             if (is_prime(i)) printf("%-4d",i);
         }
     ​
         printf("\n");
     ​
         return 0;
     }
     ​
案例2
  • 需求:通过控制台输入一个整数,校验这个整数在一个已知数组中的位置,如果找到,返回下标,找不到,返回-1。

  • 代码:

     /*************************************************************************
       > File Name:    demo02.c
       > Author:       三道渊
       > Description:  
       > Created Time: 2025年07月23日 星期三 09时47分19秒
      ************************************************************************/
     ​
     #include <stdio.h>
     ​
     /** 
      * 定义一个函数,在一个数组中查找指定数据的位置
      * @param arr[]  待查询的数组
      * @param len    数组传参,只传数组首地址,因此我们没法通过sizeof关键字计算数组大小,所以需要从外部传递进来
      * @param n      待查询的数据
      * @return       >=0:找到,-1:未找到
      */ 
     int find_index(int arr[], int len, int n)
     {
         // 定义一个标志
         int index = -1;
     ​
         // 遍历数组
         for (int i = 0; i < len; i++)
         {
             // 获取位置
             if (arr[i] == n)
             {
                 // 更新位置
                 index = i;
                 break;
             }
         }
     ​
         return index;
     }
     ​
     int main(int argc,char *argv[])
     {
         int arr[] = {11,22,33,44,55};
         int len = sizeof(arr) / sizeof(arr[0]);
     ​
         int n = 66;
         int index = find_index(arr, len, n);
         index != -1 ? printf("%d在数组中的位置是%d\n",n, index) : printf("未找到%d在数组中的位置!\n", n);
     ​
         int n2 = 33;
         int index2 = find_index(arr, len, n2);
         index2 != -1 ? printf("%d在数组中的位置是%d\n",n2, index2) : printf("未找到%d在数组中的位置!\n", n2);
     ​
         return 0;
     }
     ​
案例3
  • 需求:输入四个整数,找出其中最大的数,用函数嵌套来实现,要求每次只能两个数比较。

  • 代码:

     /*************************************************************************
       > File Name:    demo03.c
       > Author:       三道渊
       > Description:  
       > Created Time: 2025年07月23日 星期三 10时08分34秒
      ************************************************************************/
     ​
     #include <stdio.h>
     ​
     // 函数声明
     int max_2(int, int);
     int max_4(int, int, int, int);
     ​
     /**
      * 求2个数中的最大值
      */ 
     int max_2(int a, int b)
     {
         return a > b ? a : b;
     }
     ​
     /**
      * 求4个数中的最大值
      */ 
     int max_4(int a, int b, int c, int d)
     {
         // 写法1
         // int max;
         // max = max_2(a,b);
         // max = max_2(max, c);
         // max = max_2(max, d);
         
         // 写法2
         return max_2(a,b) > max_2(c,d) ? max_2(a,b) : max_2(c,d);
     }
     int main(int argc,char *argv[])
     {
         int a, b, c, d;
     ​
         printf("请输入4个整数:\n");
         scanf("%d%d%d%d",&a, &b, &c, &d);
     ​
         printf("%d,%d,%d,%d中最大数是%d\n", a, b, c, d, max_4(a,b,c,d));
     ​
         return 0;
     }
     ​

函数的递归调用

定义
递归调用的含义

在一个函数中,直接或者间接调用了函数本身,就称之为递归调用。本质上还是函数的嵌套调用。

 // 直接调用   推荐
 a() → a();
 ​
 // 间接调用
 a() → b() → a();
 a() → b() → .. → a();
递归调用的本质

递归调用是一种循环结构,它不同于我们之前学过的while、for、do..while这样的循环结构,这些循环结构是借助于循环变量;而递归调用是利用函数自身实现循环结构,如果不加以控制,很容易产生死循环。

注意事项

① 递归调用必须要有出口,一定要想办法终止递归(否则会产生死循环)

② 对终止条件的判断一定要放在函数递归之前。(先判断,在执行)

③ 进行函数的递归调用。

④ 函数递归的同时一定要将函数调用向出口逼近。

递归的底层实现

C语言函数递归的底层实现依赖于程序调用栈(Call Stack),其核心过程是通过栈帧(Stack Frame)的创建与销毁来管理函数的多次调用。具体底层逻辑如下:

  1. 栈帧的作用

每次函数调用(包括递归调用自身)时,系统会在内存的栈区为该次调用分配一块独立的内存空间,即栈帧。栈帧用于存储:

  • 函数的参数值

  • 函数内的局部变量

  • 调用结束后返回的地址(即该次调用结束后,程序应回到的原执行位置)

  1. 递归调用的底层流程

以计算阶乘 n! 的递归函数 fac(n) 为例(fac(n) = n *fac(n-1) ,终止条件 fac(1) = 1 ):

  • 第一步:调用 fac(3) 时,系统创建栈帧1,存储参数 n=3 、局部变量及返回地址(假设主函数调用处),然后执行函数体,发现需要调用 fac(2) 。

  • 第二步:调用 fac(2) 时,系统在栈顶创建栈帧2,存储参数 n=2 及新的返回地址(fac(3) 中等待结果的位置),暂停 fac(3) 的执行,转去执行 fac(2) 。

  • 第三步:同理,调用 fac(1) 时,创建栈帧3,存储 n=1 及返回地址(fac(2) 中等待结果的位置)。

  • 终止与回溯:当执行到 fac(1) 时,触发终止条件,直接返回结果 1 。此时栈帧3被销毁,程序回到栈帧2中继续执行(计算 2 * 1 = 2 ),栈帧2销毁后回到栈帧1(计算 3 * 2 = 6 ),最终栈帧1销毁,返回结果给主函数。

  1. 关键特点

  • 栈的“后进先出”特性:递归调用时,新的栈帧总是压在栈顶,而只有最顶层的栈帧(即最后一次调用)执行完毕并返回结果后,上层的栈帧才能继续执行(对应递归的“回溯”过程)。

  • 栈溢出风险:若递归次数过多(如未设置终止条件或终止条件无法触发),会导致栈帧不断创建,超出栈区内存上限,触发“栈溢出(Stack Overflow)”错误,程序崩溃。

简言之,递归的底层本质是通过栈帧的层层创建(递推)和销毁(回溯),实现函数自身的多次调用管理,而栈的特性保证了递归调用的顺序和结果传递。

案例
案例1
  • 需求:

    递归案例

    有5个人坐在一起, 问第5个人多少岁?他说比第4个人大2岁。 问第4个人岁数,他说比第3个人大2岁。 问第3个人,又说比第2个人大2岁。 问第2个人,说比第1个人大2岁。 最后问第1个人,他说是10岁。 请问第5个人多大

  • 代码:

     /*************************************************************************
       > File Name:    demo04.c
       > Author:       三道渊
       > Description:  
       > Created Time: 2025年07月23日 星期三 11时15分55秒
      ************************************************************************/
     ​
     #include <stdio.h>
     ​
     /**
      * 定义一个函数,求年龄
      * @param n 第n个人
      * @return 第n个人的年龄
      */ 
     int age(int n)
     {
         // 创建一个变量,存储当前这个人的年龄
         int _age;
     ​
         // 出口
         if (n == 1) // 第1个人
         {
             _age = 10;
         }
         else if (n > 1)
         {
             _age = age(n-1) + 2; // 当前这个人的年龄 = 上一个人的年龄 + 2
         }
         return _age;
     }
     int main(int argc,char *argv[])
     {
         int n = 5;
         printf("第%d个人的年龄是%d岁!\n", n, age(n));
     ​
         return 0;
     }
     
    案例2
    • 需求:求n的阶乘

    • 代码:

       /*************************************************************************
         > File Name:    demo05.c
         > Author:       三道渊
         > Description:  
         > Created Time: 2025年07月23日 星期三 11时31分06秒
        ************************************************************************/
       ​
       #include <stdio.h>
       ​
       /**
        * 创建一个函数,实现n的阶乘
        * @param n 阶乘上限
        * @return n的阶乘结果
        */ 
       size_t fac(int n)
       {
           // 定义一个变量,用来接收阶乘结果
           size_t res;
       ​
           // 出口校验
           if (n <= 0)
           {
               printf("n的范围不能是0及以下数字!\n");
               return -1;
           }
           else if (n == 1) // 出口
           {
               res = 1; // 出口设置
           }
           else 
           {
               res = fac(n-1) * n;
           }
           return res;
       }
       ​
       int main(int argc,char *argv[])
       {
           size_t n;
       ​
           printf("请输入一个整数:\n");
           scanf("%lu", &n);
       ​
           printf("%lu的阶乘结果是%lu\n", n, fac(n));
       ​
           return 0;
       }
      
       ​
快速排序算法

快速排序是一种高效的分治(Divide and Conquer)排序算法。它的核心思想是通过选取一个基准值(pivot),将数组划分为两个子数组:一个子数组的所有元素比基准值小,另一个子数组的所有元素比基准值大,然后递归地对子数组进行排序。

快速排序的基本步骤
  1. 选择基准值(Pivot Selection):

    • 从数组中选择一个元素作为基准值。常见的选择方式包括:

      • 第一个或最后一个元素(简单但可能效率不高,尤其在已排序或接近排序的数组中)。

      • 随机选择一个元素(减少最坏情况概率)。

      • 三数取中法(如第一个、中间、最后一个元素的中位数,提高分区均衡性)。

  2. 分区(Partition):

    • 重新排列数组,使得:

      • 所有比基准值小的元素移到基准值的左侧。

      • 所有比基准值大的元素移到基准值的右侧。

    • 分区完成后,基准值处于其最终排序后的正确位置。

    • (注:分区是快速排序的关键步骤,常见的实现有Lomuto分区和Hoare分区方案。)

  3. 递归排序(Recursion):

    • 对基准值左侧的子数组和右侧的子数组递归地调用快速排序。

    • 递归的终止条件是子数组的长度为0或1(已有序)。

  4. 合并结果(Combine):

    • 由于每次分区后基准值已位于正确位置,且左右子数组通过递归排序完成,因此无需显式合并操作,整个数组自然有序。

总结

通过一个基准值(pivot)不断拆分数组,直到子数组无法再拆分(即子数组长度为1或0),此时整个数组就有序了。

代码
 /*************************************************************************
   > File Name:    demo06.c
   > Author:       三道渊
   > Description:  
   > Created Time: 2025年07月23日 星期三 14时23分26秒
  ************************************************************************/
 ​
 #include <stdio.h>
 ​
 /**
  * 快速排序函数(递归实现)
  * @param arr 待排序数组
  * @param n   数组长度
  *
  */ 
 void QSort(int arr[], int n)
 {
     // 出口限制,如果排序数组的大小<=1,此时数组就不再排序
     if (n <= 1) return;
 ​
     // 定义两个标记,i从左向右,j从右向左,锁定排序数组的区间
     int i = 0, j = n - 1;
     // 选择排序数组的第一个元素作为基准值(可优化为取中法)
     int pivot = arr[0];
 ​
     // 分区过程
     while(i < j)
     {
         // 从右向左查找第一个小于等于基准值的元素(<=基准值的数据)
         while (i < j && arr[j] > pivot) j--;
         // 从左向右查找第一个大于基准值的元素(>基准值的数据)
         while (i < j && arr[i] <= pivot) i++;
 ​
         // 交换这两个元素
         if (i < j)
         {
             int temp = arr[i];
             arr[i] = arr[j];
             arr[j] = temp;
         }
     }
 ​
     // 将基准值更新到正确位置(i == j)
     arr[0] = arr[i];
     arr[j] = pivot;
 ​
     //  切割子数组
     QSort(arr,i); // 左边部分
     QSort(arr+i+1,n-i-1); // 右边部分
 }
 ​
 int main(int argc,char *argv[])
 {
     int arr[] = {23,45,56,24,78,22,19};
     int n = sizeof(arr) / sizeof(arr[0]);
 ​
     QSort(arr, n);// 函数的参数是数组,传递的是这个数组的首地址,函数中可以修改实参数据,符合这种条件的参数称之为输出型参数
 ​
     // 此时是可以直接遍历数组,此时的数组已经经过了修改
     for (int i = 0; i < n; i++)
     {
         printf("%-4d",arr[i]);
     }
     printf("\n");
 ​
     return 0;
 }
 ​

数组做函数参数

定义

当使用数组作为函数的实参时,形参应该使用数组形式或者指针变量来接收。需要注意的是:

  1. 这种传递方式并不是传递数组中的所有的元素数据,而是传递数组首地址,此时数组降级为指针。

  2. 形参接收到这个地址后,形参和实参指向同一块内存空间。

  3. 因此,通过形参对数组元素的修改会直接影响到实参。

这种传递方式称为“地址传递”(或“指针传递”),它与“值传递”的不同:

  • 值传递:传递数据的副本,修改形参不影响实参

  • 地址传递:传递数据的地址,通过形参可以修改实参。“地址传递”是逻辑上的说法,强调 传递的是地址,而不是数据本身。数据本质上还是值传递。

当使用数组作为函数的形参时,通常需要额外传递一个参数表示数组的元素个数。这是因为:

  1. 数组形参退化为指针 在函数参数传递时,数组名会退化为指向其首元素的指针(即 int arr[] 等价于 int *arr),因此函数内部无法直接获取数组的实际长度。

  2. 防止越界访问 由于形参仅知道数组的首地址,而不知道数组的实际大小,如果不传递元素个数,函数内部可能因错误计算或循环导致 数组下标越界(访问非法内存),引发未定义行为(如程序崩溃、数据损坏)。

  3. 通用性 即使实参数组的声明长度固定(如 int a[10]),函数仍应接收元素个数参数,因为函数可能需要处理不同长度的数组(例如动态数组或部分填充的数组)。

案例:

 #include <stdio.h>
 ​
 /**
  * 定义一个函数,将数组作为参数
  */
 void fun(int arr[], int len) // 数组传参会被降级为指针,实际传递的是地址值
 {
     for (int i = 0; i < len; i++) printf("%-4d",arr[i]); printf("\n");
 }
 ​
 void main()
 {
     int arr[] = {11,22,33,44,55};
     int len = sizeof(arr) / sizeof(arr[0]);
     
     fun(arr,len);
 }

 ​

但有一个例外,如果是用字符数组做形参,且实参数组中存放的是字符串数据(形参是字符数组,实参是字符串常量)。则不用表示数组个数的形参,原因是字符串本身会添加自动结束标志\0,举例:

 #include <stdio.h>
 ​
 /**
  * 定义一个函数,传递一个字符串
  */
 void fun(char arr[])
 {
     char c;
     int i = 0;
     while((c = arr[i]) != '\0')   // arr[i] → arr + i
     {
         printf("%c",c);
         i++;
     }
 }
 ​
 void main()
 {
     fun("hello world");
 }

为什么sizeof不能用于形参数组?在函数内部,sizeof(arr)返回的是指针的大小(32位系统返回4字节,64位系统返回8字节)。例如:

 void printSize(int arr[])
 {
     printf("%zu\n",sizeof(arr)); // 输出的是指针的大小(8字节),而非数组大小
 }
案例
案例1
  • 需求:

    有两个数组a和b,各有5个元素,将它们对应元素逐个地相比(即a[0]与b[0]比,a[1]与b[1]比……)。如果a数组中的元素大于b数组中的相应元素的数目多于b数组中元素大于a数组中相应元素的数目(例如,a[i]>b]i]6次,b[i]>a[i] 3次,其中i每次为不同的值),则认为a数组大于b数组,并分别统计出两个数组相应元素大于、等于、小于的个数。

     int a[10] = {12,12,10,18,5};
     int b[10] = {111,112,110,8,5};
  • 代码:

     /*************************************************************************
       > File Name:    demo07.c
       > Author:       三道渊
       > Description:  
       > Created Time: 2025年07月23日 星期三 16时16分46秒
      ************************************************************************/
     ​
     #include <stdio.h>
     ​
     #define LEN 5
     ​
     /**
      * 定义一个函数,实现两个数字的比较
      * @param x,y 参与比较的两个数字
      * @return x>y--> 1, x<y-->-1, x==y-->0
      */ 
     int get_large(int x, int y)
     {
         int flag = 0;
         if (x > y) flag = 1;
         else if (x < y) flag = -1;
     ​
         return flag;
     }
     ​
     int main(int argc,char *argv[])
     {
         // 定义a,b两个测试数组
         int a[LEN] = {12,12,10,18,5};
         int b[LEN] = {111,112,110,8,5};
     ​
         int max = 0, min = 0, k = 0;
     ​
         // 遍历数组,进行比较
         for (int i = 0; i < LEN; i++)
         {
             // 同一位置两个数比较
             int res = get_large(a[i], b[i]);
     ​
             if (res == 1) max++;
             else if (res == -1) min++;
             else k++;
         }
     ​
         printf("max=%d,min=%d,k=%d\n", max, min, k);
     ​
         return 0;
     }
    
     ​
案例2
  • 需求:编写一个函数,用来分别求数组score_1(有5个元素)和数组score_2(有10个元素)各元素的平均值 。

  • 代码:

     /*************************************************************************
       > File Name:    demo08.c
       > Author:       三道渊
       > Description:  
       > Created Time: 2025年07月23日 星期三 16时27分26秒
      ************************************************************************/
     ​
     #include <stdio.h>
     ​
     /**
      * 定义一个函数,求一个数组的平均值
      */ 
     float get_avg(float scores[], int len)
     {
         int i;
     ​
         float aver, sum = scores[0]; // 平均值,总分
     ​
         // 遍历数组
         for (i = 1; i < len; i++) sum += scores[i];
     ​
         // 求平均分
         aver = sum / len;
     ​
         return aver;
     }
     ​
     int main(int argc,char *argv[])
     {
         // 测试数组
         float scores1[] = {77,88,99,66,57};
         float scores2[] = {67,87,98,78,67,99,88,77,77,67};
     ​
         int len = sizeof(scores1) / sizeof(scores1[0]);
         int len2 = sizeof(scores2) / sizeof(scores2[0]);
     ​
         printf("%6.2f,%6.2f\n", get_avg(scores1, len), get_avg(scores2, len2));
     ​
         return 0;
     }
    
     ​
案例3
  • 需求:编写一个函数,实现类似strcpy的效果 。

  • 代码:

     /*************************************************************************
       > File Name:    demo09.c
       > Author:       三道渊
       > Description:  
       > Created Time: 2025年07月23日 星期三 16时40分58秒
      ************************************************************************/
     ​
     #include <stdio.h>
     ​
     /**
      * 自定义一个字符串拷贝函数
      * @param source 源数组
      * @param dest  需要替换的字符串
      */ 
     void _strcpy(char source[], const char dest[])
     {
         // 遍历源数组,以源数组的大小作为循环
         for (int i = 0; source[i] != '\0'; i++)
         {
             // 获取dest相应位置的字符
             source[i] = dest[i];
         }
     }
     ​
     int main(int argc,char *argv[])
     {
         char str[] = "hello world!";
         printf("%s\n", str);
     ​
         _strcpy(str,"hi yifan!");
         printf("%s\n", str);
         
         _strcpy(str,"娇娇");
         printf("%s\n", str);
     ​
         return 0;
     }
     ​

变量的作用域

引入问题

我们在函数设计的过程中,经常要考虑对于参数的设计,换句话说,我们需要考虑函数需要几个参数,需要什么类型的参数,但我们并没有考虑函数是否需要提供参数,如果说函数可以访问到已定义的数据,则就不需要提供函数形参。那么我们到底要不要提供函数形参,取决于什么?答案就是变量的作用域(如果函数在变量的作用域范围内,则函数可以直接访问数据,无需提供形参)

变量作用域

概念:变量的作用范围,也就是说变量在什么范围有效。

变量的分类

根据变量的作用域不同,变量可以分为:

  • 全局变量

    说明:定义在函数之外,也称之为外部变量或者全程变量。

    作用域:从全局变量定义到本源文件结束。

    初始值:整型和浮点型,默认值是0;字符型,默认值是\0;指针型,默认值NULL

    举例:

      int num1;  // 全局变量,num1能被fun1、fun2、main共同访问
      void fun1(){}
      int num2;  // 全局变量,num2能被fun2、main共同访问
      void fun2(){}
      void main(){}
      int num3;  // 全局变量,不能被任何函数访问
  • 局部变量

    说明作用域初始值
    形式参数(形参)函数作用域随机值,需要手动赋初值
    函数内定义的变量函数作用域随机值,需要手动赋初值
    复合语句中定义的变量块作用域随机值,需要手动赋初值
    for循环表达式1定义的变量块作用域随机值,需要手动赋初值

    举例:

     
     // a,b就是形式参数(局部变量)
      int add(int a, int b)
      {
          return a + b;
      }
      
      int add2(int a, int b)
      {
          // z就是函数内定义的变量(局部变量)
          int z = a + b;
          return z;
      }
      
      int list(int arr[], int len)
      {
          // i就是for循环表达式1的变量(局部变量)
          for(int i = 0; i < len; i++)
          {
              // num就是复合语句中定义的变量(局部变量)
              int num = arr[i];
          }
      }

    使用全局变量的优缺点:

    优点:

    1. 利用全局变量可以实现一个函数对外输出的多个结果数据。

    2. 利用全局变量可以减少函数形参的个数,从而降低内存消耗,以及因为形参传递带来的时间消耗。

    缺点:

    1. 全局变量在程序的整个运行期间,始终占据内存空间,会引起资源消耗。

    2. 过多的全局变量会引起程序的混乱,操作程序结果错误。

    3. 降低程序的通用性,特别是当我们进行函数移植时,不仅仅要移植函数,还要考虑全局变量。

    4. 违反了“高内聚,低耦合”的程序设计原则。

    总结:

    我们发现弊大于利,建议尽量减少对全局变量的使用,函数之间要产生联系,仅通过实参+形参的方式产生联系。

作用域举例

注意:

如果全局变量和局部变量同名,程序执行的时候,就近原则(区分作用域)

 
 int a = 10;  // 全局变量  全局作用域
  
  int main()
  {
      int a = 20;  // 局部变量  函数作用域
      
      printf("%d\n", a); // 20 就近原则
      
      for (int a = 0; a < 5; a++)  // 局部变量 块作用域
      {
          printf("%d", a); // 0 1 2 3 4  就近原则
      }
      
      printf("%d\n",a);  // 20 就近原则
  }

变量的生命周期

定义

概念:变量在程序运行中的存在时间(内存申请到内存释放的时间)

根据变量存在的时间不同,变量可分为静态存储方式和动态存储方式

变量的存储类型

语法:

  变量的完整定义格式: [存储类型] 数据类型 变量列表;

存储类型:

  • auto

    auto存储类型只能修饰局部变量,被auto修饰的局部变量是存储在动态存储区(栈区和堆区)的。auto也是局部变量默认的存储类型。

      int main()
      {
          int a;
          int b;
          // 以下写法等价于上面写法
          auto int a;
          auto int b;
          
          int a,b;
          // 以下写法等价于上面写法
          auto int a,b;
      }

  • static

    修饰局部变量:局部变量会被存储在静态存储区。局部变量的生命周期被延长。但是作用域不发生改变,不推荐

    修饰全局变量:全局变量的生命周期不变,但是作用域衰减,一般限制全局变量只能在本源文件内访问,其他文件不可访问。

    修饰函数:被static修饰的函数,只能被当前文件访问,其他引用该文件的文件是无法访问的,有点类似于java中private

  • extern

    外部存储类型:只能修饰全局变量,此全局变量可以被其他文件访问,相当于扩展了全局变量的作用域。

    extern修饰外部变量,往往是外部变量进行声明,声明该变量是在外部文件中定义的。起到一个标识作用。函数同理。

    demo01.c

     
     #include "demo01.h"
      
      int fun_a = 10;
      int fun1(){..}

    demo02.c

     
     #include "demo01.h"
      
      // 声明访问的外部文件的变量
      extern int fun_a;
      // 声明访问的外部文件的函数
      extern int fun1();
          
      int fun2();

  • register

    寄存器存储类型:只能修饰局部变量,用register修饰的局部变量会直接存储到CPU的寄存器中,往往将循环变量设置为寄存器存储类型(提高读的效率)

      
    for (register int i = 0; i < 10; i++)
      {
          ...
      }

面试题
static关键字的作用
  1. static修饰局部变量,延长其生命周期,但不影响局部变量的作用域。

  2. static修饰全局变量,不影响全局变量的生命周期,会限制全局变量的作用域仅限本文件内使用(私有化);

  3. static修饰函数:此函数就称为内部函数,仅限本文件内调用(私有化)。static int funa(){..}

内部函数和外部函数
  • 内部函数:使用static修饰的函数,称作内部函数,内部函数只能在当前文件中调用。

  • 外部函数:使用extern修饰的函数,称作外部函数,extern是默认的,可以不写(区分编译环境),也就是说本质上我们所写的函数基本上都是外部函数,建议外部函数在被其他文件调用的时候,在其他文件中声明的时候,加上extern关键字,主要是提高代码的可读性。

C语言小白入门之【C语言 " 函数 "】! 一、函数的分类 前面已经说过,C语言中的函数就是面向对象中的"方法",C语言的函数可以大概分为3类: 1.主函数,也就是main函数。每个程序中只能有一个、也必须有一个主函数。无论主函数写在什么位置,C程序总是从主函数开始执行 2.开发人员自定义的函数,可有可无,数目不限 3.C语言提供的库函数,例如stdio.h中的输出函数printf()和输入函数scanf() ... 阅读详情

相关推荐

Spring Cloud Gateway 结合OAuth2提供UAA服务

微服务做用户认证和授权一直都是一个难点,随着OAuth2.0的密码模式被作废,更是难上加难了。今天胖哥群里的一个群友搭建用户认证授权体系的时候遇到了一些棘手的问题,这让胖哥觉得是时候分享一些思路出来了。 两种思路 通常微服务的认证和授权思路有两种: 所有的认证授权都由一个独立的用户认证授权服务器负责,它只负责发放Token,然后网关只负责转发请求到各个微服务模块,由微服务各个模块自行对Token进行校验处理。 另一种是网关不但承担了流量转发作用,还承担认证授权流程,当前请求的认证信息由网关中继给下游

duan18888的博客 649

c语言函数的调用

学习笔记

qq_53661628的博客 2万+

Dynamics 365从入门到精通:一站式掌握微软智能商业应用平台

很多新手学Dynamics 365时,容易陷入“死记功能”的误区,其实这款平台的核心价值不是“功能多全”,而是“能适配企业业务流程,通过生态集成提升效率”。先从具体业务场景入手,理解每个功能的实际价值;再通过生态集成拓展系统能力;最后通过开发实现定制化,满足企业的特殊需求。按照这个思路,新手不用害怕复杂,一步步落地实战,3个月就能从“入门”到“能独立负责项目”。如果在学习过程中遇到具体问题(比如部署报错、数据迁移失败),欢迎在评论区留言,我会结合实战经验帮你解答!

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 1043

C语言-函数的调用

C语言-函数的调用

让你爱上电路设计 2万+

从零构建智能监控系统:ZLMediaKit与SpringBoot的RTSP流处理实战

本文详细介绍了如何从零构建智能监控系统,结合ZLMediaKit与SpringBoot实现RTSP流处理。通过多协议支持、高性能架构设计及工业级部署优化,解决传统监控方案的兼容性差、延迟高等问题,适用于物联网和智能安防领域。

weixin_29002209的博客 370

流媒体服务软件-LiveNVR channeltree 未授权访问

LiveNVR 能够通过简单的网络摄像机通道配置,将传统监控行业里面的高清网络摄像机 IPCamera、NVR 等具有 RTSP/Onvif 协议输出的设备接入到 LiveNVR,LiveNVR 能够将这些设备源的音/视频数据进行采集、转换、输出,进行全平台终端直播(PC、Android、iOS、微信、H5),并且LiveNVR能够将视频源的直播数据对接到第三方 CDN 网络,实现安防设备互联网级别的直播分发。对channeltree接口加上权限校验。直接访问接口即可获取数据。

iSee857的博客 1040

C语言函数及其调用

一、C语言函数 1.概念:函数是一组一起执行一个任务的语句,每个c程序都必须有一个main函数,程序员可以把代码划分到不同的函数当中去,在逻辑上,划分通常是根据每个函数执行一个特定的任务来进行的。c标准库提供了可以大量调用的库函数,比如,printf,strcmp等。 2.函数的定义 c语言中函数的一般定义如下: return_type Function_name(Parameter L...

qq_43775170的博客 7万+

PCB_Project.PrjPcb 4 Not in version control

Altium Designer中PCB项目出现"NOT in version control"错误的解决方法:1. 禁用不需要的版本控制集成(DXP→Preferences→Version Control);2. 清理项目缓存(Projects面板右键→Clean Project);3. 检查文件读写权限;4. 如无效可新建项目重新添加文件。如需使用SVN版本控制,需正确配置SVN客户端路径,创建或连接SVN仓库,并将项目文件提交至版本控制系统。注意新建SVN仓库必须使用空目录,建议使用

whm128的博客 1599

C语言中调用函数

例如,如果有一个名为“add”的函数,它接受两个整数参数并返回它们的和,那么可以这样调用它:result = add(2, 3)。在上面的代码中,我们定义了一个名为“add”的函数,它接受两个整数参数并返回它们的和。2. 引用参数(Pass by Reference): 在此方法中,函数接收的是实际参数的地址,而不是实际参数的值。通过函数表达式,我们可以将复杂的功能分解成简单的函数,使代码更加易于理解和维护。在上面的代码中,我们定义了一个名为“add”的函数,它接受两个整数参数并返回它们的和。

公众号:风景邮递Yuan的博客 3488

手撕数美风控三板斧:device_fingerprint/device_id/hmac生成逻辑全解析

摘要:逆向分析数美风控核心参数生成机制 本文详细记录了逆向分析数美(Shumei)风控系统中三个关键参数(device_id、device_fingerprint、hmac)的生成逻辑。通过抓包分析和动态调试(使用Frida、IDA等工具)发现:device_id由设备IMEI和Android ID经MD5哈希生成;device_fingerprint融合23项设备与环境信息,并采用SHA-256哈希和动态环境检测;hmac则是参数+指纹+动态盐值的SHA-256签名。文章揭示了数美"设备指纹+动

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1472

YOLOv8的二次创新 | 结合iRMB与EMA打造全新的iEMA机制,提升动态检测性能【附保姆级代码】

YOLOv8继承了YOLO系列的轻量化和高效检测特性,同时通过改进的架构设计使得其在实时场景下具有更高的检测精度。然而,随着检测需求的增加,尤其是在动态目标检测和长时间序列检测中,YOLOv8的鲁棒性依然可以提升。通过iEMA机制的引入,我们将在这些方面进行进一步优化。iRMB模块是我们对传统Residual Block的进一步改进。通过多层特征提取,iRMB能够捕捉更多层次的特征信息,并通过引入轻量化的残差结构,提升计算效率。

一键难忘的博客 3085

C语言标准库函数头文件

C语言标准库 标准库不是C语言本身的构成部分,但是支持标准C的实现会提供

weixin_43476040的博客 1939

iOS应用突然无法从App Store获取到内购(IAP)产品列表的解决

问题现象 Apple开发中,在应用中嵌入内购(IAP)功能的小伙伴们可能会发现,吃着火锅唱着歌,突然App就无法获取到产品列表了,而且产品请求操作并不会返回任何错误。 此时检查发现,并没有对App的内购逻辑做任何改动。 那么,这是什么原因导致的呢? Let’s find out!???? 问题分析 1. 排除其它原因 首先,你先得排除不是你IAP内购逻辑变更或其他原因导致的。 在SKProductsRequestDelegat代理实现的productsRequest方法里下断点: func produc

享受开发,颠倒银河 6142

C语言内存函数

(在vs2022中memcpy可以处理重叠的内存,但这对于C语言并不规范,且对环境有要求,所以不推荐)总结:这节我们介绍了C语言内存函数,下节我们要学习C语言中相对重要的内容,动态内存开辟。如果source和destination有任何的重叠,复制的结果都是未定义的。• 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以。memset是用来设置内存的,将内存中的值以字节为单位设置成想要的内容。• 比较从ptr1和ptr2指针指向的位置开始,向后的num个字节。

2301_80017277的博客 534

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

C语言中输入函数的使用

scanf( ),getchar( ),gets( )的理解和使用

2201_75413778的博客 1271

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

第七章 C语言函数_C语言递归函数(递归调用)详解[带实例演示]

一个函数在它的函数体内调用它自身称为递归调用,这种函数称为递归函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层,当最内层的函数执行完毕后,再一层一层地由里到外退出。 递归函数不是C语言的专利,Java、C#、JavaScript、PHP等其他编程语言也都支持递归函数。 下面我们通过一个求阶乘的例子,看看递归函数到底是如何运作的。阶乘 n! 的计算公式如下: 根据公式编写如下的代码: 运行结果: Input a number: 5↙ Factorial(5) = 120 fac.

qq_43205256的博客 711

C语言函数

函数的概念 每个C程序都至少有一个函数,即主函数main(),如果程序的任务比较简单,全部的代码都写在main()函数中,但是,在实际开发中,程序的任务往往比较复杂,如果全部的代码都写在main()函数中,main()函数将非常庞大臃肿,结构复杂,代码重复。 我们可以根据程序的逻辑和任务的分工把代码划分到不同的自定义函数中,main()更关心业务逻辑和处理...

C语言技术网-码农有道 5万+

C语言:函数

而C语言也引入了函数(function)这个概念,C语言中的函数就是一个完成某项特定任务的一小段代码。而这段代码有自己的特殊写法和调用方法。

weixin_51142926的博客 6523

【C语言函数基础】

文章目录1.函数2.C语言函数分类2.1 库函数2.1.1 库函数的分类2.1.1 库函数的学习2.2 自定义函数3.函数的参数3.1 实际参数3.2 形式参数4.函数的调用4.1 传值调用4.2 传址调用5.函数的声明和定义 1.函数 百度百科对函数的定义:子程序 在计算机科学中,子程序是一个大型程序中的某部分代码,有一个或者多个语句块组成,它负责完成某项特定的任务,而且相比于其他的代码,具备相对的独立性。 一般来说会有输入参数和返回值,提供对过程的封装和细节的隐藏,这些代码通常被集成为软件库。 2.

qq_43727529的博客 4973

C语言——函数

C语言中的函数又常常被称为子程序,是用来完成某项特定的工作的一段代码。就像我们生活中的模块化建造技术,类比模块化建房子的过程:整个程序好比最终要建成的房子,而函数所代表的就是每部分模块(如第一层,第二层或者屋顶···),将这些模块灵活的拼接,就搭建成了最后的房子。从函数的定义角度:我们可以把函数分为库函数和自定义函数是由用户根据需要自行编写的函数,创建格式如下:ret_typename形式参数函数体(用来描述函数执行过程的部分)创建例子:int决定了z的返回类型是整形int。

tan_run的博客 2088

带你深入了解c语言指针后续

c语言指针完结,带你更深入了解函数指针,函数指针数组,函数指针数组指针。以及浅谈回调函数。

@初阶牛的博客 3388

C语言main函数的写法

main函数是C程序的入口函数,即程序的执行是从main函数开始,对其他函数的调动也是直接或间接地在main函数中被调用。那么main函数又是被谁调用呢?答案是操作系统。C语言发展至今,对main函数有多种不同的写法,下面就来探究一下各种不同的写法。注:测试环境为Ubuntu 17.10,GCC版本为7.2.0。 NO.1 main(){}例: #include mai

αlphαβet 5万+

c语言——函数

库函数很重要但在c语言中更重要的是自定义函数,有了自定义函数我们才能写出不一样的代码,让我们更具有创造性。我们可以把函数想象成小型的⼀个加工厂,工厂得输入原材料,经过工厂加工才能生产出产品,那函数也是一样的,函数⼀般会输入一些值,这些值也就是参数(可以是0个,也可以是多个),经过函数内的计算,得出结果。在以上某年某月有多少天的程序中就实现了函数的定义与调用如果将自定义函数放在main函数后会发生什么呢?这时我们就会看到以下警告出现这种情况的原因是。

mljy.博客 1912
上一篇: C语言:函数
下一篇: C语言:函数的作用域和生命周期
三道渊
博客等级 码龄3年 118粉丝 39原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值