在C++中,提到類型定義符前還可以書寫class,即類型的自定義類型簡稱類),它和結構根本沒有區別僅有一點小小的區別,下篇說明),而之所以還要提供一個class,實際是由於C++是從C擴展而成,其中的class是C++自己提出的一個很重要的概念,只是為了與C語言兼容而保留了struct這個關鍵字。
暫時可以先認為類較結構的長足進步就是多了成員函數這個概念雖然結構也可以有成員函數),在了解成員函數之前,先來看一種語義需求。
操作與資源
程序主要是由操作和被操作的資源組成,操作的執行者就是CPU,這很正常,但有時候的確存在一些需要,需要表現是某個資源操作了另一個資源暫時稱作操作者),比如游戲中,經常出現的就是要映射怪物攻擊了玩家。之所以需要操作者,一般是因為這個操作也需要修改操作者或利用操作者記錄的一些信息來完成操作,比如怪物的攻擊力來決定玩家被攻擊後的狀態。這種語義就表現為操作者具有某些功能。為了實現上面的語義,如原來所說進行映射,先映射怪物和玩家分別為結構,如下:
- struct Monster { float Life; float Attack; float Defend; };
- struct Player { float Life; float Attack; float Defend; };
上面的攻擊操作就可以映射為void MonsterAttackPlayer( Monster &mon, Player &pla );。注意這裡期望通過函數名來表現操作者,但和前篇說的將過河方案起名為sln一樣,屬於一種本末倒置,因為這個語義應該由類型來表現,而不是函數名。為此,C++提供了成員函數的概念。
成員函數
與之前一樣,在類型定義符中書寫函數的聲明語句將定義出成員函數,如下:
- struct ABC { long a; void AB( long ); };
上面就定義了一個映射元素——第一個變量ABC::a,類型為long ABC::;以及聲明了一個映射元素——第二個函數ABC::AB,類型為void ( ABC:: )( long )。類型修飾符ABC::在此修飾了函數ABC::AB,表示其為函數類型的偏移類型,即是一相對值。但由於是函數,意義和變量不同,即其依舊映射的是內存中的地址代碼的地址),但由於是偏移類型,也就是相對的,即是不完整的,因此不能對它應用函數操作符,如:
- ABC::AB( 10 );
這裡將錯誤,因為ABC::AB是相對的,其相對的東西不是如成員變量那樣是個內存地址,而是一個結構指針類型的參數,參數名一定為this,這是強行定義的,後面說明。
注意由於其名字為ABC::AB,而上面僅僅是對其進行了聲明,要定義它,仍和之前的函數定義一樣,如下:
- void ABC::AB( long d ) { this->a = d; }
應注意上面函數的名字為ABC::AB,但和前篇說的成員變量一樣,不能直接書寫long ABC::a;,也就不能直接如上書寫函數的定義語句至少函數名為ABC::AB就不符合標識符規則),而必須要通過類型定義符“{}”先定義自定義類型,然後再書寫,這會在後面說明聲明時詳細闡述。
注意上面使用了this這個關鍵字,其類型為ABC*,由編譯器自動生成,即上面的函數定義實際等同於
- void ABC::AB( ABC *this, long d ) { this->a = d; }
而之所以要省略this參數的聲明而由編譯器來代勞是為了在代碼上體現出前面提到的語義即成員的意義),這也是為什麼稱ABC::AB是函數類型的偏移類型,它是相對於這個this參數而言的,如何相對,如下:
- ABC a, b, c;
- a.ABC::AB( 10 );
- b.ABC::AB( 12 );
- c.AB( 14 );
上面利用成員操作符調用ABC::AB,注意執行後,a.a、b.a和c.a的值分別為10、12和14,即三次調用ABC::AB,但通過成員操作符而導致三次的this參數的值並不相同,並進而得以修改三個ABC變量的成員變量a。注意上面書寫
- a.ABC::AB( 10 );
和成員變量一樣,由於左右類型必須對應,因此也可
- a.AB( 10 );
還應注意上面在定義ABC::AB時,在函數體內書寫
- this->a = d;
同上,由於類型必須對應的關系,即this必須是相應自定義類型的指針,所以也可省略this->的書寫,進而有
- void ABC::AB( long d ) { a = d; }
注意這裡成員操作符的作用,其不再如成員變量時返回相應成員變量類型的數字,而是返回一函數類型的數字,但不同的就是這個函數類型是無法用語法表示出來的,即C++並沒有提供任何關鍵字或類型修飾符來表現這個返回的類型VC內部提供了__thiscall這個類型修飾符進行表示,不過寫代碼時依舊不能使用,只是編譯器內部使用)。
也就是說,當成員操作符右側接的是函數類型的偏移類型的數字時,返回一個函數類型的數字表示其可被施以函數操作符),函數的類型為偏移類型中給出的類型,但這個類型無法表現。即a.AB將返回一個數字,這個數字是函數類型,在VC內部其類型為void ( __thiscall ABC:: )( long ),但這個類型在C++中是非法的。
C++並沒有提供類似__thiscall這樣的關鍵字以修飾類型,因為這個類型是要求編譯器遇到函數操作符和成員操作符時,如
- a.AB( 10 );
要將成員操作符左側的地址作為函數調用的第一個參數傳進去,然後再傳函數操作符中給出的其余各參數。即這個類型是針對同時出現函數操作符和成員操作符這一特定情況,給編譯器提供一些信息以生成正確的代碼,而不用於修飾數字修飾數字就要求能應付所有情況)。即類型是用於修飾數字的,而這個類型不能修飾數字,因此C++並未提供類似__thiscall的關鍵字。
和之前一樣,由於ABC::AB映射的是一個地址,而不是一個偏移值,因此可以
- ABC::AB;
但不能
- ABC::a;
因為後者是偏移值。根據類型匹配,很容易就知道也可有:
- void ( ABC::*p )( long ) = ABC::AB;
- 或
- void ( ABC::*p )( long ) = &ABC::AB;
進而就有:
- void ( ABC::**pP )( long ) = &p; ( c.**pP )( 10.0f );
之所以加括號是因為函數操作符的優先級較“*”高。再回想前篇說過指針類型的轉換只是類型變化,數值不變下篇說明數值變化的情況),因此可以有如下代碼,這段代碼毫無意義,在此僅為加深對成員函數的理解。
- struct ABC { long a; void AB( long ); };
- void ABC::AB( long d )
- {
- this->a = d;
- }
- struct AB
- {
- short a, b;
- void ABCD( short tem1, short tem2 );
- void ABC( long tem );
- };
- void AB::ABCD( short tem1, short tem2 )
- {
- a = tem1; b = tem2;
- }
- void AB::ABC( long tem )
- {
- a = short( tem / 10 );
- b = short( tem - tem / 10 );
- }
- void main()
- {
- ABC a, b, c; AB d;
- ( c.*( void ( ABC::* )( long ) )&AB::ABC )( 43 );
- ( b.*( void ( ABC::* )( long ) )&AB::ABCD )( 0XABCDEF12 );
- ( d.*( void ( AB::* )( short, short ) )ABC::AB )( 0XABCD, 0XEF12 );
- }
上面執行後,c.a為0X00270004,b.a為0X0000EF12,d.a為0XABCD,d.b為0XFFFF。對於c的函數調用,由於 AB::ABC映射的地址被直接轉換類型進而直接被使用,因此程序將跳到AB::ABC處的
- a = short( tem / 10 );
開始執行,而參數tem映射的是傳遞參數的內存的首地址,並進而用long類型解釋而得到tem為43,然後執行。
注意
- b = short( tem - tem / 10 );
實際是
- this->b = short( tem - tem / 10 );
而this的值為c對應的地址,但在這裡被認為是AB*類型因為在函數AB::ABC的函數體內),所以才能this->b正常ABC結構中沒有b這個成員變量),而b的偏移為2,所以上句執行完後將結果39存放到c的地址加2所對應的內存,並且以short類型解釋而得到的16位的二進制數存放。
對於
- a = short( tem / 10 );
也做同樣事情,故最後得c.a的值為0X0027004十進制39轉成十六進制為0X27)。
同樣,對於b的調用,程序將跳到AB::ABCD,但生成的b的調用代碼時,將參數0XABCDEF12按照參數類型為long的格式記錄在傳遞參數的內存中,然後跳到AB::ABCD。但編譯AB::ABCD時又按照參數為兩個short類型來映射參數tem1和tem2對應的地址,因此容易想到 tem1的值將為0XEF12,tem2的值為0XABCD,但實際並非如此。
接下一篇>>