我们将声明我们的函数,我们称之为:函数原型。就好像你给电脑发了一条通知:“看,我的函数原型在这里,你给我记住了”。
我们来看一个我们在上一课中给出的函数的例子(计算一个矩形的面积):
双矩形区域(双倍长,双倍宽)
{
返回长度 * 宽度;
}
上面我们函数的原型怎么声明呢?
复制,粘贴第一行
最后加一个分号;
将这一整行放在 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@>
请登录后发表评论
注册
社交帐号登录