從C#到C++容易出現的問題解答
前言
最近在學習用c++寫一下3D引擎(廢話一下,叫做WuguiEngine,首先Wugui是我的外號,也是代表這個引擎很粗糙,速度很慢,呵呵.之后等引擎成熟一點我再寫寫相關的一些文章).這幾天寫起來感覺c++好多地方和c#區別很大,這里大概寫寫這兩天碰到的一些問題,也許是從c#到c++的時候的一些通病,對c++ 新手也有一定的幫助.
另外在本文中,多數是將類拆分為.h文件和.cpp文件這樣對于工程來說更好管理.
另外閱讀本文需要一定的c++基礎,本文主要是一些雜碎的心得,而不是完整的教學,下面就開始解答從c#到c++的問題。
頭文件
頭文件是c#沒有的內容,所以用好頭文件是學習c++首先需要解決的一個內容.頭文件在正常的時候應該是只放上定義,實現代碼應該寫在同名的cpp文件里面
1) 重復包含:頭文件A.H被B.H與C.H同時包含,則在編譯的時候,A.H里面定義的代碼將被定義兩次,造成"Symbol already defined",在這種時候應該在.H文件頭尾處加上下列的代碼就可以解決這個問題:
- #ifndef _NAME_H
- #define _NAME_H
- //add defination
- #endif
其實#ifndef與#define的內容只要對于每個.h文件是唯一的就好了.
2)嵌套包含:在A.H(class A)中使用了class B,又在B.H(class B)中使用了class A,這個時候需要在類的前面加入前置定義,下面的代碼:
- #ifndef _A_H
- #define _A_H
- #include "B.H"
- //前置聲明
- class B;
- class A
- {
- //add defination
- }
- #endif
這點說明,頭文件并不能代替前置聲明,對于有namespace的情況,前置聲明更加復雜,下一小節將談談這點內容.
3)一些經驗:
頭文件包含其實是一想很煩瑣的工作,不但我們看著累,編譯器編譯的時候也很累,再加上頭文件中常常出現的宏定義。感覺各種宏定義的展開是非常耗時間的,遠不如自定義函數來得速度。我僅就不同頭文件、源文件間的句則結構問題提出兩點原則,僅供參考:
第一個原則應該是,如果可以不包含頭文件,那就不要包含了。這時候前置聲明可以解決問題。如果使用的僅僅是一個類的指針,沒有使用這個類的具體對象(非指針),也沒有訪問到類的具體成員,那么前置聲明就可以了。因為指針這一數據類型的大小是特定的,編譯器可以獲知。
第二個原則應該是,盡量在CPP文件中包含頭文件,而非在頭文件中。假設類A的一個成員是是一個指向類B的指針,在類A的頭文件中使用了類B的前置聲明并便宜成功,那么在A的實現中我們需要訪問B的具體成員,因此需要包含頭文件,那么我們應該在類A的實現部分(CPP文件)包含類B的頭文件而非聲明部分(H文件)。
第三個原則是(我自己覺得的):盡量對引用的其他的類都加上前置聲明.這樣不僅使得程序的結構更加清楚,而且使得可以少出一些編譯錯誤
namespace
- #ifndef _BASEGAME_H
- #define _BASEGAME_H
- namespace WuguiEngine
- {
- //前置聲明
- class IUpdatable;
- //前置聲明
- namespace Graphics
- {
- class GraphicsDevice;
- }
- //前置聲明
- namespace Core
- {
- class TimeUsed;
- }
- //簡化的寫法,GraphicsDevice可以在下面的代碼中直接使用
- //GraphicsDevice與WuguiEngine::Graphics::GraphicsDevice等價
- //注意:如果不加上下面這行,WuguiEngine::Graphics不能省略
- using Graphics::GraphicsDevice;
- class BaseGame : virtual public IDisposed,
- virtual public IRenderable
- {
- // add definition
- }
- }
- #endif
上面的代碼簡單的說明了namespace的一些基本的用法,該段代碼取自BaseGame.H的文件,在class BaseGame中,用到了來自不同的命名空間下的IUpdatable, GraphicsDevice等類.具體的內容可以先看看代碼里面的注釋
在需要在BaseGame.H中包含這些類的頭文件的同時,需要加上這些類的前置聲明,如果不加會出現一些詭異的編譯錯誤(有些問題可能也是在于我比較不了解c++),反正我覺得使用using namespace ...并不是一個很好的做法,不如using XXX::YYY::ZZZ來得準確(這里也希望高手來拍磚指點一下.
繼承
1)virtual繼承
在繼承接口(只具有純虛函數的類)的時候最好為繼承的方法加上virtual的說明(參考上面一段的代碼,IUpdatable與IDisposed就是接口,主要的原因是在于c++的多重繼承機制,假如A與B都繼承自IInterface,C又繼承自A,B,那么普通的繼承方式則會在C類中保存兩個IInterface的副本.
而virtual繼承就有區別了,virutal繼承告訴編譯器:"我繼承的是一個純接口!",這樣只會保存一個副本.
2)多種繼承方式
大家知道,C++對于繼承的方式有很多的修飾,有public, private, protected,而且還可以加入virtual關鍵字.在c++中,public繼承與在c#中的繼承是沒有區別的,而其他幾種繼承是具有理論的價值,而實際中的應用得非常非常的少,這里就不再贅述了.
3)實驗:重寫虛函數
下面做一個簡單的實驗,看看c++與c#在重寫虛函數時候調用基類的虛函數的時候的情況,大家自己可以對比一下c#.測試代碼:
- #include < iostream>
- using namespace std;
- class Base
- {
- public:
- virtual void Func() { cout < < "Hello Earth" < < endl; }
- };
- class Derived : public Base
- {
- public:
- virtual void Func() { cout < < "Hello World" < < endl; }
- };
- int main()
- {
- Base* p1 = new Base();
- p1->Func();
- Base* p2 = new Derived();
- p2->Func();
- }
輸出:
Hello Earth
Hello World
而在將Base的Func()改為純虛函數之后,在Base* p1 = new Base()這句話上出現編譯錯誤,提示不能實例化抽象類. 而對于p2的調用時正常的
下面繼續深入我們的實驗,三個類依次繼承,爺爺類具有一個純虛函數,爸爸類什么都不寫,兒子類重寫該函數,都使用virtual public繼承:
- #include < iostream>
- using namespace std;
- class Base
- {
- public:
- virtual void Func() = 0 ;
- };
- class Derived : virtual public Base
- {
- };
- class DerivedDerived : virtual public Derived
- {
- public:
- virtual void Func() { cout < < "Hello World" < < endl; }
- };
- int main()
- {
- Base* p1 = new DerivedDerived();
- Derived* p2 = new DerivedDerived();
- p1->Func();
- p2->Func();
- }
輸出結果都是"Hello World"
今天也有點晚了,明天還要坐火車,從c#到c++的問題先寫到這里,各位晚安,歡迎提出意見 :-D
【編輯推薦】