總結一句話就是: 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文件中顯然不符合要求。
然而不幸的是,當程序員仍然按照這種好的習慣編寫模板代碼時,卻出現了問題。比如下面這個簡單的例子:
這幾行代碼很簡單,分成了三個文件Bigger.h、Bigger.cpp以及main.cpp,分別對應模板函數Bigger的聲明、定義和使用。看起來結構清晰,符合好的編碼習慣,編譯鏈接卻得到這樣的錯誤提示:
Error 1 error LNK2019: unresolved external symbol "int __cdecl Bigger
意思是鏈接器找不到main.obj裡Bigge
模板的實例化規則
對於模板函數來說,只有被調用的模板函數才被實例化,這裡的被調用並不要求它必須被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
Bigger.cpp雖然有Bigger(T,T)的實現,但是由於在Bigger.cpp所屬翻譯單元中Bigger並沒有被調用,因此Bigger.cpp就沒有義務對模板函數Bigger(T,T)進行實例化,於是由它產生的Bigger.obj中也找不到的Bigger
本文前述例子中的鏈接錯誤信息正是表達的這個意思。
鏈接錯誤的進一步探討
既然是因為Bigger.cpp沒有義務對Bigger(T,T)進行實例化,那麼在Bigger.cpp中增加一個調用Bigger
編譯、鏈接成功,允許結果正確,進一步驗證了上述觀點。
解決方法 - 包含模型
本文列出的例子很簡單,規模小,所以按照模板的實例化規則,“人為”地介入到模板函數的實例化過程中並讓程序成功運行。但是,在規模較大的程序裡,想要人為介入加以控制幾乎是不可能的,應該使用C++推薦的包含模型。
具體做法並不復雜:把模板的聲明和定義放在一個.h文件中,凡是用到該模板的.cpp文件包含它所在的.h文件就可以了。上面的例子使用包含模型改寫,最終是代碼是這樣的:
不過仍然有一個問題值得思考:當多個.cpp文件同時包含Bigger.h時,就有可能產生多份相同類型的實例化,這樣是否會造成最終生成的.exe文件變得龐大?這個問題理論上是存在的,不過現在大多數編譯器都對此作了一定的優化,一個模板的相同類型有多份實例化體時,編譯器最終只保留一個,這樣就避免了“代碼膨脹”的問題。
下面給一個例子:
//main.cpp
#include "person.h" #include "SmartPointer.h" using namespace std; int test() { //auto_ptrp(new person("Cici")); //SmartPointer p(new person("Cici")); //p -> tell(); SmartPointer r(new person("taoqi")); SmartPointer p(new person("Cici")); p -> tell(); { SmartPointer q = p; q -> tell(); r = q; SmartPointer s(r); s -> tell(); } r -> tell(); return 0; } int main(){ test(); return 0; }
#ifndef SMARTPOINTER_H #define SMARTPOINTER_H template//person.hclass SmartPointer { public: SmartPointer(T* ptr); ~SmartPointer(); SmartPointer(SmartPointer & sptr); T* operator->(); T& operator*(); SmartPointer & operator=(SmartPointer & sptr); T getValue(); protected: T* ref; unsigned* ref_count; }; template SmartPointer ::SmartPointer(T* ptr){ ref = ptr; ref_count = (unsigned*)malloc(sizeof(unsigned)); *ref_count = 1; } template SmartPointer ::~SmartPointer(){ --*ref_count; if (*ref_count == 0) { delete ref; free(ref_count); ref = NULL; ref_count = NULL; } } template SmartPointer ::SmartPointer(SmartPointer & sptr) { ref = sptr.ref; ref_count = sptr.ref_count; ++(*ref_count); } template T* SmartPointer ::operator->() { return ref; } template T& SmartPointer ::operator*() { return *ref; } template SmartPointer & SmartPointer ::operator=(SmartPointer & sptr){ if (this != &sptr) { ref = sptr.ref; ref_count = sptr.ref_count; ++(*ref_count); } return *this; } template T getValue() { return *ref; } #endif
#ifndef PERSON_H #define PERSON_H #include#include using namespace std; class person { public: person(string name); ~person(void); void tell(); private: string name; }; #endif
#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
#includevoid show() { std::cout << "Hello" << std::endl; }
而通過之前的了解,我們知道每個編譯單元間是相互獨立不知道彼此的存在的
那麼a.cpp又是如何知道show函數的定義的呢
其實在編譯一個編譯單元(.cpp)生成相應的obj文件過程中
編譯器會將分析這個編譯單元(.cpp)
將其所能提供給其他編譯單元(.cpp)使用的函數,變量定義記錄下來。
而將自己缺少的函數,變量的定義也記錄下來。
所以可以認為a.obj和b.obj記錄了以下的信息
然後在鏈接器連接的時候就會知道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" #includeusing namespace std; int main() { MyClass myClass(30,"abc"); cout << "a2 = " << a2 << endl; cout << "s2 = " << s2 << endl; return 0; }
//MyClass.cpp #include "MyClass.h" #includeusing 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 #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
inline函數的特征是在調用的地方插入相應函數的代碼,所以編譯之後的目標文件裡是沒有inline函數體的,因為在要調用的地方它都已經用相應的語句替換掉了(當然這只限於內聯成功的情況)。
如果我們將inline函數寫在cpp文件裡,但是絕大多數情況下,在我們用第三方類庫的時候,我們只有頭文件和目標文件(沒有cpp文件),當你調用那個內聯函數時,編譯器沒辦法找到它。所以說將inline函數寫在cpp文件中是沒什麼用的
6. 最後附上一篇const, static等不同變量初始化的日志,引以為戒:
http://blog.csdn.net/gljseu/article/details/9750877
class CA
{
public:
int data;
……
public:
CA();
……
};
CA::CA():data(0)//……#1……初始化列表方式
{
//data = 0;//……#1……賦值方式
};
2、static 靜態變量:
static變量屬於類所有,而不屬於類的對象,因此不管類被實例化了多少個對象,該變量都只有一個。在這種性質上理解,有點類似於全局變量的唯一性。
class CA
{
public:
static int sum;
……
public:
CA();
……
};
int CA::sum=0;//……#2……類外進行初始化
3、const 常量變量:
const常量需要在聲明的時候即初始化。因此需要在變量創建的時候進行初始化。一般采用在構造函數的初始化列表中進行。
class CA
{
public:
const int max;
……
public:
CA();
……
};
CA::CA():max(100)
{
……
}
4、Reference 引用型變量:
引用型變量和const變量類似。需要在創建的時候即進行初始化。也是在初始化列表中進行。但需要注意用Reference類型。
class CA
{
public:
int init;
int& counter;
……
public:
CA();
……
};
CA::CA():counter(&init)
{
……
}
5、const 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++裡面是不能定義常量數組的!因為3和5的矛盾。這個事情似乎說不過去啊?沒有辦法,我只好轉而求助於靜態數據成員。
到此,我的問題解決。但是我還想趁機復習一下C++類的初始化:
1.初始化列表:CSomeClass::CSomeClass() : x(0), y(1){}
2.類外初始化:int CSomeClass::myVar=3;
3.const常量定義必須初始化,C++類裡面使用初始化列表;
4.C++類不能定義常量數組。