C语言函数

函数

形参和实参

形参(形式参数)

函数定义时指定的参数,形参是用来接收数据的,函数定义时,系统不会为形参申请内存,只有当函数调用时,才会申请内存,用于存储实参,当函数返回时,系统会自动回收形参申请的内存资源。

实参(实际参数)
  • 函数调用时主调函数传递的数据参数(常量、符号常量、变量、表达式…只要有确定的值),实参是传递数据。
  • 实参和形参必须类型相同,如果不同,按照赋值规定自动进行类型转换。
  • 在C语言,参数传递必须遵循单向值传递,实参只是将自身的值传递给形参,而不是实参本身。形参的值改变不会影响实参。
  • 实参与形参在内存中占据不同的内存空间,(尽可能实参和形参名称一样)

函数的返回值

  • 若不需要返回值,函数中可以没有return语句
  • 一个函数中可以有多个return语句,但任一时刻只有一个return语句被执行
  • 被调函数返回给主调函数的结果数据(可以是变量、常量、只要是确定值即可)
  • 返回值类型一般情况下要和函数中return语句返回的数据类型保持一致,若不一致,以函数定义时指定的返回类型为标准。也就是返回值类型和实际返回值可以存在自动类型或者强制类型转换的关系。

函数的调用

调用语句
  1. 函数语句:test(); int a=max(2,4);
  2. 函数表达式:4 + max(2,4);
  3. 函数参数:printf("%d",max(2,4));

在一个函数中调用另一个函数具备以下条件

  1. 被调用的函数必须是已经定义的函数
  2. 若使用函数库,应该在文件开头使用#incude包含
  3. 若使用自定义函数,自定义函数又在主调函数后面,则应在主调函数中对被调函数进行声明。

函数声明

函数调用时,往往需要遵循先定义后使用,但我们对函数操作出现在函数的定义之前,则,需要对函数进声明。

完整的函数使用三个部分:

  • 函数声明
  • 函数定义
  • 函数调用

函数声明的作用:

是把函数名、函数参数的个数和返回值类型等信息通知给编译系统,以便于遇到函数时,编译系统能正确识别函数,并检查函数调用的合法性。

int add(int x,int y);		//函数声明
int add(int,int);		    //简写


int main()
{
    int c = add(12,13);
    return 0;
}


int add(int x,int y)
{
    retyrn x + y;
}

函数的嵌套调用

  • 函数不支持嵌套定义,但允许嵌套调用。
  • 嵌套调用:在被调函数内又主动去调用其他函数,这样的函数调用称之为嵌套调用
#include 

int max(int a,int b)
{
    return a > b ? a : b ;
}

int a(int a, int b, int c,int d)
{
    return max(max(a,b),max(c,d));
}

int main()
{
    int a,c,b,d,max;
    printf("请输入任意四个整数:\n");
    scanf("%d%d%d%d",&a,&b,&c,&d);
    max = a(a,b,c,d);
    return 0;
}

函数的递归调用

  • 递归调用的含义:在一个函数中,直接或者间接调用了本身,就称之为函数的递归调用。

  • 递归调用的本质:

    是一种循环结构,不同于之前的while、do…while、for这样的循环结构,这些循环结构是借助循环变量;而递归是利用函数自身实现循环结构,若不控制很容易产生死循环。

  • 递归调用的注意事项:

    1. 递归调用必须要有终止条件。
    2. 对于终止条件一定放在函数递归之前(先判断,在执行)
    3. 进行函数的递归调用
    4. 函数递归的同时一定要将函数调用向出口逼近

案例:

#include 

//需求:五个人坐在一起问第五个人多少岁
//	   第四个人比第三个人大2岁
//	   第三个人比第三个人大2岁
//	   第二个人比第三个人大2岁
//	   第一个人他十岁            
          
int age(int n)
{
	//创建变量存放年龄
    int a;
    if(n == 1)
    {
        a = 10;
    }
    else if(n > 1)
    {
        a = age(n-1)+2;//当前年龄是上一个人年龄加2   
    } 
    return a;
}
            
int main()
{
    printf("%d\n",age(5));
    return 0;
}

案例二:

#include 

long a(int n)
{
    int b;
    if(n < 0) 
    {
        printf("n的范围不能是0以下");
        return -1;
    }
    else if(n == 1 || n == 0) b = 1;
    else if (n > 1) b = a(n-1)*n;
        return b;
}

int main()
{
    pruntf("%d",a(5));
    return 0;
}

关于递归:一般是由n到1在计算从1到n。

数组做函数参数

注意:

​ 当用 数组做函数的 实际参数时,则 形参应该也 要用数组/指针变量来接收,但请注意,此次并 不代表传递了数组中所有的元素数据,而是传递了第一个元素的内存地址(数组首地址),形参接 收这个地址后,则形参和实参就代表了同一块内存空间,则形参的数据修改会改变实参的。这种数 据传递方式我们可以称之为“引用传递”。

​ 如果用数组做函数形式参数,那么我们提供另一个形参表示数组的元素个数。原因是数组形参 代表的仅仅是实际数组的首地址。也就是说形参只获取到了实际数组元素的开始,并未获取元素的 结束。所以提供另一个形参表示数组的元素个数,可以防止在被调函数对实际数组元素访问的越 界。

​ 但有一个例外,如果是用字符数组做形参,且实际数组中存放的是字符串数据(形参是字符 数组,实参是字符串)。则不用表示数组元素的个数的形参,原因是字符串本身会自动结束符\0。

 /**
 * 需求:数组为参数案例-有两个数组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

使用全局变量的优缺点:

  • 优点

    1. 利用全局变量可以实现一个函数对外输出的多个结果数据
    2. 利用全局变量可以减少函数形参的个数,从而降低内存消耗,以及因为形参传递带来的时间消耗
  • 缺点

    1. 全局变量在程序的整个运行期间,始终占据内存空间,会引起资源消耗。
    2. 过多的全局变量会引起程序的混乱,操作程序结果错误。
    3. 降低程序的通用性,特别是函数移植时,不仅仅要移植函数,还要考虑全局变量。
    4. 违反了"高内聚、低耦合"的程序设计原则

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

注意:

如果全局变量和局部变量重名,执行就近原则

int a = 10;
int main()
 {
     int i = 20;
     printf("%d\n",a); // 10
     for(int i = 0;i < 5; i++)
 	{
 		printf("i=%d ",i); // 0 1 2 3 4 就近原则
	}
}

变量的生命周期

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

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

  • 变量的存储类型

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

auto

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

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

static

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

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

extern

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

extern修饰外部变量,往往是外部变量进行声明,声明该变量是在外部文件中定义的;不是变 量定义。

register

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

static 关键字的作用

  1. static修饰局部变量,延长其生命周期,但不影响局部变量的作用域。
  2. static修饰全局变量,不影响全局变量的生命周期,会限制全局变量的作用域仅限本文件内使用 (私有化);
  3. static修饰函数:此函数就称为内部函数,仅限本文件内调用(私有化)。 static int funa() {...}

值传递与指针传递

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

  • 指针传递:通过指针可以实现类似引用传递的效果,即允许函数内部修改实参的值,传递的是 地址,也就是内存空间可以被多个变量共享。

  • 引用传递: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语言,前端)