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

头文件怎么起作用

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

//Sale_Item.h  【未完全版】
#include<string>
struct Sale_Item{
std::string bookname;
double renenue;
int copies;
};
//test.cpp
#include<iostream>
#include<string>
#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<iostream>
    #include<string>
    #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;
    }

    结果

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

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<string>
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<string>来定义一个变量std::string bookname
    • test.cpp中,我们#include<string>来定义一个变量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<string>
int i;
struct Sal_Item{
std::string bookname;
double renenue;
int copies;
};
#endif //结束符
//test.cpp
#include<iostream>
#include<string>
#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;
}

2-6 C/C++ 编写头文件的更多相关文章

  1. 编写自己的C头文件

    1.       头文件用于声明而不是用于定义 当设计头文件时,记住定义和声明的区别是很重要的.定义只可以出现一次,而声明则可以出现多次. 下列语句是一些定义,所以不应该放在头文件里: extern ...

  2. 头文件里面的ifndef /define/endif的作用

    c,c++里面,头文件里面的ifndef /define/endif的作用 今天和宿舍同学讨论一个小程序,发现有点地方不大懂······ 是关于头文件里面的一些地方: 例如:要编写头文件test.h ...

  3. C++头文件为什么要加#ifndef #define #endif

    #ifndef 在头文件中的作用 在一个大的软件工程里面,可能会有多个文件同时包含一个头文件,当这些文件编译链接成一个可执行文件时 ,就会出现大量“重定义”的错误.在头文件中实用#ifndef #de ...

  4. 头文件为什么要加#ifndef #define #endif

    #ifndef 在头文件中的作用 在一个大的软件工程里面,可能会有多个文件同时包含一个头文件,当这些文件编译链接成一个可执行文件时 ,就会出现大量“重定义”的错误.在头文件中实用#ifndef #de ...

  5. 头文件中的#ifndef/#define/#endif 的作用

    在一个大的软件工程里面,可能会有多个文件同时包含一个头文件,当这些文件编译链接成一个可执行文件时,就会出现大量重定义的错误.在头文件中实用#ifndef #define #endif能避免头文件的重定 ...

  6. c++中,保证头文件只被编译一次,避免多重包含的方法

    保证头文件只被编译一次 #pragma once这是一个比较常用的C/C++杂注,只要在头文件的最开始加入这条杂注,就能够保证头文件只被编译一次. #pragma once是编译器相关的,有的编译器支 ...

  7. c语言头文件的认识

    c头文件的作用是什么,和.c文件是怎么联系的,该怎么样编写头文件呢?这些问题我一直没搞明白,在阅读uCOS-II(邵贝贝)“全局变量”部分有些疑惑,今天终于搞清楚了头文件的一些基础知识,特地分享一下. ...

  8. 头文件中ifndef/define/endif的作用以及#pragma once使用

    例如:要编写头文件test.h 在头文件开头写上两行: #ifndef _TEST_H #define _TEST_H//一般是文件名的大写 ············ ············ 头文件 ...

  9. C++学习 之 初识头文件

    声明:            本人自学C++, 没有计算机基础,在学习的过程难免会出现理解错误,出现风马牛不相及的现象,甚至有可能会贻笑大方. 如果有幸C++大牛能够扫到本人的博客,诚心希望大牛能给予 ...

  10. C/C++ 引入头文件时 #include<***.h> 与 #include"***.h" 区别

    两种情况区分: 1.#include <> 编译器只会去系统文件目录中查找,找不到就报错. 2.#include " "  编译器会先在用户目录中查找,再到编译器设定的 ...

随机推荐

  1. Winform 子窗体调用父窗体方法

    子窗体部分 1.定义委托 /// <summary> /// 双击委托事件 /// </summary> /// <param name="path" ...

  2. Prometheus 告警恢复时,怎么获取恢复时的值?

    Prometheus 告警事件中的 $value 表示当前告警触发时的值,但是在告警恢复时,Resolved 事件中的 $value 仍然是最新告警时的值,并非是恢复时的值,这是什么原因和原理?是否有 ...

  3. c++学习笔记(五):文件操作

    目录 文件操作 文本文件 写文件 include 读文件 include 二进制文件 写文件 读文件 文件操作 程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放 通过文件可以将数据持久化 ...

  4. 全面升级,票据识别新纪元:合合信息TextIn多票识别2.0

    ​ 票据识别 - 自动化业务的守门员 发票.票据识别,是OCR技术和RPA.CMS系统结合的一个典型场景,从覆盖率.覆盖面的角度来说,应该也是结合得最成功的场景之一. 旧瓶装新酒,已经成熟的产品何苦费 ...

  5. 这些年没来得及学习的一些 HTML5 标签

    认识并学习下还没来得及学习的一些 HTML5 标签 <ruby> 标签 HTML <ruby> 元素被用来展示东亚文字注音或字符注释. 比如: <ruby>兄弟&l ...

  6. 开源项目dotnet/eshop 和 dotnet/eshopsupport

    dotnet/eshop[1] 和 dotnet/eshopsupport[2] 是两个与 .NET 相关的开源项目,分别用于展示电子商务应用的不同方面. dotnet/eshop: 功能与架构:do ...

  7. [TK] 理想的正方形

    题目描述 有一个整数组成的矩阵,现请你从中找出一个指定边长的正方形区域,使得该区域所有数中的最大值和最小值的差最小. 题目分析 其实这道题和滑动窗口很像,而滑动窗口使用优先队列解决. 我们都知道优先队 ...

  8. [OI] throw

    throw 主要是用来抛出异常. throw 可以直接向主程序 throw 一个东西,可以是各种数据类型,显示在界面上就是抛出的数据类型. int main(){ throw 1; } termina ...

  9. CSP提高组模拟1

    我的微軟輸入法莫名其妙變成繁體了,你們有什麽頭緒嗎 狀態 題目 20 Time Exceeded A 最短路 25 Time Exceeded B 方格取数 0 Time Exceeded C 数组 ...

  10. 2款.NET开源且免费的Git可视化管理工具

    Git是什么? Git是一种分布式版本控制系统,它可以记录文件的修改历史和版本变化,并可以支持多人协同开发.Git最初是由Linux开发者Linus Torvalds创建的,它具有高效.灵活.稳定等优 ...