官术网_书友最值得收藏!

3.3 C語言中的變量

3.3.1 變量的本質(zhì)

在C語言中,要完成運算等功能,需要對若干個內(nèi)存空間進行讀取、修改等操作,為方便起見可以用一個名字來表示該內(nèi)存空間,這個名字便稱為變量。一個變量一旦被建立,在消除之前一直是不變的,如對于圖3-5中,變量a對應(yīng)的地址是0xFF00,變量b對應(yīng)的地址是0xFF04,在編程中,只需要使用這個名稱即可操作相應(yīng)的內(nèi)存。

圖3-5 內(nèi)存中的變量(8位)

圖3-5中,每個內(nèi)存空間包含8個二進制數(shù),即8個 bit,兩個變量 a和b對應(yīng)的地址分別為0xFF00和0xFF04。假如變量a和b分別均為1個字節(jié),則變量a和b的值均為10100001(二進制)。

本書給出了其執(zhí)行時和執(zhí)行后各變量在內(nèi)存中的分布,其中的內(nèi)存地址僅具有一般意義,因為不同的軟硬件條件、不同的編譯器版本,可能會影響運行時各變量在內(nèi)存中的位置;但是各個變量所占的字節(jié)個數(shù)、各個變量的相對位置關(guān)系是固定的。

3.3.2 變量的名稱規(guī)范

C語言中的變量名稱需要符合相應(yīng)的規(guī)范,具體包括以下內(nèi)容。

(1)變量名稱以英文字母或下劃線開頭。

(2)變零名稱除開頭外,其余字符可以用字母、數(shù)字或下劃線構(gòu)成。

(3)變量名稱不能是關(guān)鍵字。

(4)變量名稱區(qū)分大小寫,如變量a和變量A是兩個變不同的變量。

其中關(guān)鍵字是C語言中保留的單詞或字母組合,表3-1給出了幾種變量名稱及相關(guān)分析。

表3-1 幾種變量名稱診斷

3.3.3 變量的聲明和賦值

C語言規(guī)定,在使用一個變量之前必須對其進行聲明。聲明一個變量的方式如圖3-6所示,其中數(shù)據(jù)類型可以是C語言支持的基本數(shù)據(jù)類型如整型、字符型等,也可以是高級數(shù)據(jù)類型如數(shù)組、結(jié)構(gòu)體、指針等。結(jié)尾應(yīng)分號表示語句結(jié)束。C語言規(guī)定各語句之間均應(yīng)用分號隔開,為了便于閱讀,一般一條語句占據(jù)一行。聲明之后的變量就可以使用了,其數(shù)值為隨機數(shù),這是因為聲明一個變量實際上是給一段內(nèi)存起了個名字,這段內(nèi)存之前的數(shù)據(jù)是隨機的。

圖3-6 變量的聲明和賦值方法

圖3-6(a)和圖3-6(b)介紹了如何聲明一個變量以及多個變量,當(dāng)同時聲明多個變量時,各變量之間用逗號進行分隔。

圖3-6(c)、圖3-6(d)則顯示了如何給一個已經(jīng)聲明的變量進行賦值。賦值語句的左側(cè)為要進行賦值的變量,語句執(zhí)行后變量的數(shù)值便與等號右側(cè)的數(shù)值相等。程序中,變量可以進行多次賦值,完成數(shù)據(jù)記錄、更新等操作。

可以將變量的聲明和賦值結(jié)合在一個語句中,如圖3-6(e)所示,該語句既聲明了變量,同時變量也被賦值為指定值。C語言中的數(shù)字既支持十進制,又支持十六進制,其中十六進制數(shù)以0x作開頭,如0x10表示16,0x0f表示15。

表3-2給出了聲明變量并進行賦值的代碼示例,共有9條語句。其中語句(4)聲明了整型變量a;語句(5)聲明了整型變量b;語句(6)聲明了一個整型變量c并將其數(shù)值修改為0x10。語句(7)將1賦值給變量a;語句(8)通過賦值,將a的數(shù)值賦給b;在語句(8)執(zhí)行后,內(nèi)存中一共有a、b、c 3個變量,值分別為1、1、16。3個變量的生存期是由語句(3)和語句(9)中的一對大括號包含的區(qū)域。

表3-2 變量聲明和賦值代碼示例

3.3.4 基本數(shù)據(jù)類型

C語言支持多種數(shù)據(jù)類型,不同的數(shù)據(jù)類型在數(shù)據(jù)長度,是否有符號等方面各有不同,在使用各變量時候根據(jù)其數(shù)據(jù)類型即可確定其大小。例如,當(dāng)規(guī)定a為字符型(char)時候,a就表示了一個內(nèi)存單元的內(nèi)容。

C語言中的基本數(shù)據(jù)類型如下表所示,主要有無值型(void)、字符型(char)、整型(int)、單精度浮點型(float)和雙精度浮點型(double)。

3.3.4.1 無值型

void屬于一種特殊的數(shù)據(jù)類型,類型為void的變量所占的字節(jié)數(shù)為0,因此這個變量實際上是不存在的,所以void是不能直接用來聲明一個變量。

3.3.4.2 字符型

C語言中的字符型變量所占的空間是一個字節(jié)。ASCII碼表建立了字符與數(shù)據(jù)之間的關(guān)系,將英文字母、數(shù)字、英文標(biāo)點等多種字符分別與0x00~0xff之間的唯一的數(shù)字對應(yīng)(稱此數(shù)字為該字符的ASCII碼)。

根據(jù)ASCII碼表,字符“A”小于“a”,因為“A”的ASCII碼為65,而“a”的ASCII碼為97。字符“B”大于數(shù)字10,因為字符“B”的ASCII碼為66。字符“9”大于數(shù)字9,因為字符“9”的ASCII碼為57。

字符型數(shù)據(jù)類型還可以在前面加上前綴unsigned,構(gòu)成無符號字符型(unsigned char)數(shù)據(jù)類型。例如對于字符型變量 a和無符號字符型變量 b,二者對應(yīng)的內(nèi)存空間中的數(shù)據(jù)均為0xff,則在實際運用中,a為-1(此時a是有符號數(shù),-1的補碼為0xff,0xff的原碼是-1), b為255(此時b是無符號數(shù),255的補碼是0xff, 0xff的原碼是255)。這是因為二者的數(shù)據(jù)類型不同,編譯器會相應(yīng)編譯出不同的二進制代碼。

字符型數(shù)據(jù)類型在前面加上前綴signed,構(gòu)成有符號字符型(signed char)數(shù)據(jù)類型,與char型數(shù)據(jù)類型等價。

3.3.4.3 整數(shù)型

C語言中整型數(shù)據(jù)所占的字節(jié)個數(shù)與編譯器版本、操作系統(tǒng)版本、硬件版本有關(guān)。雖然有些環(huán)境下整型數(shù)據(jù)所占的字節(jié)個數(shù)是2(16位),有些環(huán)境下所占的字節(jié)個數(shù)是4(32位),但是原理都是相通的。本文介紹的是整型數(shù)據(jù)所占字節(jié)個數(shù)為4的情形。

整數(shù)型變量類型根據(jù)前綴是signed還是unsigned可以分為有符號整數(shù)(signed int)型和無符號整數(shù)(unsigned int)型。二者的區(qū)別是所代表的數(shù)字是否有符號,即是否有正數(shù)、負數(shù)之分。例如對于兩個整型變量a和b,分別是有符號整數(shù)型和無符號整數(shù)型,則假設(shè)二者對應(yīng)的內(nèi)存空間中的數(shù)據(jù)均為0xffffffff,則a為-1(此時因為a為有符號數(shù),-1的補碼是0xffffffff,0xffffffff的原碼是-1), b為4294967295 (此時因為 b為無符號數(shù),4294967295的補碼是0xffffffff,0xffffffff的原碼是4294967295,232-1=4294967295)。

有符號整數(shù)型與整型等價,在使用整型數(shù)據(jù)中,如果不寫前綴,則默認(rèn)是有符號整數(shù)型。

int類型前面還可以加上long和short前綴,從而形成新的數(shù)據(jù)類型,改變所占的字節(jié)數(shù),起到提高效率、節(jié)約內(nèi)存等作用。具體來說,添加long的int變量類型占4個字節(jié),而添加short的int變量類型占2個字節(jié)。

3.3.4.4 單精度浮點型

C語言中,單精度浮點型占據(jù)的字節(jié)數(shù)為4,且均為有符號數(shù),也就是說用unsigned或signed來做前綴修飾單精度浮點型是沒有意義的。

3.3.4.5 雙精度浮點型

雙精度浮點型與單精度浮點型很相似,不同的是,雙精度浮點型占據(jù)的字節(jié)數(shù)為8,因而可以表達更高精度的小數(shù)。雙精度浮點型同樣是有符號數(shù)。

3.3.5 基本數(shù)據(jù)類型變量的聲明和賦值

3.3.5.1 字符型變量的聲明和賦值

字符型變量主要包括有符號和無符號兩種類型。可以用等號對字符型變量進行賦值,等號右側(cè)的字符型變量或常數(shù)的值將會傳遞給等號左側(cè)的字符型變量。

表3-3中,語句(4)聲明了 a、b、c三個有符號字符型變量;語句(5)聲明了一個無符號字符型變量;對于能用符號表示的字符可直接用單引號括起來表示,如“'! '”“'@'”“ '#'”“ 'a'”“'9'”“'Z'”等,語句(6)示范了如何將字符“! ”賦給變量a;語句(7)是采用字符的ASCII碼的形式進行賦值,實現(xiàn)了將“! ”賦給變量b;語句(8)則示范了在變量之間的賦值,將變量a賦給了變量c,語句(8)執(zhí)行完畢后,變量a、b、c的值均為“! ”,而變量d的值為隨機數(shù)。查看a、b、c的內(nèi)存空間,三者的二進制數(shù)據(jù)均為“00100001”。

表3-3 字符型變量的操作代碼舉例

一些不能用符號表示的控制符,只能用 ASCII碼值來表示,如需要將換行符賦值給一個字符型變量時,可以將其相應(yīng)的ASCII碼(換行符對應(yīng)10)賦值給該變量,當(dāng)然期間也可以用十六進制的方式表示常數(shù)10,為0xA。此外,對于某些特殊符號,C語言提供了相應(yīng)的替代符號。表3-4列出了一些典型的特殊符號的信息。

表3-4 特殊符號的名稱和數(shù)值信息

表3-4中,為了對指定的特殊符號進行表示,C語言采用的方法是利用符號“\”和特殊字符一同組成新的字符組合來表示新的意義。符號“\”的這種功能稱為轉(zhuǎn)義,所以 C語言中,成符號“\”為轉(zhuǎn)義符。C語言編譯器在編譯源代碼時,如果發(fā)現(xiàn)“\”,則判斷其與后續(xù)的字符是否形成固定的組合,如果出現(xiàn),則將該組合看作一個字符,用相應(yīng)的ASCII碼進行替代,否則才將“\”看作斜杠符號。

表3-5中,聲明了3個字符型變量,分別為a、b和c。分別采用了三種形式對變量進行賦值,執(zhí)行完畢后3個變量的數(shù)值均為13。

表3-5 轉(zhuǎn)義符的使用方法示例

圖3-7為各變量在內(nèi)存中的分布,可以看出,3個變量所對應(yīng)的內(nèi)存中的數(shù)據(jù)相同,且各占據(jù)一個字節(jié)的空間。

圖3-7 變量分布圖

3.3.5.2 整型變量的聲明和賦值

表3-6給出了聲明整型變量的方法以及如何對整型變量進行操作。使用等號對整型變量進行賦值,等號右側(cè)的變量、常數(shù)或者表達式的值將會傳遞給等號左側(cè)的變量。

表3-6 整型變量的聲明與操作示例

表3-6中,語句(4)聲明了四個有符號整型變量;語句(5)聲明了兩個無符號整型變量d和e;語句(6)將十進制數(shù)100的值賦給了a;語句(7)將八進制數(shù)144的值賦給了b;語句(8)將十六進制數(shù)64的值賦給了c;語句(9)則示范了在變量之間的賦值,將變量 a的值賦給了變量 f。執(zhí)行完畢后,a、b、c和f的值均為100,而d和e為隨機數(shù)。查看變量a、b、c和f的內(nèi)存空間可知,四個變量的二進制數(shù)據(jù)均為“0000000000000000000001100100”。

表3-6同時也演示了常數(shù)的三種表示方法,分別為:十進制數(shù)(以非0開始的數(shù),如220、-560、45900),八進制數(shù)(以0開始的數(shù),如06、0106、057),十六進制數(shù)(以0X或0x開始的數(shù),如0X0D、0XFF、0x4e)。另外,可在整型常數(shù)后添加一個“L”或“l(fā)”字母表示該數(shù)為長整型數(shù),如22L、0773L、0Xae4L等。

圖3-8為中各變量在內(nèi)存中的分布,可以看出,變量a、b和c所對應(yīng)的內(nèi)存中的數(shù)據(jù)相同,且各占據(jù)4個字節(jié)的空間。

圖3-8 整型變量內(nèi)存分布圖

3.3.5.3 浮點型變量的聲明和賦值

浮點型數(shù)據(jù)類型分為單精度浮點型和雙精度浮點型,二者的主要區(qū)別在于所占的字節(jié)數(shù)和精度不同,其中單精度浮點型占4個字節(jié),而雙精度浮點型占8個字節(jié)。浮點型變量的聲明如表3-7中的代碼所示。

表3-7 浮點型變量的聲明與操作示例

圖3-7中,語句(4)聲明了 a、b、c三個單精度浮點型變量;語句(5)將0.22的值賦給了a;語句(6)將-0.22賦給了b,絕對值小于1的浮點數(shù),其小數(shù)點前面的零可以省略,因此此處寫成了-.22;語句(7)將-0.0035賦給了 c,這里采用了科學(xué)計數(shù)法的一種浮點數(shù)的表達方法;語句(8)將-0.0035賦給了 d,這里仍然使用科學(xué)計數(shù)法,但是省略了小數(shù)點前面位的0;語句(9)則示范了在變量之間的賦值,將變量a賦給了變量e,語句(9)執(zhí)行完畢后,a為0.22, b為-0.22, c和d均為-0.0034, e為0.22。浮點數(shù)只能使用十進制表述方式,雙精度浮點型變量的聲明和賦值方法與單精度浮點型變量的聲明和賦值方法基本相同,將 float用double替換即可。

3.3.5.4 基本數(shù)據(jù)類型之間的相互轉(zhuǎn)換

C語言支持基本數(shù)據(jù)類型的相互轉(zhuǎn)換,其方法為在需要進行數(shù)據(jù)類型轉(zhuǎn)換的變量前加上數(shù)據(jù)類型(數(shù)據(jù)類型用小括號包含),具體的示例代碼如表3-8所示。

表3-8 基本數(shù)據(jù)類型之間的相互轉(zhuǎn)換

3.3.6 高級數(shù)據(jù)類型

3.3.6.1 枚舉型

存在某種類型的變量,其僅可以取若干個數(shù)值中的一個。例如有一種用來表示身高的變量類型,a是該類型的一個變量,其取值為0、1或者2,共三種值。其中0表示矮,1表示中等,2代表高,顯然這不同于之前介紹的變量類型。雖然此時仍可以采用折中的辦法,將變量a仍然聲明為int型,在賦值的時候記得只可以賦0、1、2中的某個值。但是這樣應(yīng)用很不方便,而且在閱讀程序的時候還要查閱相關(guān)的程序說明才能知道0、1、2究竟代表什么意思。

使用C語言中的枚舉型變量是解決該問題的一個好辦法。若變量a只可以取0、1、2三個值,分別代表瘦、中等和胖,則可以應(yīng)用枚舉建立相應(yīng)的標(biāo)識符(如THIN、MIDDLE、FAT,標(biāo)識符的名稱規(guī)范同變量的名稱規(guī)范),并在之后用等號將THIN、MIDDLE和FAT的值賦給變量a。請看enum類型變量的聲明。

圖3-9給出了enum類型變量的聲明格式,其中a為聲明的變量。其中大括號內(nèi)為取值列表,具體來說也就是之前列舉的THIN、MIDDLE等標(biāo)識符,在聲明的同時還可以為各標(biāo)識符賦值。

圖3-9 enum類型變量的聲明

圖3-10為相應(yīng)的代碼示例,其中用person作為類型名,該類型可取的三個值分別用THIN、MIDDLE和FAT表示,同時三者分別賦值0、1、2。這樣聲明的變量a僅可以取THIN、MIDDLE和FAT三者之一,同時其取值具有可讀性。當(dāng)省略了對THIN、MIDDLE、FAT的賦值時,默認(rèn)從0開始。圖中還給出了在聲明變量a后,將a設(shè)置為FAT和THIN的操作語句。

圖3-10 enum類型變量聲明代碼舉例

3.3.6.2 數(shù)組型

數(shù)組型變量在編程中應(yīng)用十分廣泛,其本質(zhì)是內(nèi)存中的若干連續(xù)的空間,每個空間的長度和數(shù)據(jù)類型由數(shù)組的聲明語句決定,通過對內(nèi)存中數(shù)組的分布即可理解數(shù)組型變量的原理。C語言對數(shù)組提供了很好的支持,訪問數(shù)組成員的方法多種多樣,是編寫C語言程序常常用到的數(shù)據(jù)類型。

1.概念及聲明

數(shù)組是一種數(shù)據(jù)組合,其聲明方式為“數(shù)據(jù)類型+變量名+[數(shù)組長度]”,聲明一個數(shù)組變量后,便在內(nèi)存中出現(xiàn)了“數(shù)組長度”個連續(xù)的空間,每個空間的數(shù)據(jù)類型由聲明語句中的“數(shù)據(jù)類型”項指定,如圖3-11所示。圖中給出了(a)、(b)、(c)、(d)四種聲明數(shù)組的方式,其中(c)和(d)還同時完成了對數(shù)組成員的賦值。

圖3-11 數(shù)組型變量的聲明和賦值

圖3-11(a)中聲明了一個數(shù)組a,數(shù)組長度為10;圖3-11(b)中聲明了兩個數(shù)組變量a和b,其中數(shù)組a的數(shù)組長度為5, b的數(shù)組長度為10;圖3-11(c)中聲明了一個數(shù)組變量a,同時用三個值1、2、3給數(shù)組賦值(三個值用大括號包含,各值之間用“, ”分隔),該語句雖然沒有指明數(shù)組長度,但是由于值的個數(shù)為3,所以數(shù)組的長度被限制為3;圖3-11(d)中聲明了一個數(shù)組變量a,指明了數(shù)組a的長度為5,同時將5個值1、2、3、4、5賦給了數(shù)組a,此處由于已經(jīng)指明了數(shù)組長度,因此等號右側(cè)的數(shù)值的個數(shù)不能多于5個,當(dāng)少于5個時,會從第一個成員開始賦值,不足的部分會自動補零。

聲明數(shù)組變量后,變量在內(nèi)存中的分布如圖3-12所示,同時還可以用變量+[序號]的方式訪問各個數(shù)組成員。引入數(shù)組后不但增加了很多可用變量,而且由于這些變量是連續(xù)的,通過修改序號便可以訪問各個變量,因此為實現(xiàn)一些循環(huán)、數(shù)據(jù)記錄等功能提供了便利。

圖3-12 數(shù)組在內(nèi)存中的分布

圖3-12演示了數(shù)組在內(nèi)存中的分布,由于每個char型變量所占字節(jié)個數(shù)為1,因此數(shù)組a從內(nèi)存地址0xEE00開始,數(shù)組的5個成員所占的內(nèi)存空間分別為0xEE00、0xEE01、0xEE02、0xEE03、0xEE04,訪問各數(shù)據(jù)成員依次用a[0]、a[1]、a[2]、a[3]、a[4]即可。

2.多維數(shù)組

數(shù)組變量的聲明語句中,若變量后只有一對中括號,這種數(shù)組稱為一維數(shù)組。例如語句int a[5]即聲明了一維數(shù)組變量,數(shù)組的成員為a[0]、a[1]、a[2]、a[3]、a[4]。其中,中括號內(nèi)的數(shù)字為數(shù)組下標(biāo)。在多維數(shù)組情況下,變量后的中括號多于一組。圖3-13所示即為二維數(shù)組的聲明和賦值,相應(yīng)的代碼示例如圖3-14所示。

圖3-13 二維數(shù)組的聲明和賦值

圖3-14 二維數(shù)組的聲明和賦值代碼

圖3-13(a)介紹了如何聲明一個二維數(shù)組,圖3-14(a)為相應(yīng)的代碼,該代碼聲明了一個3×2的二維數(shù)組;圖3-13(b)示范了在一條語句中聲明多個二維數(shù)組變量,圖3-14(b)為相應(yīng)代碼;圖3-13(c)介紹了如何在聲明二維數(shù)組的同時為其賦值,圖3-14(c)為相應(yīng)代碼,聲明了一個3×2的數(shù)組,等號右側(cè)的大括號內(nèi)包含三個數(shù)據(jù){1,1}、{2,2}和{3,3},這三個數(shù)據(jù)分別給二維數(shù)組的3個成員a[0]、a[1]和a[2]賦值(此時可以將a[0]、a[1]和a[2]看作3個變量,從而將二維數(shù)組退化為一維數(shù)組);圖3-13(d)為圖3-13(c)的變種形式:省略了第一個方括號中的數(shù)組長度;圖3-13(e)在聲明二維數(shù)組的同時,采用了連續(xù)賦值的方式給二維數(shù)組賦值,圖3-14(e)為相應(yīng)的代碼;圖3-13(f)為圖3-13(e)的變形:省略了第一個方括號內(nèi)的數(shù)組長度。圖3-13的(b)、(c)、(d)、(e)中,如果等號右側(cè)提供的數(shù)值不夠用,則會自動補零以實現(xiàn)對數(shù)組成員的賦值。

圖3-15所示為采用圖3-14(c)語句后二維數(shù)組a在內(nèi)存中的分布圖,在這里假定計算機從內(nèi)存0xEE00處開始為二維數(shù)組分配空間。數(shù)組變量的聲明語句中,第一個方括號內(nèi)數(shù)字為3,第二個方括號內(nèi)數(shù)字為2。因此內(nèi)存中會有3個內(nèi)存塊,每個內(nèi)存塊中包含2個char型數(shù)據(jù)。a[0]、a[1]和a[2]分別代表了上述三個內(nèi)存塊的起始地址。對于a[i][j]來說(i = 0,1,2且j = 0,1),它代表了二維數(shù)組中第i個內(nèi)存塊的第j個成員的值。

圖3-15 二維數(shù)組在內(nèi)存中的分布圖

根據(jù)二維數(shù)組在內(nèi)存中的分布規(guī)律可知,最右側(cè)的下標(biāo)變化最快。多維數(shù)組在內(nèi)存中的分布與二維數(shù)組在內(nèi)存中的分布原理相同,一個三維數(shù)組的語句如圖3-16中所示。其中字符型數(shù)組變量a的后方有3個方括號,因此數(shù)組為三維數(shù)組。此時,第一個方括號中的數(shù)字2對應(yīng)于內(nèi)存中的2個“數(shù)組內(nèi)存塊”,每個“數(shù)組內(nèi)存塊”中又包含3個“中等的內(nèi)存塊”,每個“中等的內(nèi)存塊”中又包含2個“最小的內(nèi)存塊”。每個最小的內(nèi)存塊即1個char型數(shù)據(jù)。

圖3-16 三維數(shù)組的聲明和賦值

舉例來說,圖3-16中三維數(shù)組a的內(nèi)存空間為0xEE00~0xEE0B,共計2×3×2 = 12個char型數(shù)據(jù)空間(此處由于一個char型變量占8位,所以剛好也是12個字節(jié)),其內(nèi)存分布圖如圖3-17所示。

圖3-17 三維數(shù)組在內(nèi)存空間中的分配

對于三維數(shù)組a來說,a[i][j][k](i = 0、1, j = 0、1、2, k = 0、1)代表了三維數(shù)組中第i個“數(shù)組內(nèi)存塊”的第j個“中等的內(nèi)存塊”的第k個“最小內(nèi)存塊”的數(shù)值,或者說是三維數(shù)組中第(i×N + j×M + k)個成員,這里N為中等內(nèi)存塊的個數(shù),即2, M是最小內(nèi)存塊的個數(shù),即3。

多維數(shù)組的內(nèi)存分布規(guī)律可由三維數(shù)組的內(nèi)存分布規(guī)律推出:內(nèi)存塊由數(shù)組聲明語句中最右側(cè)的方括號開始進行分配,逐步擴大,各內(nèi)存塊之間連續(xù)。例如對于聲明的n維數(shù)組type a[N1][N2]…[Nn],其中type為C語言支持的任意一種變量類型,如int、char、double等。其內(nèi)存分配情況如圖3-18所示。

圖3-18 N維數(shù)組的內(nèi)存分配圖

圖3-18中,首先Nn個type型內(nèi)存空間構(gòu)成一個內(nèi)存塊,之后Nn-1個內(nèi)存塊構(gòu)成一個上級內(nèi)存塊,之后 Nn-2個新的內(nèi)存塊構(gòu)成一個更上一級的內(nèi)存塊,依次不斷擴展,最后 N1個內(nèi)存塊構(gòu)成總的多維數(shù)組內(nèi)存空間,大小為 N1×N2×…×Nn個type型變量。

3.應(yīng)用舉例

本節(jié)給出一個一維數(shù)組的應(yīng)用示例。假如一個班級有5名同學(xué)參加數(shù)學(xué)競賽,其學(xué)號為1~5,每名同學(xué)有各自考試成績,這樣便可以用數(shù)組來表示每個同學(xué)的學(xué)號和成績。由于每名同學(xué)的學(xué)號和成績都在100以內(nèi)且為非負數(shù),所以用unsigned char型數(shù)組即可,如圖3-19所示。

圖3-19 數(shù)組應(yīng)用舉例

圖3-19(a)列出了5名同學(xué)的學(xué)號和成績,圖3-19(b)聲明了2個無符號字符型數(shù)組,分別用來存儲5名同學(xué)的學(xué)號和成績。這樣對應(yīng)一個下標(biāo)i(i = 0、1、2、3、4),則number[i]表示了學(xué)號,score[i]表示了成績。圖3-19(c)對數(shù)組的各個成員賦初值。這樣通過數(shù)組便將5名同學(xué)的學(xué)號和成績存儲了起來。相應(yīng)的內(nèi)存分布圖如圖3-20所示。

圖3-20 數(shù)組示例在內(nèi)存中的分配

圖3-20中,數(shù)組score開始于0xEE00,各成員變量score[0]~score[4]的地址依次為0xEE00~0xEE04,值依次為88、89、87、95、92;數(shù)組number開始于0xEE05,各成員變量number[0]~number[4]的地址依次為0xEE05~0xEE09,值依次為1、2、3、4、5。

5.3.6.3 自定義數(shù)據(jù)類型

C語言提供了一種機制,可以使編程人員自定義數(shù)據(jù)類型,主要包括結(jié)構(gòu)體和聯(lián)合體兩種。

1.結(jié)構(gòu)體

1)結(jié)構(gòu)體的概念。

打個比方:程序中需要這樣一種數(shù)據(jù)類型,該類型的一個變量占據(jù)7個字節(jié)的長度,可以存儲3個字符和1個有符號的整型數(shù)。圖3-21中聲明了兩個變量a和b,這兩個變量就屬于我們希望的數(shù)據(jù)類型。其中使用了關(guān)鍵字struct,所以稱呼這種自定義的數(shù)據(jù)類型為“結(jié)構(gòu)體”,這種結(jié)構(gòu)體類型的變量稱為“結(jié)構(gòu)體變量”,在圖3-21中的“新數(shù)據(jù)類型”就是“結(jié)構(gòu)體”,而a、b就是“結(jié)構(gòu)體變量”。

圖3-21 結(jié)構(gòu)體及結(jié)構(gòu)體變量的常規(guī)聲明方式

圖3-21給出了結(jié)構(gòu)體以及結(jié)構(gòu)體變量的聲明方式,共計(a)、(b)、(c)三種,均可以聲明a、b兩個變量。例如采用圖3-21(a)中所示的格式,struct關(guān)鍵字后面接著的是一種數(shù)據(jù)類型名稱,然后是一對大括號內(nèi),括號內(nèi)聲明了多個變量(這些變量稱為成員變量,其類型可以是C語言中的一些基本數(shù)據(jù)類型如int、char等,也可以是高級類型如數(shù)組以及本節(jié)介紹的結(jié)構(gòu)體、指針等),大括號后結(jié)尾是分號,表示本條語句結(jié)束。在聲明了該“結(jié)構(gòu)體”之后,便可以在程序中用來聲明變量了,聲明變量的方法與之前介紹的變量的聲明方法相似。

與圖3-21相對應(yīng)的代碼如圖3-22所示。圖3-22(a)中定義的結(jié)構(gòu)體名稱為“charnew”(這里自定義的數(shù)據(jù)類型的名稱要符合一定的規(guī)范,規(guī)范與變量命名的規(guī)范相同),大括號內(nèi)的內(nèi)容表明,charnew這種數(shù)據(jù)類型包含3個char型數(shù)據(jù)和1個int型數(shù)據(jù)。在圖3-22(a)定義了結(jié)構(gòu)體“charnew”之后,便可以用來聲明變量a和b了。

圖3-22 定義結(jié)構(gòu)體及聲明結(jié)構(gòu)體變量示例代碼

圖3-22(b)是另一種定義結(jié)構(gòu)體的形式,將定義“結(jié)構(gòu)體”與聲明變量a和b放在了同一條語句中,最后以分號結(jié)束。還可以進一步簡化省去“新數(shù)據(jù)類型名稱”,如圖3-22(c)所示,雖然這樣的代碼使得定義的結(jié)構(gòu)體沒有名稱,但是a、b兩個變量依然包括“3個char型變量和1個int型變量”,代碼簡潔。

結(jié)構(gòu)體變量在內(nèi)存中的分布如圖3-23所示。可以看出,從一個結(jié)構(gòu)體變量的起始位置開始,按照聲明的順序依次分布各個成員變量。例如對于圖3-21(a)中自定義的數(shù)據(jù)類型“新數(shù)據(jù)類型名稱”和由此聲明的變量a、b,由b的起始位置開始,依次分布了 b的成員變量:變量1,變量2, ……,直至變量n,每個變量所占的空間大小由各變量的數(shù)據(jù)類型決定。這里需要注意的是,訪問各成員變量采用的方法是用“.”操作符,例如用“b.變量1”訪問b的成員變量“變量1”,用“b.變量2”訪問b的成員變量“變量2”,依次類推。當(dāng)結(jié)構(gòu)體內(nèi)又有其他自定義類型的變量時,如變量m,則對變量m成員變量的訪問仍然用“.”操作符,如變量m內(nèi)部成員變量“變量k”,則可以用“b.變量m.變量k”來訪問。上述討論對于變量a來說,亦成立。

圖3-23 結(jié)構(gòu)體變量在內(nèi)存中的分布

因此,圖3-22中聲明的變量a和b在內(nèi)存中的分布方式可表示為圖3-24。

圖3-24 結(jié)構(gòu)體“charnew”變量在內(nèi)存中的分布

圖3-24中,用戶定義的數(shù)據(jù)類型(結(jié)構(gòu)體)由3個char型成員變量和1個int型成員變量構(gòu)成。該結(jié)構(gòu)體的2個實例為變量a和b,若a的地址為0xEE07,則0xEE07、0xEE08、0xEE09、0xEE0A分別為a的成員變量c1、c2、c3、n的地址,各成員變量的訪問方式為a.c1、a.c2、a.c3、a.n。變量a所占的內(nèi)存空間為0xEE07~0xEE0D。變量b地址為0xEE00,連續(xù)分布b的3個char型成員變量為c1、c2、c3和一個int型變量n,變量b所占的內(nèi)存空間為0xEE00~0xEE06。

因此,對于一個結(jié)構(gòu)體變量來說,其在內(nèi)存中的分布即從變量的地址開始按照地址增加的方向順次安排各成員變量,訪問各個成員變量可以用變量名 +“.”的方式實現(xiàn)。

2)結(jié)構(gòu)體的位成員變量。

由于在C語言提供的基本數(shù)據(jù)類型中,最短的數(shù)據(jù)類型為char,占據(jù)1個字節(jié),即由8個二進制數(shù)構(gòu)成。而如果每個成員變量的取值范圍很小時,會帶來較大的內(nèi)存浪費。圖3-25中所示的一個結(jié)構(gòu)體password,其內(nèi)部各成員變量的取值都在0~2,即便每個變量都采用char型,變量a也要占據(jù)12個字節(jié)的內(nèi)存空間。

圖3-25 結(jié)構(gòu)體password

在結(jié)構(gòu)體的成員變量后可以規(guī)定該變量所占的二進制位數(shù),從而形成“位成員變量”,相應(yīng)的結(jié)構(gòu)體成為“位結(jié)構(gòu)”,避免內(nèi)存浪費問題,其實現(xiàn)方式如圖3-26所示,其中數(shù)據(jù)類型必須是int(unsigned或signed),整型常數(shù)必須是非負的整數(shù),范圍是0~15,表示二進制位的個數(shù),即表示有多少位。

圖3-26 結(jié)構(gòu)體中的位成員變量

圖3-27中結(jié)構(gòu)體的各成員變量均規(guī)定了位數(shù),由于每個變量的取值范圍為0~2,因此用兩位二進制數(shù)完全足夠,這樣整個結(jié)構(gòu)體所占的內(nèi)存空間為2×12/8 = 3個字節(jié),顯著節(jié)省了內(nèi)存。此時結(jié)構(gòu)體各成員變量的訪問方式仍然是用“.”操作符。例如訪問其中的key1成員可寫成“a.key1”。

圖3-27 含有為成員變量的結(jié)構(gòu)體

結(jié)構(gòu)體中的“位成員變量”可定義為unsigned,也可定義為signed,要根據(jù)成員變量具體的取值范圍和需求來確定,如兩位二進制數(shù)表示0、1或2,則應(yīng)聲明為無符號型。注意當(dāng)成員變量的長度為1時,會被認(rèn)為是unsigned類型。因為一位二進制數(shù)是不可能具有符號的。

在實際應(yīng)用中,可以將“位成員變量”與一般的成員變量混合使用出現(xiàn)在一個結(jié)構(gòu)體中,達到節(jié)省內(nèi)存和系統(tǒng)資源的目的。

3)結(jié)構(gòu)體應(yīng)用舉例。

本節(jié)給出一個應(yīng)用示例,該例子在介紹數(shù)組類型時已經(jīng)有所交代,現(xiàn)重新給出,并采用結(jié)構(gòu)體的方法描述。

假如一個班級有5名同學(xué)參加數(shù)學(xué)競賽,其學(xué)號為1~5,每名同學(xué)有各自的考試成績,則可以定義“學(xué)生”這樣一種結(jié)構(gòu)體,將每名同學(xué)的學(xué)號和成績作為其成員變量,如圖3-28所示。

圖3-28 自定義數(shù)據(jù)類型舉例

圖3-28(a)列出了5名同學(xué)各自的學(xué)號和成績,圖3-28(b)定義了一種自定義的數(shù)據(jù)類型“student”,也就是定義了結(jié)構(gòu)體“student”,該結(jié)構(gòu)體含有兩個成員變量:“number”和“score”,分別表示學(xué)號和成績,并聲明了用數(shù)組 a[5]來表示5名學(xué)生。圖3-28(b)中使用“unsigned char”型數(shù)據(jù)類型是因為每名同學(xué)的學(xué)號和成績都是小于100的非負數(shù),與使用“int”型數(shù)據(jù)相比還可以節(jié)省內(nèi)存。圖3-28(c)為每個變量賦值。“student”型數(shù)組 a聲明和賦值完畢后,在內(nèi)存中的分布情況如圖3-29所示。

圖3-29 自定義數(shù)據(jù)類型“學(xué)生”型變量的內(nèi)存分布

圖3-29表示出了“student”型數(shù)組變量在內(nèi)存中的分布,可以看出在內(nèi)存中連續(xù)分配了5個student類型的數(shù)據(jù)空間,從0xEE00至0xEE09共10個字節(jié)。通過改變數(shù)組a的下標(biāo)即可訪問各個數(shù)組成員a[0]~a[4],應(yīng)用更加方便。靈活運用結(jié)構(gòu)體,可以將底層數(shù)據(jù)進行綜合和歸納,如本例中即將學(xué)生的學(xué)號和成績綜合到了每個student類型成員的內(nèi)部,便于進行程序編寫。

2.聯(lián)合體

可以在結(jié)構(gòu)體內(nèi)部聲明多個不同類型的成員變量,并根據(jù)需要使用其中的一種。由此形成的變量可稱為多功能變量,因為它既可以存儲整型數(shù)據(jù),又可以存儲浮點型數(shù)據(jù),甚至可以存儲之前介紹過的自定義數(shù)據(jù)類型。例如,圖3-30中聲明的變量a,可以存儲char、int、double和student類型,其在內(nèi)存中的分布如圖3-31所示。當(dāng)使用a存儲char型變量時,可以用a.c1,當(dāng)存儲int型變量時,可以用a.c2,諸此類推,便可以用a存儲多種類型的變量。

圖3-30 “多功能”結(jié)構(gòu)體

圖3-31 “多功能”結(jié)構(gòu)體類型變量的內(nèi)存分布

該方案有一個缺點,那就是此時變量a所占的空間是所有成員變量所占空間的和,即在滿足了自身需求的同時,卻帶來了很大和不必要的內(nèi)存浪費。而本節(jié)介紹的“聯(lián)合體”則很好地解決了這一問題,其對應(yīng)的關(guān)鍵字為“union”,用法與關(guān)鍵字“struct”相同。

圖3-32定義了一種聯(lián)合體,其成員變量有4個,分別是一個char型變量c1,一個int型變量c2,一個double型變量c3和一個student型變量c4。a是該聯(lián)合體的一個實例。圖3-33為變量a在內(nèi)存中的分布圖,此時變量a的空間與成員變量中占空間最大者保持一致,每個成員變量的開始地址均與變量a的地址相同,由此實現(xiàn)了內(nèi)存空間的復(fù)用。此時采用a.c1、a.c2、a.c3和a.c4分別可以存儲char, int, double和student類型的變量。

圖3-32 “多功能”聯(lián)合體

圖3-33 “多功能”聯(lián)合體變量在內(nèi)存中的分布

參考關(guān)鍵字“struct”的用法,將其中的關(guān)鍵字“struct”改為“union”即可定義相應(yīng)的聯(lián)合體。采用聯(lián)合體定義數(shù)據(jù)類型的格式和示例分別如圖3-34和圖3-35所示。

圖3-34 聯(lián)合體及聯(lián)合體體變量的常規(guī)聲明方式

圖3-35 定義聯(lián)合體及聲明聯(lián)合體變量示例代碼

圖3-34給出了聯(lián)合體的常規(guī)聲明方式,共計(a)、(b)、(c)三種,均可以實現(xiàn)聲明兩個聯(lián)合體變量:變量a和變量b兩個變量。例如采用圖3-34(a)中所示的格式,union關(guān)鍵字后接一種數(shù)據(jù)類型名稱,然后接一對大括號,括號內(nèi)給出了多個變量,這些變量稱為成員變量,其類型可以是基本數(shù)據(jù)類型如int、char等,也可以是高級類型如數(shù)組以及本節(jié)介紹的結(jié)構(gòu)體、指針等。大括號后接分號表示本語句結(jié)束。之后便可以在程序中使用這種新定義的數(shù)據(jù)類型(聯(lián)合體)來聲明變量了,聲明變量的方法和之前介紹的基本類型變量的聲明方法相同。與圖3-34(a)相對應(yīng)的代碼如圖3-35(a)所示,其中聯(lián)合體類型定義為 varnew(這里自定義的數(shù)據(jù)類型的名稱要符合一定的規(guī)范,規(guī)范與變量命名的規(guī)范相同),大括號內(nèi)的內(nèi)容表明,varnew這種聯(lián)合體包含1個char型變量c1、1個int型變量c2、1個double型變量c3和1個student型變量c4。定義了charnew數(shù)據(jù)類型后,便可以用來聲明變量a和b了。

圖3-34(b)是另外一種定義聯(lián)合體的形式,即在一條語句中完成聯(lián)合體的定義和變量的聲明,其代碼如圖3-35(b)所示,聯(lián)合體名稱為 varnew,在大括號結(jié)束后,緊接著寫變量a和b,完成了聯(lián)合體的定義和變量a、b的聲明。可以進一步簡化省去聯(lián)合體的名稱,如圖3-34(c)所示,代碼更加簡潔。

聯(lián)合體變量在內(nèi)存中的分布如圖3-36所示,其中聯(lián)合體以及變量 a、b的定義如圖3-34(a)所示。可以看出,聯(lián)合體變量的起始位置也即各個成員變量的地址。當(dāng)聯(lián)合體內(nèi)又有其他自定義類型的變量時,比如變量 m,則對變量 m成員變量的訪問仍然用“.”操作符,例如變量m內(nèi)部成員變量“變量k”,則可以用“b.變量m.變量k”來訪問。上述討論對于變量a來說,亦成立。

圖3-36 聯(lián)合體變量在內(nèi)存中的分布

圖3-35中聲明的變量a和b在內(nèi)存中的分布如圖3-37所示,若a的地址為0xEE04,則各成員變量c1、c2、c3、c4的地址也是0xEE04,各成員變量的訪問方式為 a.c1、a.c2、a.c3、a.c4。變量 a所占的內(nèi)存空間為0xEE04~0xEE07。變量 b的地址為0xEE00,成員變量c1、c2、c3和c4的地址也是0xEE00,變量b所占的內(nèi)存空間為0xEE00~0xEE03。

圖3-37 聯(lián)合體“varnew”型變量的內(nèi)存分布

由此可知,對于一個聯(lián)合體類型的變量來說,其所占的內(nèi)存等于各成員變量中所占內(nèi)存的最大者,各成員變量的地址與該變量相同,復(fù)用內(nèi)存空間從而節(jié)省了內(nèi)存,訪問成員變量用變量名 +“.”的方式即可。

3.3.6.4 指針型

1.概念

C語言中,可以用“&”取得一個變量的地址,這個變量可以是任何一種C語言支持的數(shù)據(jù)類型,包括基本的數(shù)據(jù)類型如char、int等以及高級的數(shù)據(jù)類型如枚舉、結(jié)構(gòu)體、聯(lián)合體和指針等。假設(shè)變量a為字符型,變量p為指針型,則各表達式的關(guān)系如表3-9所示。

表3-9 變量及變量地址的值

對于一個指針類型的變量,其內(nèi)存單元中的數(shù)據(jù)是一個地址值,如在圖3-38中,變量a為字符型,其地址為0xFF00,則a的值是10100001,對應(yīng)的十六進制是0xA1。變量p為指針型,其地址為0xFF10,由于地址總線為16位,因此一個指針類型的變量其內(nèi)容應(yīng)該由兩個單元的變量構(gòu)成,即由內(nèi)存地址為0xFF10和0xFF11兩個單元內(nèi)的數(shù)據(jù)構(gòu)成(不同的硬件和操作系統(tǒng),指針型數(shù)據(jù)類型所占的字節(jié)數(shù)不同,但其長度都要滿足能夠存儲一個內(nèi)存地址)。所以 p的值為0000000010100001,對應(yīng)的十六進制為0x00A1,0x00A1是一個內(nèi)存的地址。

圖3-38 指針數(shù)據(jù)類型原理示意圖

對于一個指針型變量來說,其地址(例如圖3-38中的0xFF10)在內(nèi)存中是確定的,其內(nèi)容可以通過賦值進行修改。例如可以將圖3-38中 p的內(nèi)容由0000000010100001(0x00A1)改為0000000010100010(0x00A2)。

因此,一個指針型的變量隱含的意義是:它的內(nèi)容(數(shù)值)應(yīng)該被看作一個內(nèi)存地址。

2.聲明和賦值

在C語言中,聲明一個指針類型的變量可以用圖3-39所示的方法,即采用“方框+*+指針變量名稱”的格式。其中,方框內(nèi)是“所指內(nèi)存的數(shù)據(jù)類型”,可以是C語言中的基本數(shù)據(jù)類型,如char、int、double等,也可以是一些高級的類型,如指針、結(jié)構(gòu)等;方框后的“*”,表示聲明的變量是一個指針型,此時“變量1”的內(nèi)容是一個內(nèi)存地址,且該內(nèi)存地址處的數(shù)據(jù)是“方框型”。

圖3-39 指針類型變量的聲明方法

圖3-39中,方框內(nèi)的數(shù)據(jù)類型便決定了“變量1”這個指針型變量的內(nèi)容所確定的內(nèi)存空間的數(shù)據(jù)類型,或者方框內(nèi)的數(shù)據(jù)類型決定了“變量1”所指內(nèi)存的數(shù)據(jù)類型。

圖3-40給出了一個指向字符型變量的指針的聲明方法,其中方框內(nèi)的數(shù)據(jù)類型決定了指針的內(nèi)容所對應(yīng)的內(nèi)存處數(shù)據(jù)的類型;“*”號與方框一起表明聲明的變量是指針型,因而稱為“指針標(biāo)記符”;“變量1”為變量名稱。

圖3-40 聲明指針類型的語句的各部分功能

當(dāng)采用“char *p; ”聲明一個指針型變量p的時候,可以解釋為:p為一個“指向char型的指針”型變量,p的內(nèi)容0000000010100001(0x00A1)是一個具體的內(nèi)存地址,該內(nèi)存地址0x00A1處的數(shù)據(jù)為char型,值為00000001(0x01);當(dāng)采用“char **p; ”聲明一個指針型變量 p的時候,可以理解為:p的內(nèi)容0000000010100001(0x00A1)是一個具體的內(nèi)存地址,該內(nèi)存地址0x00A1處的數(shù)據(jù)是一個指針,其值0001001000000001(0x1201)是一個內(nèi)存地址值,而內(nèi)存地址0x1201處是一個char型變量。

一個指針聲明后,其內(nèi)容是隨機數(shù),或者理解為該指針指向一個隨機的內(nèi)存地址,此時的指針被形象地稱為“野指針”,要對該指針進行賦值后才能使用,否則當(dāng)使用“*”獲取其內(nèi)容時,會導(dǎo)致錯誤。通常可以用操作符“&”對變量取地址,并將該地址賦給指針。

C語言中取得一個指定內(nèi)存地址內(nèi)容的操作符為“*”。例如對于圖3-38中的指針p, “*p”便表示了p所指內(nèi)存空間的內(nèi)容。具體來說,如果p是指向字符型的指針變量(聲明語句為“char *p”),則此時自0x00A1起一個單元長度的數(shù)據(jù)即是“*p”的值;如果p是指向int型的指針變量(聲明語句為“int *p”),則此時自0x00A1起四個單元長度的數(shù)據(jù)即是“*p”的值;如果p是指向指針的指針變量(聲明語句為“char **p”“int **p”等),則此時自0x00A1起四個單元長度的數(shù)據(jù)即是“*p”的值,且該值應(yīng)看作是一個內(nèi)存地址。

總結(jié)關(guān)于指針型數(shù)據(jù)類型的聲明和賦值方法,如圖3-41所示。其中,(a)直接聲明了一個指針型變量“變量1”; (b)聲明了兩個指針型變量“變量1”和“變量2”,變量之間用逗號隔開;(c)為指針型變量“變量1”進行賦值;(d)將“變量2”的值賦給了“變量1”; (e)則聲明了指針型變量“變量1”,同時對其進行了賦值。

圖3-41 指針型變量的聲明和賦值

(c)和(e)對指針型變量進行了賦值,其中等號右側(cè)的“值”是一個地址值,就如圖3-38中所示的0x00A1和0x00A2等,也可以用“&”來求取變量的地址。

表3-10為對指針型變量進行聲明和賦值的代碼。其中語句(2)聲明了一個字符型變量a;語句(3)聲明了兩個“字符型”的指針型變量p1和p2;語句(6)將變量a設(shè)置成0x1;語句(7)將變量a的地址賦給了p1;語句(8)將變量p1的值賦給了p2。各語句執(zhí)行完畢后,p1和p2所存儲的內(nèi)容為變量a的地址,a的內(nèi)容為0x1。

表3-10 指針型變量聲明和賦值代碼

3.指針類型的相互轉(zhuǎn)換

C語言支持不同類型的指針的相互轉(zhuǎn)換,只需要將新的指針類型置于小括號內(nèi),并放置在一個指針變量左側(cè)即可,如表3-11所示。

表3-11 指針類型相互轉(zhuǎn)換

表3-11中的語句(2)聲明了一個整型變量a并將其賦值為0x1234, a的內(nèi)存地址假設(shè)為0x3300,相應(yīng)的內(nèi)存空間0x3300和0x3301分別賦值為0x34和0x12。表3-11中的語句(3)聲明了一個短整型的指針型變量 p1,語句(4)聲明了一個長整型的的指針型變量 p2,語句(7)將a的地址賦給了p1,則p1的內(nèi)容為0x3300,也就是變量a的地址。如圖3-42所示。

圖3-42 各變量的內(nèi)存分布

指針變量p1的地址為0x3302,指針變量p2的地址為0x3304。表3-11中語句(8)將p1的值賦給了p2,這里采用了指針類類型的轉(zhuǎn)換。由于p1是“short*”型,而p2是“char *”型,因此“*p1”的值為0x1234,而“*p2”的值為0x34。

4.多維指針

采用數(shù)據(jù)類型和多個“*”的組合可聲明多維指針變量。例如語句“char **p”中,p為二維指針變量,表示變量p的內(nèi)容是一個內(nèi)存地址,而該內(nèi)存地址存儲的數(shù)據(jù)仍然是一個指針。如圖3-43所示。

圖3-43 二維指針內(nèi)存示意圖

其中,二維指針變量p的內(nèi)容為二進制0010001000000000(0x2200),該值是一個內(nèi)存地址0x2200,地址0x2200處的數(shù)值仍然是一個指針,指針的值為二進制0011001100000000(0x3300),0x3300仍然是一個地址,地址0x3300處的數(shù)值,根據(jù)p的定義語句,為char型,也就是二進制00000000。

指針變量的內(nèi)容可以使用操作符“*”獲取,使用方法如表3-12所示。

表3-12 二維指針的表達式取值

多維指針的操作方法與二維指針相類似,無論指針的維數(shù)是多少,其本質(zhì)仍然是一個指針,其數(shù)值是一個內(nèi)存地址。

5.應(yīng)用舉例

1)內(nèi)存共享和修改。

由于指針的數(shù)值是一個內(nèi)存地址,因此不同的程序模塊只要傳遞共同的一個指針,便可以在不同模塊中對該指針的內(nèi)容進行修改,便于實現(xiàn)數(shù)據(jù)的共享和共同維護。舉例來說,在內(nèi)存的一個位置有n個字節(jié)的數(shù)據(jù),程序中的某個模塊只需要一個指針變量便可以對該段內(nèi)存進行訪問和修改。

2)無值指針。

以一維無值指針為例,其聲明方式為“void *p”,此時指針p的內(nèi)容是一個地址,但是并未指定該地址的數(shù)據(jù)是何種類型。借助于C語言中指針類型的強制轉(zhuǎn)換,可以根據(jù)需要將無值指針轉(zhuǎn)化為需要的指針類型。

3)存儲指針的數(shù)組。

在聲明數(shù)組時,將數(shù)據(jù)類型設(shè)定為指針型,這樣數(shù)組中的各個元素的內(nèi)容都是一個內(nèi)存地址。相應(yīng)的代碼如表3-13所示。

表3-13 存儲指針的數(shù)組的代碼示例

表3-13中,語句(2)聲明了1個指針數(shù)組p,數(shù)組的3個成員為p[0]、p[1]、p[2],均為字符型指針變量,其內(nèi)容均表示1個內(nèi)存地址;語句(3)、(4)、(5)定義了3個字符型的變量a、b、c并分別賦值為0x00、0x01、0x02;語句(8)將變量a的內(nèi)存地址賦給了p[0];語句(9)將變量b的內(nèi)存地址賦給了p[1];語句(10)將變量c的內(nèi)存地址賦給了p[2]。

圖3-44為各變量在內(nèi)存中的分布。這里假設(shè)變量a的地址為0x3306,變量b和變量c的地址分別為0x3307和0x3308。指針數(shù)組p的地址為0x3300, p的三個成員p[0]、p[1]和p[2]的地址分別為0x3300、0x3302和0x3304,內(nèi)容分別為0x3306、0x3307和0x3308,內(nèi)容分別表示了變量a、b和c的地址。

圖3-44 存儲指針的數(shù)組的內(nèi)存分布

4)指向數(shù)組的指針。

指針的內(nèi)容(是1個內(nèi)存地址)是數(shù)組的地址,示范代碼如表3-14所示。

表3-14 指向數(shù)組的指針的用法

表3-14中的語句(2)聲明了1個字符型的數(shù)組a,并給a的3個成員分別賦值為0、1、2。語句(3)聲明了1個字符型的指針p1。語句(4)聲明了1個字符型的指針p2。語句(7)將數(shù)組a的地址賦給了p1。語句(8)將數(shù)組a的第一個元素a[0]的地址賦給了p2。

圖3-45給出了變量在內(nèi)存中的分布圖。假設(shè)數(shù)組的地址為0x3300,則a[0]、a[1]、a[2]的地址分別為0x3300,0x3301和0x3302。變量p1的地址為0x3303,且p1的內(nèi)容為0x3300, p1的內(nèi)容是一個內(nèi)存地址,也就是數(shù)組a的內(nèi)存地址,它是數(shù)組a的指針。變量p2的地址為0x3305,且p2的內(nèi)容為0x3300。

圖3-45 指向數(shù)組的指針的內(nèi)存分布

5)字符指針。

在C語言中可以利用指針靈活操作字符串,圖3-46為利用字符指針操作字符串的原理。

圖3-46 字符串指針示例代碼及內(nèi)存分布

圖3-46中的左圖為字符指針的示例代碼,其中語句(3)聲明了一個char型指針并將字符串“hi! ”的值賦給了p,這里的p是一個字符指針,字符串用雙引號包含。該段程序經(jīng)過編譯器編譯后生成的機器代碼的操作是:在內(nèi)存中分配一段空間,該段空間的字節(jié)數(shù)是字符串“hi! ”的長度再加1,將字符串“hi”存儲到這段內(nèi)存后,在多出的那個字節(jié)內(nèi)存入0,這么做的目的是在使用該字符串的時候,知道是否到了字符串的結(jié)尾。

相應(yīng)的內(nèi)存分布如圖3-46的右圖所示,設(shè)字符串“hi! ”由內(nèi)存地址0x4400開始存放,則0x4400、0x4401、0x4402、0x4403處的數(shù)據(jù)分別為0x68、0x69、0x21和0x00,分別對應(yīng)字符“h”、“i”、“! ”和“空字符”。而指針p其實是一個字符指針,假設(shè)它的地址是0x3300,它的內(nèi)容是字符串“hi! ”的起始地址,即字符“h”的地址,亦即0x4400。這樣,由p便可以操作字符串“hi! ”了。

6)地址運算。

由于一個指針型變量的數(shù)值是一個地址值,所以可以對一個指針型變量的數(shù)值進行加減等操作,表3-15給出了具體的代碼示例。

表3-15 利用指針進行地址運算的代碼

表3-15中,語句(2)聲明了1個字符型變量c;語句(3)聲明了1個字符型變量b;語句(4)聲明了1個短整型指針變量q;語句(5)聲明了1個字符型指針變量p;語句(6)聲明了1個字符型數(shù)組a, a的3個成員分別為1、2、3;語句(9)設(shè)置指針p為數(shù)組a的地址,因此p指向數(shù)組a的地址亦即數(shù)組a的第一個元素 a[0]的地址;語句(10)將指針 q的值設(shè)置為數(shù)組 a的地址,因此 q指向數(shù)組a的地址亦即數(shù)組a的第一個元素a[0]的地址;語句(11)將指針p加1后的地址的數(shù)據(jù)賦給字符b;語句(12)將指針q加1后的地址的數(shù)據(jù)賦給字符c,此時為了取出(q+1)處的字符值,采用了指針類型的強制轉(zhuǎn)換:先將(q+1)轉(zhuǎn)換為char型指針,之后再用操作符“*”取其內(nèi)容,這里還用到了小括號來保證表達式的優(yōu)先級。

圖3-47所示的代碼執(zhí)行之后各變量在內(nèi)存中的分布。假設(shè)數(shù)組a在內(nèi)存中的地址為0x3306,數(shù)組 a的三個成員 a[0]、a[1]和a[2]的地址分別為0x3306、0x3307和0x3308,值分別為1、2和3。指針p的地址為0x3304,值為0x3306,即數(shù)組a的地址。指針q的地址為0x3302,值為0x3306,亦為數(shù)組a的地址。字符型變量b的地址為0x3301,其值為指針p加1后取內(nèi)容,由于p是字符型指針,所以對p加1為0x3307,此時用“*”取內(nèi)容,為2,因此b的值為2。字符型變量c的地址為0x3300,其值為指針q加1后取內(nèi)容,此時由于q是short型指針,則q加1為0x3308強制轉(zhuǎn)化為字符型指針,并取內(nèi)容后得3。

圖3-47 地址運算內(nèi)存分布

一個指針加1后,其結(jié)果是指針的內(nèi)容加上該指針的內(nèi)容所對應(yīng)的空間的數(shù)據(jù)類型的字節(jié)數(shù),即對于一個指針p,如果其定義為“type *p”(其中type為int, char,short, …),則p + 1的值為p的內(nèi)容加上sizeof(type),其中sizeof(type)表示一個type類型變量所占的字節(jié)數(shù)。

7)指針與數(shù)組。

C語言中的指針與數(shù)組有很多相似的地方,二者都可以表示1個內(nèi)存地址。表3-16給出了操作指針和數(shù)組的示例代碼,其中語句(2)聲明了1個字符型變量e,語句(3)聲明了1個字符型變量d,語句(4)聲明了1個字符型變量c,語句(5)聲明了1個字符型變量b,語句(6)聲明了1個字符型指針p,語句(7)聲明了字符型數(shù)組a,3個成員變量a[0]、a[1]和a[2]分別賦為1、2和3。

表3-16 操作指針與數(shù)組的代碼示例

表3-16中,語句(10)將a的地址賦給了p,指針p的內(nèi)容是一個地址值,該地址為數(shù)組a的地址,也即數(shù)組a的第一個成員a[0]的地址;語句(11)將數(shù)組a的成員a[1]賦給字符b;語句(12)對表達式(a+1)取內(nèi)容,并賦給字符c,這里需要注意的是,C語言中聲明一個數(shù)組后,這個數(shù)組名(比如本例中的數(shù)組a)是作為一個地址看待的,所以(a+1)代表了一種地址運算;語句(13)將指針p加1后取內(nèi)容再賦給字符d;語句(14)將指針p用操作符“[]”進行取內(nèi)容,并賦給字符e。

圖3-48為各變量在內(nèi)存中的分布圖,假設(shè)數(shù)組 a的地址為0x3306,則地址0x3306、0x3307、0x3308分別是數(shù)組a的3個成員 a[0]、a[1]、a[2],其值分別為1、2、3。指針p的地址是0x3304,內(nèi)容為0x3306,即數(shù)組a的地址。變量b的地址為0x3303,其值為2,即a[1]。變量c的地址是0x3302,其值為2,因為(a+1)的結(jié)果為地址值0x3307,該值為一個內(nèi)存地址,所以取內(nèi)容后為2。語句(13)執(zhí)行后d的值為2, d的地址為0x3301。語句(14)中,字符變量e的地址為0x3300,對指針采用了“[]”操作符,由于此時指針的值為0x3306,該操作符意味著從地址0x3306開始,取第2個成員(第一個成員為p[0]),而由于指針p為字符型指針,所以p [1]的地址為0x3307,值為2。

圖3-48 指針與數(shù)組示范代碼的內(nèi)存分布

指針和數(shù)組有很大的相似性,二者均表示了地址,也均可以用“*”操作符和“[]”操作符。例如對于數(shù)組a,其地址為0x3306,則(a+n)所表示的地址為a[n]的地址,即(a+n)與&a[n]等價;對于指針p, p的內(nèi)容是0x3306, p[n]所表示的即地址p+n*sizeof(type)處的值,其中sizeof為求取type所占的字節(jié)數(shù),type由指針的類型決定,如p定義為字符型指針,則type為char,當(dāng)p定義為int型指針,則type為int。

然而指針和數(shù)組也有一些區(qū)別,如對于一個指針來說,它占據(jù)一定的字節(jié)個數(shù),有自身的地址值,而其內(nèi)容是一個內(nèi)存地址,如圖3-48中,字符指針p的地址是0x3304,而其內(nèi)容為0x3306;而數(shù)組在程序看來,其名稱是一個地址值,占內(nèi)存空間的是其三個成員a[0]、a[1]和a[2]。

8)自定義數(shù)據(jù)類型的指針。

既然一個指針變量的值表示一個內(nèi)存地址,而其值是任意的,這也就是說理論上一個指針變量可以表示任何一個內(nèi)存地址。這里需要引起注意的是,這些內(nèi)存地址可能是非法的、受保護的,或者是被其他程序使用的,對這些內(nèi)存空間進行讀取和修改可能會影響系統(tǒng)的正常運行。

現(xiàn)在假設(shè)student是一個結(jié)構(gòu)體類型,可以用語句“student a; ”聲明一個類型為student的變量a,并用“student *p = &a; ”聲明一個指向變量a的指針p(注意“student *p; ”的意思是聲明一個指針,指針名稱為p,且p所指向的內(nèi)存里存放的是一個student類型的變量)。此時語句“a.score”和“p->score”均可以訪問成員變量score。

表3-17中,語句(2)聲明了一個字符型變量x1;語句(3)聲明了一個字符型變量x2;語句(4)定義了一個名稱為student的結(jié)構(gòu)體,并聲明一個student類型的變量a和一個指針型的變量p, p的內(nèi)容是一個內(nèi)存地址;語句(7)將a的地址賦給p,這樣p的值便是a的內(nèi)存地址;語句(8)將student類型變量a的成員變量score賦給x1;語句(9)用指針p來訪問變量a的成員變量,并將其值賦給變量x2,方法是用“->”操作符。因此,用指針p配合“->”操作符可以訪問p的內(nèi)容所表示的內(nèi)存區(qū)域的結(jié)構(gòu)體變量的成員變量。

表3-17 自定義的結(jié)構(gòu)體變量的指針用法示例

代碼執(zhí)行完畢后,各變量在內(nèi)存中的分布如圖3-49所示。假設(shè) a的地址為0x3306,則p的地址為0x3304, p的內(nèi)容為0x3306,其中0x3306即a的地址。假設(shè)內(nèi)存0x3306處的數(shù)據(jù)為0x01,內(nèi)存0x3307處的數(shù)據(jù)為0x50。變量x1的地址為0x3302,變量x2的地址為0x3303,二者的值均為0x50。

圖3-49 自定義結(jié)構(gòu)變量指針的內(nèi)存分布

因此,訪問一個自定義數(shù)據(jù)類型的變量的成員變量(如 student類型變量 a的兩個成員變量 number和score),除了用變量配合“.”的方式來訪問外,還可以用指向該種變量的指針配合“->”來實現(xiàn)。

9)安全使用指針的規(guī)則。

由于指針的特殊型,其值表示了一個內(nèi)存地址,對該指針進行的操作例如“*”(取內(nèi)容)和“->”(訪問成員變量)等均需要該內(nèi)存地址真實存在且合法。而在程序中,一個指針被聲明后,其值是隨機的,必須對其進行賦值后才能使用;有時候,一個指針?biāo)赶虻膬?nèi)存空間雖然是合法的,但是經(jīng)過一些代碼后,該段內(nèi)存空間可能已經(jīng)被釋放,此時再使用指針也會引發(fā)程序錯誤,所以使用指針需要遵守一定的規(guī)則。該規(guī)則可以概括為三點:在聲明指針后,立即進行歸0操作;在使用指針前,進行合法檢驗;在內(nèi)存釋放后,對指針進行歸0操作。

C語言中聲明一個變量,只是為某塊內(nèi)存區(qū)域起了名字。聲明一個指針類型的變量后,將其值賦為0,這個操作稱為歸0操作。當(dāng)使用該指針的時候,首先判斷其是否為0,這樣可以避免未對其進行正確賦值而引發(fā)的程序錯誤。

表3-18給出了一個安全使用指針的示例。首先語句(2)和語句(3)聲明了字符型變量a和b。語句(4)聲明了一個字符型指針p并賦值為0;語句(7)將變量a的地址賦值給了指針p;語句(8)在使用指針p的時候,首先判斷是否為0,避免了訪問非法地址。在語句(10)中,由于指針p已經(jīng)使用完畢,所以將其重新賦值為0,方便以后的代碼重新使用該指針時可以判斷該指針是否合法。

表3-18 安全使用指針的代碼示例

3.3.7 變量的生存期

在C語言中,程序的入口是main( )函數(shù),之后一對大括號括起了main( )函數(shù)的所有代碼,其間聲明的所有變量的生存期由這對大括號確定。

也就是說,聲明在main( )函數(shù)的內(nèi)部的變量,其生存期由main( )函數(shù)的一對大括號確定,此時的變量為局部變量,用關(guān)鍵字auto進行說明,當(dāng)auto省略時,所有的非全程變量都被認(rèn)為是局部變量。局部變量在函數(shù)內(nèi)部聲明時產(chǎn)生,但不會自動初始化,隨著函數(shù)的結(jié)束,這個變量也將消失。

那么在main( )函數(shù)之外聲明的變量呢?C語言中聲明在main( )函數(shù)外部的變量,也就是說不受任何大括號約束的變量,稱為全局變量,其生存期是整個程序運行期間,也就是說它可以在程序的任何位置被使用,并且在整個程序的運行中都保留其值,全局變量習(xí)慣上通常在程序的主函數(shù)前說明。

圖3-50給出全局變量和局部變量的相關(guān)示范代碼和內(nèi)存分布,其中(a)中的語句(1)聲明了一個char型變量gName,這里 gName即為全局變量,其類型屬于有符號字符型,生存期為整個程序運行期。語句(4)聲明了一個局部變量a,類型亦為有符號字符型,生存期為整個 main( )函數(shù)。語句(4)執(zhí)行完畢后,兩變量在內(nèi)存中的分布如圖3-50(b)所示,其中 a的地址為0x3300,其數(shù)據(jù)為隨機數(shù)據(jù);變量gName作為全局變量,已經(jīng)被初始化為0,且所處的內(nèi)存區(qū)域與局部變量不同。

圖3-50 全局變量和局部變量

可以在聲明全局變量的時候為變量名增加前綴“g”,這樣在使用該變量時,編程人員根據(jù)“g”便可以得知此變量為全局變量,慎重進行修改等操作以避免給程序其他部分帶來影響,這一點在程序規(guī)模很大的時候尤其有用。

用static關(guān)鍵字聲明的變量,其生存期是整個程序運行過程,卻只能在聲明該變量的代碼塊中進行訪問。這種變量稱為靜態(tài)變量,在程序開始運行前已經(jīng)分配了內(nèi)存,并且僅在程序開始運行之前初始化一遍,因此可以看作一種特殊的全局變量。例如在表3-19的代碼中,變量i會不斷自增到100,之后重新歸0并繼續(xù)增加。需要注意的是在第6行代碼中為將0賦值給變量i,僅是為靜態(tài)變量i設(shè)置了初值,之后不再起作用。

表3-19 利用靜態(tài)變量進行計數(shù)的程序示例

作為對比,在表3-20中的代碼中,變量i為臨時變量,i的值只有0和1兩種。在每次運行到第6條語句的時候,系統(tǒng)重新產(chǎn)生變量i并為其賦初值0。

表3-20 臨時變量的用法示例

主站蜘蛛池模板: 巍山| 永州市| 龙岩市| 西安市| 榆中县| 城固县| 通山县| 新巴尔虎左旗| 惠安县| 昌乐县| 宁安市| 丰原市| 张家界市| 扬中市| 吴旗县| 灌云县| 长治市| 东乡族自治县| 沐川县| 阜宁县| 三穗县| 华亭县| 神农架林区| 康保县| 靖安县| 福贡县| 宕昌县| 长海县| 桐乡市| 吉水县| 五峰| 大安市| 泸州市| 海口市| 丰都县| 钦州市| 广丰县| 平远县| 铜鼓县| 佳木斯市| 全椒县|