首页 > 代码库 > 【C语言探索之旅】 第二部分第一课:模块化编程

【C语言探索之旅】 第二部分第一课:模块化编程


技术分享



内容简介


1、课程大纲

2、第二部分第一课: 模块化编程

3、第二部分第二课预告: 进击的指针,C语言王牌



课程大纲


我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案。还会带大家用C语言编写三个游戏。


C语言编程基础知识


  • 什么是编程?

  • 工欲善其事,必先利其器

  • 你的第一个程序

  • 变量的世界

  • 运算那点事

  • 条件表达式

  • 循环语句

  • 实战:第一个C语言小游戏

  • 函数

  • 练习题

  • 习作:完善第一个C语言小游戏


C语言高级技术


  • 模块化编程

  • 进击的指针,C语言王牌

  • 数组

  • 字符串

  • 预处理

  • 创建你自己的变量类型

  • 文件读写

  • 动态分配

  • 实战:“悬挂小人”游戏

  • 安全的文本输入

  • 练习题

  • 习作:用自己的语言解释指针


用基于C语言的SDL库开发2D游戏


  • 安装SDL

  • 创建窗口和画布

  • 显示图像

  • 事件处理

  • 实战:“超级玛丽推箱子”游戏

  • 掌握时间的使用

  • 用SDL_ttf编辑文字

  • 用FMOD控制声音

  • 实战:可视化的声音谱线

  • 练习题


数据结构


  • 链表

  • 堆,栈和队列

  • 哈希表

  • 练习题



第二部分第一课:模块化编程


不好意思,小编生病了几天(发烧了),这一课内容极为多。所以现在才发布 :)


前些天微信上有朋友提议说,可以建一个讨论的群。凡是对编程感兴趣的朋友可以加,大家可以交流,互动,分享写的程序的源代码,等。

这个群已经建了,目前70多人了。

还有刚建立的QQ群:《程序员联盟》,群号:413981577

这段时间有不少朋友加我好友,但我不清楚谁是看了我的《程序员联盟》的文章之后加的,所以能否烦请大家给我发一个私信(“加程序员联盟”之类的),我会把发我私信的朋友加到这个微信的群里(程序员联盟)。

我的微信号可以在文章最后看到,发我邮件也行,邮箱也在最后。

目前也创建了《程序员联盟》的微社区,大家可以关注一下。


微社区地址和二维码如下:


http://m.wsq.qq.com/264152148

技术分享


谢谢!



大家肯定注意到了,今天的主题图片我用了老爷子:Dennis Ritchie(C语言之父)的壁纸,略表敬仰和怀念之情。

还有一个原因:因为这一部分要开始难起来了,开始学习C语言的高级技术。

图片上的The Legend,是英语“传奇”的意思。我再说一次,2011年,乔布斯乔帮主也离开了我们,不过不要忘了这个“乔布斯站在其肩头”的伟大的人,Dennis Ritchie:C语言之父,Unix操作系统之父(与Ken Thompson一起),还有其他丰功伟业。

威尔.史密斯也在拍《我是传奇》3了。

以后应该拍一系列早期计算机黑客的传奇的电影,特别是丹尼斯.里奇,肯定得拍一部纪录片。

网上有一篇图文介绍,叫《Unix英烈传:图文细数十五位计算先驱》

http://www.linuxidc.com/Linux/2013-08/89395p2.htm


还有最后一个原因:老爷子长得帅啊,给我们程序员长脸啊。

《灌篮高手》里樱木花道称呼安西教练为老爷子,愿我们的Dennis Ritchie教练也“安息”。


好了,话休絮烦(已经很烦了你,发烧发傻了。。。),我们回归正题。



话说上一课是第一部分最后一课,现在开始第二部分的探索之旅!


在这一部分中,我们会学习C语言的高级技术。这一部分内容将是一座高峰,会挺难的。但是我们一起翻越。

一口是吃不成一个胖子的,但是小口小口,慢慢吃,还是能吃成胖子的嘛。所以要细水长流,肥油慢积。一路上有你(油腻)...


一旦你跟我们的课程一直到这一部分的结束,你将会掌握C语言的核心技术,也可以理解大部分C语言写的程序了。

之后在第三部分,我们就会一起来学习C语言图形编程,写C语言的2D游戏等等。


到目前为止我们的程序都只是在一个main.c文件里捣腾,因为我们的程序还很短小,这也足够。但是之后你的程序如果有了十多个函数,甚至上百个函数,那么你就会感到全部放在main.c一个文件里是多么拥挤和混乱。


正因为如此,计算机科学家才想出了模块化编程。原则很简单:与其把所有源代码都放在一个main.c当中,我们将把它们合理地分割,放到不同的文件里面。



函数原型

到目前为止,写自定义的函数的时候,我们都要求大家暂时把函数写在main函数的前面。

这是为什么呢?(唉呀妈呀,想到了蔡明和郭达那个小品。。。)


因为这里的顺序是一个重要的问题。如果你将自己定义的函数放置在main函数之前,电脑会读到它,会知道这个函数,然后当你在main函数中调用这个函数时,电脑知道这个函数,也知道到哪里去执行它。

但是假如你把这个函数写在main函数后面,那你在main函数里调用这个函数的时候,电脑就不认识它了,你可以自己写个程序测试一下。是的,很奇怪吧?这绝对有点任性的。


那你会说:“C语言岂不是设计得不好么?”

我完全同意(别让上面的老爷子听到了...)。但是请相信,这样设计应该也是有理由的,计算机先驱们早就想到了,也提出了解决之道。


下面我们就来学一个新的知识点,借着这个,你可以把你的自定义函数放在程序的任意位置。不必操心总是好事。



用来声明一个函数的“函数原型”

我们会声明我们的函数,用我们所说的术语:函数原型(prototype)。就好比你对电脑发出一个通知:“看,我的函数的原型在这里,你给我记住啦”。


我们来看一下我们上一课举的一个函数的例子(计算矩形面积):


double rectangleArea(double length, double width)

{

return length * width;

}


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

  1. 复制,黏贴第一行

  2. 在最后放上一个分号;

  3. 把这一整行放置在main函数前面


简单吗?现在你就可以把你的函数的定义放在main函数后面啦,电脑也会认识它,因为你在main函数前面已经声明过这个函数了。


你的程序会变成这样:


#include <stdio.h>

#include <stdlib.h>


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

double rectangleArea(double length, double width);


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));


return 0;

}


// 现在我们的rectangleArea函数就可以放置在程序的任意位置啦 :

double rectangleArea(double length, double width)

{

return length * width;

}


与原先的程序相比有什么改变呢,其实就是在程序的开头加了函数的原型而已(记得不要忘了那个分号)。


函数的原型,其实是给电脑的一个提示或指示。比如上面的程序中,函数原型

double rectangleArea(double length, double width);


就是对电脑说:“老兄,存在一个函数,它的输入是哪几个参数,输出是什么类型”,这样就能让电脑更好地管理。


多亏了这一行代码,现在你的rectangleArea函数可以置于程序的任何位置啦。


记得:最好养成习惯,对于C语言程序,总是定义了函数,再写一下函数的原型。不写函数原型行不行?也行。只要你把每个函数的定义都放在main函数之前,但是你的程序慢慢会越来越大,等你有几十或者几百个函数的时候,你还顾得过来么?

所以养成好习惯,不吃亏的。


你也许注意到了,main函数没有函数原型。因为不需要,main函数是每个C程序必须的入口函数。(人家有钱,跟编译器关系好,编译器对main函数很熟悉,是经常打交道的哥们,所以不需要函数原型来“介绍”main函数。)。


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

double rectangleArea(double, double);


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


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



头文件

每次看到这个术语,我都想到刚刚结婚的“我们的青春”:周杰伦 的《头文字D》。在此祝福一下杰伦幸福美满。


到目前为止,我们的程序只有一个.c文件(称之为“源文件”),比如我们之前把这个.c文件命名为main.c,当然名字是无所谓的。你叫hello.c,hehe.c都行。


一个项目多个文件

在实际编写程序的时候,你的项目一般肯定不会把代码都写在一个main.c文件中。当然,可行是可行的。但是,试想一下,如果你把所有代码都塞到这一个main.c文件中,那代码量可能10000多行,你要在里面找一个东西太难了。也正是因为这样,通常我们每一个项目都会创建多个文件。


那以上说到的项目是指什么呢?

之前用Code::Blocks这个IDE创建第一个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文件一一对应。

files.h和files.c   

editor.h和editor.c

game.h和game.c


但我们的电脑怎么知道函数原型是在.c文件之外的另一种文件里呢?


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


请做好准备,下面要有一波“密集”的知识点“来袭”。


怎么引入一个头文件呢?其实你已经知道怎么做了,之前的课程我们已经写过了。


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


#include <stdlib.h>
#include <stdio.h>
#include "game.h"

void player(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",之后我在main.c函数中就可以调用function.c中定义的函数了


你可能又要问了:“那我怎么在项目中加入新的.h和.c文件呢?”

很简单,在codeblocks里,鼠标右键点击项目列表的主菜单处,选择Add Files

或者

在菜单栏上依次单击

Fiel->New->File...


就可以选择添加文件的类型了


引入标准库

你脑海里肯定出现一个问题:如果我们用#include来引入stdio.h和stdlib.h这样的标准库的头文件,而这些文件又不是我自己写的,那么它们肯定存在于电脑里的某个地方,我们可以去找到,对吧?


是的,完全正确!


如果你使用的是IDE(集成开发环境),那么它们一般就在你的IDE的安装目录里。如果是在纯linux环境下,那就要到系统文件夹里去找,这里不讨论了,以后开了Linux课会讲到,感兴趣的读者可以去网上搜索。


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

E:\Program Files\CodeBlocks\MinGW\include


技术分享


一般来说,都在一个叫做“include”的文件夹里。在里面,你会找到很多文件,都是.h文件,也就是C语言系统定义的标准头文件,也就是系统库的头文件(对windows,mac,linux都是通用的,C语言本来就是可移植的嘛)。在这众多的头文件当中,你可以找到我们的老朋友:stdio.h和stdlib.h。


你可以双击打开这些文件或者选择你喜欢的文本编辑器来打开,不过也许你会吓一跳,因为这些文件里的内容很多,而且好些是我们还没学到的用法,比如除了#include以外的其他的预处理指令。

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


你要问了:“ok,现在我已经知道标准库的头文件在哪里了,那与之对应的标准库的源文件(.c文件)在哪里呢?”


不好意思,你见不到它们啦。因为.c文件已经被事先编译好,转换成计算机能理解的二进制码了。

“伊人已去,年华不复,吾将何去何从?”


既然见不到原先的它们了,至少让我见一下“美图秀秀”之后的它们吧…

可以啊,你在一个叫lib的文件夹下面就可以找到,在我的windows下的路经为:

E:\Program Files\CodeBlocks\MinGW\lib


技术分享


被编译成二进制码的.c文件,有了一个新的后缀名:.a(在codeblocks的情况,因为编译器是mingw)或者.lib(在visual c++的情况,因为编译器是Visual),当然以后我们还会学到在linux下还有.so这个后缀名,等。暂时不深究。


这些被编译之后的文件被叫做库文件或library文件(library,英语:库),不要试着去阅读这些文件的内容,完全不是人看得懂的乱码…


学到这里可能有点晕,不过继续看下去就会渐渐明朗起来,下面的章节会有示意图帮助理解。


小结一下:

在我们的.c源文件中,我们可以用#include这个预处理指令来引入标准库的.h头文件或自己定义的头文件,这样我们就能使用标准库所定义的printf这样的函数,这样电脑就认识了这些函数(借着.h文件中的函数原型),就可以检验你调用这些函数时有没有用对,比如函数的参数个数,返回值类型等。



分开编译

现在我们知道了一个项目是由若干文件组成的,那我们就可以来了解一下编译器的工作原理; 之前的课里面展示的编译示例图是比较简化的,下图是一幅编译原理的略微详细的图,希望大家用心理解并记住:


技术分享


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

1. 预处理器(preprocessor):顾名思义,预处理器为编译做一些预备工作,所以预处理器是在编译之前启动的。它的任务是执行特殊的指令,这些指令是通过预处理命令给出的,预处理命令以#开头,很容易辨认。


预处理指令有好多种,目前我们学过的只有#include,它使我们可以在一个文件中引入另一个文件的内容。#include这个预处理指令也是最常用的。


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

#include <stdio.h>


预处理器在执行时会把上面这句指令替换为stdio.h文件的内容。所以到了编译的时候,你的.c文件的内容会变多,包含了所有引入的头文件的内容,显得比较臃肿。


2. 编译(compilation):这是核心的步骤,以前的课我们说过,正是编译把我们人写的代码转换成计算机能理解的二进制码(0和1组成)。编译器编译一个个.c文件。对于codeblocks这样的IDE来说,就是你放在项目列表中的所有.c文件;如果你是用gcc来编译,那么你要指定编译哪几个.c文件。


编译器会把.c文件先转换成.o文件(有的编译器会生成.obj文件),.o文件一般叫做“目标文件”(o就是英语object:目标 的首字母),是临时的二进制文件,会用于之后生成最终的可执行二进制文件。


.o文件一般会在编译完成后被删除(根据你的IDE的设置)。从某种程度上来说.o文件虽然是临时中间文件,好像没什么大用,但保留着不删除也是有好处:假如项目有10个.c文件,编译后生成了10个.o文件。之后你只修改了其中的一个.c文件,如果重新编译,那么编译器不会为其他9个.c文件重新生成.o文件了,只会重新生成你更改的那个。节省资源。


3. 链接器(linker):顾名思义,链接器的作用是链接,链接什么呢?就是编译器生成的.o文件,链接器把所有.o文件链接器来,“制作成”一个大块头:最终的可执行文件(windows下是.exe文件,linux下有不少种形式)。


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


大部分的错误都会在编译阶段被显示,但也有一些是在链接的时候显示,有可能是少了.o文件之类。


上面那幅图其实还不够完整,你可能想到了:我们用.h文件引入了标准库的头文件的内容(里面主要是函数原型),函数的具体实现的代码我们还没引入呢,怎么办呢?对了,就是之前提到过的.a或.lib这样的库文件(由标准库的.c源文件编译而成)。


所以我们的链接器(linker)的活还没完呢,它还需要负责链接标准库文件,把你自己的.c文件编译生成的.o目标文件和标准库文件整合在一起,然后链接成最终的可执行文件。如下图所示:


技术分享


这下我们的示意图终于完整了

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

之后在第三部分我们会用到系统的图形库,也是在.a库文件中定义的,包含了一系列指令的定义,比如告诉电脑怎么创建一个窗口,绘制图形,等等,好戏在后头



变量和函数的作用范围

为了结束我们这一课,我们还必须来学习最后一个知识点:

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

我们将学习它们什么时候是可以被调用的。


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

当你在一个函数里定义了一个变量之后,这个变量会在函数结尾时从内存中被删除。


int multipleTwo(int number)
{
   int result = 0;  // 变量result在内存中被创建

   result = 2 * number;
   return result;
} // 函数结束,变量result从内存中被删除


在一个函数里定义的变量,只在函数运行期间存在。这意味着什么呢?意味着你不能从另一个函数中调用它。


#include <stdio.h>


int multipleTwo(int number);

int main(int argc, char *argv[])
{
   printf("15的两倍是 %d\n", multipleTwo(15));
  
   printf("15的两倍是 %d", result);  // 错误!

   return 0;
}

int multipleTwo(int number)
{
   int result = 0;

   result = 2 * number;
   return result;
}


可以看到,在main函数中,我们试着调用result这个变量,但是因为这个变量是在multipleTwo函数中定义的,在main函数中就不能调用,会出错。


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


全局变量:避免使用


能被所有文件使用的全局变量

我们可以定义能被项目的所有文件的所有函数调用的变量。我们会展示给大家怎么做,为了告知大家这方法存在,但是一般来说,要避免使用能被所有文件使用的全局变量。可能这样做一开始会让你的代码简单一些,但是不久你就会为之烦恼了。


为了创建能被所有函数调用的全局变量,我们须要在函数之外定义。通常我们把这样的变量放在程序的开头,#include预处理指令的后面。


#include <stdio.h>

int result = 0; // 定义全局变量result

void multipleTwo(int number); // 函数原型

int main(int argc, char *argv[])
{
   multipleTwo(15);  // 调用multipleTwo函数,使全局变量result的值变为原来的两倍

printf("15的两倍是 %d\n", result);  // 我们可以调用变量 result 
   return 0;
}

void multipleTwo(int number)
{
   result = 2 * number;
}


上面的程序中,我们的函数multipleTwo不再有返回值了,而是用于将result这个全局变量的值变成2倍。之后main函数可以再使用result这个变量。


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


:这种类型的变量是很不推荐使用的,因为不安全。一般用函数里的return语句来返回一个变量的值。


只能在一个文件里被访问的全局变量

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

怎么做呢?

只需要在变量前面加上 static 这个关键字。如下所示:

static int result = 0;


static在英语里是“静止的,不变的”之意。


函数的static(静态)变量

注意:如果你在声明一个函数内部的变量时,在前面加上static这个关键字,它的涵义和加在上面我们演示的全局变量之前是不同的。函数内部的变量如果加了static,那么在函数结束后,这个变量也不会销毁,它的值会保持。下一次我们再调用这个函数时,此变量会延用上一次的值。

例如:


int multipleTwo(int number)
{
   static int result = 0;   // 静态变量result在函数第一次被调用时创建


   result = 2 * number;
   return result;
} // 变量result在函数结束时不会被销毁


这到底意味着什么呢?

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


有点晕是吗?不要紧。来看一个小程序,以便加深理解:


#include <stdio.h>


int increment();

int main(int argc, char *argv[])
{
   printf("%d\n", increment());
   printf("%d\n", increment());
   printf("%d\n", increment());
   printf("%d\n", increment());

   return 0;
}

int increment()
{
   static int number = 0;
   
   number++;
   return number;
}


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


之后,当我们第二次调用increment函数时,变量number的声明语句(static int number = 0;)会被跳过不执行(因为变量number还在内存里呢。你想一个皇帝还没驾崩,太子怎么能继位呢)。我们继续使用上一次创建的number变量,这时候变量的值沿用第一次increment函数调用结束后的值:1,再对它做++操作(自加1),number的值就变为2了。依此类推,第三次调用increment函数后number的值为3,第四次number的值为4


所以运行程序,输出如下:

1

2

3

4


一个文件中的局部函数(本地函数或静态函数)

我们用函数的作用域来结束我们关于变量和函数的作用域的学习。


正常来说,当你在一个.c源文件中创建了一个函数,那它就是全局的,可以被项目中所有其他.c文件调用。

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

聪明如你肯定想到了:对了,就是使用static关键字,与变量类似。把它放在函数前面。如下:


static int multipleTwo(int number)
{
   // 指令
}


现在,你的函数就只能被同一个文件中的其他函数调用了,项目中的其他文件中的函数就只可远观而不可亵玩焉…


总结一下变量的所有可能的作用范围:

  1. 在函数体内定义的变量,如果前面没加static关键字,则是局部变量,在函数结束时被删除,只能在本函数内被使用

  2. 在函数体内定义,但是前面加了static关键字,则为静态变量,在函数结束时不被删除,其值也会保留。

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

  4. 函数外面定义的变量,如果前面加了static关键字,那就只能被本文件的所有函数调用,而不能被项目其他的文件的函数调用。


同样地,总结一下函数的所有可能的作用范围:

  1. 一个函数在默认情况下是可以被项目的所有文件的函数调用的。

  2. 如果我们想要一个函数只能被本文件的函数所调用,只需要在函数前加上static关键字。




总结

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

  2. .h文件的内容被一个叫做预处理器(preprocessor)的程序引入到.c文件的开头。

  3. .c文件被一个叫做编译器(compiler)的程序转换成.o的二进制目标文件(o是英语object的首字母,表示“对象、目标”)。

  4. .o文件又被一个叫做链接器(linker)的程序连接成一个最终的.exe可执行文件(exe是英语executable的前三个字母,表示“可执行的”。在windows操作系统里可执行程序的扩展名是.exe,在linux系统里,可执行程序有不少扩展名(.elf,等),也可以没有扩展名)

  5. 变量和函数都有“有效范围”,某些时候是访问不到的。



第二部分第二课预告:


今天的课就到这里,一起加油咯。

下一次我们学习第二部分第二课,咳咳,我必须正襟危坐,假装严肃地来宣布一下:

来认识一下C语言的精华和王牌:指针 吧!




技术分享


程序员联盟 微信公众号*您若觉得本文不错,点击画面右上角《···》按钮“分享到朋友圈或“发送给朋友

*新朋友请关注「程序员联盟」微信搜公众号  ProgrammerLeague

小编微信号frogoscar

小编QQ号:  379641629

小编邮箱:    enmingx@gmail.com

微信和邮箱最常用

技术分享


程序员联盟”公众号专为程序员,App设计师,各位喜爱编程和热爱分享的小伙伴们推送各样编程相关知识,优秀软件推荐,业界动态等。搜索 ProgrammerLeague 加关注~


持续关注 程序员联盟 微信公众号,更多有趣,有料,有亮点的内容等着你哦!

本文出自 “Oscar” 博客,请务必保留此出处http://4526621.blog.51cto.com/4516621/1610376

【C语言探索之旅】 第二部分第一课:模块化编程