头文件中应该放什么东西,以及在多个文件中使用的全局变量如何声明

考虑这些东西:

全局变量

静态全局变量(前面有static,外部文件无法访问)

局部变量

静态局部变量

常量

类内静态成员

模板类

模板函数

宏定义 define

类型定义 typedef

内联函数

头文件中的内容在编译时会填充到include这个头文件的cpp文件中,所以头文件中有什么东西,相当于cpp文件中也有什么东西,如果有多个include这个头文件的cpp文件,

那么它们相当于都获得了这个头文件中的内容的一个副本,发生重定义错误。所以很多东西的定义不能放在头文件,只能放声明,否则会出现多个副本。

 

static的使用

c++引入命名空间之前,程序需要将名字声明成static的以使其只对整个文件有效,而对外部文件不可见。这个做法继承自C语言,但引入命名空间后,这个做法已经被取消了,现在的做法是在未命名的命名空间内存放对象。(c++ primer P701

所以:

1. 如果希望对象只能在文件内访问,就把它放到文件内的未命名空间。

 

2. 如果是需要多个文件共同访问的全局对象,则在其中一个cpp文件中(注意全局对象的定义不能放在头文件)使用单例模式:

extern int targetObj(){

static int obj = 1;

return obj;

}

使用单例模式的原因:

c++对“定义于不同的编译单元内的non-local static对象”的初始化相对次序并无明确定义。比如A,B两个对象分别定义于不同的cpp文件,A对象的初始化依赖B对象,所以B对象的初始化必须先于A对象。

c++保证,函数内的local static对象会在“该函数被调用期间”“首次遇到这个local static对象”时被初始化(类似的还有全局变量初始化,类模板实例化),所以把B对象放到类似上面的targetObj函数内,A在使用B时调用单例模式函数,就能保证B已经被初始化。(effective c++ P31

(线程的地址空间包括全局和静态变量存储区,未初始化的全局/静态变量放在.bss段,已初始化的全局/静态变量放在.data段)https://blog.csdn.net/xiandang8023/article/details/126036564

 

3. 对于会被多个不同的cpp文件调用的全局对象(例如上面这个单例模式函数),它的定义可以放在某个cpp文件中,前面加上extern,然后在头文件的函数声明中也加上extern

这样其他cpp文件要调用这个函数只需要include这个头文件即可。c++ primer P54

对于类模板而言,模板的定义是放在头文件中(因为模板只有在代码运行使用到的时候才会生成具体模板类的定义,所以要提前知道模板的定义),

如果多个源文件使用同一个模板来实例化同一个类,为了防止出现多个相同的模板类副本(在多个文件中进行相同的实例化),可以对这个模板类进行显示实例化,然后这几个文件再共用同一份代码。

注意显示实例化模板的定义要放在cpp文件中(不能像类模板定义那样放在头文件中),但是显示实例化模板的声明应放在头文件中,并且在声明的前面加上extern,其他要用到这个显式实例化模板的源文件再include这个头文件即可。

(c++ primer P598,书中的做法不是在头文件中放extern声明,而是在其他要使用的源文件中放extern声明,其实是一样的,

include头文件相当于把头文件中的东西都copy到源文件中去,如果你不需要这个头文件里的所有东西,那就可以像书中那样单独地在这个源文件中加一个extern声明,这样就不用include头文件了)

 

总结:

头文件内只放各种对象的声明,而定义放在cpp文件中(内联函数,函数模板,模板类,模板显式实例化除外),对于多个cpp共用的全局变量,将其定义放在其中一个cpp文件(需要保证初始化顺序时使用单例模式),

然后在定义和声明前面都加上extern,其他cpp文件使用的使用直接在文件内进行extern声明即可。对于只在文件内使用的对象,将其定义放到未命名空间内。

对于宏定义和typedef,如果只在一个cpp文件中使用,则放在这个cpp文件中;若果多个源文件中需要使用这个宏,则放在头文件中定义。

// radio.h
#ifndef __RADIO_H__
#define __RADIO_H__

// 应包含内容
class Radio                                // 正确:类定义
{
    static int s_count;                    // 正确:静态数据成员声明
    static const double S_PI;              // 正确:静态常量数据成员声明
    int d_size;                            // 正确:数据成员定义
    // ...
public:
    int size() const;                     // 正确:成员函数声明
    // ...
};

inline int Radio::size() const            // 正确:内联函数定义
{
    return d_size;
}

// 不应包含内容
int Radio::s_count;                       // 错误:静态数据成员定义,应放在 .cpp 文件中
double Radio::S_PI = 3.1415926;           // 错误:静态常量数据成员定义,应放在 .cpp 文件中
int Radio::size() const { /*...*/ }       // 错误:成员函数定义,应放在 .cpp 文件中
int z;                                    // 错误:外部数据定义
extern int LENGTH = 10;                   // 错误:外部数据定义
const int WIDTH = 5;                      // 避免:常量数据定义
static int y;                             // 避免:静态数据定义
static void func() { /*...*/ }            // 避免:静态函数定义

#endif // __RADIO_H__

 在 C++ 头文件的作用域内放置带有内部链接的定义,如静态函数或数据,是合法的,但是这种做法并不理想。这样不仅污染了全局名称空间,而且包含该头文件的每一个编译单元中消耗数据空间。

posted @ 2023-05-04 19:35  大黑耗  阅读(580)  评论(0编辑  收藏  举报