C语言基础:函数(作用域、生命周期、值传递与指针传递)

数组做函数参数

注意:

当用数组做函数的实际参数时,则形参应该也要用数组/指针变量来接收,但请注意,此次并不代表传递了数组中所有的元素数据,而是传递了第一个元素的内存地址(数组首地址),形参接收这个地址后,则形参和实参就代表了同一块内存空间,则形参的数据修改会改变实参的。这种数据传递方式我们可以称之为“引用传递”。如果用数组做函数形式参数,那么我们提供另一个形参表示数组的元素个数。原因是数组形参代表的仅仅是实际数组的首地址。也就是说形参只获取到了实际数组元素的开始,并未获取元素的结束。所以提供另一个形参表示数组的元素个数,可以防止在被调函数对实际数组元素访问的越界。但有一个例外,如果是用字符数组做形参,且实际数组中存放的是字符串数据(形参是字符数组,实参是字符串)。则不用表示数组元素的个数的形参,原因是字符串本身会自动结束符\0。

案例

案例1:

/**
 * 需求:数组为参数案例-有两个数组a和b,各有10个元素,将它们对应元素逐个地相比(即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数组,并分别统计出两
 个数组相应元素大于、等于、小于的个数。
 *
 */
 #include 
     {11,22,33,44}
     {05,11,22,54}
     /* 定义一个函数,实现两个数的比较 */
 int large(int x,int y)
 {
     int flag;// 用来存放比较结果
     if(x > y) flag = 1;
         else if(x < y) flag = -1;
         else flag = 0;
         return flag;
 }
 int main()
     {
         // 比较用的两个数组,循环变量,最大,最小,相等
         int a[10],b[10],i,max=0,min=0,k=0;
         printf("请给数组a添加十个整型数据:\n");
         for(i = 0;i < sizeof(a)/sizeof(int);i++)
         {
             scanf("%d",&a[i]);
         }
         printf("\n");
         printf("请给数组b添加十个整型数据:\n");
         for(i = 0;i < sizeof(b)/sizeof(int);i++)
             scanf("%d",&b[i]);
         printf("\n");
         // 遍历
         for(i = 0;i < sizeof(a)/sizeof(int);i++)
         {
             if(large(a[i],b[i])==1)
         {
             max++;
         }
         else if(large(a[i],b[i])==0)
         {
             k++;
         }
         else
         {
             min++;
         }
     }
     printf("max=%d,min=%d,k=%d\n",max,min,k);
     return 0;
 }

变量的作用域
引入问题

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

变量的作用域

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

变量的分类

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

  • 全局变量

  • 局部变量

全局变量
序号 解释 作用域
1 定义在函数之外的变量,也成为外部变量或全程变量 从全局变量定义处到本源文件的结束
局部变量
序号 解释 作用域
1 形式参数(形参) 函数作用域
2 函数内定义的变量 函数作用域
3 复合语句中定义的变量 块作用域
4 for循环表达式1定义的变量 块作用域

建议在全局变量定义时初始化,如果不初始化,系统会将全局变量初始化为 0 (0 | \0 | 0.0)

  • 使用全局变量的优缺点:

优点:

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

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

缺点:

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

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

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

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

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

作用域举例:

C语言基础:函数(作用域、生命周期、值传递与指针传递)_第1张图片

注意:

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

变量的生命周期

  • 概念:变量在程序运行中的存在时间。

  • 根据变量存在的时间不同,变量可分为静态存储方式和动态存储方式。C语言基础:函数(作用域、生命周期、值传递与指针传递)_第2张图片

  • 变量的存储类型

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

auto

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

 int a = 10; 等价于 auto int a = 10;

static

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

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

demo01.c

 #include "demo01.h"
 // 全局变量
 static int fun_a = 10;
 int fun1()

demo02.c

 #include "demo01.h"
 main()
 {
 // 此时fun_a就不能被其他文件访问
 fun_a = 20;
 }
#include 
 void counter() {
 static int count = 0;
 printf("Count: %d\n", count);
 count++;
 }
 int main() {
 counter(); // 输出 Count: 0
 counter(); // 输出 Count: 1
 counter(); // 输出 Count: 2
 return 0;
 }

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();
 main()
 {
 fun_a = 20;
 fun1();
 }

register

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

 register int a = 10;
static关键字的作用
  1. static修饰局部变量,延长其生命周期,但不影响局部变量的作用域。

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

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

值传递与指针传递

  • 值传递:单向传递,基本数据类型默认是通过值传递的,也就是传递的是数值,也就是内存空间只能被当前变量独享。

  • C语言基础:函数(作用域、生命周期、值传递与指针传递)_第3张图片

  • 指针传递:通过指针可以实现类似引用传递的效果,即允许函数内部修改实参的值,传递的是地址,也就是内存空间可以被多个变量共享。C语言基础:函数(作用域、生命周期、值传递与指针传递)_第4张图片

  • 引用传递:C语言本身不支持引用传递的语法,但可以通过指针来实现类似的功能。 引用传递意味着在函数调用时,会将实参的引用(即一个别名)传递给形参。这样,函数内部对形参的任何修改都会直接影响到实参。然而,C语言本身并不支持引用传递这种语法。在其他一些编程语言(如C++、Java、Python等)中,引用传递是原生支持的。

案例:

 
// 值传递(整型、浮点型、字符型..)
 fun(int x)
 {
 printf("%d\n",x); // x = 10
 x = 20; // x = 20
 }
 main()
 {
 int a = 10; // a = 10
 fun(a);
 printf("%d\n",a);// a = 10
 }
 -------------------------------------------------------------------------------------
 // 地址传递(数组、指针、结构体..)
 fun(int x[10])
 {
 printf("%d\n",x[9]);// x[9] = 0
 x[9] = 20; // x[9] = 20
 }
 main()
 {
 int a[10] = {1,2,3};
 fun(a);
 printf("%d\n",a[9]);// a[9] = 20
 }

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

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

你可能感兴趣的:(算法,c语言,linux,ubuntu,开发语言,数据结构,vscode)