c ++多文件单片机编程时头文件用一个头文件作为各文件接口的程序编写实例

为什么应该用模块取代C/C++中的头文件?
发表于 11:32|
摘要:本文整理自Apple C++工程师Doug Gregor的演讲Slide,他表示希望使用模块(Module)这一概念替代C/C++中的头文件,现已被C++标准化委员会任命为Module研究组的主席,研究该提议的可能性。考虑到Apple的开源项目LLVM在编辑器领域中的地位,这一提议非常值得重视。
为什么应该使用模块(Module)替代头文件(Header)?
头文件糟透了!
众所周知,C程序在编译时一般会预处理头文件:
常规解决办法如下:
LLVM_WHY_PREFIX_UPPER_MACROS&&&&&LLVM_CLANG_INCLUDE_GUARD_H&&&&&template&class&_Tp&&&const&_Tp&&min(const&_Tp&&__a,&&&&&&&&&&&&&&&&const&_Tp&&__b);&&#include&&windows.h&&#undef&min&//&because&#define&NOMINMAX&#undef&max&//&doesn&t&
但结果依然不够理想,比较一下代码与程序大小你会发现:
另外,头文件形式的可扩展性天生不足。假设有n个源文件,每个源文件引用了m个头文件,那么构建过程的开销会是m&n。这在C++中表现得尤为糟糕。所以预说处理头文件是一个非常糟糕的解决方案。
C家族的模块系统
模块是什么?
库的接口(API)
使用&import&导入已命名的模块:
import会在源文件中忽略预处理状态,并且选择性导入,所以弹性(resilience)非常好。
使用&import&会导入什么?
函数、变量、类型、模板、宏,等等;
公开API&&其它的都隐藏;
没有特别的命名空间机制。
C/C++引入模块会怎么样?
引入模块的目标在于:
在源文件中指定模块名称;
没有头文件!
要编写一个模块非常简单,只需要使用export:
但是这么做会遇到很多遗留问题:
需要迁移现在基于头文件的类库;
与不支持模块的编译器的互操作性;
工具需要理解模块;
所以应该使用引入模块的过渡方案&&直接从头文件中构建模块。这么做有以下好处:
头文件有利于互操作;
程序员不需要完全改变自己习惯的开发模式;
模块地图(Module Map)
模块地图是模块的关键,用来定位模块相关(子)模块,包含以下功能:
模块定义命名(子)模块
头文件在(子)模块中包含命名头文件的内容
保护伞头文件(Umbrella Header)
保护伞头文件会在其目录下包含所有头文件信息
使用通配符submodules (module *) 可以为每一个包含的头文件创建一个子模块:
AST/Decl.h&-&&ClangAST.Decl&AST/Expr.h&-&&ClangAST.Expr&
模块编译过程:
找到命名模块的module map;
产生一个独立编译器实例;
在module map中解析头文件。
编辑模块文件过程:
在&import&声明处导入模块文件;
把模块文件保存在缓存中待重用。
从头文件到模块化
从头文件编程转换到使用模块非常简单:
库方面:合并复合定义的结构、函数、宏,并且为头文件导入依赖,最后编写好模块地图;
开发者方面只需要从&#include&过渡到&import&:
把&#inlude&都换成&import&;
使用module maps确定(子)模块(类似头文件里的&#include&);
当然,你也可以使用工具来自动化重写代码,非常简单。
使用模块能够提升语法解析性能:
模块化的头文件只需要解析一次,之后放在缓存中,于是m&n --& m+n
所有基于源(source-based)工具都能带来好处
自动链接大大简化了库的使用
自动导入可以阻止&#include&带来的可怕的调试结果
通过DWARF的双程调试有损耗:
只能获得&用过&类型和函数
丢失了行内函数、模板
另外调试过程还会出现信息冗余
那使用模块的调试又会怎样?
1.提高了构建性能
编译器发出的DWARF更少
链接器清除重复的DWARF也更少
2.提高了调试体验
调试器的ASF精度非常完美
调试器不需要寻找DWARF
总而言之,C/C++使用模块化非常有潜力:
编译/构建时间的缩短
修复各种预处理问题
更好的工具体验
通过设计,能够平稳地过渡
Clang实现已经在进行了
这个在Hacker News上引起了激烈讨论,大部分网友还是赞成模块化的方式:
baberman:对我来说没什么不便,而且还给出了过渡方案,可能会很适合某些C/C++项目。我们应该对任何提升C/C++性能的想法持开放态度。
greggman:预处理有什么不好吗?如果我不想用预处理,我完全可以使用Objective-C等。现在的机器性能已经够强大了,import在编译上的性能优势对我来说没有任何吸引力,我更喜欢C/C++的传统方式。
msbarnett反驳greggman:我认为这正是这份提议的精髓所在,你既可以保留使用#include,也可以从现在开始就转向import模块的方式。
_djo_:这个想法会非常有前途!要说缺点的话就是来得太迟了!
nkurz:我不同意&m个头文件+n个源文件 --& m&n倍编译性能&。只要使用&#ifndef _HEADER_H&就不会出现这个问题,或者,为什么不使用#include_once &header.h&来解决呢?预处理很可怕吗?预处理确实有一些问题,但是却是可以克服的。
SeoxyS:我不关心性能,现在性能已经不是问题了,我更关心怎样给开发者更少的负担。
各位CSDN网友是怎样看的呢?欢迎一起讨论。
相关链接:、
推荐阅读相关主题:
CSDN官方微信
扫描二维码,向CSDN吐槽
微信号:CSDNnews
相关热门文章& & & & 对于小型代码而言,可以将所有的文件写到一个源文件中。但是对于一个大的项目工程,代码量极大,如果把所有的源码都放在一个源文件,就会显得代码杂乱,不利于阅读,更不便于代码的修改和维护。这时候我们需要合理的组织代码,可以使用多个文件,把源码归类放到不同的文件中。
程序多文件一般分为两类:
1、头文件(.h):像我们常用的stdio.h,stdlib.h....这些都是C为我们提供的
2、源文件(.c):一般一个头文件,对应一个.c文件。
先说下自定义的头文件:
头文件中一般包括
(1)头文件区:包含源码所需的头文件,例如stdio.h,stdlib.h等。
(2)全局宏区:定义模块公用的宏(#define),例如缓冲区的大小。
(3)全局变量区:所有模块共用的变量(非static)。
(4)函数接口区:包含所有相关模块的函数接口。
源文件就是头文件对应的那个.c文件,它一般包括函数的具体实现。
下面以一个操作链表的程序为例,具体说明多文件的用法及编译方法
首先定义一个头文件link.h
//start link.h
#include &stdio.h&
#include &stdlib.h&//所有模块所需的头文件
struct node
//节点结构体
struct node *
typedef struct node * N //为了简化代码,我们将节点指针变量用Node代替
extern N //链表头,extern的作用是引用外部文件中的Node head
//链表的操作函数(接口)
extern int insert(int d);
extern int del(int d,Node *res);
extern void print();
extern void destory();
//end link.hlink.c 文件定义所有的操作链表的函数
//start link.c
#include &link.h& //包含自定义的link.h
N //链表的头结点
int insert(int d)
Node p=NULL,q=NULL;
if(p!=NULL)
while(p-&next!=NULL)
}//查找尾结点
q = (Node)malloc(sizeof(struct node));
if(q==NULL)
return -1;
q-&next=NULL;
if(p==NULL) //如果是空链表
return 1; //插入成功返回1
int del(int d,Node *res)
Node p=NULL,q=NULL;
if(p==NULL) //如果是空表返回-1
return -1;
if(p-&data==d)
head = p-&
} else if(p-&next==NULL)
return -1;
while(p!=NULL)
if(p-&data!=d)
q-&next = p-&
p-&next = NULL;
return -1;
void print()
while(p!=NULL)
printf(&%d\n&,p-&data);
void destory()
Node p=NULL;
while(head!=NULL)
p = head-&
free(head);
head = NULL;
//end link.cmain.c 里面包含了整个程序的main函数,程序执行的入口
//start main.c
#include &link.h&
int main(void)
for(i=1;i&8;i++)
insert(i);
printf(&destory......\n&);
destory();
}以上就是全部文件
多文件的编译方法:
gcc -o 生成的可执行文件 源文件1 源文件2...
如果要编译的文件较多可以使用通配符*
gcc -o 生成的可执行文件 *.c
给大家推荐一个Windows版的gcc,TDM-gcc,它的使用和linux系统上的gcc完全一样,使用的命令行模式
,非常好用,下载地址:
http://tdm-gcc.tdragon.net/download
对于本例,编译命令为:
gcc link.c main.c -o main
输出结果为:
destory......
需要注意的是C++与C语言的多文件并不相同,c++头文件中一般放的是类的定义,格式如下
#ifndef _***_H_
#define _***_H_
//类的定义
#endifC++ .cpp 文件中一般放的是类的实现
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:497次
排名:千里之外21ic官方微信
后使用快捷导航没有帐号?
查看: 4466|回复: 14
51单片机多文件C程序问题
&&已结帖(5)
主题帖子积分
初级技术员, 积分 117, 距离下一级还需 -17 积分
初级技术员, 积分 117, 距离下一级还需 -17 积分
主题帖子积分
专家等级:结帖率:100%
主题帖子积分
初级技术员, 积分 117, 距离下一级还需 -17 积分
初级技术员, 积分 117, 距离下一级还需 -17 积分
我在学51单片机,用c语言编程。我建立的工程由多个文件构成。有一些常用器件的驱动程序(18B20,ds1302等)都是反复使用的,这些驱动程序由多个函数构成,那么,把这些函数体和函数声明都放在一个.h的头文件中(例如18B20drive.h)好,还是把函数声明放在.h头文件中( 例如18B20drive.h
),把函数体放在.c的文件中(例如18B20drive.c)好?这两种方式有何区别,哪种更好、更规范?请高手指教。
满意回复+2
函数体肯定放在.c文件里,外部函数和全局变量才需要在.h里声明,内部函数及局部变量都在.c里
要不多个文件包含这个头文件就会重复编译这些函数体 ...
一个C对应一个H
函数声明放在.h头文件中,函数体放在.c的文件中
一个C对用一个H文件,函数和全局变量放在C文件里,包含头文件、函数声明、变量声明、宏定义放在H文件里,这个C文件只包含对应H文件即可, 其他文件想用C文件里的 ...
主题帖子积分
技术总监, 积分 39593, 距离下一级还需 10407 积分
技术总监, 积分 39593, 距离下一级还需 10407 积分
主题帖子积分
专家等级:结帖率:3%
主题帖子积分
技术总监, 积分 39593, 距离下一级还需 10407 积分
技术总监, 积分 39593, 距离下一级还需 10407 积分
一个C对应一个H
主题帖子积分
中级工程师, 积分 4557, 距离下一级还需 443 积分
中级工程师, 积分 4557, 距离下一级还需 443 积分
主题帖子积分
专家等级:结帖率:100%
主题帖子积分
中级工程师, 积分 4557, 距离下一级还需 443 积分
中级工程师, 积分 4557, 距离下一级还需 443 积分
函数体肯定放在.c文件里,外部函数和全局变量才需要在.h里声明,内部函数及局部变量都在.c里
要不多个文件包含这个头文件就会重复编译这些函数体
主题帖子积分
中级技术员, 积分 256, 距离下一级还需 44 积分
中级技术员, 积分 256, 距离下一级还需 44 积分
主题帖子积分
专家等级:结帖率:50%
主题帖子积分
中级技术员, 积分 256, 距离下一级还需 44 积分
中级技术员, 积分 256, 距离下一级还需 44 积分
函数声明放在.h头文件中,函数体放在.c的文件中
主题帖子积分
资深技术员, 积分 408, 距离下一级还需 92 积分
资深技术员, 积分 408, 距离下一级还需 92 积分
主题帖子积分
专家等级:结帖率:100%
主题帖子积分
资深技术员, 积分 408, 距离下一级还需 92 积分
资深技术员, 积分 408, 距离下一级还需 92 积分
那使用时是不是只调用.h就可以使用.c里的函数了?
广交单片机编程的朋友,QQ:
主题帖子积分
高级技术员, 积分 699, 距离下一级还需 301 积分
高级技术员, 积分 699, 距离下一级还需 301 积分
主题帖子积分
专家等级:结帖率:0%
主题帖子积分
高级技术员, 积分 699, 距离下一级还需 301 积分
高级技术员, 积分 699, 距离下一级还需 301 积分
如果&&你搞得东西的某些是非常固定的 那么&&那些固定的东西 放到一个.h里面 也是不错的。比如&&你看看那个UCOS 就是这样弄得。
当然&&如果 不是很固定 那么 就 一个.c& &对应一个.h
比如 那些&&ARM芯片的官方库&&都是这样弄得。
有些东西要搞得 死板&&有些要活用& &。关键是要自己好好思考。
否则 别人说好&&自己却不知道好在哪里&&也是个麻烦事
循序渐进,道自然而至...........
主题帖子积分
高级技术员, 积分 699, 距离下一级还需 301 积分
高级技术员, 积分 699, 距离下一级还需 301 积分
主题帖子积分
专家等级:结帖率:0%
主题帖子积分
高级技术员, 积分 699, 距离下一级还需 301 积分
高级技术员, 积分 699, 距离下一级还需 301 积分
晕&&.h里面当然不能放函数了& &甚至 不要定义变量
为什么呢?
循序渐进,道自然而至...........
主题帖子积分
初级工程师, 积分 2565, 距离下一级还需 435 积分
初级工程师, 积分 2565, 距离下一级还需 435 积分
主题帖子积分
专家等级:结帖率:94%
主题帖子积分
初级工程师, 积分 2565, 距离下一级还需 435 积分
初级工程师, 积分 2565, 距离下一级还需 435 积分
赞成2L的说法
把一切平凡的事做好即不平凡。把一切简单的事做正确即不简单!
主题帖子积分
初级技术员, 积分 117, 距离下一级还需 -17 积分
初级技术员, 积分 117, 距离下一级还需 -17 积分
主题帖子积分
专家等级:结帖率:100%
主题帖子积分
初级技术员, 积分 117, 距离下一级还需 -17 积分
初级技术员, 积分 117, 距离下一级还需 -17 积分
那么,请问多个文件都需要reg52.h这个头文件,是在每个.c文件都写#include&reg52.h&还是只在一个.c文件中写?
主题帖子积分
初级技术员, 积分 117, 距离下一级还需 -17 积分
初级技术员, 积分 117, 距离下一级还需 -17 积分
主题帖子积分
专家等级:结帖率:100%
主题帖子积分
初级技术员, 积分 117, 距离下一级还需 -17 积分
初级技术员, 积分 117, 距离下一级还需 -17 积分
那么,请问多个文件都需要reg52.h这个头文件,是在每个.c文件都写#include&reg52.h&还是只在一个.c文件中写?
主题帖子积分
初级工程师, 积分 2629, 距离下一级还需 371 积分
初级工程师, 积分 2629, 距离下一级还需 371 积分
主题帖子积分
专家等级:结帖率:92%
主题帖子积分
初级工程师, 积分 2629, 距离下一级还需 371 积分
初级工程师, 积分 2629, 距离下一级还需 371 积分
mcustudypeace
可以建一个标准头文件如 stdafx.h 把通用性特别强的文件 如这个reg52。h,和一些宏定义 放进去。 个人习惯 仅供参考。
Stay foolish,stay hungry.
主题帖子积分
主题帖子积分
专家等级:结帖率:100%打赏:0.10受赏:5.10
主题帖子积分
提示: 作者被禁止或删除 内容自动屏蔽
主题帖子积分
中级技术员, 积分 277, 距离下一级还需 23 积分
中级技术员, 积分 277, 距离下一级还需 23 积分
主题帖子积分
专家等级:结帖率:9%
主题帖子积分
中级技术员, 积分 277, 距离下一级还需 23 积分
中级技术员, 积分 277, 距离下一级还需 23 积分
一个C对用一个H文件,函数和全局变量放在C文件里,包含头文件、函数声明、变量声明、宏定义放在H文件里,这个C文件只包含对应H文件即可, 其他文件想用C文件里的函数,只需包含次C文件对应的H文件即可,包含C文件的话会出现重复定义错误,记住,一定要把C文件添加到工程中
主题帖子积分
助理工程师, 积分 1273, 距离下一级还需 727 积分
助理工程师, 积分 1273, 距离下一级还需 727 积分
主题帖子积分
专家等级:结帖率:50%
主题帖子积分
助理工程师, 积分 1273, 距离下一级还需 727 积分
助理工程师, 积分 1273, 距离下一级还需 727 积分
H盘里我放的是岛国的片子。。。。:lol
人生就像一场旅行 在乎的不是目的地 而是沿途的风景 以及看风景的心情
主题帖子积分
中级技术员, 积分 129, 距离下一级还需 171 积分
中级技术员, 积分 129, 距离下一级还需 171 积分
主题帖子积分
专家等级:结帖率:100%
主题帖子积分
中级技术员, 积分 129, 距离下一级还需 171 积分
中级技术员, 积分 129, 距离下一级还需 171 积分
荣誉元老奖章
等级类勋章
坚毅之洋流
发帖类勋章
时间类勋章
技术领袖奖章
人才类勋章
突出贡献奖章
等级类勋章
沉静之湖泊
发帖类勋章
技术奇才奖章
人才类勋章
时间类勋章
涓涓之细流
发帖类勋章
时间类勋章
技术导师奖章
人才类勋章
湍急之河流
发帖类勋章
精华达人奖章
等级类勋章
时间类勋章
时间类勋章
无冕之王奖章
等级类勋章
时间类勋章
技术高手奖章
人才类勋章
时间类勋章
欢快之小溪
发帖类勋章
社区建设奖章
等级类勋章
热门推荐 /32470人阅读
c/c++(3)
一 .h .c 文件
&&& 先来看看这些文件的编译连接过程,然后引出一些具体的问题,如.h文件的相关作用、头文件编写规则、
(1)编译、链接
&&& 先来分析下下面这个小例子:
&&&&& int&& f(int&& t);
#include&& &a.h&
int&& A::f(int&& t)
&&& return&&
#include&& &a.h&
void&& main()
&&&&& a.f(3);
1 预处理阶段:预处理器看到#include &文件名&就把这个文件读进来,比如它编译main.c,看到#include&a.h&,它就把a.h的内容读进来,它知道了,有一类A,包含一个成员函数f,这个函数接受一个int型的参数,返回一个int型的值。
2 编译/汇编阶段:再往下编译很容易就把A a这行读懂了,它知道是要拿A这个类在栈上生成一个对象。再往下,它知道了下面要调用A的成员函数f了,参数是3,由于它知道这个函数要一个整形数用参数,这个3正好匹配,那就正好把它放到栈上,生成一条调用f(int)函数的指令(一般可能是一句call),至于这个f(int)函数到底在哪里,它不知道,它留着空,链接时再解决。该阶段生成a.o和main.o文件
3 链接阶段:链接a.o和main.o文件,即明确了f(int)函数的实现所在的地址,把main.o中空着的这个地址位置填上正确的地址。最终生成了可执行文件main.out。
大致过程如下:
编译、汇编
Main.sàmain.o
main.c文件中引用了#include”a.h”,根据C语言的特性,任何变量、函数在使用前都必须先声明,(方法一)包含相应的声明头文件是一种方法,当然若不怕麻烦,(方法二)可以将main中用到的每一个变量、函数都重新声明一遍。
1 在编译main.c,其包含了头文件#include“a.h“,但是此时根本不需要知道a.c中的实现内容。也就是说各个.c 文件的编译是相互独立的(C语言中一个.c文件对应一个模块)
2 根据方法二,说明.h文件并不是必须的,也就是说并不是每个.c文件都需要一个对应的.h文件,
二 头文件的由来
&&& 刚开始并没有.h文件,编译器也不认识头文件,大家的代码都是.c& .cpp,但是人们渐渐的发现了这样组织的缺点:1、很多.c(.cpp)文件中的声明语句就是相同的,但他们却不得不一个字一个字地重复地将这些内容敲入每个.c(.cpp)文件。如上面所说的方法二。2、当其中一个声明有变更时,就需要检查所有的.c(.cpp)文件,并修改其中的声明,啊~简直是世界末日降临!
&&& 形成.h文件:将重复的部分提取出来,放在一个新文件里,然后在需要的.c(.cpp)文件中敲入#includeXXXX这样的语句。具体叙述见【参考1】、具体的例子演变说明头文件的必要性【参考二】
三 头文件的构成
(1)一般的写法
//mymath.h
#ifndef _mymath_H
#define _mymath_H
extern int Global_A; //声明必要的全局变量
extern void fun(); //声明必要的外部函数
& & & & & & & & & & & & & & //根据下面的准则5,这里的extern最好不要,因为在顶层作用域中
& & & & & & & & & & & & & &//函数、变量的默认存储类说明符为extern
在头文件中声明了全局变量和外部函数都添加了必要这两个字,说明这是提供给别的模块使用的函数即接口,对于那些只在自己本模块中用的函数不必放在头文件中进行声明,只需在实现文件中进行声明即可,见下面的实现代码。
#ifndef、#define、#endif的作用见【文章】。
//mymath.c
#include &mymath.h &
#include &一些需要使用的C库文件&
-----------------------------------------------------------------
int Global_A ; //定义必要的全局变量和函数
void fun();
Static int a,b,c; //声明一些内部使用的全局变量及函数
//这里的内部指的是本编译模块如第一小节说的main.c 或者 a.c
//既然是本模块的变量和函数,应该声明为static
Static void somefun();
-----------------------------------------------------------------
//函数实现体
void fun()
Static void somefun()
在【参考3】中,作者提出对全局变量的定义进行了重新的布局。一个模块与其他模块的数据传递最好通过专有的模块进行,而不要直接通过数据单元直接传递(这是VC++的思想),因此不建议在模块的头文件中声明全局变量;全局变量最好统一定义在一个固定的文件中,所以可以采用下面的方法:
//Globel_Var.c
/*******定义本工程中所用到的全局变量*******/
//Globel_Var.H
/*******声明本工程中所用到的全局变量*******/
定义一个Global_Var.C文件来放全局变量,然后在与之相对应的Globel_Var.H文件中来声明全局变量。
这样哪个文件用到这两个变量就可以在该文件的开头处写上文件包含命令;例如aa.C文件要用到speed,toque两个变量,可以在aa.H文件中包含Globel_Var.H文件。(这里不是很明白原作者为什么将Global_Var.H头文件包含在aa.H中,而不是包含在要用到它的实现模块中?求大神帮忙)
(2)头文件类型
1、头文件是为用户提供调用接口,这种头文件中声明了模块中需要给其他模块使用的函数和数据。更多规则见【参考3】
2、说明性头文件,这种头文件不需要有一个对应的代码文件,在这种文件里大多包含了大量的宏定义,没有暴露的数据变量和函数。
(3)一些准则
1头文件中不能有可执行代码,也不能有数据的定义,只能有宏、类型(typedef,struct,union,menu,class),数据和函数的声明。
(1)要么是纯纯的声明 用extern声明并无赋值或者函数体
(2)在程序执行时只有一份数据如const常量、全局静态变量
(3)唯一宗旨保持数据的一次定义规则
#define NAMESTRING “name”
menu{ flag1,flag2};
typedef struct
extent Fun(void);
//全局变量这样的形式就是的定义,所以不能放在头文件中
2头文件中不能包本地数据(模块自己使用的数据或函数,不被其他模块使用)
这一点相当于面向对象程序设计里的私有成员,即只有模块自己使用的函数,数据,不要用extern在头文件里声明,只有模块自己使用的宏,常量,类型也不要在头文件里声明,应该在自己的*.c文件里声明。
3含一些需要使用的声明。在头文件里声明外部需要使用的数据,函数,宏,类型。
4 防止重复包含
5 在.h文件中声明的函数,如果在其对应的.c文件中有定义,那么我们在声明这个函数时,不使用extern修饰符,如果反之,则必须显示使用extern修饰符.
6 不要在.c文件中使用extern,这是在.h文件中使用的。
四 头文件的作用
(1)简化了声明的书写。
(2)通过头文件来调用库功能。在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,而不必关心接口怎么实现的。编译器会从库中提取相应的代码。
(3)头文件能加强类型安全检查。如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。
&&& 拼拼筹筹总算把这个写完,但是大部分的内容都是参考里的博文,感谢他们的分享。
&&& 头文件刚开始的主要作用是简化各个模块之间互相引用时声明的方便性,也就是头文件包含的是非本模块要用到的函数接口或变量或类型。再着有了小节四中的其他作用。
&&& 头文件的出现于存在与C语言本身的特性也息息相关。因为C语言中不管是变量还是函数,在使用前都需要声明;变量、函数的作用域也十分的重要【文章】
要想写好头文件,需明白一些知识点:1、C语言中声明和定义的区别【文章】 2 哪些声明该写到头文件中 3 头文件在程序模块中的作用
1 extern 应用感觉比较混乱,一个是人为的一些规定,另外编译器默认认为顶层作用域的存储类说明符为extern
2 在C++中class A{};这是一个定义,而classA;这是一个声明。
关于类的声明和定义。
&&& class A;& //类的声明
&&& 类的声明和普通变量声明一样,不产生目标代码,可以在同一,以及多个编译单元重复声明。
&& class A {
&& };&&//类的定义
&& 类的定义就特殊一点了,可能会有疑问,为什么不能把这样的变量定义放到.h中(见4)但是可以把&&
&& 类的定义放在头文件中重复引用呢?同时类的函数非inline定义(写在类定义里面的函数是inline,除外)不能写在&
& 头文件中呢。
& 这是因为类的定义,只是告诉编译器,类的数据格式是如何的,实例话后对象该占多大空间。
& 类的定义也不产生目标代码。因此它和普通变量的声明唯一的区别是不能在同一编译单元内出现多次。
& //source1.cc
&& class A;
&& class A;& //类重复声明,OK
&& class A{
&& class A{
&& };&&&&&&&
&& class A{
& };&&& //同一编译单元内,类重复定义,会编译时报错,因为编译器不知道在该编译单元,A a;的话要生产怎样的a.
&&&&&&&& //如果class A{};定义在head.h ,而head.h 没有&
&&&&&&&& //#ifndef& #endif 就很可能在同一编译单元出现类重复定义的编译错误情况。
但是在不同编译单元内,类可以重复定义,因为类的定义未产生实际代码。
& //source1.cc
& class A{
& //source2.cc
& class A{
& }&&&&&&&&&&&&&&&&&&&&& //不同编译单元,类重复定义,OK。所以类的定义可以写在头文件中!
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&& //source1.cc
&&&&& class A{
&&&&& //source2.cc
&&&&& class A{
&&&&& }&&&&&&&&&&&&&&&&&&&&& //不同编译单元,OK
3&//head.h
&&&& //source1.cc
&&&& #include “head.h”
&&&& //source2.cc
&&&& #include “head.h”&
&&&& 头文件不被编译,.cc中的引用 include “ head.h”其实就是在预编译的时候将head.h中的内容插入到.cc中。
&&&& 所以上面的例子如果
&&&&&&&&&g++ –o test source1.cc source2.cc, 同样会链时发现重复定义的全局变量x。
&&&& 因此变量定义,包括函数的定义不要写到头文件中,因为头文件很可能要被多个.cc引用。
&&&& 那么如果我的head.h如下这么写呢,是否防止了x的链接时重定义出错呢?
&&&& //head.h
&&&& #ifndef _HEAD_H_
&&&& #define _HEAD_H_
&&&& #endif
&&&& //source1.cc
&&&& #include “head.h”
&&&& //source2.cc
&&&& #include “head.h”&
&&&&&&& 现在是否g++ –o test source1.cc source2.cc就没有问题了呢,答案是否定的。
&&&&&&所有的头文件都是应该如上加#ifndef&&& #endif的,但它的作用是防止头文件在同一编译单元被重复引用。
&&&&& 就是说防止可能的
&&&& //source1.cc
&&&& #include “head.h”
&&&& #include “head.h”
&&&& 这种情况,当然我们不会主动写成上面的形式但是,下面的情况很可能发送
&&&& //source1.cc
&&&& #include “head.h”
&&&& #inlcude “a.h”
&&&& //a.h
&&&& #include “head.h”
&&&& 这样就在不经意见产生了同一编译单元的头文件重复引用,于是soruc1.cc 就出现了两个定义。
&&&& 但是对于不同的编译单元source1.cc,source2.cc他们都是还会引用head.h的,即使#ifndef #endif的存在。【参考5】
六 参考资料:
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:67351次
排名:千里之外
原创:16篇
转载:14篇
(1)(1)(1)(1)(3)(6)(1)(5)(11)

我要回帖

更多关于 面向接口编程实例 的文章

 

随机推荐