2-6 C/C++ 编写头文件


目录
  • 头文件怎么起作用
  • 避免头文件被重复引用
  • 避免头文件被重复引用的方法:条件编译
    • 1. 给每个头文件添加一个预编译变量(preprocessor variable)作为标记(Label)
    • 2. 使用头文件保护符:ifdef/ifndef
    • 3. 关于使用条件编译的必要性的探讨
  • 总结:创建自己的头文件

建议直接看总结,如果有地方不懂在回头看细节

头文件怎么起作用

当一个test.cpp#include "Sal_Item.h"时,实际上编译器会把Sal_Item.h文件内的内容全部复制test.cpp

image-20220111202629386

//Sale_Item.h  【未完全版】
#include
struct Sale_Item{
    std::string bookname;
    double renenue;
    int copies;
};
//test.cpp
#include
#include
#include"Sale_Item.h"  //Sale_Item.h的内容会被复制到这里
using namespace std;
int main(){
    Sale_Item data;
    return 0;
}

避免头文件被重复引用

  • 因为头文件Sale_Item.h内容会被复制到引用它的文件test.cpp

  • 所以如果头文件Sale_Item.h被重复引用,就会导致在Sale_Item.h中定义的变量在test.cpp中被定义了多次,造成编译错误。

  • 因此,需要避免头文件被重复引用以及不同头文件中定义了同名的全局变量【当这些定义了同名变量的文件被#include到同一个程序时,同样会造成编译错误】

  • 必须对上面的Sale_Item.h进行改进,否则一旦被重复引用就会报错

    执行下面的代码

    #include
    #include
    #include"Sale_Item.h"
    #include"Sale_Item.h"  //重复调用了Sale_Item.h,对于此时的头文件Sale_Item.h是不允许的
    using namespace std;
    int main(){
        Sale_Item data;
        std::string name = "xxx";
        data.bookname = name;
        return 0;
    }
    

    结果

    image-20220111205747597

避免头文件被重复引用的方法:条件编译

1. 给每个头文件添加一个预编译变量(preprocessor variable)作为标记(Label)

#define SALE_ITEM_H

  • 此时define的作用是定义预编译变量SALE_ITEM_H,而不是定义宏
  • 为什么预编译变量如此命名?
    • 头文件内容会被复制到test.cpp中,而且预编译变量无视作用域规则,也就是说整个test.cpp中不能出现与预编译变量SALE_ITEM_H同名的变量
    • 一般约定俗成把预编译变量写成头文件名的大写,这样既直观,又不容易和test.cpp和其他头文件的预编译变量产生冲突

2. 使用头文件保护符:ifdef/ifndef

ifdef XXX:如果预编译变量XXX已经被定义,则执行该指令与endif之间的代码块

ifndef XXX:如果预编译变量XXX还没有被定义,则执行该指令与endif之间的代码块

常规写法

//Sale_Item.h
#ifndef SAL_ITEM_H  //如果SAL_ITEM_H未被定义
#define SAL_ITEM_H  //定义SAL_ITEM_H预编译变量作为标记(Lable)。
//如果`test.cpp`在之前已经引用过该头文件,那么SAL_ITEM_H就已经被定义过
//在ifndef的作用下,之后的Sale_Item.h文件都不会执行
#include
int i;
struct Sal_Item{
    std::string bookname;
    double renenue;
    int copies;
};
#endif  //结束符

3. 关于使用条件编译的必要性的探讨

  • 或许读者会有一个疑问:既然重复引用Sale_Item.h会导致test.cpp出错,那么不重复引用不就行了,何必那么麻烦写条件编译呢
  • 在上述例子中,Sale_Item.h不写条件编译确实可以,但在很多情况下,我们不得不重复引用同一个头文件
  • 还是以上述例子为例,对于头文件string
    • Sale_Item.h中,我们#include来定义一个变量std::string bookname
    • test.cpp中,我们#include来定义一个变量std::string name来给对象赋值
    • 所以test.cpp实际上就引用了头文件string两次,一次是显式地引用,一次是在#include"Sale_Item.h"时隐式地引用了string【Sale_Item.h中也includestring
  • 所以,无论是否有必要,我们建议在书写头文件时,都习惯性地使用条件编译

总结:创建自己的头文件

//Sale_Item.h
#ifndef SAL_ITEM_H  //如果SAL_ITEM_H未被定义
#define SAL_ITEM_H  //定义SAL_ITEM_H预编译变量作为标记(Lable)。
//如果`test.cpp`在之前已经引用过该头文件,那么SAL_ITEM_H就已经被定义过
//在ifndef的作用下,之后的Sale_Item.h文件都不会执行
#include
int i;
struct Sal_Item{
    std::string bookname;
    double renenue;
    int copies;
};
#endif  //结束符
//test.cpp
#include
#include
#include"Sale_Item.h"
// #include"Sale_Item.h" //加上不要紧,因为进行了条件编译
using namespace std;
int main(){
    Sale_Item data;
    std::string name = "xxx";
    data.bookname = name;
    return 0;
}

相关