類別互相使用 (mutual reference, circular relationship) 是一個在 C/C++ 程式裡常常遇到的狀況範例一--- A.h --- class A { public: int fun(B& b); private: B* ptrB; }; --- B.h --- class B { public: int fun(); private: A a; }; 或是 範例二--- A.h --- class A { public: int fun(B& b); private: B* ptrB; }; --- B.h --- class B { public: int fun(); private: A* a; }; 或是 範例三--- A.h --- class A { public: int fun(B b); private: B* ptrB; }; --- B.h --- class B { public: int fun(); private: A a; }; 注意下面這種狀況不可能發生--- A.h --- class A { public: int fun(); private: B b; }; --- B.h --- class B { public: int fun(); private: A a; }; 該如何使用 #include 呢以範例一來說, 直覺的作法當然就 --- A.h --- #pragma once #include "B.h" class A { public: int fun(B& b); private: B* ptrB; }; --- B.h --- #pragma once #include "A.h" class B { public: int fun(); private: A a; }; 在你知道怎麼解決之前, 先弄清楚到底發生了什麼事??? 某一個單一狀況的解決方案並不重要, 重要的是為什麼, 以後發生類似的狀況你才能夠解決 |
原因C/C++ 的編譯器在編譯每一個 .c/.cpp 的檔案時, 都只由第一列順序看到最後一列, 看一遍 (one pass) 而已, 所以所有程式裡定義的變數, 型態, 函式 名稱在使用之前一定要先宣告(declaration)或事先定義(definition) 過, 編譯器在看到使用的敘述時才知道如何檢查用法是否正確, 如何幫你轉換資料的型態 先宣告/定義, 再使用以範例一為例, 讓我們看看上面互相 #include 的解決方法會造成什麼錯誤: C/C++ 的程式編譯的時候是用 .c/.cpp 檔案為單位的, 所以我們先看 A.cpp --- A.cpp --- #include "A.h" int A::fun(B& b) { ptrB->fun(); b.fun(); } 前處理器會把 A.h 引入, 得到 --- A.cpp --- #include "B.h" class A { public: int fun(B& b); private: B* ptrB; }; int A::fun(B& b) { ptrB->fun(); b.fun(); } 然後前處理器再把 B.h 引入, 得到 --- A.cpp --- #include "A.h" class B { public: int fun(); private: A a; // vc2010 error C2146: 語法錯誤 : 遺漏 ';' (在識別項 'a' 之前) 這個時候前處理器看到 #include "A.h", 但是因為 #pragma once 的關係, 不再繼續引入
A.h 了, 你也看到類別 B 會在類別 A 之前定義, 所以在編譯器看到類別 B 的定義裡面的 A
a; 這個敘述的時候就不行了.... 編譯器編譯 B.cpp 時會看到 --- B.cpp --- #include "B.h" int B::fun() { a.fun(); } 前處理器會把 B.h 引入, 得到 --- B.cpp --- #include "A.h" class B { public: int fun(); private: A a; }; int B::fun() { a.fun(); } 前然前處理器後再把 A.h 引入, 得到 --- B.cpp --- #include "B.h" class A { public: int fun(B& b); // error C2061: 語法錯誤 : 識別項 'B' 這個時候前處理器看到 #include "B.h", 但是因為 #pragma once 的關係, 不再繼續引入
B.h 了, 你看到類別 A 會在類別 B 之前定義, 所以在編譯器看到類別 A 的定義裡面的 B
&b; 這個敘述的時候就不行了.... 所以上面這兩種狀況都會導致編譯錯誤 |
該怎麼解決??範例一和範例二是可以很快解決的, 範例三的話需要把它轉成範例一才可以, 下面的藍字部份是範例一的修改方法--- A.h --- class B; // forward declaration 前向宣告, 跟編譯器說 B 是個類別 class A { public: int fun(B& b); private: B* ptrB; }; --- A.cpp --- #include "A.h" #include "B.h" // 需要引入完整的類別 B 的定義, 處理接下來 ptrB->fun() 敘述時, // 編譯器才知道 fun() 是 B 的成員函式 int A::fun(B& b) { ptrB->fun(); b.fun(); } --- B.h --- #include "A.h" class B { public: int fun(); private: A a; }; --- B.cpp --- #include "B.h" int B::fun() { a.fun(); } |
再一次檢查 A.cpp 和 B.cpp 分別的編譯是不是所有需要使用到的識別字都符合「先定義再使用」的原則 先看 A.cpp--- A.cpp --- #include "A.h" #include "B.h" int A::fun(B& b) { ptrB->fun(); b.fun(); } 前處理器會把 A.h 引入, 得到 --- A.cpp --- class B; class A { public: int fun(B& b); private: B* ptrB; }; #include "B.h" int A::fun(B& b) { ptrB->fun(); b.fun(); } 然後前處理器再把 B.h 引入, 得到 --- A.cpp --- class B; class A { public: int fun(B& b); private: B* ptrB; }; class B { public: int fun(); private: A a; 這個時候編譯器可以完全正確運作, 用到 B&, B* 前編譯器先知道 B 是一個類別, 用到 A a; 之前需要有完整的 A 的定義 編譯器編譯 B.cpp 時會看到--- B.cpp --- #include "B.h" int B::fun() { a.fun(); } 前處理器會把 B.h 引入, 得到 --- B.cpp --- #include "A.h" class B { public: int fun(); private: A a; }; int B::fun() { a.fun(); } 前然前處理器後再把 A.h 引入, 得到 --- B.cpp --- class B; class A { public: int fun(B& b); private: B* ptrB; }; class B { public: int fun(); private: A a; }; int B::fun() { a.fun(); } 這個時候編譯器可以完全正確運作, 用到 B&, B* 前編譯器先知道 B 是一個類別, 用到 A a; 之前需要有完整的類別 A 的定義 |
回
C++ 物件導向程式設計課程
首頁
製作日期: 04/21/2016 by 丁培毅 (Pei-yih Ting) E-mail: pyting@mail.ntou.edu.tw TEL: 02 24622192x6615 海洋大學 電機資訊學院 資訊工程學系 Lagoon |