C语言探索之旅:模块化编程的亮点

我们将声明我们的函数,我们称之为:函数原型。就好像你给电脑发了一条通知:“看,我的函数原型在这里,你给我记住了”。

我们来看一个我们在上一课中给出的函数的例子(计算一个矩形的面积):

双矩形区域(双倍长,双倍宽)

{

返回长度 * 宽度;

}

上面我们函数的原型怎么声明呢?

复制,粘贴第一行

最后加一个分号;

将这一整行放在 main 函数之前

简单吗?现在你可以把你的函数定义放在主函数之后,计算机会识别它,因为你在主函数之前声明了这个函数。

您的程序将如下所示:

#包括

#包括

// 下面一行是 rectangleArea 函数的函数原型:

双矩形区域(双倍长,双倍宽);

int main(int argc, char *argv[])

{

printf(“一个长10宽5的矩形的面积 = %f\n”, rectangleArea(10, 5));

printf(“一个长3.5宽2.5的矩形的面积 = %f\n”, rectangleArea(3.5, 2.@ >5)) ;

printf(“一个长9.7宽4.2的矩形的面积 = %f\n”, rectangleArea(9.7, 4.@ >2)) ;

返回0;

}

// 现在我们的 rectangleArea 函数可以放在程序的任何地方:

双矩形区域(双倍长,双倍宽)

{

返回长度 * 宽度;

}

与原来的程序相比,有什么变化?实际上,函数的原型是在程序的开头添加的(记住不要忘记分号)。

函数的原型实际上是对计算机的提示或指令。例如,在上面的程序中,函数原型

双矩形区域(双倍长,双倍宽);

就是对电脑说:“哥们,有个函数,它的输入是什么参数,输出是什么类型”,这样电脑才能更好的管理它。

多亏了这行代码,您的 rectangleArea 函数现在可以放置在程序中的任何位置。

记住:最好养成一个习惯,对于C语言程序,总是先定义一个函数,然后再编写函数的原型。不能写函数原型吗?还。只要你把每个函数的定义放在main函数之前,但是你的程序会逐渐变得越来越大,当你有几十个或者几百个函数的时候,你还管得着吗?

所以养成一个好习惯,你就不会受苦。

您可能已经注意到,主函数没有函数原型。因为不需要,所以main函数是每个C程序必备的入口函数。(人家有钱,和编译器关系不错,编译器对main函数很熟悉,是经常打交道的小伙伴,所以没必要用函数原型来“引入”main函数。) .

还有一点就是写函数原型的时候,括号里面的函数参数的名字不一定要写,只写类型就可以了,因为函数原型只是对计算机的介绍,所以计算机只需要知道输入参数。什么类型就够了,不用知道名字。所以我们上面的函数原型也可以简写如下:

双矩形区域(双,双);

看,我们可以省略两个变量名length和width,只保留类型名double(双精度浮点)。

注意:不要忘记函数原型末尾的分号,因为这是编译器区分函数原型和函数定义开头的重要标志。如果没有分号,编译时会出现比较难理解的错误信息。

头文件

每次看到这个词,我都会想起刚刚结婚的“我们的青春”:周杰伦的“头文字D”。在此祝周杰伦生活愉快。

到目前为止,我们的程序只有一个.c文件(称为“源文件”)c语音游戏程序,例如我们之前将这个.c文件命名为main.c,当然名称无所谓。您可以将其命名为 hello.c 或 hehe.c。

一个项目有多个文件

在实际编写程序时,您的项目通常不会将所有代码都写在一个 main.c 文件中。当然,这是可以做到的。但是,想象一下,如果你把所有的代码都放到这个 main.c 文件中,那么代码量可能会超过 10000 行,你在里面找东西太难了。也正是因为如此,我们通常会为每个项目创建多个文件。

上面提到的项目指的是什么?

当我使用 IDE Code::Blocks 创建第一个 C 语言项目时,我实际上已经接触过它。但我们将解释更多。

一个项目(英文的project),简单来说就是指你的程序的所有源代码(以及一些其他的文件),项目中的文件类型很多。

目前我们的项目只有一个源文件:main.c

查看您的 IDE,通常项目列在左侧。

如上图,可以看到这个项目(在Projects栏中)只有一个文件:main.c

现在让我们展示一个包含多个文件的项目:

在上图中,我们可以看到这个项目中有几个文件。大多数实际项目都是这样的。你看过 main.c 文件吗?一般来说,在我们的程序中,main函数只定义在main.c中。当然不一定非要这样,每个人都有自己的编程风格。不过,我希望关注本课程的读者能够与我们保持一致的风格,以便于理解。

那你又要问了:“为什么要创建多个文件?我怎么知道有多少文件适合我的项目?”

答案是:这是你的选择。通常,我们将相同主题的函数放在一个文件中。

.h 文件和 .c 文件

在上图中,我们可以看到有两种类型的文件:一种以 .h 结尾,一种以 .c 结尾。

.h 文件:称为“头文件”,这些文件包含函数的原型

.c 文件:称为“源文件”并包含函数本身(定义)

所以,一般来说,我们通常不会将函数原型放在.c 文件中,而是放在.h 文件中,除非你的程序很小。

对于每个 .c 文件,都有一个同名的 .h 文件。在上面的工程图片中,可以看到.h和.c文件一一对应。

文件.h 和文件.c

editor.h 和 editor.c

游戏.h 和游戏.c

但是我们的计算机怎么知道函数原型在.c文件之外的另一个文件中呢?

需要使用我们之前介绍的预处理指令#include 将其引入.c 文件。

请做好准备,一波“密集”知识点“来袭”。

如何导入头文件?其实你已经知道怎么做了,我们在之前的课程中已经写过。

比如我们看上面我们game.c文件的开头

#包括

#包括

#include “游戏.h”

无效播放器(SDL_Surface* ecran)

{

// …

}

看,其实你已经很熟悉了。导入头文件,只需要使用#include预处理指令

因此,我们在game.c源文件中引入了三个头文件:stdlib.h、stdio.h、game.h

注意到区别了吗?

标准库头文件(stdlib.h、stdio.h)和您自己定义的头文件(game.h)的导入方式略有不同:

用于导入标准库的头文件在IDE中一般位于安装目录的include文件夹中,在linux中位于系统的include文件夹中

“”用于导入自定义头文件,位于自己项目的目录下

我们来看看我们对应的game.h头文件的内容:

看,函数原型存储在 .h 文件中。

你已经对一个项目有了大概的了解

那你又会问:“为什么要这样安排?把函数原型放在.h头文件中,在.c源文件中用#include导入,为什么不把函数原型写在.c文件中? “

答案是:易管理、清晰、不易出错、省心

因为如前所述,你的计算机在调用它之前必须“知道”一个函数,我们需要函数原型让其他使用该函数的函数提前知道

如果使用.h头文件的管理方式,只需在每个.c文件开头使用#include指令导入头文件的所有内容,那么头文件中声明的所有函数原型当前的 .c 文件。,您不必担心这些函数的定义顺序或其他函数是否知道它们

比如我的main.c函数要使用functions.c文件中的函数,那么我只需要在main.c开头写#include “functions.h”,就可以调用该函数了。 c 在 main.c 函数中。定义函数

您可能会再次问:“那么如何将新的 .h 和 .c 文件添加到项目中?”

很简单,在codeblocks中,在项目列表的主菜单上右击选择Add Files

或者

点击菜单栏

字段->新建->文件…

您可以选择要添加的文件类型

导入标准库

你脑子里肯定有一个问题:如果我们用#include来导入标准库头文件stdio.h和stdlib.h,而这些文件不是我写的,那么它们一定存在于电脑某处我们可以去的地方并找到,对吗?

对,就是这样!

如果您使用的是 IDE(集成开发环境),它们通常位于 IDE 的安装目录中。如果是在纯linux环境下,那你就得去系统文件夹找了。这里不会讨论。以后会在Linux课上讨论。有兴趣的读者可以上网搜索。

在我的情况下,因为安装的IDE是代码块,所以在windows下,我的头文件被“隐藏”在这个路径下:

E:\Program Files\CodeBlocks\MinGW\include

通常,它位于名为“include”的文件夹中。在里面你会发现很多文件,都是.h文件,是C语言系统定义的标准头文件,也就是系统库的头文件(常见于windows、mac、linux ,C语言本来是可以移植的)。在众多头文件中,您可以找到我们的老朋友:stdio.h 和 stdlib.h。

你可以双击打开这些文件,或者选择你喜欢的文本编辑器打开它们,但是你可能会感到惊讶,因为这些文件中有很多内容,还有一些我们还没有学过的用法,比如#以外包括其他预处理指令。

可以看到这些头文件中都充满了函数原型,例如可以在stdio.h中找到printf函数的原型。

你要问:“好的,现在我知道标准库的头文件在哪里,标准库的相应源文件(.c文件)在哪里?”

对不起,你看不到他们。因为.c文件是事先编译好的,转换成计算机可以理解的二进制代码。

“伊人已逝,岁月已逝,我将去往何方?”

既然看不到原版,那至少让我在《美图秀秀》之后再看看吧……

是的,您可以在名为 lib 的文件夹下找到它。我的窗户下的路径是:

E:\Program Files\CodeBlocks\MinGW\lib

编译成二进制代码的.c文件有了新的后缀:.a(对于codeblocks来说,因为编译器是mingw)或者.lib(对于visual c++来说,因为编译器是Visual),当然以后,我们还会了解到linux下有.so后缀,以此类推。暂时不要深究。

这些编译出来的文件称为库文件或库文件(library,英文:library),不要试图去读这些文件的内容,不是乱码让人看懂…

在这里学习可能会有些头晕,但是如果你继续阅读,就会逐渐清晰,后面的章节中会有图表帮助你理解。

总结一下:

在我们的.c源文件中,我们可以使用#include预处理指令来导入标准库的.h头文件或者自己定义的头文件,这样就可以使用标准库定义的printf等函数了,所以即计算机识别出这些函数(通过.h文件中的函数原型),并可以检查你是否正确调用了这些函数,例如函数的参数个数、返回值的类型等。

单独编译

既然知道了一个项目是由几个文件组成的,那么我们就可以了解一下编译器的工作原理了;上一课展示的编译示例图比较简化,下图是稍微详细的编译原理。希望你能理解并记住:

上图基本详细的展示了编译时发生的事情,我们仔细分析一下:

1. 预处理器:顾名思义,预处理器为编译做一些准备工作,所以预处理器是在编译之前启动的。它的任务是执行特殊指令,这些指令由预处理命令给出,以#开头,易于识别。

预处理指令有很多种,目前我们学习的唯一一种是#include,它允许我们将一个文件的内容包含在另一个文件中。#include 预处理器指令也是最常用的。

预处理器会将#include所在的那句替换成它引入的头文件的内容,比如

#包括

预处理器在执行时会将上述命令替换为 stdio.h 文件的内容。所以在编译的时候,你的.c文件的内容会增加,包括所有导入的头文件的内容,比较臃肿。

2. 编译:这是核心步骤。正如我们在上一课中所说,将我们编写的代码转换为计算机可以理解的二进制代码(由0和1组成)是编译。编译器编译一个 .c 文件。对于像代码块这样的 IDE,它是你放在项目列表中的所有 .c 文件;如果您使用 gcc 进行编译,则必须指定要编译的 .c 文件。

编译器会先将 .c 文件转换为 .o 文件(有些编译器会生成 .obj 文件)。.o文件一般称为“目标文件”(o是英文object:目标的第一个字母),是一个临时的二进制文件,后面会用到它来生成最终的可执行二进制文件。

.o 文件通常在编译完成后被删除(取决于您的 IDE 设置)。在一定程度上,.o 文件虽然是一个临时的中间文件,看起来并没有多大用处,但保留它而不是删除它也有好处:如果项目有 10 个 .c 文件,则 10 个 .c 文件。 o 编译后生成文件。之后,您只修改了其中一个 .c 文件。如果您重新编译,编译器不会为其他 9 个 .c 文件重新生成 .o 文件,只会为您更改的那个重新生成。节省资源。

3.链接器:顾名思义,链接器的作用就是链接,链接是什么?它是编译器生成的.o 文件。链接器链接所有 .o 文件并“制作”一个大标题:最终的可执行文件(windows 下的 .exe 文件,linux 下的许多形式)。

既然知道了代码生成可执行程序的内部原理,下面我们给大家展示的这张图就很重要了,希望大家理解并记住。

大多数错误是在编译时显示的,但有些是在链接时显示的,可能缺少 .o 文件等。

上图其实并不完整,大家可能已经想到了:我们用.h文件介绍了标准库头文件的内容(主要是函数原型),具体实现的代码我们没有介绍函数,我们应该怎么做?毛呢布?顺便说一下,就是前面提到的.a或.lib等库文件(从标准库的.c源文件编译而来)。

所以我们的链接器还没有结束,它还需要负责链接标准库文件,将自己编译的.c文件生成的.o目标文件与标准库文件整合,然后链接成最终的可执行文件. 如下所示:

2.0@>

现在我们的图表终于完成了

这样,我们就有了一个完整的可执行文件,里面包含了它所需要的所有指令的定义,比如printf的定义

后面的第三部分,我们会用到系统的图形库,也是在.a库文件中定义的,里面包含了一系列指令的定义,比如告诉计算机如何创建窗口,绘制图形,等等

变量和函数的范围

为了结束我们的课程,我们必须学习最后一点:

变量和函数的作用域(有效作用域)。

我们将了解何时可以调用它们。

函数的私有变量(局部变量)

在函数中定义变量时,该变量会在函数结束时从内存中删除。

int multipleTwo(整数)

{

整数结果 = 0; // 在内存中创建变量结果

结果 = 2 * 数字;

返回结果;

} // 函数结束,变量结果从内存中删除

函数中定义的变量仅在函数运行时存在。这是什么意思?意味着你不能从另一个函数调用它。

#包括

int multipleTwo(int number);

int main(int argc, char *argv[])

{

printf(“两倍 15 是 %d\n”, multipleTwo(15));

printf(“两倍 15 是 %d”, 结果); // 错误!

返回0;

}

int multipleTwo(整数)

{

整数结果 = 0;

结果 = 2 * 数字;

返回结果;

}

可以看到,在main函数中,我们尝试调用result变量,但是因为这个变量是在multipleTwo函数中定义的,所以在main函数中无法调用,会报错。

记住:函数中定义的变量只能在函数内部使用,我们称之为局部变量。

全局变量:避免使用

所有文件都可以使用的全局变量

我们可以在项目的所有文件中定义可以被所有函数调用的变量。我们将向您展示如何,以便通知您此方法存在,但一般情况下,请避免使用可被所有文件使用的全局变量。也许这样做会让你的代码一开始更容易,但你很快就会开始担心它。

为了创建一个可以被所有函数调用的全局变量,我们需要在函数外部定义它。通常我们将这些变量放在程序的开头,在#include 预处理器指令之后。

#包括

整数结果 = 0; // 定义全局变量 result

无效multipleTwo(整数);// 函数原型

int main(int argc, char *argv[])

{

multipleTwo(15); // 调用multipleTwo函数将全局变量result的值加倍

printf(“两倍 15 是 %d\n”, 结果); // 我们可以调用变量结果

返回0;

}

无效multipleTwo(整数)

{

结果 = 2 * 数字;

}

在上面的程序中,我们的函数multipleTwo不再有返回值,而是用于将全局变量result的值加倍。然后主函数可以再次使用结果变量。

由于这里的result变量是一个完全开放的全局变量,可以被项目的所有文件调用,也可以被所有文件的任意函数调用。

注意:这种类型的变量被高度弃用,因为它不安全。函数中的 return 语句通常用于返回变量的值。

只能在文件中访问的全局变量

我们刚刚学习的完全开放的全局变量可以被项目中的所有文件访问。我们还可以使全局变量只能由它所在的文件调用。也就是说,它可以被其所在文件中的所有函数调用,但不能被项目的其他文件中的函数调用。

怎么做?

只需在变量前面添加关键字 static 即可。如下:

静态 int 结果 = 0;

静态在英语中的意思是“静止、不变”。

函数的静态变量

注意:如果在函数内部的变量前加上关键字static,其含义与我们上面演示的全局变量不同。如果在函数内部给变量加上static,函数结束后变量不会被销毁,它的值会保持不变。下次我们调用这个函数时,这个变量会继承之前的值。

例如:

int multipleTwo(整数)

{

静态 int 结果 = 0; // 第一次调用函数时会创建静态变量 result

结果 = 2 * 数字;

返回结果;

} // 变量result不会在函数结束时销毁

这到底是什么意思?

也就是说:变量result的值,我们下次调用这个函数时,会使用上一次调用结束时的值。

有点头晕,对吧?没关系。让我们看一个小程序,以便更好地理解:

#包括

整数增量();

int main(int argc, char *argv[])

{

printf(“%d\n”, increment());

printf(“%d\n”, increment());

printf(“%d\n”, increment());

printf(“%d\n”, increment());

返回0;

}

整数增量()

{

静态整数 = 0;

数字++;

返回号码;

}

在上面的程序中,我们第一次调用自增函数时,会创建number变量,初始值为0,然后对其进行自增操作(++运算符),所以number的值变为1。函数结束后,number变量并没有从内存中删除,而是保持值1。

之后,当我们第二次调用自增函数时,变量号(static int number = 0;)的声明语句会被跳过c语音游戏程序,不执行(因为变量号还在内存中。你以为皇帝有还没死,太子怎么可能继承王位)。我们继续使用上次创建的数字变量。这时变量的值跟在第一次自增函数调用后的值之后:1,然后对其进行++操作(自加2.3@>,number的值就变成了2。而以此类推,第三次调用自增函数后number值为3,第四次调用number值为4

于是运行程序,输出如下:

1

2

3

4

文件中的本地函数(本地或静态)

我们用函数作用域结束了对变量和函数作用域的研究。

通常,当您在 .c 源文件中创建函数时,它是全局的,并且可以被项目中的所有其他 .c 文件调用。

但是有时候我们需要创建一个只能被这个文件调用的函数,怎么做呢?

聪明如你所想:没错,就是使用static关键字,类似于变量。放在函数前面。如下:

静态 int multipleTwo(int number)

{

// 操作说明

}

现在,你的函数只能被同一个文件中的其他函数调用,而项目中其他文件中的函数只能在远处看到,不能玩……

总结所有可能的变量范围:

如果函数体中定义的变量前面没有static关键字,则为局部变量,在函数末尾删除,只能在该函数内部使用。

定义在函数体中,但是前面加了static关键字,是一个静态变量,在函数结束时不会被删除,它的值会被保留。

在函数外部定义的变量称为全局变量。如果前面没有static关键字,它的作用域就是整个项目的所有文件,也就是说,它可以被项目所有文件的函数调用。

如果在函数外定义的变量前面有static关键字,则只能被该文件中的所有函数调用,而不能被项目其他文件中的函数调用。

同样,总结所有可能的功能范围:

默认情况下,一个函数可以被项目所有文件中的函数调用。

如果我们希望一个函数只能被该文件中的函数调用,只需在函数前添加 static 关键字即可。

总结

一个程序包含一个或多个.c文件(通常称为源文件,source。当然,我们一般也将所有高级语言代码称为源代码)。通常,每个 .c 文件都有一个同名但扩展名不同的 .h 文件(有点像同父异母的兄弟)。.c 文件包含函数的实际定义,而 .h 文件包含函数的原型声明。

.h 文件的内容被称为预处理器的程序带入 .c 文件的开头。

.c 文件通过称为编译器的程序转换为 .o 二进制对象文件(o 是英文 object 的首字母缩写,意思是“object, target”)。

.o文件通过一个叫做链接器的程序连接到最终的.exe可执行文件(exe是英文executable的前三个字母,意思是“可执行”。windows操作系统中的可执行程序扩展名为.exe。在linux系统,可执行程序有很多扩展名(.elf等)

变量和函数具有“有效范围”,有时无法访问。

第二课预习第二部分:

今天的课就到这里,让我们一起加油吧。

下次我们在第二部分,第二课时,咳咳,我得坐下来假装认真宣布:

来了解C语言的本质和王牌:指针!

2.4@>

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论