头文件,宏,extern

关于头文件,宏和extern这几个概念,一直让我混乱不以。上周借着hr姐姐的问题,我决定进行一个简单的梳理,弄清楚基本的概念。

首先我们先来看在普通的类中引用头文件的两种方式:
1. #include< > :表示只从从标准库文件目录下搜索,对于标准库文件搜索效率快
2. #include” ” :表示首先从用户工作目录下开始搜索,对于自定义文件搜索比较快,然后搜索整个磁盘
头文件一旦改变,相关的源文件必须重新编译以获取更新过声明

预处理器描述
在计算机系统里面,确保头文件多次包含仍能安全工作的常用技术是预处理器(ppreprocessor),它由C++语言从C语言继承而来。预处理器是在编译之前执行的一段程序,可以部分改变我们所写的程序。比如我们上文所说到的一项预处理功能#include,当预处理器看到#include标记时就会用指定的头文件的内容代替#include。

C++程序还会用到的一项预处理功能是头文件保护符(header guard),
头文件保护符依赖于预处理变量(比如说NULL指针,空指针常量NULL就是就是我们所说的预处理变量)。预处理变量有两种状态,已定义和未定义。#define 指令把一个名字设定为预处理变量,ifdef当且仅当变量已定义时为真,#ifndef当且仅当变量未定义时为真。一旦检查结果为真,则执行后续操作直至遇到#endif指令为止。比如说我们编写自己的头文件:Sales_data.h:

#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
struct Sales_data{
       std::string bookNo;
       unsigned units_sold =0;
       double revenue=0.0;
}
#endif

当我们的程序中第一次包含Sales_data.h时,#ifndef的检查结果为真,预处理器将顺序执行后面的操作直至遇到#endif为止。此时,预处理变量SALES_DATA_H(称为预处理变量,并没有具体的意义)已经定义,而且Sales_data.h也会被拷贝到我们的程序中来。后面如果再一次包含Sales_data.h,则#ifndef的检查结果将为假,编译器将忽略#ifndef到#endif之间的部分。所以说编译宏(头文件保护符)的实际意义就是:防止该头文件被重复引用

extern关键字
extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用:

extern int a;

仅仅是一个变量的声明,其并不是在定义变量a,并未为a分配内存空间。变量a在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在链接阶段中从模块A编译生成的目标代码中找到此函数。
比如:

//file.cc定义并初始化了一个常量,该常量可以被其他文件访问
extern const int bufsize=fcn();
//file.h头文件
extern const int bufsize;

如上所述,file.cc定义并初始化了bufSize,这是一条定义语句。然而,因为bufSize是一个常量,必须用extern加以限定使其被其他文件使用。file_1.h头文件中的声明也有extern做了限定,起作用是指明bufsize并非本文件所独有,它的定义将在其他地方定义,但是只能定义一次。

更多关于extern的知识,请看

宏定义
面试中遇到要求用宏定义MAX(a,b)函数的题目,下面我来写一写:

#define MAX(a,b)   (a>b) ?a :b
#define MAX(a,b,c) (a>b?(a>c?a:c):(b>c?b:c))

下面讲一讲预处理宏assert,所谓预处理宏其实就是一个预处理变量:

assert(expr);

首先对expr求值,如果表达式为假(即0),assert输出信息并终止程序的执行。如果表达式为真,assert什么都不做。这个assert宏定义在cassert头文件中。assert宏常用于检查“不能发生”的条件。例如,一个对输入文本进行操作的的程序,可能要求所有给定的单词的长度都大于某个阈值。此时,程序可以包含一条如下所示的语句:

assert(word.size()>threshold);

其实assert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NDEBUG,则assert什么也不做,默认状态下没有定义NDEBUG,此时assert将执行运行时检查。我们可以使用一个#define语句定义NEDBUG,从而关闭调试状态,比如在windows的编译器里面:

$CC -D NEDBUG main.c 

这行命令的的作用等价于在main.c文件的一开始写#define NDEBUG。assert预处理宏和NDEBUG预处理变量,实际上作为一种调试的手段。

除了用于assert之外,也可以使用NDEBUG编写自己的条件调试代码。如果NDEBUG未定义,将执行#ifndef和#endif之间的代码;如果定义NDEBUG,这些代码将被忽略掉:

void  print(const int a[],size_t size){
     #ifndef NDEBUG
     //__func__编译器定义一个局部静态变量,用于存放函数的名字
     cerr<<__func__<<":array size is"<<size<<endl;
     #endif  
}

在这段代码中,我们使用变量__func__输出当前调试的函数的名字。编译器为为每个函数都定义__func__ ,它是一个const char的一个静态数组,用于存放函数的名字。

除了C++编译器定义的func之外,预处理器还定义了另外4个对于程序调试很有用的名字:
__FILE__ 存放文件名的字符串的字面值
__LINE__ 存放当前行号的整型字面值
__TIME__ 存放文件编译时间的字符串字面值
__DATE__ 存放文件编译日期的字符串字面值

具体示例请看C++ Primer 219p

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值