C/C++文件

C/C++程序文件包括 .h .c .hpp .cpp,其中源文件(.c .cpp)是基本的编译单元,头文件(.h .hpp)不会被编译器编译。

C/C++项目构建(build)过程,分为以下几个步骤 预处理 → 编译 → 链接。

预编译

预编译的过程可以理解为编译器(实际上是预处理器,这里统称为编译器就可以了)在正式编译之前处理C/C++文件中的预处理命令,即#开头的代码。

常用的几个预处理命令如下:

#include ......

#ifdef ...... #else......#endif

#define ......

#pragma ......

举个例子,下面是个很简单的类定义:

MyClass.h

MyClass.cpp

预编译完成后的样子:

可以看到编译器把.h文件替换到了.cpp文件中的#include 位置上,把DEFAULT_VALUE定义的值也替换到了相应的位置。

编译

预编译之后,编译器会编译每个源文件(.c .cpp),如果编译成功,会生成对应的目标文件,Linux为.o文件,Windows平台下为.obj文件。

以Linux平台为例,上面的MyClass.cpp编译完成后会生成MyClass.o文件

使用objdump可以看到目标文件MyClass.o的内容

编译器会把MyClass::Fun()的名字改成_ZN7MyClass3FunEv,这个过程叫Mangle,由于C++支持重载,覆盖等特性,所以编译器必须把函数用一个唯一的标识表示。这个字符串就是编译器生成的唯一标识。

这里还要单独说一下头文件,头文件的既然不是编译单元,那么它的作用是什么?

头文件就是负责”声明“,编译器在编译MyClass.cpp的时候,对于MyClass这个类以及Fun()这个成员函数,编译器必须找到它的声明,这个函数才能被正确编译。

如果有其他cpp需要使用MyClass这个类的时候,也需要它的的声明。例如

main.cpp

加上#include "MyClass.h" 编译器在编译main.cpp的时候才知道怎么编译MyClass这个类。MyClass.h里声明是不会真正被编译到main.o中,.h文件中的内容在目标文件中只是以列表的形式存在,这个表在后面链接时会用到。

当然,头文件不仅可以用来声明,还可以定义(定义全局变量,全局函数等),在头文件中的定义要小心,可能会引起链接错误。

链接

链接就是将一堆目标文件加静态库文件装配成可执行文件的过程。(或者是装配成静态/动态库的过程)

上面两个cpp分别被编译成了MyClass.o, 和main.o,我们要生成可执行程序的话,就必须经过链接的过程,把两个目标文件合成一个可执行文件。

main.o中,main函数会构造MyClass, 并且调用Fun()函数,那么main就根据MyClass.h生成的表,找到MyClass.o中的函数,这个就是链接器要做的工作。

常见错误

构建c/c++工程的时候,最常见的就是两种错误:

-- 编译错误,在编译过程中产生的错误,通常是语法错误,没有声明,重复声明导致编译目标文件错误

其中没有声明通常是由于没有#include相应的头文件,或者头文件缺少相应的声明。

而重复声明通常是#include了相同的头文件,比如 B.h 和 C.h 都包含 A.h,然后 main.h 包含了 B.h 和 C.h,这就导致A.h 在main中被包含了两次。

解决这个问题的方法是可以在所有.h文件的第一行加上

#pragmaonce

或者,使用#ifndef ... #define ... #endif 语句块

-- 链接错误,常见的错误也是两种,没有定义和重复定义,和上面的没有声明,重复声明类似。(这里定义指的就是函数实现)

先讨论没有定义(undefined reference to xxx)

通常是因为函数有声明,而且被使用了,但是没有被定义。比如上面MyClass.cpp中,如果Fun()没有被实现的话,MyClass.cpp和main.cpp编译时都不会报错,但是链接时会报告找不到Fun()。

当然,如果Fun()没被main.cpp调用的话,即使不实现它,整个构建过程也不会出错,因为链接器根本不会去找这个函数的定义。

然后是重复定义(multiple definition)

指的一份相同的定义在两个目标文件中都存在,链接的时候链接器不知道时用哪个了。这种问题通常由于全局函数,和全局变量定义在了头文件中。导致多个目标文件包含相同的全局函数和全局变量的定义。

解决方法就是在头文件中声明,定义放到cpp文件中,或者为定义加上const 或 static这样的修饰符,链接时会对这些带有const和修饰符的变量特殊处理的。

const只适用于定义常量变量,static定义的是静态全局变量,只在当前cpp有效,所以链接它也不会被别的目标文件链接,就不会有重复定义的问题了。

总之在头文件中定义变量和函数要特别主意,可能会导致链接错误。

当然也不是所有定义都不能放到头文件中,比如刚才说的const常量,static全局变量就是例外,还有内联函数,可以定义在.h文件中,因为内联函数会被拷贝到每个目标文件中,也不会参与链接的过程。

还有模板类必须放在头文件中定义,这个下面会讨论这个。

关于模板,静态成员变量

模板类模板函数必须声明和定义在头文件中,原因是什么,举个例子,假设MyClass是模板类

MyClass.h

MyClass.cpp

main.cpp

编译的时候没有问题,但是链接时会报错,main.cpp找不到MyClass::Fun(),如下图

MyClass虽然定义了Fun函数,但是MyClass.o中存在MyClass::Fun(),而根据MyClass.h文件,main.o中需要找到MyClass::Fun()的定义

结果链接器哪都找不到,只好报错了。(实际上通过objdump查看MyClass.o,编译器都没有生成MyClass::Fun(),因为编译器认为这个函数没人使用,就直接优化掉了)

如果非得在cpp中定义模板类的成员函数呢,有一种方法就是要显示的在cpp文件中声明,比如

MyClass.cpp

加上下面这行就不会有问题了,但是缺点就是开发MyClass的程序员无从知道其他类是怎么使用这个模板的,不可能把所有可能的模板参数全都一一的列在这里。

所以模板类的定义还是要写在.h文件中,

那么如果main.cpp使用到了MyClass, 另外一个cpp也使用到了MyClass,会不会产生重复代码导致重复定义呢,不会,编译器会处理好模板类的。

下面是静态成员变量,为什么静态成员变量的定义要放在cpp里,(模板类的静态成员变量除外)

静态成员变量和静态全局成员变量不同。

静态成员变量的作用域可以是整个工程,而静态全局变量的作用域只是当前的cpp。所以静态成员变量定义在.h中就会发生重定义错误。

想要在程序员生涯内有更高的成就的话,C/C++就是一个既可以强化思维能力,又可以打好编程基础的编程语言,你想要做软件开发,成为核心程序员的话,学习C/C++的话笔者有一个C/C++的编程俩千人羣(Q艘索:C/C++编程学习13群)你如果感觉自学C/C++语言有困难的话,有兴趣学习或者了解一下C/C++编程的小伙伴就可以进来交流。

c 包含其他文件_C/C++编程笔记:C/C++的编译和链接,计算机专业大学生必备知识...相关推荐

  1. status c语言_C/C++编程笔记:C语言编程风格个人总结,初学小白可借鉴

    总结一下我个人的编程风格及这样做的原因吧,其实是为了给实验室写一个统一的C语言编程规范才写的.首先声明,我下面提到的编程规范,是自己给自己定的,不是c语言里面规定的. 一件事情,做成和做好中间可能隔了 ...

  2. c malloc 头文件_c++个人学习笔记——1.头文件声明

    简单介绍了C++头文件声明与C语言的差异,并对常见的部分头文件作了介绍. //C++中常用写法 最简单的C++程序往往是上面这样声明头文件. #include为C/C++中包含头文件命令,用于将指定头 ...

  3. c语言if多条件并列_C/C++编程笔记:C语言预编译指令—条件编译,零基础推荐收藏

    一. 内容概述 本文主要介绍c语言中条件编译相关的预编译指令,包括#define.#undef.#ifdef.#ifndef.#if.#elif.#else.#endif.defined. 二.条件编 ...

  4. 学了四年编程,感觉啥也不会!计算机专业到底教会了我们什么?

    现在计算机专业的大学课程和毕业之后工作中用到的知识有点脱节,我们在大学里学的知识跟不上企业的需求,那么作为计算机专业的学生该怎么做呢? 现在计算机专业的毕业生很多, 属于竞争最激烈的专业之一,同时企业 ...

  5. c语言遍历文件内容_C/C++编程笔记:C语言开发电脑益智游戏【扫雷】(源代码分享)...

    咱们先和大家说好,本次C语言开发的扫雷游戏是通过Easy X实现的,但是很多和我一样的新手,一开始不知道Easy X是什么,到时源码拿过去写之后,运行报错.Easy X是很多和我一样的新手在学习的时候 ...

  6. c++ ifstream 文件不结束_C/C++编程笔记:你不知道的windows保存文件的坑

    序 对于c/c++来说,写文件是必不可少的事情.但是如果大家不仔细研究,真的会掉进某些坑里. 在某些紧急情况下,可能要保证数据安全且及时的写到磁盘上,否则就会有丢失的风险. 小文件可能还不那么明显,对 ...

  7. visual c++ 6.0原版_C/C++编程笔记:C语言函数指针的理解与使用,就是这么简单明了!...

    1.函数指针的定义 顾名思义,函数指针就是函数的指针.它是一个指针,指向一个函数.看例子: 看看上面三个表达式分别是什么意思? C)这很容易,fun3是函数名,p1,p2是参数,其类型为char *型 ...

  8. c 获取char*的长度_C/C++编程笔记:C语言字符串比较函数,超详细,值得收藏!...

    前方干货准备!!! void *memset(void *dest, int c, size_t count); 将dest前面count个字符置为字符c. 返回dest的值. void *memmo ...

  9. c++ 小游戏_C/C++编程笔记:C语言写推箱子小游戏,大一学习C语言练手项目

    C语言,作为大多数人的第一门编程语言,重要性不言而喻,很多编程习惯,逻辑方式在此时就已经形成了.这个是我在大一学习 C语言 后写的推箱子小游戏,自己的逻辑能力得到了提升,在这里同大家分享这个推箱子小游 ...

最新文章

  1. 异形隔离java剧情_异形隔离攻略 系统上手教程 全剧情流程图文攻略(41)
  2. rabittmq java spring_消息队列 RabbitMQ 与 Spring 整合使用的实例代码
  3. getRealPath(““)与getRealPath(“/“)区别及用法——计算机网络相关学习笔记
  4. C# Job System
  5. linux mysql 系统时间函数吗_Linux 宝库 - Mysql日期和时间函数不求人
  6. [你必须知道的.NET] 第七回:品味类型---从通用类型系统开始
  7. 迈向万亿市场的直播电商
  8. C# 或Asp.Net 将excel表格导入数据库
  9. 乐鑫科技线上笔试什么内容_2020广东省公务员考试笔试考什么内容?笔试如何去备考?...
  10. [转载] python中日期和时间格式化输出的方法
  11. 全网首发:LINUX编译JNA:编译libffi
  12. 声纹识别demo_科学网—声纹识别、说话人识别软件,SPEAKER v0.1 - 石自强的博文...
  13. DW2019HTML中没有文本对象,Dreamweaver CC2019文字或图片添加空连接方法
  14. PHP 网页支付支付宝支付接口对接
  15. Cuba 设置debug模式
  16. python dxf_使用Python读取AutoCAD DXF文档
  17. 预言机(Oracle)
  18. java坐标排序_几种实现经纬度查询排序
  19. (转)802.1Q标准中TAG字段简单说明
  20. linux打开xml文件,xml文件扩展名,xml文件怎么打开?

热门文章

  1. BIO,NIO,AIO
  2. CNN卷积神经网络(吴恩达《卷积神经网络》笔记一)
  3. 唐刘之辩:行业知识图谱的schema构建的难点、重点与困惑
  4. 不能头脑一热,就布局颠覆性技术、上马未来产业
  5. spoj839 Optimal Marks(最小割,dinic)
  6. 2017-9-26 NOIP模拟赛
  7. 在MyEclipse中更换或修改svn的用户名和密码
  8. JAVA并发编程3_线程同步之synchronized关键字
  9. 4.3-沛县欢乐多(DHCP,ARP,ICMP协议)
  10. 【计算机网络复习 数据链路层】3.1 数据链路层功能概述