一文剖析C语言函数

前言

       函数是C语言重要的一个组成部分,可以这么说,只要是使用C语言来写一个项目,那么就必须会使用到函数这个功能,也正因为函数的存在,使得我们在编写代码时能够模块化,一个函数对应一个功能,避免重复造轮子。本文将通过先理论后实战的方式来为大家进行C语言函数的讲解。

一、函数的定义

1. 何谓函数?

       C语言中的函数其实就是为了完成一个特定功能的代码模块,也就是说一个函数就是一个小功能。当我们在某种场景下需要实现这个小功能那么也就只需要调用这个函数即可,倘若没有函数这个功能的话,我们可能就要每次实现一个功能的话就要实现其完整过程,但是每次的操作都是一样的,这样我们的代码就会有很多一样的地方,这样不仅代码可读性低,而且体积大,且都是重复的,这一点肯定是不能接受的。而函数的引入就解决了这一难题。对于一个大项目中,我们可以将其拆分为若干个小的项目,每一个小的项目又有若干个小功能,通过若干个函数对其进行封装并实现,最终实现整个项目。

2. 如何定义函数?

2.1 函数的定义(重要理论)

<数据类型>  <函数名称>( <形式参数1, 形式参数2, ......> )
{
     语句序列;
     return [(<表达式>)];
} 
  1. 数据类型:
    (1)数据类型是整个函数的返回值类型。 return 后面的表达式类型需要这里的数据类型保持一致。
    (2)如果对于一个函数不想返回值,那么返回值类型应该写成 void。
    (3)如果返回值类型写成 void,那么该函数可以不用写 return,也可以写成 return ;,也就是 return 后面的表达式需要省略。
    (4)如果一个函数的数据类型省略了,那么函数的数据类型就是 int 类型。,换句话讲,当函数的返回值类型为 int 时,和返回值不写的效果是一样的。但是站在编程规范的角度来看,省略函数的数据类型这种做法是不被推荐的。
  2. 函数名称:
           须遵循C语言标识符命名规则即可。
  3. 形式参数:
           这里是函数的形式参数,简称形参,形参由变量数据类型加 变量名组成,倘若有多个形式参数,每个形式参数用逗号分隔开。形参在括号的里面,如果没有形参可以省略不写,可以只写一个 void,但是括号不能省略。
           形式参数的作用域只能在函数的大括号中进行访问。函数外部不能进行访问。通过这句话也就间接表明,我们可以在函数的外面以及函数的形参那里分别定义相同的变量,以及两个变量的数据类型相同,函数外面的变量作用域在函数的外面,函数的形参作用域在函数里面,他们两个最然变量名相同,但是作用域不同,也就是两个变量的地址空间不一样,操作其中任何一个变量,另一个同名的变量不会受影响。这点对于新手尤其值得注意。
  4. 一对大括号:
           大括号里面就是函数的实现过程,由若干语句序列组成。调用一个函数函数,也就是会执行大括号中的每一条语句。
  5. return
           return后面的表达式就是函数的返回值。当一个函数运行到 return 时,函数就会退出,函数后面的语句也就不会运行,当一个函数没有 return 时,那么函数会运行到大括号中的反括号前面一条有效执行的语句时退出。

知识点补充:

  1. 函数在调用时参入的参数为实参,函数括号里面的参数为形参,函数在调用时,实参会对形参进行赋值操作。
  2. 函数的定义如果写在 main() 函数之后,需要先进行函数声明,写在 main() 函数之前不需要声明。
  3. 函数的声明可以放在函数的调用前面任何位置。甚至可以放在 main() 函数里面,但是一般不推荐。
  4. 当形参是以数组的形式进行传递时,其实本质也就是在传递指针。把数组作为实参进行传递时,其实就是把实参的数组的首地址进行了传递,因此如果我们想要在函数中访问数组的全部变量时还需要传递数组元素的个数。因为是以指针的方式传递,所以在函数中对形参数组进行 sizeof 运算时大小只有 4 字节,同时这也就说通了为什么传递数组时必须要传递数组元素的个数。

2.2 小试牛刀

        通过上面的讲解来具体实现一个函数吧。要求:求一个数的阶乘。

源代码:

#include <stdio.h>

/*函数声明*/
int sum_func(int n);

int main()
{
    int data = 0;
    int sum = 0;

    printf("please input a num: ");
    scanf("%d", &data);

    while (data < 0) {
        printf("input errror! please input again: ");
        scanf("%d", &data);
    }

    sum = sum_func(data);  //data为实参

    printf("%d! = %d\n", data, sum);

    return 0;
}

/*
函数的返回值类型是 int。
函数名是 sum_func
函数只有一个形参,形参是 int 类型。
*/
int sum_func(int n)
{
    int i = 1, sum = 1;

    if (n < 0) {
        return -1;
    } else if (n == 0) {
        return 1;
    } else {
        while (i <= n) {
            sum *= i;
            i++;
        }
    } 

    return sum;
}

运行结果:

please input a num: -1
input errror! please input again: -10
input errror! please input again: 5
5! = 120

二、函数的参数传递

函数的参数在进行传递时分为两种情况:

  1. 值传递
  2. 地址传递

        函数在被调用时,会将实参传递给形参,形参是新开辟的空间,如果在进行值传递是时候,改变形参的值,实参的值是不会被改变的,但是如果是进行地址传递,由于指针的特性,改变形参中的地址的值实参是会被改变的。其实说白了,值传递与地址传递究其本质就是形参与实参进行了一个赋值操作。
        大家看看下面这个程序,看那个函数才能改变 main 函数中的值。

源代码:

#include <stdio.h>

void exchange_func_1(int a, int b)
{
    int t;
    
    t = a;
    a = b;
    b = t;
}

void exchange_func_2(int *a, int *b)
{
    int t;
    
    t = *a;
    *a = *b;
    *b = t;
}

int main()
{
    int a =  3, b = 4;

    exchange_func_1(a, b);   //值传递
    printf("exchange_func_1: a = %d, b = %d\n", a, b);

    exchange_func_2(&a, &b);   //地址传递
    printf("exchange_func_2: a = %d, b = %d\n", a, b);

    return 0;
}

运行结果:

exchange_func_1: a = 3, b = 4
exchange_func_2: a = 4, b = 3

        对于上述运行结果,首先需要明确的一点是 main 函数中的变量 a,b 和 exchange_func_1() exchange_func_2() 这两个中的变量 a , b 在内存空间上是不同的,这点我们在上面的理论部分已经讲解了的,因为形参是重新开辟的内存,在函数调用时,只是进行了一次赋值操作。因此在进行值传递时,改变 exchange_func_1() 中的变量 a,b,main() 函数中的变量 a,b 并不会变化。在进行地址传递时,因为 exchange_func_2() 中 a, b 此时是指向 main() 函数中 a, b 的地址,通过取值运算符,可以操作地址上的内容。因此 exchange_func_2() 函数可以改变 main() 函数中变量 a,b 的值的。
在这里插入图片描述

三、递归函数

        递归函数是指一个函数的函数体中直接或间接调用了该函数本身。

递归函数调用的执行过程分为两个阶段:
递推阶段:从原问题出发,按递归公式递推从未知到已知,最终达到递归终止条件
回归阶段:按递归终止条件求出结果,逆向逐步代入递归公式,回归到原问题求解

源代码(阶乘的递归实现):

#include <stdio.h>

int recursion_func(int a)
{
    int i = a;

    if (a <= 1) {
        return 1;
    } else {
        return a * recursion_func(a - 1);
    }
}

int main()
{
    int data = 0;
    int sum = 0;

    printf("please input a num: ");
    scanf("%d", &data);

    while (data < 0) {
        printf("input errror! please input again: ");
        scanf("%d", &data);
    }

    sum = recursion_func(data); 

    printf("%d! = %d\n", data, sum);

    return 0;
}

运行结果:

please input a num: -1
input errror! please input again: -10
input errror! please input again: 5
5! = 120

递推阶段:

recursion_func(5) = 5 * recursion_func(4)
recursion_func(4) = 4 * recursion_func(3)
recursion_func(3) = 3 * recursion_func(2)
recursion_func(2) = 2 * recursion_func(1)
recursion_func(1) = 1

回归阶段:

recursion_func(1) = 1
recursion_func(2) = 2 * recursion_func(1) = 2 * 1 = 2
recursion_func(3) = 3 * recursion_func(2) = 3 * 2 = 6
recursion_func(4) = 4 * recursion_func(3) = 4 * 6 = 24
recursion_func(5) = 5 * recursion_func(4) = 5 * 24 = 120

        当函数运行到 recursion_func(5) 时,因为满足条件需要运行 recursion_func(4),运行 recursion_func(4) 时,又因为满足条件运行 recursion_func(3),一直到 recursion_func(1)时能够返回具体值,此时 recursion_func(2) 也因为 recursion_func(1) 返回具体值才运行完函数,一直以此类推运行到 recursion_func(5)。 像这样,当在外部调用这个递归函数时,都会有递推阶段和回归阶段。因此我们也会发现递归函数的一个缺点,在递推阶段只有当运行到最深一层返回具体值时,才会依次释放之前函数开辟的栈空间。也就是说在递推阶段运行 recursion_func(5) 时,recursion_func(5) 函数所占用的栈空间得不到释放还会去调用 recursion_func(4) 再来占用栈空间,一直到 recursion_func(1)。也就是说在 递推阶段会一直开辟栈空间,只有在回归阶段才能依次释放栈空间,如果递归的层数过深,会导致栈空间不足,因此递归函数的效率比较低,但是在一些特殊场景下递归函数却又是一个不错的选择。

  • 59
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 49
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 49
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值