c/c++ 奇技淫巧(一些c語言的技巧)
一. 變長數(shù)組
嚴(yán)格說來,變長數(shù)組的實(shí)現(xiàn)在c++中并不是一件麻煩的事情。Stl中的vector本身就是一個(gè)變長數(shù)組,并且有自動(dòng)管理內(nèi)存的能力。
但是在c中,實(shí)現(xiàn)變長數(shù)組就稍顯麻煩。用C實(shí)現(xiàn),必然需要一個(gè)結(jié)構(gòu),結(jié)構(gòu)當(dāng)中應(yīng)當(dāng)有一個(gè)指針,指針分配一段內(nèi)存空間,空間大小根據(jù)需要而定,而且必須有另外一個(gè)字段記錄究竟開辟了多大多長的空間。
大致描述如下:
Struct MutableLenArray { Int count; Char* p; };
P = new Char[Count];
沒什么問題,但是C語言的使用者有個(gè)最大的自豪就在于對(duì)于效率、空間使用的掌控。他們會(huì)有這樣的疑問,如果count=0,那么p就沒必要了,白白占了4(64位系統(tǒng)為8)個(gè)字節(jié)的空間,簡直浪費(fèi)。
那有沒有更好的方式能實(shí)現(xiàn)上面的需求,又保證空間合理呢?答案是有的,用0長度
Struct MutableLenArray { Int count; Char p[0]; };
和上面的結(jié)構(gòu)使用方法一致,但是我們可以用sizeof嘗試讀取其大小,發(fā)現(xiàn)竟然只有count字段的長度4字節(jié),p沒有被分配空間。完美!
二. 宏的妙用
1. #和
“#”符號(hào)把一個(gè)符號(hào)直接轉(zhuǎn)換為字符串,例如:
#define TO_STRING(x) #x const char *str = TO_STRING( test );
str的內(nèi)容就是”test “,也就是說#會(huì)把其后的符號(hào) 直接加上雙引號(hào)。
這個(gè)特性為c++反射的實(shí)現(xiàn)提供了極大便利,可以參考博主的下一篇文章,c++反射的簡單實(shí)現(xiàn)。
##符號(hào)會(huì)連接兩個(gè)符號(hào),從而產(chǎn)生新的符號(hào)(詞法層次),例如:
#define SIGN( x ) INT_##x int SIGN( 1 );
宏被展開后將成為:int INT_1;
可以把##看成連字符,連字符為則為新符號(hào)的產(chǎn)生提供了方便。Google的Gtest框架就巧妙的運(yùn)用了連字符來生成新的測試案例。
2. 變參宏
#define LOG( format, ... ) printf( format, __VA_ARGS__ ) LOG( "%s %d", str, count );
VA_ARGS是系統(tǒng)預(yù)定義宏,被自動(dòng)替換為參數(shù)列表。
經(jīng)常需要進(jìn)行輸出格式化,重定義時(shí),可以用到以上技巧。
3. 宏參數(shù)的prescan
prescan的定義:當(dāng)一個(gè)宏參數(shù)被放進(jìn)宏體時(shí),這個(gè)宏參數(shù)會(huì)首先被全部展開(有例外,見下文)。當(dāng)展開后的宏參數(shù)被放進(jìn)宏體時(shí), 預(yù)處理器對(duì)新展開的宏體進(jìn)行第二次掃描,并繼續(xù)展開。例如:
#define PARAM( x ) x #define ADDPARAM( x ) INT_##x PARAM( ADDPARAM( 1 ) );
因?yàn)锳DDPARAM( 1 ) 是作為PARAM的宏參數(shù),所以先將ADDPARAM( 1 )展開為INT_1,然后再將INT_1放進(jìn)PARAM。
例外情況是,如果PARAM宏里對(duì)宏參數(shù)使用了#或##,那么宏參數(shù)不會(huì)被展開:
#define PARAM( x ) #x #define ADDPARAM( x ) INT_##x
PARAM( ADDPARAM( 1 ) ); 將被展開為”ADDPARAM( 1 )”。
所以此時(shí)要得到“INT_1”的結(jié)果,必須加入一個(gè)中間宏:
#define PARAM(x) PARAM1(x) #define PARAM1( x ) #x
PARAM( ADDPARAM( 1 ) );此時(shí)的結(jié)果將會(huì)是“INT_1”。根據(jù)prescan原則,當(dāng)ADDPARAM(1)傳入,會(huì)展開得到INT_1,然后將INT_1帶入PARAM1宏,最終得到“INT_1”的結(jié)果。
4. 接口宏
以下部分,摘自網(wǎng)上博客,僅作聲明。
C++的目標(biāo)之一就是把類的聲明和定義分離開來,這對(duì)于項(xiàng)目的開發(fā)極其有利——這可以使開發(fā)人員不用看到類的實(shí)現(xiàn)就能知曉類的功能。但是,C++實(shí)現(xiàn)類的聲明與類定義的分離的方法會(huì)導(dǎo)致一些額外的工作——每個(gè)非內(nèi)聯(lián)函數(shù)的表示都需要寫兩次,一次在類聲明中,一次在類定義中。
代碼如下:
// .h File class Element { void Tick (); }; // .cpp File void Element ::Tick () { // todo }
由于Tick的標(biāo)識(shí)在兩個(gè)地方都出現(xiàn)了,因此如果我們需要改變這個(gè)方法的參數(shù)的時(shí)候(改變函數(shù)名、返回類型或者加const),我們需要改變兩個(gè)地方。
當(dāng)然通常這沒有什么工作量,但是有些情況下這個(gè)特性會(huì)帶來不少麻煩。
舉個(gè)例子,如果我們有一個(gè)叫做BaseClass的基類,有三個(gè)從BaseClass繼承而來的子類——D1、D2和D3.其中BaseClass聲明了一個(gè)虛函數(shù)Foo()并且有一個(gè)缺省實(shí)現(xiàn),并且D1、D2、D3中重載了Foo()函數(shù)。
現(xiàn)在,如果說我們給BaseClass::Foo()添加一個(gè)參數(shù),但是忘了給D3中做相應(yīng)的修改。麻煩來了——編譯可以通過,編譯器會(huì)把BaseClass::Foo(…)和D3::Foo()當(dāng)成兩個(gè)完全不同的函數(shù)。當(dāng)我們想通過虛函數(shù)機(jī)制來調(diào)用D3的Foo的時(shí)候,這就容易出一些問題。
UE4中光繼承自AActor類的類就有上千個(gè),如果需要對(duì)AActor類做一個(gè)修改,那么如果使用傳統(tǒng)方法,我們還要針對(duì)上千個(gè)派生類進(jìn)行修改,而且萬一有一個(gè)派生類沒有修改,編譯器也不會(huì)報(bào)錯(cuò)!
這么看來,理想的情況是我們希望一個(gè)函數(shù)的表示只在一個(gè)地方存在,如果說只聲明BaseClass::Foo()一次,然后再它的派生類中不用再額外聲明Foo就好了。
而且在效率方面來說,在C++中使用繼承的時(shí)候我們經(jīng)常會(huì)使用很多淺層次的類繼承關(guān)系,一個(gè)父類往往有一堆子類。很多時(shí)候我們只需要把很多互不相關(guān)的功能集成到一個(gè)單獨(dú)的類繼承家族里面。
對(duì)于淺繼承來說,我們只是把開始的父類聲明為一個(gè)接口——也就是說它聲明了一些虛函數(shù)(大部分是純虛函數(shù))。在大多數(shù)情況下,我們會(huì)在這個(gè)類家族里面有一個(gè)基類以及其余的派生類。
如果說我們的基類有10個(gè)函數(shù),我們從這個(gè)基類派生了20個(gè)類,那么我們就需要額外做200個(gè)函數(shù)聲明。但是這些聲明的目的往往只是為了Implement基類中的那些方法而已,這就或多或少的容易使得頭文件不好維護(hù)。
傳統(tǒng)方法的實(shí)現(xiàn)
如果說我們有一個(gè)Animal的類,這個(gè)類被視為基類,我們希望從這個(gè)基類派生出不同的子類。在Animal中有3個(gè)純需函數(shù),如下所示:
class Animal { public: virtual std :: string GetName () const = 0 ; virtual Vector3f GetPosition () const = 0; virtual Vector3f GetVelocity () const = 0; };
同時(shí),這個(gè)基類擁有三個(gè)派生類——Monkey,Tiger,Lion。
那么我們?nèi)齻€(gè)方法的每一個(gè)都會(huì)在7個(gè)地方存在:Animal中一次,Monkey、Lion、Tiget的聲明和定義各一次。
然后假設(shè)我們做一個(gè)小改動(dòng)——我們想將GetPosition和GetVelocity的返回類型改為Vector4f以適應(yīng)Transform變換,那么我們就要在7個(gè)地方進(jìn)行修改:Animal的.h文件,Lion、Tiger和Monkey的.h文件和.cpp文件。
使用宏的實(shí)現(xiàn)
有一種很妙的處理方法就是將這些方法進(jìn)行包裝,改成所謂接口宏的形式。我們可以試試看:
#define INTERFACE_ANIMAL(terminal) \ public: \ virtual std::string GetName() const ##terminal \ virtual IntVector GetPosition() const ##terminal \ virtual IntVector GetVelocity() const ##terminal #define BASE_ANIMAL INTERFACE_ANIMAL(=0;) #define DERIVED_ANIMAL INTERFACE_ANIMAL(;)
值得一提的是,##符號(hào)代表的是連接,\符號(hào)代表的是把下一行的連起來。
通過這些宏,我們就可以大大簡化Animal的聲明,還有所有從它派生的類的聲明了:
// Animal.h class Animal { BASE_ANIMAL ; }; // Monkey.h class Monkey : public Animal { DERIVED_ANIMAL ; }; // Lion.h class Lion : public Animal { DERIVED_ANIMAL ; }; // Tiger.h class Tiger : public Animal { DERIVED_ANIMAL ; };
上一篇:C語言中 int main(int argc,char *argv[])的兩個(gè)參數(shù)詳解
欄 目:C語言
本文標(biāo)題:c/c++ 奇技淫巧(一些c語言的技巧)
本文地址:http://mengdiqiu.com.cn/a1/Cyuyan/1724.html
您可能感興趣的文章
- 04-02c語言沒有round函數(shù) round c語言
- 01-10深入理解C++中常見的關(guān)鍵字含義
- 01-10使用C++實(shí)現(xiàn)全排列算法的方法詳解
- 01-10c++中inline的用法分析
- 01-10用C++實(shí)現(xiàn)DBSCAN聚類算法
- 01-10全排列算法的非遞歸實(shí)現(xiàn)與遞歸實(shí)現(xiàn)的方法(C++)
- 01-10C++大數(shù)模板(推薦)
- 01-10淺談C/C++中的static與extern關(guān)鍵字的使用詳解
- 01-10深入C/C++浮點(diǎn)數(shù)在內(nèi)存中的存儲(chǔ)方式詳解
- 01-10深入理解C/C++混合編程


閱讀排行
本欄相關(guān)
- 04-02c語言函數(shù)調(diào)用后清空內(nèi)存 c語言調(diào)用
- 04-02func函數(shù)+在C語言 func函數(shù)在c語言中
- 04-02c語言的正則匹配函數(shù) c語言正則表達(dá)
- 04-02c語言用函數(shù)寫分段 用c語言表示分段
- 04-02c語言中對(duì)數(shù)函數(shù)的表達(dá)式 c語言中對(duì)
- 04-02c語言編寫函數(shù)冒泡排序 c語言冒泡排
- 04-02c語言沒有round函數(shù) round c語言
- 04-02c語言分段函數(shù)怎么求 用c語言求分段
- 04-02C語言中怎么打出三角函數(shù) c語言中怎
- 04-02c語言調(diào)用函數(shù)求fibo C語言調(diào)用函數(shù)求
隨機(jī)閱讀
- 04-02jquery與jsp,用jquery
- 01-10C#中split用法實(shí)例總結(jié)
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 01-10使用C語言求解撲克牌的順子及n個(gè)骰子
- 01-10delphi制作wav文件的方法
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 01-11ajax實(shí)現(xiàn)頁面的局部加載
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改