编写自己的C头文件
1. 头文件用于声明而不是用于定义
当设计头文件时,记住定义和声明的区别是很重要的。定义只可以出现一次,而声明则可以出现多次。
下列语句是一些定义,所以不应该放在头文件里:
|
extern int ival = 10; // initializer, so it's a definition double fica_rate; // no extern, so it's a definition |
虽然ival声明为extern,但是它有初始化式,代表这条语句是一个定义。类似地,fica_rate的声明虽然没有初始化式,但也是一个定义,因为没有关键字extern。同一个程序中有两个以上文件含有上述任一个定义都会导致多重定义链接错误。
因为头文件包含在多个源文件中,所以不应该含有变量或函数的定义。
对于头文件不应该含有定义这一规则,有三个例外。头文件可以定义类、值在编译时就已知道的const对象和inline函数。这些实体可在多个源文件中定义,只要每个源文件中的定义是相同的。在头文件中定义这些实体,是因为编译器需要它们的定义(不只是声明)来产生代码。例如:为了产生能定义或使用类的对象的代码,编译器需要知道组成该类型的数据成员。同样还需要知道能够在这些对象上执行的操作。类定义提供所需要的信息。在头文件中定义const对象则需要更多的解释。
2. 预处理器的简单介绍
#include指示只接受一个参数:头文件名。预处理器用指定的头文件的内容替代每个#include。我们自己的头文件存储在文件中。系统的头文件可能用特定于编译器的更高效的格式保存。无论头文件以何种格式保存,一般都含有支持分离编译所需的类定义及变量和函数的声明。
2.1. 头文件经常需要其他头文件
头文件经常#include其他头文件。头文件定义的实体经常使用其他头文件的设施。例如,定义Sales_item类的头文件必须包含string库。Sales_item类含有一个string类型的数据成员,因此必须可以访问string头文件。包含其他头文件是如此司空见惯,甚至一个头文件被多次包含进同一源文件也不稀奇。例如,使用Sales_item头文件的程序也可能使用string库。该程序不会(也不应该)知道Sales_item头文件使用了string库。在这种情况下,string头文件被包含了两次:一次是通过程序本身直接包含,另一次是通过包含Sales_item头文件而间接包含。
因此,设计头文件使其可以多次包含在同一源文件中,这一点很重要。我们必须保证多次包含同一头文件不会引起该头文件定义的类和对象被多次定义。使得头文件安全的通用做法,是使用预处理器定义头文件哨兵(header guard)。头文件哨兵用于避免在已经见到头文件的情况下重新处理该头文件的内容。
2.2. 避免多重包含
在编写头文件之前,我们需要引入一些额外的预处理器设施。预处理器允许我们自定义变量。
预处理器变量的名字必须在程序中唯一。任何与预处理器变量相匹配的名字的使用都关联到该预处理器变量。
为了避免名字冲突,预处理器变量经常用全大写字母表示。
预处理器变量有两种状态:已定义或未定义。定义预处理器变量和检测其状态用到不同的预处理器指示。#define指示接受一个名字并定义该名字为预处理器变量。#ifndef指示检测指定的预处理器变量是否未定义。如果预处理器变量未定义,那么跟在其后的所有指令都被处理,直到出现#endif。
可以使用这些设施来预防多次包含同一头文件:
#ifndef SALESITEM_H
#define SALESITEM_H
// Definition of Sales_item class and related functions goes here
#endif
条件指令
#ifndef SALESITEM_H
测试SALESITEM_H预处理器变量是否未定义。如果SALESITEM_H未定义,那么#ifndef测试成功,跟在#ifndef后面的所有行都被执行,直到发现#endif。相反,如果SALESITEM_H已定义,那么#ifndef指示测试为假,该指示和#endif指示间的代码都被忽略。
为了保证头文件在给定的源文件中只处理过一次,我们首先检测#ifndef。第一次处理头文件时,测试会成功,因为SALESITEM_H还未定义。下一条语句定义了SALESITEM_H。那样的话,如果我们编译的文件恰好又一次包含了该头文件。#ifndef指示会发现SALESITEM_H已经定义,并且忽略该头文件的剩余部分。
头文件应该含有哨兵,即使这些头文件不会被其他头文件包含。编写头文件哨兵并不困难,而且如果头文件被包含多次,它可以避免难以理解的编译错误。
当没有两个头文件定义和使用同名的预处理器常量时,这个策略相当有效。我们可以为定义在头文件里的实体(如类)命名预处理器变量来避免预处理器变量重名的问题。一个程序只能含有一个名为Sales_item的类。通过使用类名来组成头文件和预处理器变量的名字,可以使得很可能只有一个文件将会使用该预处理器变量。
2.3. 使用自定义的头文件
#include指示接受以下两种形式:
#include <standard_header>
#include "my_file.h"
如果头文件名括在尖括号(< >)里,那么认为该头文件是标准头文件。编译器将会在预定义的位置集查找该头文件,这些预定义的位置可以通过设置查找路径环境变量或者通过命令行选项来修改。使用的查找方法因编译器的不同而差别迥异。建议你咨询同事或者查阅编译器用户指南来获得更多的信息。如果头文件名括在一对引号里,那么认为它是非系统头文件,非系统头文件的查找通常开始于源文件所在的路径。
1. 注释,版权,作者,重大修订记录等信息
2. 防重入开关,也就是常见的 #ifndef… #define… #endif
3. C编译器自适应开关,也就是常见的 #ifdef __cplusplus… extern “C” { } #endif
4. #include ,头文件里应该 include 所有该文件中所使用的其它接口头文件。这里有也有两层含义,一是说头文件应做到自包含,即使用头文件的用户不需要再为该头文件 include 其它头文件;二是从模块耦合内聚角度来说,头文件中本身不应该 include 太多其它头文件,一般就是通用数据类型定义, include 其它头文件意味着强耦合——引用了其它头文件中的类型定义,宏或是函数。
5. 接口声明及注释,包括函数,结构体等,但不应该出现全局变量,和 static 类型的接口,这些都应该是放置在 C 文件中。函数的注释中应该包括功能说明,参数使用方法,可能的返回值,及其它注意事项。结构体的注释中应该包括每个成员变量所表示的含义。我们也提倡自注释,即通过合理的命名达到见名知意的效果。
6. 接口总体上来说是越少,越简单越好,时刻检查头文件中是否存在冗余的信息,及时删除,合并。
编写自己的C头文件的更多相关文章
- C/C++:提升_头文件的使用
C/C++:提升_头文件的使用 ◇写在前面 学到现在,很多人编写程序时只会使用一个文件.这样在代码量较小的时候,更利于表达程序,但是随着代码量的逐步增加,程序的思维逻辑不是我们一下子就可以完全理清的, ...
- 编写自己的C语言头文件
一些初学C语言的人,不知道头文件(*.h文件)原来还可以自己写的.只知道调用系统库 函数时,要使用#include语句将某些头文件包含进去.其实,头文件跟.C文件一样,是可以自己写的.头文件是一种文本 ...
- xcode编写c/c++静态库使用系统头文件问题
c/c++编写的静态库中有引用ios系统头文件比如: #include <EGL/egl.h> 在xcode编译的时候需要设置静态库程序: Build Settings-Header Se ...
- 如何编写自己的C语言头文件
一些初学C语言的人,不知道头文件(*.h文件)原来还可以自己写的.只知道调用系统库函数时,要使用#include语句将某些头文件包含进去.其实,头文件跟.C文件一样,是可以自己写的.头文件是一种文本文 ...
- 编写linux驱动所用到的头文件(转)
转自:http://blog.csdn.net/lufeiop02/article/details/6448497 关于linux驱动(应用)程序头文件使用 收藏 驱动程序: #include < ...
- MCU_头文件编写
头文件中一般放一些重复使用的代码,如:常量.变量.宏等的定义,函数的声明.当使用#include语句引用头头文件时,相当于将头文件中的内容复制到#include处. 头文件一般形式: #ifndef ...
- C/C++头文件的编写
在C语言的学习过程中,我们一般把所有的代码写在一个文件中.随着自身水平的提高,我们发现代码越写越长,代码行数越来越多,把一个工程的所有代码写在一个文件中让人看起开非常吃力.于是我们开始想把代码中的函数 ...
- 编写一个通用的Makefile文件
1.1在这之前,我们需要了解程序的编译过程 a.预处理:检查语法错误,展开宏,包含头文件等 b.编译:*.c-->*.S c.汇编:*.S-->*.o d.链接:.o +库文件=*.exe ...
- 转:C语言中的头文件可以自己写吗?
转自:http://www.eefocus.com/computer00/blog/08-09/155791_9ebdc.html 一些初学C语言的人,不知道头文件(*.h文件)原来还可以自己写的. ...
随机推荐
- Windows与Linux文件共享
Windows与Linux文件共享 Samba服务器 安装Samba服务器 rpm –ivh /mnt/Packages/Samba-3.5.10-125.el6.i686.rpm 添加用户并修改密码 ...
- jQuery 表单验证插件——Validation(基础)
这个插件不错,是用jquery写的.能进行表单验证.我喜欢它的原因是因为 1.他有自带的验证规则 2.你可以自己写验证规则 3.可以通过ajax与后台交互,与后台数据比较.最后返回结果!我在表单中要验 ...
- IE8 placeholder兼容+Password兼容
对于placeholder兼容问题 IE系列的大部分不兼容 使用JQ插件解决这个问题,确实用法很简单 jS下载地址http://www.ijquery.cn/js/jquery.placeholder ...
- c - 计算1到20的阶乘
#include <stdio.h> /* 题目:求 1+2!+3!+...+20!的和 */ unsigned long long int factorial(long n) { uns ...
- dom4j 笔记【转】
SAXReader reader = new SAXReader(); Document doc = reader.read(...); List childNodes = doc.selectNod ...
- JavaScript--对象+函数
1. 复杂数据类型 Object ECMAScript中的对象其实就是一组数据(属性)和功能(方法)的集合. 1) 创建Object实例: 1.使用构造函数创建,new Object() ...
- javascript基础学习(十三)
javascript之文档对象 学习要点: 文档对象 文档对象的应用 一.文档对象 Document对象是代表一个浏览器窗口或框架中的显示HTML文件的对象.javascript会为每个HTML文档自 ...
- Linux下的Job Control(转:http://blog.chinaunix.net/uid-26495963-id-3062757.html)
一.Job的概念 Job是指在批处理的环境中,为完成某一任务而进行一系列操作的总称.在个人接触计算机的年代,批处理的环境已经不容见到了,只有一些特殊的行业和环境下还在使用这样的概念,仅在书本中接触过. ...
- winform 剔除空格与换行显示
string strTeachingPlan = this.txtTeachingPlan.Text.ToString().Trim(); string[] maarr = strTeachingP ...
- js Math函数
1.丢弃小数部分,保留整数部分parseInt(5/2) 2.向上取整,有小数就整数部分加1 Math.ceil(5/2) 3,四舍五入. Math.round(5/2) 4,向下取整 Math.fl ...