首页 > 代码库 > C++ 文件include规则 常量定义

C++ 文件include规则 常量定义

总结一句话就是: C++的函数声明,变量声明,类定义写在头文件里,而函数实现,变量定义,类方法实现写在.cpp文件中;但是对于内联函数和模版类,函数的实现也要写在头文件里!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


1. 将类的成员变量、类方法的定义写在.h中,将类方法的实现写在.cpp中,不要include .cpp文件,不要在.h文件中只写class MyClass; ,一定要写类成员变量和方法的全部定义!!!类方法的实现写在.cpp文件中。

2. 类模版或者模版的定义一定要写在同一个.h中,不要写在.cpp中,不能分开写!!!可以参考 http://blog.csdn.net/ixsea/article/details/6695496 中的解释:对于模板函数来说,只有被调用的模板函数才被实例化,这里的被调用并不要求它必须被main函数调用。某个普通函数调用了模板函数,该模板函数就将对应产生一个实例,而调用它的普通函数可能并不被main调用,也即有可能并不被执行。而普通函数.h 和 .cpp可以分开的原因是已经实例化好的,因此可以根据.h中的定义找到函数实现的位置!!!全文为:

----------------------------------------------------------------------------------------------------------------------------------------

观点

包含模型是C++模板源代码的一种组织方式,它鼓励将模板代码全部放在一个.h头文件中,这样可以避免莫名其妙的链接错误。

莫名其妙的链接错误

一般而言,程序员习惯将函数和类的声明放在.h文件、把它们的实现放在.cpp文件,这种多文件组织方式一直被倡导。一方面,这种分离使得代码逻辑清晰,想要了解程序用到哪些全局函数和类,只要查看.h文件就可以。如果把声明和实现都揉在一起,带来的麻烦可想而知,要在一堆乱糟糟的代码中寻找函数名、类名、成员名是一种折磨。另一方面,在构建动态链接库时,这种组织方式是必需的。因为动态链接库是二进制级别上的代码复用,它的一大优点就是具体的实现过程被隐藏起来,全部揉在一个.h文件中显然不符合要求。

然而不幸的是,当程序员仍然按照这种好的习惯编写模板代码时,却出现了问题。比如下面这个简单的例子:

  1. // Bigger.h  
  2. template<typename T>  
  3. T Bigger(T,T);  
  4.   
  5. //Bigger.cpp  
  6. #include"Bigger.h"  
  7. template<typename T>  
  8. T Bigger(T a,T b)  
  9. {  
  10.     return a>b?a:b;  
  11. }  
  12.   
  13. //main.cpp  
  14. #include"Bigger.h"  
  15. #include<iostream>  
  16. using namespace std;  
  17. int main()  
  18. {  
  19.     cout<<Bigger(10,20)<<endl;  
  20.     system("pause");  
  21.     return 0;  
  22. }  

这几行代码很简单,分成了三个文件Bigger.h、Bigger.cpp以及main.cpp,分别对应模板函数Bigger的声明、定义和使用。看起来结构清晰,符合好的编码习惯,编译链接却得到这样的错误提示:

Error 1 error LNK2019: unresolved external symbol "int __cdecl Bigger<int>(int,int)" (??$Bigger@H@@YAHHH@Z) referenced in function _main E:\Codes\Chapter3Lab\includeModel\main.obj
意思是链接器找不到main.obj里Bigge<int>函数的实现。这种看起来毫无道理的链接错误,也很好的体现了模板的实例化规则。

模板的实例化规则

对于模板函数来说,只有被调用的模板函数才被实例化,这里的被调用并不要求它必须被main函数调用。某个普通函数调用了模板函数,该模板函数就将对应产生一个实例,而调用它的普通函数可能并不被main调用,也即有可能并不被执行。
模板类也有类型的实例化规则,特别的是即使显式实例化了类模板,类模板的成员函数也未必被实例化,这是模板类的“不完全”实例化规则,读者可以点击这里了解更多。

链接错误的解释

了解了模板的实例化规则,就可以对上面的链接错误做出解释了。main.cpp中调用了Bigger(10,20),按理说这将引起模板函数Bigger(T,T)被实例化为普通函数,然而在main.cpp所属的翻译单元里并没有Bigger(T,T)的实现,对main.cpp所属的翻译单元来说,Bigger(T,T)的实现是不可见的。因此,由main.cpp所属翻译单元编译得到main.obj时,编译器假设Bigger<int>(int,int)在其它翻译单元中。

Bigger.cpp虽然有Bigger(T,T)的实现,但是由于在Bigger.cpp所属翻译单元中Bigger并没有被调用,因此Bigger.cpp就没有义务对模板函数Bigger(T,T)进行实例化,于是由它产生的Bigger.obj中也找不到的Bigger<int>(int,int)。

本文前述例子中的链接错误信息正是表达的这个意思。

链接错误的进一步探讨

既然是因为Bigger.cpp没有义务对Bigger(T,T)进行实例化,那么在Bigger.cpp中增加一个调用Bigger<int>(int,int)函数的普通函数是否就可以了呢?在Bigger.cpp文件中添几行代码,如下所示:

  1. //Bigger.cpp  
  2. #include"Bigger.h"  
  3. template<typename T>  
  4. T Bigger(T a,T b)  
  5. {  
  6.     return a>b?a:b;  
  7. }  
  8. void g()  //增加一个调用Bigger<int>(int,int)的普通函数g()  
  9. {  
  10.     Bigger(1,2);  
  11. }  

编译、链接成功,允许结果正确,进一步验证了上述观点。

解决方法 - 包含模型

本文列出的例子很简单,规模小,所以按照模板的实例化规则,“人为”地介入到模板函数的实例化过程中并让程序成功运行。但是,在规模较大的程序里,想要人为介入加以控制几乎是不可能的,应该使用C++推荐的包含模型。

具体做法并不复杂:把模板的声明和定义放在一个.h文件中,凡是用到该模板的.cpp文件包含它所在的.h文件就可以了。上面的例子使用包含模型改写,最终是代码是这样的:

  1. // Bigger.h  
  2. template<typename T>  
  3. T Bigger(T a,T b)  
  4. {  
  5.     return a>b?a:b;  
  6. }  
  7.   
  8. //main.cpp  
  9. #include"Bigger.h"  
  10. #include<iostream>  
  11. using namespace std;  
  12. int main()  
  13. {  
  14.     cout<<Bigger(10,20)<<endl;  
  15.     system("pause");  
  16.     return 0;  
  17. }  

不过仍然有一个问题值得思考:当多个.cpp文件同时包含Bigger.h时,就有可能产生多份相同类型的实例化,这样是否会造成最终生成的.exe文件变得庞大?这个问题理论上是存在的,不过现在大多数编译器都对此作了一定的优化,一个模板的相同类型有多份实例化体时,编译器最终只保留一个,这样就避免了“代码膨胀”的问题。


下面给一个例子:

//main.cpp

#include "person.h"
#include "SmartPointer.h"

using namespace std;
int test() {
  //auto_ptr<person> p(new person("Cici"));
  //SmartPointer<person> p(new person("Cici"));
  //p -> tell();
  SmartPointer<person> r(new person("taoqi"));
  SmartPointer<person> p(new person("Cici"));  

  p -> tell();  
  {  
	SmartPointer<person> q = p;  
    q -> tell();  
    r = q;  
	SmartPointer<person> s(r);  
    s -> tell();  
  }  
  r -> tell();  

  return 0;
}
int main(){
  test();
  return 0;
}

//SmartPointer.h
#ifndef SMARTPOINTER_H
#define SMARTPOINTER_H


template<typename T>
class SmartPointer
{
public:
  SmartPointer(T* ptr);
  ~SmartPointer();
  SmartPointer(SmartPointer<T>& sptr);
  T* operator->();
  T& operator*();
  SmartPointer<T>& operator=(SmartPointer<T>& sptr);
  T getValue();
protected:
  T* ref;
  unsigned* ref_count;
};

template<typename T>
SmartPointer<T>::SmartPointer(T* ptr){
  ref = ptr;
  ref_count = (unsigned*)malloc(sizeof(unsigned));
  *ref_count = 1;
}

template<typename T>
SmartPointer<T>::~SmartPointer(){
  --*ref_count;
  if (*ref_count == 0) {
    delete ref;
    free(ref_count);

    ref = NULL;
    ref_count = NULL;
  }
}
template<typename T>
SmartPointer<T>::SmartPointer(SmartPointer<T>& sptr) {
  ref = sptr.ref;
  ref_count = sptr.ref_count;
  ++(*ref_count);
}
template<typename T>
T* SmartPointer<T>::operator->() {
  return ref;
}
template<typename T>
T& SmartPointer<T>::operator*() {
  return *ref;
}
template<typename T>
SmartPointer<T>& SmartPointer<T>::operator=(SmartPointer<T>& sptr){
  if (this != &sptr) {
    ref = sptr.ref;
    ref_count = sptr.ref_count;
    ++(*ref_count);
  }
  return *this;
}
template<typename T>
T getValue() {
  return *ref;
}

#endif
//person.h

#ifndef PERSON_H
#define PERSON_H

#include <string>
#include <iostream>
using namespace std;
class person
{
public:
  person(string name);
  ~person(void);
  void tell();
private:
  string name;
};

#endif

//person.cpp

#include "person.h"

person::person(string name):name(name){
}
void person::tell(){
  cout << "Hi! I am " << name << endl;
}
person::~person(){
  cout << "Bye!" << endl;
}

另外一篇文章关于内部链接和外部连接的解释:

http://www.cnblogs.com/magicsoar/p/3840682.html


内部链接与外部链接

那么什么内部链接和外部链接又是什么呢?

我们知道C++中声明和定义是可以分开的

例如在vs中,我们可以一个函数声明定义放在b.cpp中,在a.cpp只需再声明一下这个函数,就可以在a.cpp中使用这个函数了

a.cpp

复制代码
void show();

int main()
{
    show();
return 0;
}
复制代码

b.cpp

#include <iostream>
void show()
{
    std::cout << "Hello" << std::endl;
}

而通过之前的了解,我们知道每个编译单元间是相互独立不知道彼此的存在的

那么a.cpp又是如何知道show函数的定义的呢

其实在编译一个编译单元(.cpp)生成相应的obj文件过程中

编译器会将分析这个编译单元(.cpp)

将其所能提供给其他编译单元(.cpp)使用的函数,变量定义记录下来。

而将自己缺少的函数,变量的定义也记录下来。

所以可以认为a.obj和b.obj记录了以下的信息

image

 

然后在链接器连接的时候就会知道a.obj需要show函数定义,而b.obj中恰好提供了show函数的定义,通过链接,在最终的可执行文件中我们能看到show函数的运行

哪这些又和内部链接,外部链接有什么关系呢?

那些编译单元(.cpp)中能向其他编译单元(.cpp)展示,提供其定义,让其他编译单元(.cpp)使用的的函数,变量就是外部链接,例如全局变量

 

而那些编译单元(.cpp)中不能向其他编译单元(.cpp)展示,提供其定义的函数,变量就是内部链接,例如static函数,inline函数等

 

好了让我们看下编译单元,内部链接和外部链接比较正式的定义吧

编译单元:当一个c或cpp文件在编译时,预处理器首先递归包含头文件,形成一个含有所有 必要信息的单个源文件,这个源文件就是一个编译单元。

内部连接:如果一个名称对编译单元(.cpp)来说是局部的,在链接的时候其他的编译单元无法链接到它且不会与其它编译单元(.cpp)中的同样的名称相冲突。

外部连接:如果一个名称对编译单元(.cpp)来说不是局部的,而在链接的时候其他的编译单元可以访问它,也就是说它可以和别的编译单元交互。

----------------------------------------------------------------------------------------------------------------------------------------

3. 使用下面两种方式防止重复include:

#ifndef PERSON_H
#define PERSON_H
#endif

或者

#pragma once


4. 给出在定义类内部可用常量,文件作用域常量,全局常量的写法:

a. 类成员变量是无法在成员变量定义的时候初始化的(除非是const static),因此在这个时候,成员变量初始化列表是const变量初始化的唯一机会了。。。写成

class MyClass {

 private:

  const int a1 = 3;

  const char* s1 = "abc"; 

}

是大错而且特错的!!!!同时注意只能在构造函数的初始化列表里初始化

b. 定义全局变量时,extern int a; 只是声明,并没有定义,但是extern int a = 3却是在定义;当然可以在a.cpp中 extern int a = 3; 在b.cpp 中extern int a;来声明。但是比较规范的做法是可以把extern int a; 扔到b.cpp 的头文件b.h中,在b.cpp中只是定义int a = 3; 其他文件用的时候只是#include "b.h"即可。

c. static const int a = 3; 要写在.cpp中,因为只在这个文件中使用,.h文件是供其他人include用的:

因此正确的代码是:

//main.cpp

//main.cpp  
#include"MyClass.h"  
#include<iostream>  
using namespace std;  

int main()  
{  
  MyClass myClass(30,"abc");
    
  cout << "a2 = " << a2 << endl;
  cout << "s2 = " << s2 << endl;

  return 0;  
}  

//MyClass.cpp

//MyClass.cpp
#include "MyClass.h"  
#include <iostream>
using namespace std;

const int a2 = 2;
const char* const s2 = "s2";

static const int a3 = 3;
static const char* const s3 = "s3";

MyClass::MyClass(const int a = 30, const char* const s = "abc"):a1(a),s1(s){
  cout << "a3 = " << a3 << endl;
  cout << "s3 = " << s3 << endl;
}

//MyClass.h

//MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H

extern const int a2;
extern const char* const s2; 

class MyClass {
private:
  const int a1;
  const char* const s1;
public:
  MyClass(const int a, const char* const s);
};
#endif

5. 内联函数一定要写在头文件里:

inline函数的特征是在调用的地方插入相应函数的代码,所以编译之后的目标文件里是没有inline函数体的,因为在要调用的地方它都已经用相应的语句替换掉了(当然这只限于内联成功的情况)。
如果我们将inline函数写在cpp文件里,但是绝大多数情况下,在我们用第三方类库的时候,我们只有头文件和目标文件(没有cpp文件),当你调用那个内联函数时,编译器没办法找到它。所以说将inline函数写在cpp文件中是没什么用的

6. 最后附上一篇const, static等不同变量初始化的日志,引以为戒:

http://blog.csdn.net/gljseu/article/details/9750877


1、普通的变量:一般不考虑啥效率的情况下 可以在构造函数中进行赋值。考虑一下效率的可以再构造函数的初始化列表中进行。

class CA

{

public:

int data;

……

public:

CA();

……

};

CA::CA():data(0)//……#1……初始化列表方式

{

//data = http://www.mamicode.com/0;//……#1……赋值方式

};

2static 静态变量:

static变量属于类所有,而不属于类的对象,因此不管类被实例化了多少个对象,该变量都只有一个。在这种性质上理解,有点类似于全局变量的唯一性。

class CA

{

public:

static int sum;

……

public:

CA();

……

};

int CA::sum=0;//……#2……类外进行初始化

3const 常量变量:

const常量需要在声明的时候即初始化。因此需要在变量创建的时候进行初始化。一般采用在构造函数的初始化列表中进行。

class CA

{

public:

const int max;

……

public:

CA();

……

};

CA::CA():max(100)

{

……

}

4Reference 引用型变量:

引用型变量和const变量类似。需要在创建的时候即进行初始化。也是在初始化列表中进行。但需要注意用Reference类型。

class CA

{

public:

int init;

int& counter;

……

public:

CA();

……

};

CA::CA():counter(&init)

{

……

}

5const static integral 变量:

对于既是const又是static 而且还是整形变量,C++是给予特权的(但是不同的编译器可能有会有不同的支持,VC 6好像就不支持)。可以直接在类的定义中初始化。short可以,但float的不可以哦。

// 例float类型只能在类外进行初始化

// const float CA::fmin = 3.14;

class CA

{

public:

//static const float fmin = 0.0;// only static const integral data members can be initialized within a class

const static int nmin = 0;

……

public:

……

};

总结起来,可以初始化的情况有如下四个地方:

1、在类的定义中进行的,只有const  static  integral 的变量。

2、在类的构造函数初始化列表中, 包括const对象和Reference对象。

3、在类的定义之外初始化的,包括static变量。因为它是属于类的唯一变量。

4、普通的变量可以在构造函数的内部,通过赋值方式进行。当然这样效率不高。

类的定义体中只能初始化const integral data型的量。对于static型的量,那就放在.cpp文件中吧!当然了,还不能放在成员函数中(非静态成员函数可以使用静态数据成员的吧! 静态成员函数只能调用静态数据成员。),因为static量是类的,不是某个对象的。那样的话每个对象都来操作属于所有对象(类)的东西,岂不是会乱套,所以不能允许这种行为。
但是,static量可以在类的构造函数中赋值,当然是不可以放在初始化成员列表中的,可是在构造函数中赋值时不可以使用copy construction,提示这样的错误
term does not evaluate to a function taking 1 arguments
那么,对于类里面的static函数的声明和定义是这样的:
static
函数的声明可以像普通成员函数一样声明,只是在前面加上一个static关键字。
如:
private
 
static int GetXYZ();
而在,定义时,像static变量那样,也是不可以加上static关键字,若写成:
static int A::GetXYZ()
{
…………
}
就会提示:
‘static‘ should not be used on member functions defined at file scope
所以应该写成是这样:
int A::GetXYZ()
{//
他是static型函数的性质,就用其他方法来辨别吧,比如在这儿写上:this is a static function
…………
}
至于static函数的使用,可以再你所编写的代码段中这样插入:
………………
A::GetXYZ(); //
可以看出他是类的东东,不是对象的
………………
当然,对于public型的static量(假设叫CString S_str),可以这样使用:
A::S_str = "Hello !";
CString str = A::S_str;

c++成员变量初始化问题 分类: c/c 小结 2009-11-03 17:19

C++为类中提供类成员的初始化列表

类对象的构造顺序是这样的:

1.分配内存,调用构造函数时,隐式/显示的初始化各数据成员

2.进入构造函数后在构造函数中执行一般计算

1.类里面的任何成员变量在定义时是不能初始化的。

2.一般的数据成员可以在构造函数中初始化。

3.const数据成员必须在构造函数的初始化列表中初始化。

4.static要在类的定义外面初始化。

5.数组成员是不能在初始化列表里初始化的。

6.不能给数组指定明显的初始化。

6条一起,说明了一个问题:C++里面是不能定义常量数组的!因为35的矛盾。这个事情似乎说不过去啊?没有办法,我只好转而求助于静态数据成员。

到此,我的问题解决。但是我还想趁机复习一下C++类的初始化:

1.初始化列表:CSomeClass::CSomeClass() : x(0), y(1){}

2.类外初始化:int CSomeClass::myVar=3;

3.const常量定义必须初始化,C++类里面使用初始化列表;

4.C++类不能定义常量数组。