C语言归纳五-函数

目录

一、比较字符串大小的代码封装成函数

二、形参和实参的区别

三、头文件的讲究

四、全局变量的讲究(static的作用)

五、单独的代码块

六、递归函数

6.1 斐波那契数列:

6.2 递归函数的空间开销:

6.3 递归函数的时间开销 clock_t CLOCKS_PER_SEC

七、需要注意的地方


一、比较字符串大小的代码封装成函数

int strcmp_alias(char *s1, char *s2){
    int i, result;
    for(i=0; (result = s1[i] - s2[i]) == 0; i++){
        if(s1[i] == '\0' || s2[i] == '\0'){
            break;
        }
    }
   
    return result;
}

二、形参和实参的区别

(1) 形参变量只有在函数被调用时才会分配内存,调用结束后,立刻释放内存,所以形参变量只有在函数内部有效,不能在函数外部使用。
(2)在函数调用过程中,形参的值发生改变并不会影响实参。

三、头文件的讲究

很多初学者认为 stdio.h 中包含了函数定义(也就是函数体),只要有了头文件就能运行,其实不然,头文件中包含的都是函数声明,而不是函数定义,函数定义都放在了其它的源文件中,这些源文件已经提前编译好了,并以动态链接库或者静态链接库的形式存在,只有头文件没有系统库的话,在链接阶段就会报错,程序根本不能运行。

对于多个文件的程序,通常是将函数定义放到源文件(.c文件)中,将函数的声明放到头文件(.h文件)中,使用函数时引入对应的头文件就可以,编译器会在链接阶段找到函数体。

四、全局变量的讲究(static的作用)

全局变量的默认作用域是整个程序,也就是所有的代码文件,包括源文件(.c文件)和头文件(.h文件)。如果给全局变量加上 static 关键字,它的作用域就变成了当前文件,在其它文件中就无效了。

五、单独的代码块

C语言还允许出现单独的代码块,它也是一个作用域。请看下面的代码:

#include <stdio.h>
int main(){
    int n = 22;  //编号①
    //由{ }包围的代码块
    {
        int n = 40;  //编号②
        printf("block n: %d\n", n);
    }
    printf("main n: %d\n", n);
   
    return 0;
}

这里有两个 n,它们位于不同的作用域,不会产生命名冲突。{ } 的作用域比 main() 更小。

六、递归函数

6.1 斐波那契数列:

//递归计算斐波那契数
long fib(int n) {
    if (n <= 2) {
        return 1;
    }
    else {
        return fib(n - 1) + fib(n - 2);
    }
}

6.2 递归函数的空间开销:

每个线程都拥有一个栈,如果一个程序包含了多个线程,那么它就拥有多个栈。

对每个线程来说,栈能使用的内存是有限的,一般是 1M~8M,这在编译时就已经决定了,程序运行期间不能再改变。如果程序使用的栈内存超出最大值,就会发生栈溢出(Stack Overflow)错误。

递归函数内部嵌套了对自身的调用,除非等到最内层的函数调用结束,否则外层的所有函数都不会调用结束。通俗地讲,外层函数被卡主了,它要等待所有的内层函数调用完成后,它自己才能调用完成。

每一层的递归调用都会在栈上分配一块内存,有多少层递归调用就分配多少块相似的内存,所有内存加起来的总和是相当恐怖的,很容易超过栈内存的大小限制,这个时候就会导致程序崩溃。

例如,一个递归函数需要递归 10000 次,每次需要 1KB 的内存,那么最终就需要 10MB 的内存。下面我们用递归的方式来求 1+2+3+ ...... + (n-1) + n 的值:

long sum(int n) {
    //为了增加每次函数调用的内存,额外增加了一个无用的数组,它占用 1KB 的内存
    int arr[250];
    if (n <= 1) {
        return n;
    } else {
        return  n + sum(n-1);
    }
}

这是因为,每次递归调用都需要超过 1KB 的内存(仅仅数组就占用了 1KB 内存),而要得到最终的结果需要 1000 次递归调用,这样一来,所有内存的总和就超过了 1MB。

上面我们说过,Visual Studio 默认的栈内存只有 1MB,超过这个界限程序就无法运行了,只能让它崩溃。使用其它的编译器也许程序不会崩溃,读者可以亲自尝试。

6.3 递归函数的时间开销 clock_t CLOCKS_PER_SEC

每次调用函数都会在栈上分配内存,函数调用结束后再释放这一部分内存,内存的分配和释放都是需要时间的。

每次调用函数还会多次修改寄存器的值,函数调用结束后还需要找到上层函数的位置再继续执行,这也是需要时间的。

所有的这些时间加在一起是非常恐怖的。

下面我们以「求斐波那契数」为例来演示双层递归的时间开销。

#include <stdio.h>
#include <time.h>
// 递归计算斐波那契数
long fib(int n) {
    if (n <= 2) {
        return 1;
    }
    else {
        return fib(n - 1) + fib(n - 2);
    }
}
int main() {
    int a;
    clock_t time_start, time_end;
    printf("Input a number: ");
    scanf("%d", &a);
    time_start = clock();
    printf("Fib(%d) = %ld\n", a, fib(a));
    time_end = clock();
    printf("run time: %lfs\n", (double)(time_end - time_start)/ CLOCKS_PER_SEC );
    return 0;
}

运行结果:
Input a number: 42↙
Fib(42) = 267914296
run time: 13.137000s
可以看到,为了求 42 的斐波那契数程序竟然运行了 13 秒,简直让人发指。

最合适的办法:使用迭代来替换递归函数,迭代函数实现时间近乎于0s。

七、需要注意的地方

(1)int b = a + 20;是具有运算功能的语句,要放在函数内部。

(2)标准C语言(ANSI C)定义了15个头文件,成为“C标准库”。

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值