自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

C++基礎(chǔ)之類的詳細(xì)介紹(二)

開發(fā) 后端
本文介紹的是C++中的類,針對初學(xué)者而言,這是很好的教程。希望對大家有幫助,一起來看。

C++中,提到類型定義符前還可以書寫class,即類型的自定義類型(簡稱類),它和結(jié)構(gòu)根本沒有區(qū)別(僅有一點(diǎn)小小的區(qū)別,下篇說明),而之所以還要提供一個class,實(shí)際是由于C++是從C擴(kuò)展而成,其中的class是C++自己提出的一個很重要的概念,只是為了與C語言兼容而保留了struct這個關(guān)鍵字。接上一篇>>

聲明的含義

前面已經(jīng)解釋過聲明是什么意思,在此由于成員函數(shù)的定義規(guī)則這種新的定義語法,必須重新考慮聲明的意思。注意一點(diǎn),前面將一個函數(shù)的定義放到main函數(shù)定義的前面就可以不用再聲明那個函數(shù)了;同樣如果定義了某個變量,就不用再聲明那個變量了。這也就是說定義語句具有聲明的功能,但上面成員函數(shù)的定義語句卻不具有聲明的功能,下面來了解聲明的真正意思。

聲明是要求編譯器產(chǎn)生映射元素的語句。所謂的映射元素,就是前面介紹過的變量及函數(shù),都只有3欄(或3個字段):類型欄、名字欄和地址欄(成員變量類型的這一欄就放偏移值)。即編譯器每當(dāng)看到聲明語句,就生成一個映射元素,并且將對應(yīng)的地址欄空著,然后留下一些信息以告訴連接器——此.obj文件(編譯器編譯源文件后生成的文件,對于VC是.obj文件)需要一些符號,將這些符號找到后再修改并完善此.obj文件,最后連接。

回想之前說過的符號的意思,它就是一字符串,用于編譯器和連接器之間的通信。注意符號沒有類型,因?yàn)檫B接器只是負(fù)責(zé)查找符號并完善(因?yàn)橛行┯成湓氐牡刂窓谶€是空的)中間文件(對于VC就是.obj文件),不進(jìn)行語法分析,也就沒有什么類型。

定義是要求編譯器填充前面聲明沒有書寫的地址欄。也就是說某變量對應(yīng)的地址,只有在其定義時才知道。因此實(shí)際的在棧上分配內(nèi)存等工作都是由變量的定義完成的,所以才有聲明的變量并不分配內(nèi)存。但應(yīng)注意一個重點(diǎn),定義是生成映射元素需要的地址,因此定義也就說明了它生成的是哪個映射元素的地址,而如果此時編譯器的映射表(即之前說的編譯器內(nèi)部用于記錄映射元素的變量表、函數(shù)表等)中沒有那個映射元素,即還沒有相應(yīng)元素的聲明出現(xiàn)過,那么編譯器將報錯。

但前面只寫一個變量或函數(shù)定義語句,它照樣正常并沒有報錯啊?實(shí)際很簡單,只需要將聲明和定義看成是一種語句,只不過是向編譯器提供的信息不同罷了。

如:

  1. void ABC( float );  
  2. 和  
  3. void ABC( float ){} 

 

編譯器對它們相同看待。前者給出了函數(shù)的類型及類型名,因此編譯器就只填寫映射元素中的名字和類型兩欄。由于其后只接了個“;”,沒有給出此函數(shù)映射的代碼,因此編譯器無法填寫地址欄。而后者,給出了函數(shù)名、所屬類型以及映射的代碼(空的復(fù)合語句),因此編譯器得到了所有要填寫的信息進(jìn)而將三欄的信息都填上了,結(jié)果就表現(xiàn)出定義語句完成了聲明的功能。

對于變量,如long a;。同上,這里給出了類型和名字,因此編譯器填寫了類型和名字兩欄。但變量對應(yīng)的是棧上的某塊內(nèi)存的首地址,這個首地址無法從代碼上表現(xiàn)出來(前面函數(shù)就通過在函數(shù)聲明的后面寫復(fù)合語句來表現(xiàn)相應(yīng)函數(shù)對應(yīng)的代碼所在的地址),而必須由編譯器內(nèi)部通過計算獲得,因此才硬性規(guī)定上面那樣的書寫算作變量的定義,而要變量的聲明就需要在前面加extern。即上面那樣將導(dǎo)致編譯器進(jìn)行內(nèi)部計算進(jìn)而得出相應(yīng)的地址而填寫了映射元素的所有信息。

上面難免顯得故弄玄虛,那都是因?yàn)樽远x類型的出現(xiàn)??紤]成員變量的定義,如

 

  1. struct ABC { long a, b; double c; }; 

 

上面給出了類型——long ABC::、long ABC::和double ABC::;給出了名字——ABC::a、ABC::b和ABC::c;給出了地址(即偏移)——0、4和8,因?yàn)槭墙Y(jié)構(gòu)型自定義類型,故由此語句就可以得出各成員變量的偏移。上面得出三個信息,即可以填寫映射元素的所有信息,所以上面可以算作定義語句。對于成員函數(shù),如下:

 

  1. struct ABC { void AB( float ); }; 

 

上面給出了類型——void ( ABC:: )( float );給出了名字——ABC::AB。不過由于沒有給出地址,因此無法填寫映射元素的所有信息,故上面是成員函數(shù)ABC::AB的聲明。按照前面說法,只要給出地址就可以了,而無需去管它是定義還是聲明,因此也就可以這樣:

 

  1. struct ABC { void AB( float ){} }; 

 

上面給出類型和名字的同時,給出了地址,因此將可以完全填寫映射元素的所有信息,是定義。上面的用法有其特殊性,后面說明。注意,如果這時再在后面寫ABC::AB的定義語句,即如下,將錯誤:

 

  1. struct ABC { void AB( float ){} };  
  2. void ABC::AB( float ) {} 

 

上面將報錯,原因很簡單,因?yàn)楹笳咧皇嵌x,它只提供了ABC::AB對應(yīng)的地址這一個信息,但映射元素中的地址欄已經(jīng)填寫了,故編譯器將說重復(fù)定義。再單獨(dú)看成員函數(shù)的定義,它給出了類型void ( ABC:: )( float ),給出了名字ABC::AB,也給出了地址,但為什么說它只給出了地址這一信息?

首先,名字ABC::AB是不符合標(biāo)識符規(guī)則的,而類型修飾符 ABC::必須通過類型定義符“{}”才能夠加上去,這在前面已多次說明。因此上面給出的信息是:給出了一個地址,這個地址是類型為void ( ABC:: )( float ),名字為ABC::AB的映射元素的地址。

結(jié)果編譯器就查找這樣的映射元素,如果有,則填寫相應(yīng)的地址欄,否則報錯,即只寫一個void ABC::AB( float ){}是錯誤的,在其前面必須先通過類型定義符“{}”聲明相應(yīng)的映射元素。這也就是前面說的定義僅僅填充地址欄,并不生成映射元素。

聲明的作用

定義的作用很明顯了,有意義的映射(名字對地址)就是它來做,但聲明有什么用?它只是生成類型對名字,為什么非得要類型對名字?它只是告訴編譯器不要發(fā)出錯誤說變量或函數(shù)未定義?任何東西都有其存在的意義,先看下面這段代碼。

 

  1. extern"C" long ABC( long a, long b );  
  2. void main(){ long c = ABC( 10, 20 ); } 

 

假設(shè)上面代碼在a.cpp中書寫,編譯生成文件a.obj,沒有問題。但按照之前的說明,連接時將錯誤,因?yàn)檎也坏椒朹ABC。因?yàn)槊謃ABC對應(yīng)的地址欄還空著。接著在VC中為a.cpp所在工程添加一個新的源文件b.cpp,如下書寫代碼。

 

  1. extern"C" float ABC( float a ){ return a; } 

 

編譯并連接,現(xiàn)在沒任何問題了,但相信你已經(jīng)看出問題了——函數(shù)ABC的聲明和定義的類型不匹配,卻連接成功了?

注意上面關(guān)于連接的說明,連接時沒有類型,只管符號。上面用extern"C"使得a.obj要求_ABC的符號,而b.cpp提供_ABC的符號,剩余的就只是連接器將b.obj中_ABC對應(yīng)的地址放到a.obj以完善a.obj,最后連接a.obj和b.obj。

那么上面什么結(jié)果,編譯器即使沒有地址也依舊可以生成代碼以實(shí)現(xiàn)函數(shù)操作符的功能——函數(shù)調(diào)用。之所以能這樣就是因?yàn)槁暶鲿r一定必須同時給出類型和名字,因?yàn)轭愋透嬖V編譯器,當(dāng)某個操作符涉及到某個映射元素時,如何生成代碼來實(shí)現(xiàn)這個操作符的功能。

也就是說,兩個char類型的數(shù)字乘法和兩個long類型的數(shù)字乘法編譯生成的代碼不同;對long ABC( long );的函數(shù)調(diào)用代碼和void ABC( float )的不同。即,操作符作用的數(shù)字類型的不同將導(dǎo)致編譯器生成的代碼不同。

那么上面為什么要將ABC的定義放到b.cpp中?因?yàn)楦髟次募g的編譯是獨(dú)立的,如果放在a.cpp,編譯器就會發(fā)現(xiàn)已經(jīng)有這么個映射元素,但類型卻不匹配,將報錯。而放到b.cpp中,使得由連接器來完善a.obj,到時將沒有類型的存在,只管符號。下面繼續(xù)。

 

  1. struct ABC { long a, b; void AB( long tem1, long tem2 ); void ABCD(); };  
  2. void main(){ ABC a; a.AB( 1020 ); } 

 

由上面的說法,這里雖然沒有給出ABC::AB的定義,但仍能編譯成功,沒有任何問題。仍假設(shè)上面代碼在a.cpp中,然后添加b.cpp,在其中書寫下面的代碼。

 

  1. struct ABC { float b, a; void AB( long tem1, long tem2 ); long ABCD( float ); };  
  2. void ABC::AB( long tem1, long tem2 ){ a = tem1; b = tem2; } 

 

這里定義了函數(shù)ABC::AB,注意如之前所說,由于這里的函數(shù)定義僅僅只是定義,所以必須在其前面書寫類型定義符“{}”以讓編譯器生成映射元素。但更應(yīng)該注意這里將成員變量的位置換了,這樣b就映射的是0而a映射的是4了,并且還將a、b的類型換成了float,更和a.cpp中的定義大相徑庭。但沒有任何問題,編譯連接成功,

  1. a.AB( 10,20 ); 

 

執(zhí)行后a.a為0X41A00000,a.b為0X41200000,而*( float* )&a.a為20,*( flaot* )&a.b為10。

為什么?因?yàn)榫幾g器只在當(dāng)前編譯的那個源文件中遵循類型匹配,而編譯另一個源文件時,編譯其他源文件所生成的映射元素全部無效。因此聲明將類型和名字綁定起來,而名字就代表了其所關(guān)聯(lián)的類型的地址類型的數(shù)字,而后繼代碼中所有操作這個數(shù)字的操作符的編譯生成都將受這個數(shù)字的類型的影響。即聲明是告訴編譯器如何生成代碼的,其不僅僅只是個語法上說明變量或函數(shù)的語句,它是不可或缺的。

還應(yīng)注意上面兩個文件中的ABC::ABCD成員函數(shù)的聲明不同,而且整個工程中(即a.cpp和b.cpp中)都沒有ABC::ABCD的定義,卻仍能編譯連接成功,因?yàn)槁暶鞑⒉皇歉嬖V編譯器已經(jīng)有什么東西了,而是如何生成代碼。

頭文件

上面已經(jīng)說明,如果有個自定義類型ABC,在a.cpp、b.cpp和c.cpp中都要使用它,則必須在a.cpp、b.cpp和c.cpp中,各自使用 ABC之前用類型定義符“{}”重新定義一遍這個自定義類型。如果不小心如上面那樣在a.cpp和b.cpp中寫的定義不一樣,則將產(chǎn)生很難查找的錯誤。為此,C++提供了一個預(yù)編譯指令來幫忙。

預(yù)編譯指令就是在編譯之前執(zhí)行的指令,它由預(yù)編譯器來解釋執(zhí)行。預(yù)編譯器是另一個程序,一般情況,編譯器廠商都將其合并進(jìn)了C++編譯器而只提供一個程序。

在此說明預(yù)編譯指令中的包含指令——#include,其格式為#include <文件名>。應(yīng)注意預(yù)編譯指令都必須單獨(dú)占一行,而<文件名>就是一個用雙引號或尖括號括起來的文件名,

如:#include "abc.c"、#include "C:\abc.dsw"或#include <C:\abc.exe>。它的作用很簡單,就是將引號或尖括號中書寫的文件名對應(yīng)的文件以ANSI格式或MBCS格式解釋,并將內(nèi)容原封不動地替換到#include所在的位置,比如下面是文件abc的內(nèi)容。

 

  1. struct ABC { long a, b; void AB( long tem1, long tem2 ); }; 

 

則前面的a.cpp可改為:

 

  1. #include "abc"  
  2. void main() { ABC a; a.AB( 10, 20 ); } 

 

而b.cpp可改為:

 

  1. #include "abc"  
  2. void ABC::AB( long tem1, long tem2 ){ a = tem1; b = tem2; } 

這時,就不會出現(xiàn)類似上面那樣在b.cpp中將自定義類型ABC的定義寫錯了而導(dǎo)致錯誤的結(jié)果(a.a為0X41A00000,a.b為0X41200000),進(jìn)而a.AB( 10, 20 );執(zhí)行后,a.a為10,a.b為20。

注意這里使用的是雙引號來括住文件名的,它表示當(dāng)括住的只是一個文件名或相對路徑而沒有給出全路徑時,如上面的abc,則先搜索此時被編譯的源文件所在的目錄,然后搜索編譯器自定的包含目錄(如:C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\include等),里面一般都放著編譯器自帶的SDK的頭文件,如果仍沒有找到,則報錯(注意,一般編譯器都提供了一些選項以使得除了上述的目錄外,還可以再搜索指定的目錄,不同的編譯器設(shè)定方式不同,在此不表)。

如果是用尖括號括起來,則表示先搜索編譯器自定的包含目錄,再源文件所在目錄。為什么要不同?只是為了防止自己起的文件名正好和編譯器的包含目錄下的文件重名而發(fā)生沖突,因?yàn)橐坏┱业轿募瑢⒉辉偎阉骱罄^目錄。

所以,一般的C++代碼中,如果要用到某個自定義類型,都將那個自定義類型的定義分別裝在兩個文件中,對于上面結(jié)構(gòu)ABC,則應(yīng)該生成兩個文件,分別為 ABC.h和ABC.cpp,其中的ABC.h被稱作頭文件,而ABC.cpp則稱作源文件。頭文件里放的是聲明,而源文件中放的是定義,則ABC.h的內(nèi)容就和前面的abc一樣,而ABC.cpp的內(nèi)容就和b.cpp一樣。

然后每當(dāng)工程中某個源文件里要使用結(jié)構(gòu)ABC時,就在那個源文件的開頭包含 ABC.h,這樣就相當(dāng)于將結(jié)構(gòu)ABC的所有相關(guān)聲明都帶進(jìn)了那個文件的編譯,比如前面的a.cpp就通過在開頭包含abc以聲明了結(jié)構(gòu)ABC。

為什么還要生成一個ABC.cpp?如果將ABC::AB的定義語句也放到ABC.h中,則a.cpp要使用ABC,c.cpp也要使用ABC,所以 a.cpp包含ABC.h,由于里面的ABC::AB的定義,生成一個符號?AB@ABC@@QAEXJJ@Z(對于VC);同樣c.cpp的編譯也要生成這個符號,然后連接時,由于出現(xiàn)兩個相同的符號,連接器無法確定使用哪一個,報錯。因此專門定義一個ABC.cpp,將函數(shù)ABC::AB的定義放到 ABC.obj中,這樣將只有一個符號生成,連接時也就不再報錯。

注意上面的

  1. struct ABC { void AB( float ){} }; 

 

如果將這個放在ABC.h中,由于在類型定義符中就已經(jīng)將函數(shù)ABC::AB的定義給出,則將會同上,出現(xiàn)兩個相同的符號,然后連接失敗。為了避開這個問題,C++規(guī)定如上在類型定義符中直接書寫函數(shù)定義而定義的函數(shù)是inline函數(shù),出于篇幅,下篇介紹。

成員的意義

上面從語法的角度說明了成員函數(shù)的意思,如果很昏,不要緊,實(shí)現(xiàn)不能理解并不代表就不能運(yùn)用,而程序員重要的是對語言的運(yùn)用能力而不是語言的了解程度(雖然后者也很重要)。下面說明成員的語義。

本文一開頭提出了一種語義——某種資源具有的功能,而C++的自定義類型再加上成員操作符“.”和“->”的運(yùn)用,從代碼上很容易的就表現(xiàn)出一種語義——從屬關(guān)系。如:a.b、c.d分別表示a的b和c的d。某種資源具有的功能要映射到C++中,就應(yīng)該將這種資源映射成一自定義類型,而它所具有的功能就映射成此自定義類型的成員函數(shù),如最開始提到的怪物和玩家,則如下:

 

  1. struct Player { float Life; float Attack; float Defend; };  
  2. struct Monster { float Life; float Attack; float Defend; void AttackPlayer( Player &pla ); };  
  3. Player player; Monster a; a.AttackPlayer( player ); 

 

上面的語義就非常明顯,代碼執(zhí)行的操作是怪物a攻擊玩家player,而player.Life就代表玩家player的生命值。假設(shè)如下書寫Monster::AttackPlayer的定義:

 

  1. void Monster::AttackPlayer( Player &pla )  
  2. {  
  3. pla.Life -= Attack - pla.Defend;  

 

上面的語義非常明顯:某怪物攻擊玩家的方法就是將被攻擊的玩家的生命值減去自己的攻擊力減被攻擊的玩家的防御力的值。語義非常清晰,代碼的可讀性好。而如原來的寫法:

  1. void MonsterAttackPlayer( Monster &mon, Player &pla )  
  2. {  
  3. pla.Life -= mon.Attack - pla.Defend;  

 

則代碼表現(xiàn)的語義:怪物攻擊玩家是個操作,此操作需要操作兩個資源,分別為怪物類型和玩家類型。這個語義就沒表現(xiàn)出我們本來打算表現(xiàn)的想法,而是怪物的攻擊功能的另一種解釋,其更適合表現(xiàn)收銀工作。比如收銀臺實(shí)現(xiàn)的是收錢的工作,客戶在柜臺買了東西,由營業(yè)員開出單據(jù),然后客戶將單據(jù)拿到收銀臺交錢。

這里收銀臺的工作就需要操作兩個資源——錢和單據(jù),這時就應(yīng)該將收錢這個工作映射為如上的函數(shù)而不是成員函數(shù),因?yàn)樵谶@個算法中,收銀臺沒有被映射成自定義類型的必要性,即我們對收銀的工作由誰做不關(guān)心,只關(guān)心它如何做。

責(zé)任編輯:于鐵 來源: 互聯(lián)網(wǎng)
相關(guān)推薦

2011-07-14 16:26:01

2011-07-14 17:17:21

C++指針

2011-06-21 10:37:56

const

2011-07-14 23:27:05

C++引用

2011-07-20 14:12:48

2011-07-14 17:02:09

C++指針

2011-07-20 15:58:53

C++引用

2011-07-13 16:49:59

C++

2011-07-20 13:57:06

C++STL

2011-07-15 10:08:11

C++運(yùn)算符重載

2023-12-18 11:15:03

2011-07-20 16:43:34

C++

2011-07-13 11:12:43

C++MFC

2010-01-11 09:56:07

C++編程實(shí)例

2010-01-19 13:17:05

C++數(shù)據(jù)類型

2011-06-21 15:00:07

JAVAC++

2011-07-20 13:57:06

C++STL

2011-07-20 17:16:50

C++重載函數(shù)

2009-08-13 15:40:28

C#基礎(chǔ)知識

2020-09-28 08:12:59

CC++時間
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號