头文件中应该放什么东西,以及在多个文件中使用的全局变量如何声明
考虑这些东西:
全局变量
静态全局变量(前面有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++ 头文件的作用域内放置带有内部链接的定义,如静态函数或数据,是合法的,但是这种做法并不理想。这样不仅污染了全局名称空间,而且包含该头文件的每一个编译单元中消耗数据空间。