歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> 詳解C++ 類的前置聲明的使用

詳解C++ 類的前置聲明的使用

日期:2017/3/1 9:30:13   编辑:Linux編程

剛開始學習C++的人都會遇到這樣的問題:
定義一個類 class A,這個類裡面使用了類B的對象b,然後定義了一個類B,裡面也包含了一個類A的對象a,就成了這樣:

//a.h  
#include "b.h"  
class A  
{  
....  
private:  
    B b;  
};  


//另一個頭文件  b.h  
#include "a.h"  
class B  
{  
....  
private:  
    A a;  
};  

一編譯,就出現了一個互包含的問題了,這時就有人跳出來說,這個問題的解決辦法可以這樣,在a.h文件中聲明類B,然後使用B的指針。

//a.h   
//#include "b.h"  
class B;   
class A   
{  
 ....   
private:  
 B *b;   
};   


//b.h   
#include "a.h"   
class B  
{  
 ....   
private:  
 A a;   
};  

然後,問題就解決了。

但是,有人知道問題是為什麼就被解決的嗎,也就是說,加了個前置聲明為什麼就解決了這樣的問題。下面,讓我來探討一下這個前置聲明。

類的前置聲明是有許多的好處的。我們使用前置聲明的一個好處是,從上面看到,當我們在類A使用類B的前置聲明時,我們修改類B時,只需要重新編譯類B,而不需要重新編譯a.h的(當然,在真正使用類B時,必須包含b.h)。

另外一個好處是減小類A的大小,上面的代碼沒有體現,那麼我們來看下:

//a.h  
class B;  
class A  
{  
    ....  
private:  
    B *b;  
....  
};  



//b.h  
class B  
{  
....  
private:  
    int a;  
    int b;  
    int c;  
};  

我們看上面的代碼,類B的大小是12(在32位機子上)。
如果我們在類A中包含的是B的對象,那麼類A的大小就是12(假設沒有其它成員變量和虛函數)。如果包含的是類B的指針*b變量,那麼類A的大小就是4,所以這樣是可以減少類A的大小的,特別是對於在STL的容器裡包含的是類的對象而不是指針的時候,這個就特別有用了。

在前置聲明時,我們只能使用的就是類的指針和引用(因為引用也是居於指針的實現的)。

那麼,問你一個問題,為什麼前置聲明只能使用類型的指針和引用呢

如果你回答到:那是因為指針是固定大小,並且可以表示任意的類型,那麼可以給你80分了。為什麼只有80分,因為還沒有完全回答到。想要更詳細的答案,我們看下下面這個類:

class A  
{  
public:  
    A(int a):_a(a),_b(_a){} // _b is new add  

    int get_a() const {return _a;}  
    int get_b() const {return _b;} // new add  
private:  
    int _b; // new add  
    int _a;  
};  

我們看下上面定義的這個類A,其中_b變量和get_b()函數是新增加進這個類的。
那麼我問你,在增加進_b變量和get_b()成員函數後這個類發生了什麼改變,思考一下再回答。

好了,我們來列舉這些改變:

  1. 增加了_b變量和get_b()成員函數;
  2. 這個類的大小改變了,原來是4,現在是8。
  3. 成員_a的偏移地址改變了,原來相對於類的偏移是0,現在是4了。

上面的改變都是我們顯式的、看得到的改變。還有一個隱藏的改變,想想是什麼。。。
這個隱藏的改變是A的默認構造函數和默認拷貝構造函數發生了改變
由上面的改變可以看到,任何調用類A的成員變量或成員函數的行為都需要改變,因此,我們的a.h需要重新編譯。

如果我們的b.h是這樣的:

//b.h  
#include "a.h"  
class B  
{  
...  
private:  
    A a;  
}; 

那麼我們的b.h也需要重新編譯。

如果是這樣的:

//b.h  
class A;  
class B  
{  
...  
private:  
    A *a;  
}; 

那麼我們的b.h就不需要重新編譯。

像我們這樣前置聲明類A

class A;

是一種不完整的聲明,只要類B中沒有執行需要了解類A的大小或者成員的操作,則這樣的不完整聲明允許聲明指向A的指針和引用。
而在前一個代碼中的語句

A a;

是需要了解A的大小的,不然是不可能知道如果給類B分配內存大小的,因此不完整的前置聲明就不行,必須要包含a.h來獲得類A的大小,同時也要重新編譯類B

再回到前面的問題,使用前置聲明只允許的聲明是指針或引用的一個原因是只要*這個聲明沒有執行需要了解類A的大小或者成員的操作就可以了,所以聲明成指針或引用是沒有執行需要了解類A的大小或者成員的操作的。

------------------------------分割線------------------------------

C++ Primer Plus 第6版 中文版 清晰有書簽PDF+源代碼 http://www.linuxidc.com/Linux/2014-05/101227.htm

讀C++ Primer 之構造函數陷阱 http://www.linuxidc.com/Linux/2011-08/40176.htm

讀C++ Primer 之智能指針 http://www.linuxidc.com/Linux/2011-08/40177.htm

讀C++ Primer 之句柄類 http://www.linuxidc.com/Linux/2011-08/40175.htm

將C語言梳理一下,分布在以下10個章節中:

  1. Linux-C成長之路(一):Linux下C編程概要 http://www.linuxidc.com/Linux/2014-05/101242.htm
  2. Linux-C成長之路(二):基本數據類型 http://www.linuxidc.com/Linux/2014-05/101242p2.htm
  3. Linux-C成長之路(三):基本IO函數操作 http://www.linuxidc.com/Linux/2014-05/101242p3.htm
  4. Linux-C成長之路(四):運算符 http://www.linuxidc.com/Linux/2014-05/101242p4.htm
  5. Linux-C成長之路(五):控制流 http://www.linuxidc.com/Linux/2014-05/101242p5.htm
  6. Linux-C成長之路(六):函數要義 http://www.linuxidc.com/Linux/2014-05/101242p6.htm
  7. Linux-C成長之路(七):數組與指針 http://www.linuxidc.com/Linux/2014-05/101242p7.htm
  8. Linux-C成長之路(八):存儲類,動態內存 http://www.linuxidc.com/Linux/2014-05/101242p8.htm
  9. Linux-C成長之路(九):復合數據類型 http://www.linuxidc.com/Linux/2014-05/101242p9.htm
  10. Linux-C成長之路(十):其他高級議題

Copyright © Linux教程網 All Rights Reserved