C语言编程周期为2 的函数,一起学习C语言:函数(二)

上一篇 中,我们了解了函数的概念,以及函数实现与程序编译过程。本章节,我们分析内部函数和外部函数,以及变量的生命周期。

章节预览:

4. 外部函数与内部函数

4.1 外部函数

4.2 内部函数

5. 变量的生命周期与作用域

目录预览

章节内容:

4. 外部函数与内部函数

默认情况下,我们定义或声明的函数属于“外部”函数,又称为“全局”函数。全局函数即可以被本文件中的其他函数调用,也可以被其他文件中的函数调用。首先,我们分析函数如何在其他文件中调用到。

接下来,编写一个多文件工程(预编译加载函数实现):

math.c代码:

int Add(int a, int b)

{

return a + b;

}

main.c代码:

#include #include “math.c”

int main()

{

int res = Add(2, 3);

printf(“res:%d.”, res);

return 0;

}

在这个工程中,math.c属于源文件,main.c属于主文件(包含main函数)。 我们在math.c文件内实现了Add全局函数,在main.c内的main内调用Add函数。这个程序中,我们相当于把math.c当做头文件使用,为什么是这样呢?接下来,我们分析一下编译过程:

一. 预编译:

在控制台中输入gcc -E main.c -o main.i,预处理器把main.c中包含的文件经过预处理后写入main.i,打开main.i文件可以看到stdio.h和math.c文件预处理后的内容,而Add函数定义也在这一步保存在了main.i文件中。

二. 编译为汇编代码:

在控制台中输入gcc -S main.i -o main.s,编译器把main.i中的内容转换为汇编代码后写入main.s,打开main.s文件可以看到Add和main函数定义的汇编实现代码,以及main函数调用Add函数的过程。

三. 生成为目标文件:

在控制台中输入gcc -c main.s -o main.o,汇编器把mian.s中的内容编译为机器码后写入main.o,这里面就包含了供程序执行使用的Add和main函数的实现部分(可以通过hexdump -C main指令查看具体内容)。

四. 链接:

在控制台中输入gcc -o main main.o,链接器把main.o中的Add和main函数实现部分打包生成一个可执行程序main(可以通过hexdump -C main指令查看具体内容)。

实际编译:

实际编译时我们不需要了解其中的细节,控制台中输入gcc -o main main.c。

另一种形式,编写一个多文件工程(链接加载函数实现):

math.c代码:

int Add(int a, int b)

{

return a + b;

}

math.h代码:

extern int Add(int a, int b);

main.c代码:

#include #include “math.h”

int main()

{

int res = Add(2, 3);

printf(“res:%d.”, res);

return 0;

}

在这个工程中,math.c属于源文件,math.h属于头文件,main.c属于主文件(包含main函数)。我们在math.c文件内实现了Add全局函数,在math.h文件内声明Add全局函数,在main.c内的main内调用Add函数。这个程序中,我们相当于把math.c当做外部库使用(链接时加载),而math.h内的Add函数声明作为生成目标文件(main.o)时的Add函数原型配对。

实际编译:

在上个示例中,我们了解工程编译过程,这里我们简单介绍一下编译流程。

控制台中输入gcc -o main main.c math.c,这里相当于把main.c和math.c当做两个模块分别执行预编译、编译为汇编代码、生成为目标文件,每一步得到两个文件,比如分别执行gcc -E main.c -o main.i、gcc -E math.c -o math.i后,得到main.i和math.i。执行到链接时,把main.o和math.o中的Add、main函数实现部分打包生成一个可执行程序main。

4.1 外部函数

上述示例中,我们了解了自定义函数如何编译到可执行程序中,以及在别的文件内的调用方式。实际编程时,如果被调用的函数定义不在本文件中,可以通过在函数声明左侧增加extern关键字,表示这个函数来自外部(别的文件或外部库)。比如,我们为程序执行预编译操作后,查看main.i文件可以了解到Add函数声明,参考图4-1。

c79f8f8a81a8104d32cf1f581561accf.png

图4-1 预处理文件

C语言中,函数声明分为两种形式:一种是形式声明(函数原型,上述所说的函数声明),另一种是实体声明(函数定义)。

当程序编译时,一个函数可以存在多个形式声明,只能存在一个实体声明。比如上述示例中,可以在main.c中形式声明Add函数,但不能实体声明Add函数。

形式声明的作用也可以分为两种情况:一种是为了编程人员方便查看函数原型,另一种是在生成目标文件时编译器匹配函数原型。

4.2 内部函数

在定义函数时,可以通过指定函数存储类别表示函数的作用域 (6)。函数存储类别分为“static”和上述所说的“extern”,其中static表示内部函数,extern表示外部函数。

定义函数时,如果省略extern关键字,则默认为是外部函数。但定义内部函数时,不能省略static关键字。

内部函数定义形式:

static 返回类型 函数名称(参数列表)

{

函数体

}

内部函数定义举例:

static void func(int a)

{

printf(“a的值为:%d”, a);

}

内部函数又称为静态函数,它只能被本文件内的其他函数所调用。使用内部函数可以确保其他文件中即使有相同命名的内部函数,也互不干扰。

(6):编程世界中,作用域一般表示某个变量、某个常量、某个函数的可使用范围区域。比如Add全局函数可以在math.c文件内使用,也可以在main.c文件内使用,它的作用域属于整个工程内。

5. 变量的生命周期与作用域

在之前编写的示例中,我们都是在函数中定义变量。比如在main函数中定义int a、自实现函数中定义char c等,这些变量随函数入栈 (7) 后分配栈内的内存空间(一般称作压入栈),然后随函数出栈时反顺序出栈(后入先出),最后栈地址跳转到函数入栈时的地址。从这里我们可以了解到,函数中定义的变量生命周期随函数入栈分配变量内存空间生效,随函数出栈时失效,这类变量统称为局部变量。当然,局部变量还有另外一种情况,在变量定义时指定存储类别改变变量的生命周期。

总体来看,在一个函数内部定义的变量只在本函数内有效,也可以认为只能在本函数中使用这个变量,而这个变量的有效范围也是由“作用域”表示。

(7):栈内存属于扩充形式增长内存。比如当前栈内存分配100k,如果在一个函数中栈内存用到500k,则栈内存自动扩充到500k(大致数据,满足栈内存需求),当这个函数退出后栈内存还是会保持在500k,一直到进程退出后归还栈内空间。

首先,我们了解局部变量的定义方式:

1.在函数内的开头定义变量:

5b6343f125fa31d49839780b638aa745.png

2.在函数内的复合语句内定义变量:

b83ac4b1c6de581d7af334930bff5849.png

3.在函数内的执行语句后定义变量:

c2d42985b7b79a9d32ce0faf8d36b63a.png

4.函数内的形参变量:

f1b0927aea6fe6381545e9bdeb0b0260.png

如果我们需要变量在多个函数内部都允许使用时,可以定义全局变量。全局变量定义在函数外部,又称为全程变量,可以在本文件或别的文件内的不同函数内部使用。

通常情况下,全局变量的有效区域在定义变量处至文件末尾,生命周期从程序开始执行到程序执行结束。

接下来,我们了解全局变量的定义方式:

54dc2756b9aca6405cb13f3268e532c5.png

全局变量定义在预处理、宏定义下方至函数之间比较常见,如int a,可以在本文件中的所有函数内部使用。示例中,全局变量a和c随main函数执行生效至main函数退出失效。

目录预览

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值