C++查缺补漏之头文件

在C++里面,支持所谓的"分别编译",这样程序可以由多个文件组成,这些文件进行分别编译,最后再链接到一起组成可执行的文件(elf文件),我们在程序里面最常见到的就是在一个头文件class.h里面定义一个类,在另外一个源文件class.cc里面定义该类的方法和变量

//class.h

#ifndef _CLASS_H

#define _CLASS_H

class A

{

public:

void printHello();

};

#endif

//class.cc

#include "class.h"

#include <iostream>

void A::printHello()

{

std::cout<<"Hello"<<std::endl;

}

//main.cc

#include "class.h"

int main(int argc, char const *argv[])

{

A a ;

a.printHello();

return 0;

}

//下面我们通过makefile来编译上面的程序

//makefile

#下面讲main.cc和class.cc编译生成的.o文件链接到一起,生成可执行文件main

main:main.o class.o

g++ main.o class.o -o main

#下面是分别编译nain.cc和class.cc 注意这里的-c选项表明只是编译,而不生成可执行文件

main.o:main.cc

g++ -c main.cc -o main.o

class.o:class.cc

g++ -c class.cc -o class.o

.PHONY:clean

clean:

rm -f *.o

虽然上面的程序也可以直接g++ class.cc main.cc -o main,但是这里为了更加突出说明分别编译,故使用makefile,要是源文件数量比较多,makefile的优势久比较明显了

在上面的程序中,main.cc使用了class A这个类,但是这个类printHello()函数的实现却是在class.cc这个源文件里面,main.cc只用知道class A的定义即可,那么这个定义就是在class.h这个头文件中,在预处理阶段就将头文件展开,下面我们来看一下预处理以后的文件

g++ -E main.cc > main.e

生成如下文件:

//main.e

//...

class A

{

public:

void printHello();

};

# 2 "main.cc" 2

int main(int argc, char const *argv[])

{

A a ;

a.printHello();

return 0;

}

//...

下面我们就来编写一个头文件需要注意那些东西:

1.头文件是用于声明而不是定义

这个比较好理解,如果我们在头文件里面定义了一个变量,而一个程序里面的多个文件又包含了这个头文件,那么这个在头文件里面定义的变量将会在多个文件里面定义,但是我们知道一个程序里面一个变量可以声明多次但是只能定义一次,虽然各个文件在编译阶段不会有任何问题,但是最后链接成可执行文件的时候就会报错。比如在头文件里面这样写就会有问题,除非你的头文件只被包含一次

extern int a = 10;//error,这里不是声明,而是定义

double dNumber;//error,定义

但是在头文件里面不应该有定义有三个例外:

  • 类的定义
  • 常量初始化的const变量
  • inline内联函数

上面的三个中类型(这里也可以加上一个模板)都有一个共同的特征,就是文件在编译的时候就需要知道他们的定义:

1.对于类定义来说,为了产生能定义或使用类的对象的代码,编译器需要知道组成该类型的数据成员。同样还要知道能够在这些对象上执行的操作。

2.常量初始化的const变量,我们知道const变量默认是定义该变量的文件的局部变量,而且一般来说,const变量在编译阶段就会被替换成为常量表达式,所以在实践中不会有任何存储空间用于存储常量表达式初始化的const变量,反正变量都不会改变,还不如直接替换为常量来节省空间

3.内联函数(inline),我们知道inline函数是在程序的每个调用点上“内联的”展开,所以内联函数在编译阶段就必须是有定义的,可见的。

2.避免头文件的多重包含

我们来看一下下面这种情况

//class.h

class A

{

public:

void printHello();

};

//class1.h

#include "class.h"

/* other code */

//main.cc

#include "class.h"

#include "class1.h"

mian(){}

下面我们编译一下,发现报错了

g++ -c main.cc -o main.o

In file included from class1.h:1:0,

from main.cc:2:

class.h:4:7: 错误: ‘class A’重定义

class.h:4:7: 错误: ‘class A’的前一个定义

make: *** [main.o] 错误 1

报错的原因是因为class A的重复定义,因为我们在class.h里面定义了class A,让后class1.h包含了class.h,最后main.cc包含两个class.h和class1.h,最后两个头文件同时展开:

g++ -E main.cc > main.e

//main.e

class A

{

public:

void printHello();

};

# 2 "main.cc" 2

# 1 "class1.h" 1

# 1 "class.h" 1

class A

{

public:

void printHello();

};

# 1 "class1.h" 2

# 3 "main.cc" 2

int main(int argc, char const *argv[])

{

A a ;

a.printHello();

return 0;

}

我们这时main.cc里里面就两个class A的定义,这肯定是编译不过的,要避免这种多重包含的错误导致的重复定义的错误,要人工避免那是不可能的,一旦文件变多,要理清有没有多重包含,那就痴人说梦了。但是我们可以借助下面方法解决多重包含的问题:

#ifndef _CLASS_H

#define _CLASS_H

/* 头文件内容 */

#ednif

这里在class.h里面第一次检测没有被包含,那么就define _CLASS_H,在class1.h里面再次#include "class.h"时候,就会检测是否定义了_CLASS_H,如果是,那么就跳过。

当然某些编译器也支持下面这种写法

#program once

/* 头文件内容 */

不过我还是建议使用上一种

时间: 2024-08-30 02:52:31

C++查缺补漏之头文件的相关文章

Android查缺补漏(View篇)--自定义 View 的基本流程

View是Android很重要的一部分,常用的View有Button.TextView.EditView.ListView.GridView.各种layout等等,开发者通过对这些View的各种组合以形成丰富多彩的交互界面,一个应用中界面交互的体验往往在应用的受欢迎程度上起了很关键得作用,所以开发者们大多会想方设法的做出一个更加精美的界面,例如:通过自定义View.深入学习View的原理以便更好的对其优化使其在操作起来更加流畅等等,也正因为如此,在面试中View也常常作为面试官重点考察的对象之一

Android查缺补漏(View篇)--自定义View利器Canvas和Paint详解

上篇文章介绍了自定义View的创建流程,从宏观上给出了一个自定义View的创建步骤,本篇是上一篇文章的延续,介绍了自定义View中两个必不可少的工具Canvas和Paint,从细节上更进一步的讲解自定义View的详细绘制方法.如果把自定义View比作盖一座房子,那么上篇文章就相当于教会了我们怎么一步步的搭建房子的骨架,而本篇文章将要教会我们的是为房子的骨架添砖加瓦直至成型,甚至是怎么装修. Canvas 为了后文更为方便的讲解Canvas的常用方法的使用,我们先来做一些准备工作,创建一个自定义V

Android查缺补漏--Service和IntentService

Service的运行不依赖界面,即使程序被切换到后台,Service仍然能够保持正常运行.当某个应用程序进程被杀掉时,所有依赖于该进程的Service也会停止运行. Service 分为启动状态和绑定状态.当处于仅启动状态时,通过 stopService或 stopSelf 即可停止 Service.当处于绑定状态时需要通过 unBindService 和 stopService 结合使用才能完全停止 Service. 一.Service的生命周期(onCreate()-onStartComma

Android查缺补漏--ContentProvider的使用

ContentProvider (内容提供者)是一种共享型组件,可以为系统内应用于与应用之间提供访问接口. ContentProvide要想正常工作需要三个关键点: ContentProvider:对外提供数据的访问接口. Uri:ContentProvider的唯一标识,外界可根据其访问对应的ContentProvider. ContentResolver 比如,当应用A想把自己数据暴露出来让别的应用也可以操作的话,就可以在应用A内部创建一个ContentProvider实现相关方法并添加UR

Android查缺补漏(View篇)--在 Activity 的 onCreate() 方法中为什么获取 View 的宽和高为0?

在 Activity 的 onCreate() 方法中为什么获取 View 的宽和高为0 ? @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my_view); myview = ViewUtils.find(this, R.id.myview); getViewSize("onCr

Android查缺补漏(View篇)--事件分发机制源码分析

在上一篇博文中分析了事件分发的流程及规则,本篇会从源码的角度更进一步理解事件分发机制的原理,如果对事件分发规则还不太清楚的童鞋,建议先看一下上一篇博文 <Android查缺补漏(View篇)--事件分发机制> ,先来看一下本篇的分析思路,一会儿会按照事件传递的顺序,针对以下几点进行源码分析: Activity对点击事件的分发过程 PhoneWindow是如何处理点击事件的 顶级View对点击事件的分发过程 View对点击事件的处理过程 Activity对点击事件的分发过程 通过上一篇博文中我们

20165306 第二周查缺补漏

第二周查缺补漏 1.位运算符 位运算符主要针对两个二进制数的位进行逻辑运算,所以要先把其他进制数转化为二进制数. 例如: 128&129 128转换成二进制就是10000000,129转换成二进制就是10000001,根据与运算符的运算规律,"只有两个位都是1,结果才是1",可以知道结果就是10000000,即128. 128|129 根据或运算符的运算规律,"只要两个位有一个是1,结果就是1",可以知道结果就是10000001,即129. 15^2 15转

Android查缺补漏(线程篇)-- IntentService的源码浅析

本文作者:CodingBlock 文章链接:http://www.cnblogs.com/codingblock/p/8975114.html 在Android中有两个比较容易弄混的概念,Service和Thread,我们都知道这两个东西都可以执行后台任务,但要注意的是Service是跑在主线程中的,如果不做特殊处理是会阻塞主线程的,而IntentService正好弥补了这一点,在<Android查缺补漏--Service和IntentService>这篇博文中已经简单介绍过了IntentSe

React查缺补漏之二

译文链接 通过给一个通用函数传入参数定制特定函数的用法 _onFieldChange函数是一个通用实例方法,通过给这个函数传入不同的参数来实现返回结果的不同. 在构造函数中,进行绑定(没有想过这种用法). 1. `this._onNameChanged = this._onFieldChange.bind(this, 'name');` 2. `this._onPasswordChanged =this._onFieldChange.bind(this, 'password');` **注意点击