1. 程式人生 > >C++中的前向宣告(ForwardDeclaration)

C++中的前向宣告(ForwardDeclaration)

  本文描述了對C++前向宣告的理解,和c++的標頭檔案以及實現檔案的重新理解,更重要的一點是對c++中兩個類相互包含引用問題的總結。文章引用了部分前人經驗並加上一點自己的理解,因此寫下來以加深自己的理解。

  1、c++的#include的預編譯

       例如:#include<iostream>        該預編譯指令導致前處理器將iostream檔案的內容新增到程式中。#指令:預處理指令以#號開頭,並且#號必須是該行除了任何空白字元外的第一個字元。
#後是指令關鍵字,在關鍵字和#號之間允許存在任意個數的空白字元。整行語句構成了一條預處理指令,該指令將在編譯器進行編譯之前對原始碼做某些轉換。#include預處理指令的作用是在指令處展開被包含的檔案。展開被包含的檔案之後,在程式碼就可以正常地呼叫該檔案中所宣告的變數和函式。#include指令有兩種使用方法:第一種:
#include <xxx.h>  第二種:#include "xxx.h"。
第一種方法將待包含的標頭檔案使用尖括號括起來,預處理程式會在系統預設目錄或者括號內的路徑查詢,通常用於包含系統中自帶的公共標頭檔案。
第二種方法將待包含的標頭檔案使用雙引號引起來,預處理程式會在程式原始檔所在目錄查詢,如果未找到則去系統預設目錄查詢,通常用於包含程式作者編寫的私有標頭檔案。

2、c++中的單獨編譯

  c++允許甚至程式設計師將元件函式放在獨立的檔案中,可以單獨編譯這些檔案,然後將它們連結成可執行的程式。
  • 首先,我們可以將所有東西都放在一個.cpp檔案內,然後編譯器就將這個.cpp編譯成.obj。

    就是編譯單元了,一個程式,可以由一個編譯單元組成,也可以有多個編譯單元組成.。如果你不想讓你的原始碼變得很難閱讀的話,就請使用多個編譯單元吧(一個函式不能放到兩個編譯單元裡面,但兩個以上就可以分別放在一個單元,也就是cpp裡面)。那麼就是一個.cpp對應一個.obj,然後將所有的obj連結起來(通過一個叫連結器的程式),

    組成一個.exe,也就是程式了。如果一個.cpp要用到另一個.cpp定義的函式怎麼辦? 只需在這個.cpp種寫上他的函式宣告

    就可以了.其餘工作由連結器幫你完成,你可以隨便呼叫該函式。


  • c++程式預編譯和連結階段是分開的,更加詳細需要了解編譯原理,宣告僅僅是將一個符號引入到一個作用域。而定義提供了一個實體在程式中的唯一描述。在一個給定的定義域中重複宣告一個符號是可以的 

    ,但是卻不能重複定義,否則將會引起編譯錯誤。在編譯時,編譯器只檢測程式語法和函式、變數是否被宣告。如果函式未被宣告,編譯器會給出一個警告,但可以生成目標檔案。而在連結程式時,連結器會在所有的目標檔案中找尋函式的實現。如果找不到,那到就會報連結錯誤碼( Linker Error)。在VC下,這種錯誤一般是:Link 2001錯誤,意思說是說,連結器未能找到函式的實現。連結把不同編譯單元產生的符號聯絡起來。有兩種連結方式:內部連結和外部連結。

3、c++中的前向宣告以及兩個類相互包含引用問題

  •     前向宣告:可以宣告一個類而不定義它。這個宣告被稱為前向宣告(forward declaration)。例如:class name。在宣告之後,定義之前,類name是一個不完全型別(incompete type),即已知name是一個型別,但不知道包含哪些成員。不完全型別只能以有限方式使用,不能定義該型別的物件,不完全型別只能用於定義指向該型別的指標及引用,或者用於宣告(而不是定義)使用該型別作為形參型別或返回型別的函式。
    類的前向宣告之適用於指標和引用的定義,如果是普通類型別就得使用include了。 
  • 兩個類之前的相互引用包含問題。   
     一下文章轉載於http://www.cppblog.com/yycmmc/archive/2012/06/04/177427.html和http://www.cnblogs.com/zendu/p/4987971.html 在構造自己的類時,有可能會碰到兩個類之間的相互引用問題,例如:定義了類A類B,A中使用了B定義的型別,B中也使用了A定義的型別
class A
{
      int i;
      B b;
}
class B
{
      int i;
      A* a;
}
請注意上面的定義內容,一般情況下是不能出現類A,類B相互引用都定義物件,即如下的樣子
class A
{
    int i;
    B b;
}
class B
{
     int i;
     A a;
}
在這種情況下,想想能夠有a.b.a.b.a.b.a.b.a.b…………,很有點子子孫孫無窮盡之狀,那麼我的機器也無法承受。最主要的還是這種關係很難存在,也很難管理。這種定義方式類同程式中的死迴圈。所以,一般來說,兩者的定義,至少有一方是使用指標,或兩者都使用指標,但是決不能兩者都定義實體物件。 
言歸正傳,那麼,在定義時因為相互引用肯定會需要相互包含頭文件,假如僅僅只是在各自的頭文件中包含對方的頭文件,是不能通過編譯的,如下:
//class A.h
#include "B.h"
class A
{
     int i;
     B b;
}
//class B.h
#include "A.h"
class B
{
    int i;
    A *a;
}
如上的包含方式可能會造成編譯器有錯誤提示:A.h文件中使用了未知型別B。怎麼辦?
一般的做法是:兩個類的頭文件之中,選一個包含另一個類的頭文件,但另一個頭文件中只能採用class *的申明形式,而在實現文件中(*.cpp)中包含頭文件,如下:
//class A.h
#include "B.h"
class A
{
     int i;
     B b;
}
//class B.h
class A;

class B
{
    int i;
    A *a;
}
//B.cpp
//在B.cpp中的文件包含處要有下面語句,否則不能呼叫成員a的任何內容

#include "A.h"
B::B()
{
    ……
}
當兩個類互相包含和互相在本類中定義另外一個類的物件指標引用時候,必須保證當一個類先宣告或者定義時候才可以在另外一個類中使用
如在類A的前面宣告Class B 在B類前宣告Class A,這種方法是隻適用於用來定義類指標或者類應用 而不能來定義類物件變數或者函式的變數名 這是因為上面僅僅聲明瞭類 說明這個類是一個標識 還沒有實現所以不能定義物件變數。
但是對於如果想要在一個類中定義物件來說比如在A.h檔案中定義B類物件這樣的話,就需要在A.h中包含B類標頭檔案 #include “B.H” 。
我覺得吧,只有包含了一個類的標頭檔案 ,這樣相當於把這個類定義插入到裡面了, 所以可以直接定義物件 ,定義物件需要實在的內容,而不僅僅只是宣告一下類,說明這個類是個標識 必須定義好實體內容才可以定義變數。
一般所見的內容就是定義了B類物件變數後 則只能在B.H中定義A類指標或者引用了,所以只需在B.H裡面宣告 CLASS A就可以了。
確實一般都是如此:兩個類一個包含標頭檔案,另外一個宣告類並且在其實現檔案中當然還得包含這個標頭檔案了。所以一旦是個類名字宣告 則只能是定義物件指標或引用(無論是單個定義還是對於函式引數 定義都如此)。