程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++ 文件include規則 常量定義

C++ 文件include規則 常量定義

編輯:C++入門知識

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
  3. T Bigger(T,T);
  4. //Bigger.cpp
  5. #include"Bigger.h"
  6. template
  7. T Bigger(T a,T b)
  8. {
  9. return a>b?a:b;
  10. }
  11. //main.cpp
  12. #include"Bigger.h"
  13. #include
  14. using namespace std;
  15. int main()
  16. {
  17. cout< system("pause");
  18. return 0;
  19. }

    這幾行代碼很簡單,分成了三個文件Bigger.h、Bigger.cpp以及main.cpp,分別對應模板函數Bigger的聲明、定義和使用。看起來結構清晰,符合好的編碼習慣,編譯鏈接卻得到這樣的錯誤提示:

    Error 1 error LNK2019: unresolved external symbol "int __cdecl Bigger(int,int)" (??$Bigger@H@@YAHHH@Z) referenced in function _main E:\Codes\Chapter3Lab\includeModel\main.obj
    意思是鏈接器找不到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(int,int)在其它翻譯單元中。

    Bigger.cpp雖然有Bigger(T,T)的實現,但是由於在Bigger.cpp所屬翻譯單元中Bigger並沒有被調用,因此Bigger.cpp就沒有義務對模板函數Bigger(T,T)進行實例化,於是由它產生的Bigger.obj中也找不到的Bigger(int,int)。

    本文前述例子中的鏈接錯誤信息正是表達的這個意思。

    鏈接錯誤的進一步探討

    既然是因為Bigger.cpp沒有義務對Bigger(T,T)進行實例化,那麼在Bigger.cpp中增加一個調用Bigger(int,int)函數的普通函數是否就可以了呢?在Bigger.cpp文件中添幾行代碼,如下所示:

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

      編譯、鏈接成功,允許結果正確,進一步驗證了上述觀點。

      解決方法 - 包含模型

      本文列出的例子很簡單,規模小,所以按照模板的實例化規則,“人為”地介入到模板函數的實例化過程中並讓程序成功運行。但是,在規模較大的程序裡,想要人為介入加以控制幾乎是不可能的,應該使用C++推薦的包含模型。

      具體做法並不復雜:把模板的聲明和定義放在一個.h文件中,凡是用到該模板的.cpp文件包含它所在的.h文件就可以了。上面的例子使用包含模型改寫,最終是代碼是這樣的:

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

        不過仍然有一個問題值得思考:當多個.cpp文件同時包含Bigger.h時,就有可能產生多份相同類型的實例化,這樣是否會造成最終生成的.exe文件變得龐大?這個問題理論上是存在的,不過現在大多數編譯器都對此作了一定的優化,一個模板的相同類型有多份實例化體時,編譯器最終只保留一個,這樣就避免了“代碼膨脹”的問題。


        下面給一個例子:

        //main.cpp

        #include "person.h"
        #include "SmartPointer.h"
        
        using namespace std;
        int test() {
          //auto_ptr p(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;
        }

        //SmartPointer.h
        #ifndef SMARTPOINTER_H
        #define SMARTPOINTER_H
        
        
        template
        class 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
        
        //person.h

        #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
        

        //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 
        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  
        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 
        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 = 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++類不能定義常量數組。






  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved