【C++】兩個類的相互引用
有時候在設計資料結構的時候,可能會遇到兩個類需要相互引用的情形。比如類A有型別為B的成員,而類B又有型別為A的成員。
那麼這種情形下,兩個類的設計上需要注意什麼呢?
本文例項原始碼github地址:https://github.com/yngzMiao/yngzmiao-blogs/tree/master/2021Q1/20210103。
同一檔案
嘗試方案
將A和B的定義都放在一個檔案中,例如:
#include <iostream>
class A {
public:
A() {
aa_ = 'A';
}
char aa_;
B b_;
};
class B {
public:
B() {
bb_ = 'B';
}
char bb_;
A a_;
};
int main() {
return 0;
}
編譯這一段程式碼,編譯出錯:
yngzmiao@yngzmiao-virtual-machine:~/test$ g++ main.cpp -o main --std=c++11
main.cpp:9:5: error: ‘B’ does not name a type
B b_;
^
編譯的報錯提示,B不是一個資料型別。可能你會想,會不會前置宣告
一下就可以了?即將程式碼修改為:
#include <iostream>
class B;
class A {
public:
A() {
aa_ = 'A';
}
char aa_;
B b_;
};
class B {
public:
B() {
bb_ = 'B';
}
char bb_;
A a_;
};
int main() {
return 0;
}
編譯這一段程式碼,編譯仍然出錯:
yngzmiao@yngzmiao-virtual-machine:~/test$ g++ main.cpp -o main --std=c++11
main.cpp:11:7: error: field ‘b_’ has incomplete type
B b_;
^
編譯時出現"field has incomplete type",通常的錯誤原因為:類或結構體的前向宣告只能用來定義指標物件或引用,因為編譯到這裡時還沒有發現定義,不知道該類或者結構的內部成員,沒有辦法具體的構造一個物件,所以會報錯。
解決辦法:將類成員改成指標就好了。程式中使用incomplete type實現前置宣告,有助與實現資料型別細節的隱藏。
按照這個辦法來進行修改,將b_的型別由B的物件修改成指向型別B的指標:
#include <iostream>
class B;
class A {
public:
A() {
aa_ = 'A';
}
char aa_;
B *b_;
};
class B {
public:
B() {
bb_ = 'B';
}
char bb_;
A a_;
};
int main() {
A tmp1;
std::cout << tmp1.aa_ << " " << tmp1.b_->bb_ << std::endl;
B tmp2;
std::cout << tmp2.bb_ << " " << tmp2.a_.aa_ << std::endl;
return 0;
}
編譯這一段程式碼,編譯順利,沒有問題。執行這段程式碼:
yngzmiao@yngzmiao-virtual-machine:~/test$ g++ main.cpp -o main --std=c++11
yngzmiao@yngzmiao-virtual-machine:~/test$ ./main
A H
B A
又有問題了,tmp1.b_->bb_的列印結果為H。這個問題很容易檢查出問題:在類A的定義中,定義了指向型別B的指標b_,但是並沒有對該指標分配記憶體空間,當然會有一些奇怪的值列印出來。可以修改為:
#include <iostream>
class B;
class A {
public:
A() {
aa_ = 'A';
b_ = new B();
}
char aa_;
B *b_;
};
class B {
public:
B() {
bb_ = 'B';
}
char bb_;
A a_;
};
int main() {
A tmp1;
std::cout << tmp1.aa_ << " " << tmp1.b_->bb_ << std::endl;
B tmp2;
std::cout << tmp2.bb_ << " " << tmp2.a_.aa_ << std::endl;
return 0;
}
編譯又出現問題了:
yngzmiao@yngzmiao-virtual-machine:~/test$ g++ main.cpp -o main --std=c++11
main.cpp: In constructor ‘A::A()’:
main.cpp:9:18: error: invalid use of incomplete type ‘class B’
b_ = new B();
^
main.cpp:3:7: error: forward declaration of ‘class B’
class B;
^
編譯錯誤的原因還是incomplete type,即型別B的結構還不知道,怎麼能new出來呢?
最終程式碼
如果想要獲得正確的程式碼,不能將new的操作放在建構函式中,放在其他地方手動建立即可:
#include <iostream>
class B;
class A {
public:
A() {
aa_ = 'A';
}
char aa_;
B *b_;
};
class B {
public:
B() {
bb_ = 'B';
}
char bb_;
A a_;
};
int main() {
A tmp1;
tmp1.b_ = new B();
std::cout << tmp1.aa_ << " " << tmp1.b_->bb_ << std::endl;
B tmp2;
std::cout << tmp2.bb_ << " " << tmp2.a_.aa_ << std::endl;
return 0;
}
編譯並執行這段程式碼:
yngzmiao@yngzmiao-virtual-machine:~/test$ g++ main.cpp -o main --std=c++11
yngzmiao@yngzmiao-virtual-machine:~/test$ ./main
A B
B A
原因分析
類A和類B相互引用比較麻煩的根本原因
在於:定義A的時候,A的裡面有B,所以就需要去檢視B的佔空間大小,但是檢視的時候又發現需要知道A的佔空間大小,從而造成死迴圈。
不同檔案
不同檔案指的是:型別A定義在A.h檔案,型別B定義在B.h檔案,同時在main.cpp中建立A、B型別的物件進行輸出。通過上文的一些經驗,可能還需要create()函式來對B指標進行new操作。
由於不同檔案的寫法,坑比較多,接下來就直接給出正確的程式碼內容。
在A.h檔案中,定義類A:
#ifndef A_H
#define A_H
class B; // highlight 1
class A {
public:
A() {
aa_ = 'A';
}
void create();
char aa_;
B *b_;
};
#endif
在A.cpp檔案中,定義類A方法的實現:
#include "B.h" // highlight 2
void A::create() {
b_ = new B();
}
在B.h檔案中,定義類B:
#ifndef B_H
#define B_H
#include "A.h" // highlight 3
class B {
public:
B() {
bb_ = 'B';
}
char bb_;
A a_;
};
#endif
最終在main.cpp檔案中,使用類A和類B:
#include "B.h"
#include <iostream>
int main() {
A tmp1;
tmp1.create();
std::cout << tmp1.aa_ << " " << tmp1.b_->bb_ << std::endl;
B tmp2;
std::cout << tmp2.bb_ << " " << tmp2.a_.aa_ << std::endl;
return 0;
}
對這一段程式碼中的坑進行羅列:
- A.h中包含B.h且B.h中包含A.h,標頭檔案不能迴圈include。需要在定義非指標類的那個.h檔案include另一個;而定義指標類的那個.h檔案需要使用前置宣告;
- create()函式不能在.h檔案中進行定義,因為在該函式中需要進行new操作,而該操作需要又另一個類的完整定義,即需要include。由於第一點原因,只好在.cpp檔案中進行方法的實現。
總結
兩個類相互引用,一個用物件、include;另一個用指標、前置宣告、create手動new。手動new的過程不能在建構函式中進行,同時需要知道另一個類的完整定義(include)。
注意:本文所舉例的部分都沒有對new出來的空間進行delete操作,會引發記憶體洩漏。這部分需要讀者自行補充。
相關閱讀
相關文章
- C++ 中兩個類互相引用的問題C++
- C++ 引用 (交換兩個數的值)C++
- c++ 類的函式引用 指標C++函式指標
- AWS 兩個VPC相互連線
- android之兩個activity相互跳轉Android
- 兩個select下拉選單的option相互移動
- 簡單實現兩個activity相互跳轉
- c與c++的相互呼叫C++
- 【C++】引用C++
- c++引用C++
- C++ 引用C++
- C++的引用技術C++
- 資源相互引用時 需新增 PerformSubstitution=TrueORM
- c++中string類物件和字元陣列之間的相互轉換C++物件字元陣列
- 洗牌的一個C++類! (轉)C++
- python和c++的相互呼叫教程PythonC++
- C++取反交換兩個數的值C++
- C++ 一種交換兩個數的思路C++
- C++引用的作用和用法C++
- Java Pom兩個模組需要互相引用怎麼辦Java
- C++左值引用與右值引用C++
- c++ 左值引用與右值引用C++
- C++ 右值引用和左值引用C++
- 一個隨機數的類c++隨機C++
- 詳解C++引用C++
- c++筆記_引用C++筆記
- C++右值引用C++
- 【c++】引用計數C++
- java的引用:用C++/C的引用和指標去理解JavaC++指標
- C++箴言:理解typename的兩個含義(轉)C++箴言
- 119 C++中的引用&C++
- C++ 11 中的右值引用C++
- C++中的指標與引用C++指標
- 這兩個java類的作用是什麼?Java
- c++的一個int128類C++
- C++類內成員變數可以定義引用型別嗎C++變數型別
- C++ 左值引用和右值引用之間的轉換C++
- Android activity相互跳轉後臺出現兩個頁面的坑Android