(学c语言首先需要什么)(学c语言需要哪些基础)

作为退休C程序员,昨天在头条看到一篇类似文章,洋洋洒洒几千字,完全不得要领,弄得一堆新手C程序员在评论中疑惑,在下忍不住手痒,放下钓鱼竿来写这篇文章。C语言新手仔细阅读后,会有一定的领悟和提升。

首先开宗明义:头文件的作用只是声明变量和函数,C文件才是做定义的地方。

我们一步步来解释, 为了节省篇幅,我们尽量简化程序逻辑,不做错误判断。

新手C程序员李雷上了两节课以后,他会写的都是把整个程序都写在一个C文件里面:

main.c:

(学c语言首先需要什么)(学c语言需要哪些基础)

这个程序的作用很简单,就是不停地从终端输入数字,然后累加,打印出结果。

教授很满意,给了李雷一个赞。

然后教授开始引导李雷学习C语言工程的概念,也就是说将来的程序越来越大,你横不能把所有的东西都写在 main.c 里面吧,聪明的李雷想了想,非常有道理,于是教授告诉他,要把一些可以复用的代码从 main.c 里面分离出来,这样既不至于让编译器对着一个超大c文件发疯,也方便程序员维护。

聪明的李雷立刻领会了教授的意思,

他把 adjust_money() 函数分离出来了,于是写下了三个文件:

func.h

(学c语言首先需要什么)(学c语言需要哪些基础)

func.c

(学c语言首先需要什么)(学c语言需要哪些基础)

main.c

(学c语言首先需要什么)(学c语言需要哪些基础)

教授看了看,疑惑地问李雷: 你为啥要把 int money 放在头文件里面呢?

李雷说:“我考虑到万一需要直接访问这个变量,所以把它定义为全局变量了。”

教授点点头,说:“这个考虑是没问题的,但你想过没有,你在 func.h 里面定义了这个 money , 会导致你的程序无法连接。”

李雷摇头说:“没问题啊,不信你看。”

(学c语言首先需要什么)(学c语言需要哪些基础)

李雷得意地看着教授说:“你看,编译没问题啊。”

教授摇头说:“我说的是你没办法连接,不是说你没办法编译。不信你试试看把它连接成一个可执行文件。”

李雷将信将疑地试着连接:

(学c语言首先需要什么)(学c语言需要哪些基础)

果然,编译器报错了, money 这个变量被重复定义了, 李雷没办法按预期的获得可执行文件。

聪明的李雷这下有点傻了。

教授微笑着说:“看来你对头文件还没有本质的认识。其实你记住一个原则就好了:你每编译一个c文件,其本质就是,编译器将这个c文件所包含的头文件内容按顺序原样添加到这个c文件里面的。”

李雷还是有点懵。

教授说:“现在让我们来看看你编译 main.c 的时候实际上发生了什么。”

我们再来看看 main.c 的内容:

(学c语言首先需要什么)(学c语言需要哪些基础)

编译器读到第一行, #include "func.h"

编译器就在可搜索路径中找到这个 func.h, 然后读取 func.h , 将 func.h 的内容添加进来。

这时在编译器眼中, main.c 其实变成了:

(学c语言首先需要什么)(学c语言需要哪些基础)

聪明的李雷恍然大悟,说:“我懂了!所以当编译器读到 #include <stdio.h> 的时候,它又继续把 stdio.h 的内容插入到这里来。”

教授点头说:“本质就是这样。那么现在你看, 编译器编译 main.c 的时候,是不是等同于 main.c 里面定义了一个 int money ? ”

李雷说:“是的。”

教授说:“那么你在编译 func.c 的时候,会发生什么呢?”

聪明的李雷这下完全明白了,编译器编译 func.c 的时候,实际上是在编译如下内容:

(学c语言首先需要什么)(学c语言需要哪些基础)

所以 func.c 里面其实又定义了一次 int money。

所以 main.o func.o 两个obj文件里面都各自有了一次 int money 的定义。

所以当你试图把 main.o func.o 连接成可执行文件的时候, 连接器就会发现有两个 money 变量的定义冲突。

所以正确的做法是什么呢?

教授告诉李雷:“我再说一遍,头文件只做声明,C文件才做定义。所以你的 func.c func.h 分别应该是这样的。”

func.h

(学c语言首先需要什么)(学c语言需要哪些基础)

func.c

(学c语言首先需要什么)(学c语言需要哪些基础)

按这样的正确做法,你编译出来的 func.o 里面有 int money 变量的唯一定义, 而 main.o 里面就不会有,这样连接器在连接obj文件的时候就不会出错了。

李雷恍然大悟,但是他又问道:“那么 func.h 里面那句 extern int money 到底起什么作用呢?”

教授给了李雷一巴掌,说:“当然是让编译器在编译其他依赖C文件,比如 main.c 的时候, 知道有 int money 这么一个外部变量可以用,但是具体这个 int money 的定义在哪里,是连接器在连接阶段负责去定位的(在这里就是 func.o 里面),编译器并不负责定位。”

教授又说:“我看你还是没搞懂 编译时期 和 连接时期 的区别。你再去看看资料,把这个问题搞清楚,那么今天的课题你就理解更深刻了。”

杠精出身的李雷又想到一个问题,说:

gang.h

(学c语言首先需要什么)(学c语言需要哪些基础)

我在这个头文件里面定义了一个 杠精 的结构,为什么就可以呢?

教授大怒,骂道:“我们说的是变量和函数,你狗日的跟我扯类型。类型和宏都是在编译器的生存期的,跟连接期没关系,当然没这个问题。那一堆 #ifndef 不就是为了避免这种结构在编译期的重复定义的吗?”

李雷这下被骂得有点懵,教授叹了口气说:“下节课再跟你说这个问题,老子累了要去钓鱼了。”



声明:我要去上班所有作品(图文、音视频)均由用户自行上传分享,仅供网友学习交流,版权归原作者黑哥在澳洲所有,原文出处。若您的权利被侵害,请联系删除。

本文标题:(学c语言首先需要什么)(学c语言需要哪些基础)
本文链接:https://www.51qsb.cn/article/m78cf.html

(0)
打赏微信扫一扫微信扫一扫QQ扫一扫QQ扫一扫
上一篇2022-12-20
下一篇2022-12-20

你可能还想知道

发表回复

登录后才能评论