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

来源:互联网 发布:linux oracle启动命令 编辑:程序博客网 时间:2024/04/29 05:07

总结一句话就是: 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_Htemplate<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_Hextern 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 类型默认作用域是这个文件,只有加了extern才可以被外部的单元引用!!!!!!

所以:

int w = 1; static int x = 2;const int y = 3;extern const int z = 4;

只有w 和 z可以被外部引用,const int y = 3; 默认是当前文件!!!!!!!!!!!!!!!!!!!!!!!!!

7. 再补充一点,对于const static 只能在类中定义的时候初始化,不能写在初始化的列表中:

例如:

class A {

 public:

  static const int a = 3; 

};

这一点请牢记!!!!!!!

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

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


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

class CA

{

public:

int data;

……

public:

CA();

……

};

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

{

//data = 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++类不能定义常量数组。






0 0
原创粉丝点击