C語言變量

C語言二進制、八進制、十六進制詳解

什么是二制制?

在數學計算中,二進制計數系統的公分母是最小的,它以2為基數。你還記得在小學或中學時所學的不同的計數系統嗎?筆者在上小學時,曾在一堂數學課中學過以6為基數的計數系統;你先數1,2,3,4,5,然后是10,11,12,13,14,15,然后是20,等等,實際上,應該先數0,1,2,3,4,5,然后是10,1l,12,13,14,15,等等。從O開始數,能比較清楚地看出每6個數字組成一組——因此6就是基數。注意,你應該從O開始一起數到比基數小1的數(因為基數是6,所以你應該從O數到5)。當你數到5后,接著應該開始數兩位數。如果你思考一下,你就會發現這與以10為基數(十進制)的計數系統是類似的——在你數到比基數小1的數(9)后,就轉到兩位數,并繼續往下數。

計算機中的計數系統以2為基數——即二進制。由于以2為基數,所以你先數O,1,然后是10,11,然后是100,101,110,111,然后是1000,1001,1010,1011,1100,1101,1110,1111,等等。與以6為基數時不同,在以2為基數時,在數到兩位數之前,只需從O數到1。

那么,為什么在計算機中要以2為基數呢?其原因在于計算機中使用了晶體管。晶體管使現代計算機的出現成為可能。晶體管就象電燈開關,電燈開關有“開”和“關”兩種狀態,晶體管也是如此。你可以認為“關”表示0,“開”表示1,這樣,你就可以用一個晶體管(如果你愿意,也可以用一個電燈開關)來進行從。到1的計數了。僅僅使用兩個數字(O到1)還不能做任何復雜的計算,但是我們還可以繼續下去。假設有一個電燈開關控制面板,上面有4個大電燈開關,盡管每個開關只有兩種狀態,但是這些開關組合起來就會有16或2。(4個開關,每個2種狀態)種不同的狀態。這樣,你就可以用4個開關來進行從。到15的計數了,見表20.22。

? 表20.22? 進制計數
-------------------------------------------------------
? ??? 開關???????? 十進制值???????? ?? 冪
-------------------------------------------------------
?????? O???????????? O
?????? 1???????????? 1???????????????? 20
????? 10???????????? 2???????????????? 21
????? 11???????????? 3
??? ?100???????????? 4?????????????????22
???? 101???????????? 5
???? 110??????????? ?6
?????111???????????? 7
?? ?1000???????????? 8???????????????? 23
??? 1001???????????? 9
?? ?1010??????????? 10
?? ?1011??????????? 11
?? ?1100??????????? 12
?? ?1101?????????? ?13
?? ?1110??????????? 14
?? ?1111??????????? 15
-------------------------------------------------------
上表說明了很重要的三點:
通過把開關并排放在一起,你就可以用它們來計數了——在本例中最多可以數到15(總共16次計數);
你可以把每個開關看作是一個二進制位,就象十進制系統中的十進制位一樣;
如果每個開關都代表一個二進制位,那么它們剛好也都代表一個2的冪(20,21,22,23,等等)。

此外,請注意,在表中出現2的冪的地方,計數結果就要增加一個二進制位。這與十進制系統是相同的,每增加一個十進制位時,這個新的十進制位也正是一個10的冪(1=100,10=101,100=102,等等)。明白了這一點后,你就可以很容易地把二進制數轉換為十進制數了,例如,二進制數10111就是(1×24)+(O×23)+(1×22)+(1×21)+(1×20),它等于十進制的(16+0+4+2+1)或23。10 1110 1011,一個大得多的二進制數,就是(1×29)+(O×28)+(1×27)+(1×26)+(1×25)+(0×24)+(1×23)+(O×22)+(1×21)+(1×20),它等于十進制的(512+0+128+64+32+0+8+0+2+1)或747。

那么所有這些和我們有什么關系呢?在計算機領域中,存在著位(bit),半字節(nibble)和字節(byte)。一個半字節是4位,一個字節是8位。什么是一個位呢?它就是一個晶體管。因此,一個字節就是8個相鄰的晶體管,就象表20.1中的4個開關一樣。記住,如果你有4個組合在一起的開關(或晶體管),你就可以數24或16,你可以把這看作是由開關組成的一個半字節。如果一個半字節是4個晶體管組合在一起,那么一個字節就是8個晶體管組合在一起。你可以用8個晶體管數到2。或256,從另一個角度看,這意味著一個字節(含8個晶體管)可以表示256個不同的數字(從0到 255)。再深入一點,Intel 386,486和Pentium處理器被叫做32位處理器,這意味著這些Intel芯片所進行的每一次運算都是32位寬或32個晶體管寬的。32個晶體管,或32位,等價于232或4,294,967,296,即它們能表示超過40億個不同的數字。

當然,上述介紹還不能解釋計算機是如何利用這些數字產生那種神奇的計算能力的,但它至少解釋了計算機為什么要使用以及是如何使用二進制計數系統的。

什么是八進制?

八進制是以8為基數的一種計數系統。在八進制系統中,你是這樣計數的:O,1,2,3,4,5,6,7,10,ll,12,13,等等。下面比較了八進制(第二行)和十進制(第一行)中的計數過程:
??? O,l,2,3,4,5,6,7,8,9,10.11,12,13,14,15,16
??? 0,1,2.3,4,5,6,7,10,11,12,13,14,15,16,17,20
注意,在八進制中,在數到7后,就要增加一個八進制位,第二個八進制位顯然就是8?(等于十進制的8)。如果你數到第三個八進制位(八進制的100),那將是8?或十進制的64,因此,八進制的100等于十進制的64。

現在,八進制已經不象以前那樣常用了,這主要是因為現在的計算機使用的是8,16,32或64位處理器,最適合它們的計數系統是二進制或十六進制(見20.24中有關十六進制計數系統的介紹)

C語言支持八進制字符集,這種字符要用反斜杠字符來標識。例如,在C程序中,下面的語句并不少見:
??? if(x=='\007')break;
這里的"\007"恰好就是ASCII值為7的字符;該語句用來檢查終端鳴笛字符。另一個常見的八進制數是"\033",即Escape字符(在程序中它通常表示為"\033")。然而,八進制數現在已經很少見了——它們被十六進制數代替了。

什么是十六進制?

十六進制(hexadecimal,縮寫為hex)是以16為基數的計數系統,它是計算機中最常用的計數系統。十六進制中的計數過程為:O,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,10,11,12,13,14,15,16,17,18,19,1A,1B,1C,1D,1E,1F,等等。十六進制中的字母是幾個單位數標識符,表示十進制的10到15。要記住在不同基數下的計數規則,即從O數到比基數小1的數字,在十六進制中這個數就是十進制的15。因為西式數字中沒有表示大于9的單位數,所以就用A,B,c,D,E和F來表示十進制的10到15。在十六進制中,數到F之后,就要轉到兩位數上,也就是1OH或Ox1O。下面對十六進制(第二行)和十進制(第一行)的計數過程作一下比較:
??? 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,……
??? 1,2,3,4,5,6,7,8,9,A, B, C, D, E, F, 10,……
?
注意,十進制的10等于十六進制的A。與前面討論過的計數系統一樣,每增加一個十六進制位,實際上就增加了一個16的冪,即160(1),161(16),162(256),163(4096),等等。因此,十六進制數3F可以展開為(3×161)+(F×160),等于十進制的(48+15)或63;十六進制數13F可以展開為(1×162)+(3×161)+(F×160),等于十進制的(256+48+15)或319。在c程序中,這兩個數用0x3F或Oxl3F這樣的形式來表示,其中的“0x”前綴用來告訴編譯程序(和程序員)該數字應被當作十六進制數來處理。如果不加“0x”前綴,你就無法判斷一個數究竟是十六進制數還是十進制數(或者是八進制數)。

對表20.22稍作改進,加入十六進制的計數過程,就得到了表20.24:
—————————————————————————————————
? 二進制??? 十進制值??? 二進制冪??? 十六進制??? 十六進制冪
—————————————————————————————————
? 0000??????? O????????????????????? ?O
? 0001??????? 1??? 20??????????????? ?1??????????? 160
? 0010??????? 2??? 21??????????????? ?2
? 0011??????? 3????????????????????? ?3
? 0100??????? 4??? 22??????????????? ?4
? 0101??????? 5????????????????????? ?5
? 0110??????? 6???????????????????????6
? 0111??????? 7???????????????????????7
? 1000??????? 8??? 23???????????????? 8
? 1001??????? 9???????????????????? ? 9
? 1010??????? 10????????????????????? A
? 1011??????? 11???????????????????? ?B
? 1100??????? 12??????????????????????C
? 1101??????? 13???????????????????? ?D
? 1110??????? 14????????????????????? E
? 1111?????? ?15???????????????????? ?F
? 10000?????? 16???24??????????????? 10???????????? 161
—————————————————————————————————
筆者在上表的最后又加了一行,使計數達到十進制的16。通過比較二進制、十進制和十六進制·你就會發現:“十”在二進制中是“1010”,在十進制中是“10”,在十六進制中是“A”;。。十六”在二進制中是“1 0000"或“10000”,在十進制中是“16”,在十六進制中是“1O”,,(見上表的最后一行)。這意味著什么呢?因為今天的16,32和64位處理器的位寬恰好都是16的倍數,所以在這些類型的計算機中用十六進制作為計數系統是非常合適的。

十六進制位和二進位之間有一種“倍數”關系。在上表的最后一行中,二進制值被分為兩部分(1 0000)。4個二進制位(或者4位)可以計數到15(包括O在內共16個不同的數字),而4位(bit)正好等于一個半字節(nibble)。在上表中你還可以發現,一個十六進制位同樣可以計數到15(包括。在內共l 6個不同的數字),因此,一個十六進制位可以代表4個二進制位。一個很好的例子就是用二進制表示十進制的15和16,在二進制中,十進制的15就是1111,正好是4個二進制位能表示的最大數字;在十六進制中,十進制的15就是F,也正好是一個十六進制位能表示的最大數字。十進制的16要用5個二進制位(1 0000)或兩個十六進制位(10)來表示。下面把前文提到過的兩個數字(0x3F和0x13F)轉換為二進制:
??? 3F???????????? ?111111
??? l3F??????????100111111

如果把前面的空格換為O,并且把二進制位分成4位一組,那么看起來就會清楚一些:
??? 3F?????0 0011?1111
??? l3F??? 1 0011 1111

你并不一定要把二進制位分成4位一組,只不過當你明白了4個二進制位等價于一個十六進制位后,計數就更容易了。為了證明上述兩組數字是相等的,可以把二進制值轉換為十進制值(十六進制值到十進制值的轉換已經在前文中介紹過了);二進制的111111就是(1×25)+(1×24)+(1×23)+(1×22)+(1×21)+(1×20),等于十進制的(32+16+8+4+2+1)或63,與0x3F的轉換結果相同。二進制的1 0011 1111就是(1×28)+(O×27)+(0×26)+(1×25)+(1×24)+(1×23)+(1×22)++(1×21)+(1×20),等于十進制的(256+32+1 6+8+4+2+1)或319。因此,十六進制和二進制能象手掌和手套那樣相互匹配。

C語言中,如何把十六進制或八進制的值賦給一個變量

怎樣把一個十六進制的值賦給一個變量?

c語言支持二進制、八進制、十進制和十六進制的計數系統,在表示一個數字時,用某個特殊的字符來區別其所屬的計數系統是必要的。在表示二進制數時,要在數字的末尾加上“b”(如101b);在表示八進制數時,要使用反斜杠(如\014);在表示十六制數時,要使用“0x”字符序列(如0x34);顯然,在表示十進制數時,不需要任何標識符,因為十進制是缺省的計數系統。

要把一個十六進制的值賦給一個變量,你可以象下面這樣做:
int x ;
x=0x20;???????????????? /* put hex 20(32 in decimal) into x */
x='0x20' ;??????????????? / * put the ASCII character whose value is
??????????????????????????????????? hex 20 into x * /
只有了解了十六進制計數系統,你才能知道要賦的值應該如何表示,詳見20.24。

怎樣把一個八進制的值賦給一個變量?

把一個八進制的值賦給一個變量與把一個十六進制的值賦給一個變量一樣簡單:
int x ;
x=\033;???????????????? / * put octal 33 (decimal 27) into x * /
x='\033' ;?????????????? / * put the ASCII character whose value is
??????????????????????????????????? octal 33 into x * /
同樣,只有了解了八進制計數系統,你才能知道要賦的值應該如何表示,詳見20.23。

編寫C語言程序時為什么要使用靜態變量

靜態變量作為一個局部變量是很合適的,它在函數退出后不會失去其本身的值。例如,有一個要被調用很多次的函數,它的一部分功能就是計算自己被調用的次數。你不能用一個簡單的局部變量來實現這部分功能,因為每次進入該函數時,這個變量都沒有被初始化。如果把這個計數變量說明為靜態的,那么它就會象一個全局變量那樣保留自己的當前值。

那么為什么不直接使用一個全局變量呢?你可以使用一個全局變量,而且這樣做沒有錯誤。問題是使用了大量全局變量的程序維護起來很麻煩,尤其是有許多函數都各自訪問一個全局變量的程序。再說一遍,這樣做沒有錯誤,這只是一個程序設計和可讀性是否好的問題。如果你把這樣的變量說明為靜態的,你就可以提醒自己(或者其它可能讀你的程序的人)它是局部變量,但要象全局變量那樣被處理(保留自己的值)。如果你把它說明為全局的,那么讀這個程序的人一定會認為有很多地方要引用它,盡管實際上并不是這樣。

總而言之,當你需要一個能保持自己的值的局部變量時,使用靜態變量是一種好的編程習慣。

怎樣才能只得到一種特定類型的數據,例如字符型數據?

與幾乎所有有關計算機科學的問題一樣,這個問題的答案也依賴于你要做什么。例如,如果你要從鍵盤上讀入字符,你可以使用scanf():???
? scanf("%C",&c);
此外,你也可以使用一些現成的C庫函數:
??? c=getchar();
這些方法所產生的結果基本上都一樣,只不過使用scanf()能為程序員提供更多的安全性檢查。

如果要接收其它類型的數據,有兩種方法可供使用。你可以逐個字符地讀入數據,并且每次都檢查讀入的數據是否正確。你也可以使用scanf(),并通過檢查其返回值來確定讀入的數據是否都正確。

你可以用第二種方法簡單而高效地讀入一串記錄,并檢查它們是否都正確。下例就實現了這一點:
??? #include<stdio.h>
??? main()
??? {??
?????? int i,a,b:
?????? char c;
?????? void ProcessRecord(int,int,char);
?????? for(i=O;i<i100;++a)/*Read 100 records*/
?????? {
????????? if(scanf("%d%d%c",&a,&b,&c)!=3)
???????????? printf("data line %d is in error.\n");
????????? else
???????????? ProcessRecord(a,b,c);
?????? }??
?????? return(O);
??? }

C語言的char,short,int和long類型分別有多長?

其長度分別為一字節,至少兩字節,至少兩字節和至少4字節。除此之外,不要再依賴任何約定。

char類型的長度被定義為一個8位字節,這很簡單。

short類型的長度至少為兩字節。
在有些計算機上,對于有些編譯程序,short類型的長度可能為4字節,或者更長。

int類型是一個整數的“自然”大小,其長度至少為兩字節,并且至少要和short類型一樣長。在16位計算機上,int類型的長度可能為兩字節;在32位計算機上,可能為4字節;當64位計算機流行起來后,int類型的長度可能會達到8字節。這里說的都是“可能”,例如,早期的Motorala 68000是一種16/32位的混合型計算機,依賴于不同的命令行選項,一個68000編譯程序能產生兩字節長或4字節長的int類型。

long類型至少和int類型一樣長(因此,它也至少和short類型一樣長)。long類型的長度至少為4字節。32位計算機上的編譯程序可能會使short,int和long類型的長度都為4字節——也可能不會。

如果你需要一個4字節長的整型變量,你不要想當然地以為int或long類型能滿足要求,而要用typedef把一種固有的類型(一種確實存在的類型)定義為你所需要的類型,并在它的前后加上相應的#ifdef指令:
??? #ifdef FOUR_BYTE_LONG
??? typedef long int4;
??? #endif
如果你需要把一個整型變量以字節流的方式寫到文件中或網絡上,然后再從不同的計算機上讀出來,你可能就會用到這樣的類型。

如果你需要一個兩字節長的整型變量,你可能會遇到一些麻煩!因為并不一定有這樣的類型。但是,你總是可以把一個較小的值存放到一個由兩個char類型組成的數組中。

怎樣在程序中存取重要的DOS內存位置?

與DOS和BIOS函數一樣,有很多內存位置也包含了計算機的一些有用和有趣的信息。你想不使用中斷就知道當前顯示模式嗎?該信息存儲在40:49H(段地址為40H,偏移量為49H)中。你想知道用戶當前是否按下了Shift,Ctrl或Alt鍵嗎?該信息存儲在40:17H中。你想直接寫屏嗎?單色顯示(Monochrome)模式的視頻緩沖區起始地址為B800:O,彩色文本模式和16色圖形模式(低于640×480 16色)的視頻緩沖區起始地址為B8000:0,其余標準圖形模式(等于或高于640×480 16色)的視頻緩沖區起始地址為A000:O,詳見14.8。下面的例子說明了如何把彩色文本模式的字符打印到屏幕上,注意它只是對前文中的例子做了一點小小的修改。
# include <stdlib. h>
# include <dos. h>
char GetAKey(void) ;
void OutputString(int, int, unsigned int, char * );
main (int argc, char * *? argv)
{
???? char str[l28];
???? union REGS regs;
???? int ch, tmp;
???? / * copy argument string; if none, use "Hello World" * /
???? strcpy(str,? (argv[1] == NULL? ? "Hello World"? : argv[1]));
???? / * print the string in red at top of screen * /
???? for(tmp = 0;((ch =? GetAKeyO)? ! = 27); tmp+=strlen(str)) {
??????? outputString(0,? tmp, 0x400,str);
???? }
?}
char
GetAKey()
?{
???? union REGS regs;
??? regs. h. ah =? 0;????????? / *? get character * /
??? int86(0xl6, &regs, &regs);
??? return((char)regs. h. al);
}
void
OutputString(int row, int col, unsigned int video Attribute,? char * outStr)
{
??? unsigned short far *? videoPtr;
??? videoPtr= (unsigned short far * )? (0xB800L <<16);
videoPtr + = (row *? 80) + col;? /*? Move videoPtr to cursor position * /
videlAttribute & = 0xFF00;??????? / *? Ensure integrity of attribute? * /
??? / *? print string to RAM * /
??? while ( * outStr ! = '\0'){
?????? / *? If newline was? sent, move pointer to next line, column 0? * /
?????? if( (* outStr ==? '\n')? || (*outStr ==? 'V') ){
????????? videoPtr + = (80- (((int)FP-OFF(videoPtr)/2) % 80));
????????? outStr+ + ;
????????? continue;
?????? }
?????? / *? If? backspace was requested, go back one? * /
?????? if( *outStr = = 8){
??????????? videoPtr -- ;
??????????? outStr++ ;
??????????? continue;
?????? }
?????? /*? If BELL was requested, don't beep,? just print a? blank
?????????? and go on? * /
?????? if ( * outStr = =? 7) {
??????????? videoPtr+ + ;
??????????? outStr++ ;
??????????? continue ;
?????? }
?????? / *? If TAB was requested, give it eight spaces? * /
?????? if ( * outStr ==? 9){
???????????? * videoPtr++? =? video Attribute? |? '?? ' ;
???????????? * videoPtr++? =? video Attribute? |? '?? ' ;
???????????? * videoPtr++? =? video Attribute? |? '?? ' ;
???????????? * videoPtr++? =? video Attribute? |? '?? ' ;
???????????? * videoPtr++? =? video Attribute? |? '?? ' ;
???????????? * videoPtr++? =? video Attribute? |? '?? ' ;
???????????? * videoPtr++? =? video Attribute? |? '?? ' ;
???????????? * videoPtr++? =? video Attribute? |? '?? ' ;
?????????? outStr+ + ;
?????????? continue;
????? }
????? / * If? it was a regular character, print it? * /
?????? * videoPtr = videoAttribute? |? (unsigned char) * outStr;
????? videoPtr+ + ;
????? outStr + + ;
??? }?
??? return;
}
顯然,當你自己來完成把文本字符打印到屏幕上這項工作時,它是有些復雜的。筆者甚至已經對上例做了一些簡化,即忽略了BELL字符和一些其它特殊字符的含義(但筆者還是實現了回車符和換行符)。不管怎樣,這個程序所完成的任務與前文中的例子基本上是相同的,只不過現在打印時你要控制字符的顏色和位置。這個程序是從屏幕的頂端開始打印的。如果你想看更多的使用內存位置的例子,可以閱讀20.12和20.17——其中的例子都使用了指向DOS內存的指針來查找關于計算機的一些有用信息。

mso-bidi-font-family:宋體; mso-font-kerning:0pt'>正在執行重要的代碼時,把這一情況通知DOS。然而,該標志對程序員也是很有用的,因為他們能由此知道什么時候DOS處于忙狀態。盡管從DOS 2.0版開始就有這個函數了,但因為Microsoft最近已經公開了這個函數,所以從技術角度上講它已不再是一個未公開的函數。有幾本很不錯的書介紹了已公開和未公開的DOS函數,對這個問題有興趣的讀者可以去閱讀這些書。

怎樣檢索環境變量(environment variables)的值?

ANSI C標準提供了一個名為getenv()的函數來完成這項任務。getenv()函數很簡單一把指向要查找的環境串的指針傳遞給它,它就返回一個指向該變量值的指針。下面的程序說明了如何從C中獲得環境變量PATH的值:
# include <stdlib. h>
main(int argc, char *? *? argv)
{
??? char envValue[l29];???????????? / *? buffer to store PATH * /
??? char *? envPtr = envValue ;???? / *? pointer to this buffer? * /
??? envPtr = getenv("PATH");??????? /*? get the PATH */
??? printf ("PATH= %s\n" , envPtr) ;?? / * print the PATH * /
}
如果你編譯并運行了這個程序,你就會看到與在DOS提示符下輸入PATH命令完全相同的結果。事實上,你可以用getenv()檢索AUTOEXEC.BAT文件中的或者系統引導后在DOS揭示符下輸入的所有環境變量的值。

這里有一個小技巧。當運行Windows時,Windows設置了一個名為WINDIR的新的環境變量,它包含了Windows目錄的路徑全名。下面這段簡單的程序用來檢索這個串:
# include <stdlib. h>
main(int argc, char * *? argv)
{
??? char envValue[l29];
??? char *? envPtr = envValue ;
?? envPtr = getenv("windir");
??? / * print the Windows directory * /
??? printf("The Windows Directory is? %s\n" ,? envPtr);
}
這個程序還可以用來判斷當前是否正在運行Windows,以及DOS程序是否運行在一個DOS shell下,而不是運行在“真正的"DOS下。注意,程序中的windir字符串是小寫——這一點很重要,因為它對大小寫是敏感的。如果你使用WINDIR,getenv()就會返回一個NULL串(表示變量未找到錯誤)。

用一putenv()函數也可以設置環境變量。但要注意,該函數不是一個ANSI標準函數,在某些編譯程序中它可能不以這個名字出現,或者根本就不存在。你可以用一putenv()函數做許多事情。實際上,在上面那個例子中,Windows正是用這個函數創建了windir環境變量。

16位和32位的數是怎樣存儲的?

一個16位的數占兩個字節的存儲空間,即高位字節和低位字節(見10.5中的介紹)。如果你是在紙上書寫一個16位的數,你總是會把高位字節寫在前面,而把低位字節寫在后面。然而,當這個數被存儲到內存中時,并沒有固定的存儲順序。

如果我們用M和L分別表示高位字節和低位字節,那么可以有兩種方式把這兩個字節存儲到內存中,即M在前L在后或者L在前M在后。把M存儲在前的順序被稱為“正向(forward)”或“高位優先(big—endian)”順序;把L存儲在前的順序被稱為“逆向(reverse)”或“低位優先(little—endian)”順序。

big—endian這個術語的含義是數的“高位(big end)”存儲在前,同時這也是對《Gulliver'sTravels》這本書中的一個詞的引用,在該書中big—endian一詞是指那些從大頭開始吃一個煮雞蛋的人。

大多數計算機按正向順序存儲一個數,Intel CPU按逆向順序存儲一個數,因此,如果試圖將基于Intel CPU的計算機連到其它類型的計算機上,就可能會引起混亂。

一個32位的數占4個字節的存儲空間,如果我們按有效位從高到低的順序,分別用Mm,Ml,Lm和Ll表示這4個字節,那么可以有4!(4的階乘,即24)種方式來存儲這些字節。在過去的這些年中,人們在設計計算機時,幾乎用遍了這24種方式。然而,時至今天,只有兩種方式是最流行的,一種是(Mm,MI,Lm,LD,也就是高位優先順序,另一種是(Ll,Lm,Ml,Mm),也就是低位優先順序。和存儲16位的數一樣,大多數計算機按高位優先順序存儲32位的數,但基于Intel CPU的計算機按低位優先順序存儲32位的數。

C語言中的高位字節和低位字節是什么意思?

通常我們從最高有效位(most significant digit)開始自左向右書寫一個數字。在理解有效位這個概念時,可以想象一下你的支票數額的第一位增加1和最后一位增加1之間的巨大區別,前者肯定會讓你喜出望外。

計算機內存中一個字節的位相當于二進制數的位,這意味著最低有效位表示1,倒數第二個有效位表示2×1或2,倒數第三個有效位表示2×2×1或4,依此類推。如果用內存中的兩個字節表示一個16位的數,那么其中的一個字節將存放最低的8位有效位,而另一個字節將存放最高的8位有效位,見圖10.5。存放最低的8位有效位的字節被稱為最低有效位字節或低位字節,而存放最高的8位有效位的字節被稱為最高有效位字節或高位字節。
??????????? 高位字節?????????????????????? 低位字節
? ↓--------------------------↓ ↓---------------------------↓????????????
?
1514131211109.8.7.6.5.4.3.2.1.0.
?
????????????????? 圖 10.5 雙字節整數中的位

C語言里的移位和乘以2這兩種方式中哪一種更好?

不管你采用哪種方式,任何合格的優化編譯程序都會產生相同的代碼,因此你可以采用使程序的上下文更易讀的那種方式。你可以用DOS/Windows上的CODEVIEW或UNIX機上的反匯編程序(通常被稱為"dis”)這樣的工具來查看下述程序的匯編代碼:

例10.4乘以2和左移一位經常是相同的
??? void main()
??? {
????? unsigned int test_nbr = 300;
????? test_nbr * =2;
????? test_nbr = 300;
????? test_nbr << = 1;
??? }

C語言的位域(bit fields)是可移植的嗎?

位域是不可移植的。因為位域不能跨越機器字,而且不同計算機中的機器字長也不同,所以一個使用了位域的程序在另一種計算機上很可能無法編譯。

假設你的程序能在另一種計算機上編譯,將位分配給位域時所遵循的順序仍然是沒有定義的。因此,不同的編譯程序,甚至同一編譯程序的不同版本所產生的代碼,很可能無法在由原來的編譯程序所生成的數據上工作。通常應該避免使用位域,除非計算機能直接尋址內存中的位并且編譯程序產生的代碼能利用這種功能,并且由此而提高的速度對程序的性能是至關重要的。

C語言中的位屏蔽(bit masking)是怎么回事

位屏蔽的含義是從包含多個位集的一個或一組字節中選出指定的一(些)位。為了檢查一個字節中的某些位,可以讓這個字節和屏蔽字(bit mask)進行按位與操作(C的按位與運算符為&)——屏蔽字中與要檢查的位對應的位全部為1,而其余的位(被屏蔽的位)全部為0。例如,為了檢查變量flags的最低位,你可以讓flags和最低位的屏蔽字進行按位與操作:
??? flags&1;
為了置位所需的位,可以讓數據和屏蔽字進行按位或操作(C的按位或運算符為|)。例如,你可以這樣置位flags的最低位:
??? flags = flags | 1;
或者這樣:
??? flags |= 1;
為了清除所需的位,可以讓數據和對屏蔽字按位取反所得的值進行按位與操作。例如,你可以這樣清除flags的最低位:
? ? flags = flags& ~1;
或者這樣:
??? flags&=~1 ;
有時,用宏來處理標志會更方便,例10.2中的程序就是通過一些宏簡化了位操作。

例10.2 能使標志處理更方便的宏
/* Bit Masking * /
/ * Bit masking can be used to switch a character
? between lowercase and uppercase * /
#define BIT_POS(N)??????????? ( 1U ?(N) )
#define SET_FLAG(N,F)???????? ( (N) |? = (F) )
#define CLR_FLAG(N,F)???????? ( (N) &= -? (F) )
#define TST_FLAGCN,F)???????? ( (N) & (F)? )
#define BIT_RANGE(N,M)???????? ( BIT_POS((M) + 1- (N))-1<<(N))
#define BIT_SHIFTL(B,N)??????? ( (unsigned)(B)?(N) )
#define BIT_SHIFTR(B,N)??????? ( (unsigned)(B)?(N) )
#define SET_MFLAG(N,F,V)?????? ( CLR_FLAG(N,F), SET_FLAG(N,V) )
#define CLR_MFLAG(N,F)???????? ( (N) &= ~(F) )
#define GET_MFLAG(N,F)???????? ( (N) & (F) )

# include <stdio. h>
void main()
{
? unsigned char ascii_char = 'A';??? /* char? = 8 bits only */
? int test_nbr = 10;
? printf("Starting character =? %c\n" , ascii_char);
? /"?? The 5th bit position determines if the character is
?????? uppercase or lowercase.
?????? 5th bit =? 0 - Uppercase
?????? 5th bit =? 1- Lowercase??? * /
? printf ("\nTurn 5th bit on = %c\n" , SET_FLAG(ascii_char, BIT_POS(5)));
? printf ("Turn 5th bit? off = %c\n\n",CLR_FLAG(ascii_char, BIT_POS(5)));
? printf ("Look at shifting bits\n");
? printf (" = = = = = = = = = = = = = = = =\n" );
? printf ("Current value = %d\n" , test_nbr)i
? printf ("Shifting one position left = %d\n" ,
????? ? test_nbr = BIT_SHIFTL(test_nbr, 1) );
? printf ("Shifting two positions right =? %d\n" ,
??? ??? BIT_SHIFTR(test_nbr,? 2) );
}

宏BIT_POS(N)能返回一個和N指定的位對應的屏蔽字(例如BIT_POS(O)和BIT_POS(1)分別返回最低位和倒數第二位的屏蔽字),因此你可以用
??? #define A_FLAG BIT_POS(12)
??? #define A_FLAG BIT_P0S(13)
代替
??? #define A_FLAG 4096
??? #define A_FLAG 8192
這樣可以降低出錯的可能性。

宏SET_FLAG(N,F)能置位變量N中由值F指定的位,而宏CLR_FLAG(N,F)則剛好相反,它能清除變量N中由值F指定的位。宏TST_FLAG(N,F)可用來測試變量N中由值F指定的位,例如:
??? if (TST_FLAG (flags, A_FLAG))
????? ??? /* do something * /;
宏BIT_RANGE(N,M)能產生一個與由N和M指定的位之間的位對應的屏蔽字,因此,你可以用
??? # define FIRST_OCTAL_DIGIT???? BIT_RANGE (0,2)?? /*111"/
??? # define SECOND-OCTAL-DIGIT??? BIT-RANGE(3,5)??? /* 111000*/
代替
??? #define FIRST_OCTAL_DIGIT 7????? /*111*/
??? #define SECOND_OCTAL_DIGIT 56??? /* 111000 * /
這樣可以更清楚地表示所需的位。

宏BIT_SHIFT(B,N)能將值B移位到適當的區域(從由N指定的位開始)。例如,如果你用標志C表示5種可能的顏色,你可以這樣來定義這些顏色:
??? #define C_FLAG??????? BIT-RANGE(8,10)???? /* 11100000000 */
?? /* here are all the values?the C flag can take on * /??
?? # define C_BLACK? ?BIT-SHIFTL(0,8)?? /*?ooooooooooo */
?? # define C-RED??? ?BIT_SHIFTL(1,8)? ?/*?00100000000?*/
?? # define C-GREEN? ?BIT_SHIFTL(2,8)???/*?01000000000 */
?? # define C-BLUE??? BIT-SHIFTL(3,8)?? /*?01100000000 */
?? # define C_WHITE?? BIT-SHIFTL(4,8)?? /*?10000000000 */
?? # defineC-ZERO?C-BLACK??
?? # defineC-LARGEST?C-WHITE??
? ?/* A truly paranoid programmer might do this?*/
?? #if C_LARGEST > C_FLAG
??? ????? Cause an error message. The flag C_FLAG is not
??? ????? big enough to hold all its possible values.
?? #endif /* C_LARGEST > C_FLAG */
宏SET_MFLAG(N,F,V)先清除變量N中由值F指定的位,然后置位變量N中由值V指定的位。宏CLR_MFLAG(N,F)的作用和CLR_FLAG(N,F)是相同的,只不過換了名稱,從而使處理多位標志的宏名字風格保持一致。宏GET_MFLAG(N,F)能提取變量N中標志F的值,因此可用來測試該值,例如:
??? if (GET_MFLAG(flags, C_FLAG) == C_BLUE)
??? ???? /*do something */;
注意:宏BIT_RANGE()和SET_MFLAG()對參數N都引用了兩次,因此語句
??? SET_MFLAG(*x++,C_FLAG,C_RED);
的行為是沒有定義的,并且很可能會導致災難性的后果。

C語言中存儲標志(flag)效率最高的方法

標志的作用是對程序執行過程中的兩種或更多種選擇作出決定。例如,在執行MS-DOS的dir命令時,可以用“/w”標志使該命令在屏幕上顯示若干列文件名而不是每行只顯示一個文件名。在3.5中你可以看到另外一個例子,該例通過一個標志從兩種可能類型中選擇一種在一個聯合中使用。因為一個標志一般只有少數幾個(通常是兩個)值,所以,為了節省內存空間,ǔ2換嶠桓霰曛敬娣旁諞桓鍪粲謁約旱膇nt或char類型中。

存儲標志值的效率是存儲空間和存取速度之間的一種折衷。存儲空間利用效率最高的存儲方法是用數目足夠的位來存儲標志值的所有可能值,但大多數計算機不能直接尋址內存中單獨的一位,因此標志值要從存放它的字節中提取。存取速度最快的存儲方法是將每個標志值都存放到一個屬于它自己的整型變量中,但是,當一個標志只需要一位存儲空間而變量的長度為32位時,那么其余的31位就全部浪費掉了,因此這種方法的存儲空間利用效率非常低。

如果標志的數目不多,那么使用哪種存儲方法是沒有關系的。如果標志的數目很多,那么最好將它們壓縮存儲在一個字符數組或整型數組中。這時,需要通過一種被稱為位屏蔽(bit masking)的過程來提取這些標志值,即屏蔽掉不需要的位,只處理所需的位。

有時,為了節省存儲空間,可能會將一個標志和另外一個值存放在一起。例如,如果一個整型的值小于整型所能表示的最大值,那么就可用它的高階位來存放標志;如果某些數據總是2或4的倍數,那么就可用它的低階位來存放標志。在3.5的例子中,就使用了一個指針的低階位來存放一個標志,該標志的作用是從兩種可能的類型中選擇一種作為該指針所指向的對象類型。

細說C語言位(bit)和字節(byte)

位指的是二進制系統中的一位,它是最小的信息單位。位的用處可以從兩方面去分析:第一,計算機對位的值可以有任意多種解釋,例如表示"yes’’或"no”,或者表示磁盤是否已插入驅動器,或者表示某個鼠標鍵是否被按下;第二,將若干位的值連接起來后,就可以表示更復雜的數據,而且每增加一位,可以表示的可能的值的數目就會增加一倍。

換句話說,一位可以表示兩種可能的值,即“O”和“1”;兩位可以表示2×2或4種可能的值,即“00”,“01”,“10”和“11”;類似地,三位可以表示2×2×2或8種可能的值……。對計算機來說,位的這種特性既是最有力的支持——因為很復雜的數據(例如本書內容)可以被分解為位的表示后存儲起來,又是最大的限制——因為在現實生活中許多事物的值是不精確的,這樣的值無法用數目有限的若干位來表示。

程序員始終必須清楚每一項數據需要用多少位來表示。因為位作為單位太小,所以為了方便起見,大多數計算機所處理的信息單位是被稱為字節的位塊。字節是大多數計算機中最小的可尋址的信息單位,這意味著計算機給每一個字節的信息都賦予一個地址,并且一次只能存取一個字節的信息。一個字節中的位的數目可以是任意的,并且在不同的計算機中可以不同。最常見的情況是每個字節中有8位,即可以存放256個不同的值。8位這樣的長度非常適合于存放表示ASCII(the American Standard Code for Information Interchange)字符的數據。

下述程序可以顯示空格符以后的ASCII字符和PC機的圖形字符集:
# include <stdio. h>
void main (void);
void main()
{
? /" Display ASCII char set " /
? unsigned char space? =? '' ;???? /* Start with SPACE
???????????????????????????????????????? char =? 8 bits only * /
? int ctr =? 0;
? printf(" ASCII Characters\n" )?
? printf (" = = = = = = = = = = = = = = = =\n" ) ;
? for? (ctr = O; ctr + space <256; ctr+ + )
??? printf("%c", ctr? + space);
? printf ("\n");
}
請注意,變量ctr必須是int類型,而不能是char類型,因為char類型只含8位,只能存放從0至255之間的值(signed char類型只能存放從-128至127之間的值)。如果ctr是char類型,它就永遠不會存放256或比256更大的值,程序也就永遠不會結束。此外,如果你在非PC機的計算機上運行上述程序,那么程序所打印的非ASCII字符可能會導致亂屏。

因為計算機是以字節塊的方式工作的,所以大多數程序也以這種方式工作,有時,考慮到要存放的數據項的數目,或者移動每一位的信息所需的時間,節省內存空間就顯得很有必要。這時,我們通常會用少于一個字節的空間來存放那些只有少數可能值的數據,這也就是本章要討論的主要內容。

C語言中,用const說明常量有什么好處?

使用關鍵字const有兩個好處;第一,如果編譯程序知道一個變量的值不會改變,編譯程.序就能對程序進行優化;第二,編譯程序會試圖保證該變量的值不會因為程序員的疏忽而被改變。

當然,用#define來定義常量也有同樣的好處。用const而不用#define來定義常量的原因是const變量可以是任何類型(如結構,而用#define定義的常量不能表示結構)。此外,const變量是真正的變量,它有可供使用的地址,并且該地址是唯一的(有些編譯程序在每次使用用#define定義的字符串時都會生成一份新的拷貝)。

可以在C語言頭文件中說明static變量嗎?

如果說明了一個static變量,就必須在同一個文件中定義該變量(因為存儲類型修飾符static和extern是互斥的)。你可以在頭文件中定義一個static變量,但這會使包含該頭文件的源文件都得到該變量的一份私有拷貝,而這通常不是你想得到的結果。

C語言中,說明一個變量和定義一個變量有什么區別?

說明一個變量意味著向編譯程序描述變量的類型,但并不為變量分配存儲空間。定義一個變量意味著在說明變量的同時還要為變量分配存儲空間。在定義一個變量的同時還可以對變量進行初始化。下例說明了一個變量和一個結構,定義了兩個變量,其中一個定義帶初始化:
extern int decll;? / * this is a declaration * /
struct decl2 {
??????? int member;
} ;? / * this just declares the type--no variable mentioned * /
int def1 = 8;?????? / * this is a definition * /
int def2;??????? / * this is a definition * /

換句話說,說明一個變量相當于告訴編譯程序“在程序的某個位置將用到一個變量,這里給出了它的名稱和類型”,定義一個變量則相當于告訴編譯程序“具有這個名稱和這種類型的變量就在這里”。

一個變量可以被說明許多次,但只能被定義一次。因此,不應該在頭文件中定義變量,因為一個頭文件可能會被一個程序的許多源文件所包含。

C語言可以在頭文件中說明或定義變量嗎?

被多個文件存取的全局變量可以并且應該在一個頭文件中說明,并且必須在一個源文件中定義。變量不應該在頭文件中定義,因為一個頭文件可能被多個源文件包含,而這將導致變量被多次定義。如果變量的初始化只發生一次,ANSIC標準允許變量有多次外部定義;但是,這樣做沒有任何好處,因此最好避免這樣做,以使程序有更強的可移植性。

注意:變量的說明和定義是兩個不同的概念,在2.16中將講解兩者之間的區別。

僅供一個文件使用的“全局”變量應該被說明為static,而且不應該出現在頭文件中。

C語言編程中,什么時候不應該使用類型強制轉換(typecast)?

不應該對用const或volatile說明了的對象進行類型強制轉換,否則程序就不能正確運行。
?? 不應該用類型強制轉換把指向一種結構類型或數據類型的指針轉換成指向另一種結構類型或數據類型的指針。在極少數需要進行這種類型強制轉換的情況下,用共用體(union)來存放有關數據能更清楚地表達程序員的意圖。

C語言 什么時候應該使用類型強制轉換(typecast)?

在兩種情況下需要使用類型強制轉換。第一種情況是改變運算分量的類型,從而使運算能正確地進行。下面的程序與2.12中的例子相似,但有不同之處。變量n被賦值為整數i除以整數j的結果,因為是整數相除,所以結果為0。變量f2也被賦值為i除以j的結果,但本例通過(float)類型強制轉換把i轉換成一個float類型,因此執行的是浮點數除法運算(見2.11),結果為0.75。
#include <stdio.h>
main ( )
{
?? int?i = 3;
?? int?j = 4
?? float f1 =i/j;
?? float f2= (float) i/j;
?? printf("3/4== %g or %g depending on the type used. \n",f1, f2);
}

第二種情況是在指針類型和void * 類型之間進行強制轉換,從而與期望或返回void指針的函數進行正確的交接。例如,下述語句就把函數malloc()的返回值強制轉換為一個指向foo結構的指針:
??? struct foo *p=(struct foo *)malloc(sizeof(struct foo));

C語言中的運算符升級(operatorpromotion)是什么?

當兩個不同類型的運算分量(operand)進行運算時,它們會被轉換為能容納它們的最小的類型,并且運算結果也是這種類型。下表列出了其中的規則,在應用這些規則時,你應該從表的頂端開始往下尋找,直到找到第一條適用的規則。
-------------------------------------------------------------
??? 運算分量1????????? 運算分量2?????????? 轉換結果
-------------------------------------------------------------
??? long double????? 其它任何類型??????? long double
??? double?????????? 任何更小的類型????? double
??? float??????????? 任何更小的類??????? float
??? unsigned long??? 任何整數類????????? unsigned long
??? long???????????? unsigned>LONG_MAX?? unsigned long
??? long???????????? 任何更小的類型????? long
??? unsigned???????? 任何有符號類型????? unsigned
-------------------------------------------------------------

下面的程序中就有幾個運算符升級的例子。變量n被賦值為3/4,因為3和4都是整數,所以先進行整數除法運算,結果為整數0。變量f2被賦值為3/4.0,因為4.0是一個float類型,所以整數3也被轉換為float類型,結果為float類型0.75。???
#include <stdio.h>
main ()
{
?????????? float f1 = 3/4;
?????????? float f2 = 3/4.0
?????????? printf("3/4== %g or %g depending on the type used. \n",f1, f2);
}

對不同類型的C語言變量進行算術運算會有問題嗎?

C有三類固有的數據類型:指針類型、整數類型和浮點類型。

指針類型的運算限制最嚴,只限于以下兩種運算:
??? -? 兩個指針相減,僅在兩個指針指向同一數組中的元素時有效。運算結果與對應于兩個指針的數組下標相減的結果相同。???
??? +? 指針和整數類型相加。運算結果為一個指針,該指針與原指針之間相距n個元素,n就是與原指針相加的整數。

浮點類型包括float,double和longdouble這三種固有類型。整數類型包括char,unsigned char,short,unsigned short,int,unsigned int,long和unsigned long。對這些類型都可進行以下4種算術運算:
??? +? 加???
??? -? 減
??? *? 乘???
??? /? 除

對整數類型不僅可以進行上述4種運算,還可進行以下幾種運算:
??? %??? 取模或求余???
??? >>??? 右移
??? <<??? 左移
??? &??? 按位與
??? |??? 按位或
??? ^??? 按位異或
??? !??? 邏輯非
??? ~??? 取反
??? 盡管C允許你使用“混合模式”的表達式(包含不同類型的算術表達式),但是,在進行運算之前,它會把不同的類型轉換成同一類型(前面提到的指針運算除外)。這種自動轉換類型的過程被稱為“運算符升級(operator promotion)”。

一個C語言的數字型變量,怎樣判斷它可以容納的最大值?

要判斷某種特定類型可以容納的最大值或最小值,一種簡便的方法是使用ANSI標準頭文件limits.h中的預定義值。該文件包含一些很有用的常量,它們定義了各種類型所能容納的值,下表列出了這些常量:
----------------------------------------------------------------
??? 常? 量????????????????????????? 描??? 述
----------------------------------------------------------------
? CHAR—BIT?????? char的位數(bit)???
? CHAR—MAX?????? char的十進制整數最大值
? CHAR—MIN?????? char的十進制整數最小值
? MB—LEN—MAX??? 多字節字符的最大字節(byte)數
? INT—MAX??????? int的十進制最大值???
? INT—MIN??????? int的十進制最小值?
? LONG—MAX?????? long的十進制最大值
? LONG—MIN?????? long的十進制最小值
? SCHAR—MAX????? signedchar的十進制整數最大值
? SCHAR—MIN????? signedchar的十進制整數最小值
? SHRT—MIN?????? short的十進制最小值
? SHRT—MAX?????? short的十進制最大值
? UCHAR—MAX????? unsignedchar的十進制整數最大值
? UINT—MAX?????? unsignedint的十進制最大值
? ULONG—MAX????? unsignedlongint的十進制最大值
? USHRT—MAX????? unsignedshortint的十進制最大值
-----------------------------------------------------------------
??? 對于整數類型,在使用2的補碼運算的機器(你將使用的機器幾乎都屬此類)上,一個有符號類型可以容納的數字范圍為-2 位數-1到(+2 位數-1-1),一個無符號類型可以容納的數字范圍為0到(+2 位數-1)。例如,一個16位有符號整數可以容納的數字范圍為--2 15(即-32768)到(+2 15-1)(即+32767)。

C語言浮點數比較(floating-point comparisons)的可靠性如何?

浮點數是計算機編程中的“魔法(black art)”,原因之一是沒有一種理想的方式可以表示一個任意的數字。電子電氣工程協會(IEEE)已經制定出浮點數的表示標準,但你不能保證所使用的每臺機器都遵循這一標準。

??? 即使你使用的機器遵循這一標準,還存在更深的問題。從數學意義上講,兩個不同的數字之間有無窮個實數。計算機只能區分至少有一位(bit)不同的兩個數字。如果要表示那些無窮無盡的各不相同的數字,就要使用無窮數目的位。計算機只能用較少的位(通常是32位或64位)來表示一個很大的范圍內的數字,因此它只能近似地表示大多數數字。

??? 由于浮點數是如此難對付,因此比較一個浮點數和某個值是否相等或不等通常是不好的編程習慣。但是,判斷一個浮點數是否大于或小于某個值就安全多了。例如,如果你想以較小的步長依次使用一個范圍內的數字,你可能會編寫這樣一個程序:
#include <stdio.h>
const float first = O.O;
const float last = 70.0
const float small= O.007
main ( )
{
??????? float? f;
??????? for (f=first; f !=last && f<last+1.O; f +=small)
??????? printf("f is now %g\n", f);
}
?然而,舍入誤差(rounding error)和變量small的表示誤差可能導致f永遠不等于last(f可能會從稍小于last的一個數增加到一個稍大于last的數),這樣,循環會跳過last。加入不等式"f<last+1.0"就是為了防止在這種情況發生后程序繼續運行很長時間。如果運行該程序并且被打印出來的f值是71或更大的數值,就說明已經發生了這種情況。

一種較安全的方法是用不等式"f<last"作為條件來終止循環,例如:
??? float? f;
??? for(f=first; f<last; f+=small)
?????? ;

你甚至可以預先算出循環次數,然后通過這個整數進行循環計數:
??? float? f;
??? int? count=(last-first)/small;
??? for(f=first;count-->0;f+=small)
?????? ;

C語言編程中,什么時候應該使用const修飾符

使用const修飾符有幾個原因:
第一個原因是這樣能使編譯程序找出程序中不小心改變變量值的錯誤。

請看下例:
while ( * str=0) / * programmer meant to write * str! =0 * /
{
???? / * some code here * /
????? strq++;
}

其中的“=”符號是輸入錯誤。如果在說明str時沒有使用const修飾符,那么相應的程序能通過編譯但不能被正確執行。

第二個原因是效率。如果編譯程序知道某個變量不會被修改,那么它可能會對生成的代碼進行某些優化。???

如果一個函數參數是一個指針,并且你不希望它所指向的數據被該函數或該函數所調用的函數修改,那么你應該把該參數說明為const指針。如果一個函數參數通過值(而不是通過指針)被傳遞給函數,并且你不希望其值被該函數所調用的函數修改,那么你應該把該參數說明為const。然而,在實際編程中,只有在編譯程序通過指針存取這些數據的效率比拷貝這些數據更高時,才把這些參數說明為const。???

請參見:???
1、一個變量可以同時被說明為const和volatile嗎????
2、什么時候不應該使用類型強制轉換(typecast)????
3、用const說明常量有什么好處?

一個C語言變量可以同時被說明為const和volatile嗎

可以。const修飾符的含義是變量的值不能被使用了const修飾符的那段代碼修改,但這并不意味著它不能被這段代碼以外的其它手段修改。

例如,在2.6的例子中,通過一個volatile const指針t來存取timer結構。函數time_addition()本身并不修改t->value的值,因此t->value被說明為const。不過,計算機的硬件會修改這個值,因此t->value又被說明為volatile。如果同時用const和volatile來說明一個變量,那么這兩個修飾符隨便哪個在先都行。

請參見:
1、什么時候應該使用volatile修飾符?
2、什么時候應該使用const修飾符?
3、什么時候不應該使用類型強制轉換(typecast)?

對于C語言什么時候應該使用volatile修飾符

volatile修飾符告訴編譯程序不要對該變量所參與的操作進行某些優化。在兩種特殊的情況下需要使用volatile修飾符:第一種情況涉及到內存映射硬件(memory-mapped hardware,如圖形適配器,這類設備對計算機來說就好象是內存的一部分一樣),第二種情況涉及到共享內存(shared memory,即被兩個以上同時運行的程序所使用的內存)。

大多數計算機擁有一系列寄存器,其存取速度比計算機主存更快。好的編譯程序能進行一種被稱為“冗余裝入和存儲的刪去”(redundant load and store removal)的優化,即編譯程序會·在程序中尋找并刪去這樣兩類代碼:

一類是可以刪去的從內存裝入數據的指令,因為相應的數據已經被存放在寄存器中;另一種是可以刪去的將數據存入內存的指令,因為相應的數據在再次被改變之前可以一直保留在寄存器中。

如果一個指針變量指向普通內存以外的位置,如指向一個外圍設備的內存映射端口,那么冗余裝入和存儲的優化對它來說可能是有害的。

例如,為了調整某個操作的時間,可能會用到下述函數:???
time_t time_addition(volatile const struct timer * t, int a),
{
???????? int??? n
???????? int??? x
???????? time_t? then
???????? x=O;
???????? then= t->value
???????? for (n=O; n<1O00; n++)
???????? {
?????????????? x=x+a ;
?????????}
????? return t->value - then;
}

在上述函數中,變量t->value實際上是一個硬件計數器,其值隨時間增加。該函數執行1000次把a值加到x上的操作,然后返回t->value在這1000次加法的執行期間所增加的值。

如果不使用volatile修飾符,一個聰明的編譯程序可能就會認為t->value在該函數執行期間不會改變,因為該函數內沒有明確地改變t->value的語句。這樣,編譯程序就會認為沒有必要再次從內存中讀入t->value并將其減去then,因為答案永遠是0。因此,編譯程序可能會對該函數進行“優化”,結果使得該函數的返回值永遠是0。

如果一個指針變量指向共享內存中的數據,那么冗余裝入和存儲的優化對它來說可能也是有害的,共享內存通常用來實現兩個程序之間的互相通訊,即讓一個程序把數據存到共享的那塊內存中,而讓另一個程序從這塊內存中讀數據。如果從共享內存裝入數據或把數據存入共享內存的代碼被編譯程序優化掉了,程序之間的通訊就會受到影響。???

請參見:???
1、一個變量可以同時被說明為const和volatile嗎?
2、什么時候不應該使用類型強制轉換(typecast)?

對于C語言什么時候應該使用register修飾符?它真的有用嗎?

register修飾符暗示編譯程序相應的變量將被頻繁使用,如果可能的話,應將其保存在CPU的寄存器中,以加快其存取速度。但是,使用register修飾符有幾點限制。

首先,register變量必須是能被CPU寄存器所接受的類型。這通常意味著register變量必須是一個單個的值,并且其長度應小于或等于整型的長度。但是,有些機器的寄存器也能存放浮點數。

其次,因為register變量可能不存放在內存中,所以不能用取址運算符“&”來獲取register變量的地址。如果你試圖這樣做,編譯程序就會報告這是一個錯誤。


register修飾符的用處有多大還受其它一些規則的影響。因為寄存器的數量是有限的,而且某些寄存器只能接受特定類型的數據(如指針和浮點數),因此,真正能起作用的register修飾符的數目和類型都依賴于運行程序的機器,而任何多余的register修飾符都將被編譯程序所忽略。

在某些情況下,把變量保存在寄存器中反而會降低運行速度,因為被占用的寄存器不能再用于其它目的,或—者變量被使用的次數不夠多,不足以抵消裝入和存儲變量所帶來的額外開銷。

那么,什么時候應該使用register修飾符呢?回答是,對現有的大多數編譯程序來說,永遠不要使用register修飾符。早期的C編譯程序不會把變量保存在寄存器中,除非你命令它這樣做,這時register修飾符是C語言的一種很有價值的補充。然而,隨著編譯程序設計技術的進步,在決定哪些變量應該被存到寄存器中時,現在的C編譯程序能比程序員作出更好的決定。

實際上,許多C編譯程序會忽略register修飾符,因為盡管它完全合法,但它僅僅是暗示而不是命令。

在極罕見的情況下,程序運行速度很慢,而你也知道這是因為有一個變量被存儲在內存中,也許你最后會試圖在該變量前面加上register修飾符,但是,如果這并沒有加快程序的運行速度,你也不要感到奇怪。

請參見:
1、什么時候應該使用volatile修飾符?

C語言 什么是const指針

如果希望一個變量在被初始化后其值不會被修改,程序員就會通過cons,修飾符和編譯程序達成默契。編譯程序會努力去保證這種默契——它將禁止程序中出現對說明為const的變量進行修改的代碼。

const指針的準確提法應該是指向const數據的指針,即它所指向的數據不能被修改。只要在指針說明的開頭加入const修飾符,就可說明一個cosnt指針。盡管const指針所指向的數據不能被修改,但cosnt指針本身是可以修改的。

下面給出了const指針的一些合法和非法的用法例子:
??? const char *str="hello";
??? char c=*str;? /*legal*/
??? str++;?????? /*legal*/
??? *str='a';? ?? /* illegal */
??? str[1]='b';? /*illegal*/
前兩條語句是合法的,因為它們沒有修改str所指向的數據;后兩條語句是非法的,因為它們要修改str所指向的數據。

在說明函數參數時,常常要使用const指針。例如,一個計算字符串長度的函數不必改變字符串內容,它可以寫成這樣:
??? my_strlen(const char *str)
??? {
??? ??? int count=0;
????????while ( * str++)
????????{
?????? ???? count ++;
??????? }
??????? return?? count;
??? }

注意,如果有必要,一個非const指針可以被隱式地轉換為const指針,但一個const指針不能被轉換成非const指針。這就是說,在調用my_strlen()時,它的參數既可以是一個const指針,也可以是一個非const指針。

請參見:
1、一個變量可以同時被說明為const和volatile嗎?
2、什么時候應該使用const修飾符?
3、什么時候不應該使用類型強制轉換(type cast)?
4、用const說明常量有什么好處?、

C語言 什么是頁抖動(pagethrashing)

有些操作系統(如UNIX和增強模式下的Windows)使用虛擬內存,這是一種使機器的作業地址空間大于實際內存的技術,它是通過用磁盤空間模擬RAM(random—access memory)來實現的。

在80386和更高級的Intel CPU芯片中,在現有的大多數其它微處理器(如Motorola 68030,sparc和Power PC)中,都有一個被稱為內存管理單元(Memory Management Unit,縮寫為MMU)的器件。MMU把內存看作是由一系列“頁(page)”組成的來處理。一頁內存是指一個具有一定大小的連續的內存塊,通常為4096或8192字節。操作系統為每個正在運行的程序建立并維護一張被稱為進程內存映射(Process Memory Map,縮與為PMM)的表,表中記錄了程序可以存取的所有內存頁以及它們的實際位置。

每當程序存取一塊內存時,它會把相應的地址(虛擬地址,virtualaddress)傳送給MMU,MMU會在PMM中查找這塊內存的實際位置(物理地址,physical address),物理地址可以是由操作系統指定的在內存中或磁盤上的任何位置。如果程序要存取的位置在磁盤上,就必須把包含該地址的頁從磁盤上讀到內存中,并且必須更新PMM以反映這個變化(這被稱為pagefault,即頁錯)。

希望你繼續讀下去,因為下面就要介紹其中的難點了。存取磁盤比存取RAM要慢得多,所以操作系統會試圖在RAM中保持盡量多的虛擬內存。如果你在運行一個非常大的程序(或者同時運行幾個小程序),那么可能沒有足夠的RAM來承擔程序要使用的全部內存,因此必須把一些頁從RAM中移到磁盤上(這被為pagingout,即頁出)。???

操作系統會試圖去判斷哪些頁可能暫時不會被使用(通常基于過去使用內存的情況),如果它判斷錯了,或者程序正在很多地方存取很多內存,那么為了讀入已調出的頁,就會產生大量頁錯動作。因為RAM已被全部使用,所以為了調入要存取的一頁,必須調出另一頁,而這將導致更多的頁錯動作,因為此時不同的一頁已被移到磁盤上。在短時間內出現大量頁錯動作的情形被稱為頁抖動,它將大大降低系統的執行效率。

頻繁存取內存中大量散布的位置的程序更容易在系統中造成頁抖動。如果同時運行許多小程序,而實際上已經不再使用這些程序,也很容易造成頁抖動。為了減少頁抖動,你應該減少同時運行的程序的數目。對于大的程序,你應該改變它的工作方式,以盡量使操作系統能準確地判斷出哪些頁不再需要。為此,你可以使用高速緩沖存儲技術,或者改變用于大型數據結構的查找算法,或者使用效率更高的malloc()函數。當然,你也可以考慮增加系統的RAM,以減少頁出動作。

請參見:
1、怎樣說明一個大于640KB的數組?
2、什么是堆(heap)?
3、怎樣才能使DOS程序獲得超過64KB的可用內存?
4、Windows是怎樣組織內存的?

C語言變量必須初始化嗎

不。使用變量之前應該給變量一個值,一個好的編譯程序將幫助你發現那些還沒有被給定一個值就被使用的變量。不過,變量不一定需要初始化。在函數外部定義的變量或者在函數內部用static關鍵字定義的變量(被定義在數據段中的那些變量,見2.1)在沒有明確地被程序初始化之前都已被系統初始化為0了。在函數內部或程序塊內部定義的不帶static關鍵字的變量都是自動變量,如果你沒有明確地初始化這些變量,它們就會具有未定義值。如果你沒有初始化一個自動變量,在使用它之前你就必須保證先給它賦值。

調用malloc()函數從堆中分配到的空間也包含未定義的數據,因此在使用它之前必須先進行初始化,但調用calloc()函數分配到的空間在分配時就已經被初始化為0了。

請參見:
1、什么是局部程序塊(10calblock)?
2、什么是棧(stack)?
3、什么是堆(heap)?

C語言變量存儲在內存(memory)中的什么地方

變量可以存儲在內存中的不同地方,這依賴于它們的生存期。在函數外部定義的變量(全局變量或靜態外部變量)和在函數內部定義的static變量,其生存期就是程序運行的全過程,這些變量被存儲在數據段(datasegment)中。數據段是在內存中為這些變量留出的一段大小固定的空間,它分為兩部分,一部分用來存放初始化變量,另一部分用來存放未初始化變量。

在函數內部定義的auto變量(沒有用關鍵字static定義的變量)的生存期從程序開始執行其所在的程序塊代碼時開始,到程序離開該程序塊時為止。作為函數參數的變量只在調用該函數期間存在。這些變量被存儲在棧(stack)中。棧是內存中的一段空間,開始很小,以后逐漸自動增大,直到達到某個預定義的界限。在象DOS這樣的沒有虛擬內存(virtual memory)的系統中,這個界限由系統決定,并且通常非常大,因此程序員不必擔心用盡棧空間。關于虛擬內存 的討論,請參見2.3。???

第三種(也是最后一種)內存空間實際上并不存儲變量,但是可以用來存儲變量所指向的數據。如果把調用malloc()函數的結果賦給一個指針變量,那么這個指針變量將包含一塊動態分配的內存的地址,這塊內存位于一段名為“堆(heap)”的內存空間中。堆開始時也很小,但當程序員調用malloc()或calloc()等內存分配函數時它就會增大。堆可以和數據段或棧共用一個內存段(memorysegment),也可以有它自己的內存段,這完全取決于編譯選項和操作系統。

與棧相似,堆也有一個增長界限,并且決定這個界限的規則與棧相同。

請參見:
1、什么是局部程序塊(10calblock)?
2、變量必須初始化嗎?
3、什么是頁抖動(pagethrashing)?
4、什么是棧(stack)?
5、什么是堆(heap)?

C語言變量作用域和生存期

C語言的強大功能之一是可以靈活地定義數據的存儲方式。C語言從兩個方面控制變量的性質:作用域(scope)和生存期(lifetime)。作用域是指可以存取變量的代碼范圍,生存期是指可以存取變量的時間范圍。

作用域有三種:
  1. extern(外部的)? 這是在函數外部定義的變量的缺省存儲方式。extern變量的作用域是整個程序。
  2. static(靜態的)? 在函數外部說明為static的變量的作用域為從定義點到該文件尾部;在函數內部說明為static的變量的作用域為從定義點到該局部程序塊尾部。
  3. auto(自動的)? 這是在函數內部說明的變量的缺省存儲方式。auto變量的作用域為從定義點到該局部程序塊尾部。
變量的生存期也有三種,但它們不象作用域那樣有預定義的關鍵字名稱。
  1. 第一種是extern和static變量的生存期,它從main()函數被調用之前開始,到程序退出時為止。
  2. 第二種是函數參數和auto變量的生存期,它從函數調用時開始,到函數返回時為止。
  3. 第三種是動態分配的數據的生存期,它從程序調用malloc()或calloc()為數據分配存儲空間時開始,到程序調用free()或程序退出時為止。

可以把C語言變量保存在局部程序塊中嗎

用局部程序塊來保存變量是不常見的,你應該盡量避免這樣做,但也有極少數的例外。
  1. 例如,為了調試程序,你可能要說明一個全局變量的局部實例,以便在相應的函數體內部進行測試。
  2. 為了使程序的某一部分變得更易讀,你也可能要使用局部程序塊。
    例如,在接近變量被使用的地方說明一個變量有時就會使程序變得更易讀。
然而,編寫得較好的程序通常不采用這種方式來說明變量,你應該盡量避免使用局部程序塊來保存變量。


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/450435.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/450435.shtml
英文地址,請注明出處:http://en.pswp.cn/news/450435.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spring Data JPA - 參考文檔 地址

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 Spring Data JPA - 參考文檔 文檔地址

JS內置方法(Array)

concat() 用于連接兩個或多個數組&#xff0c;該方法不會改變現有的數組&#xff0c;而是返回被連接數組的一個副本。join() 把數組中的所有元素放入一個字符串&#xff0c;元素是通過指定的分隔符進行分隔的。若省略了分隔符參數&#xff0c;則使用逗號作為分隔符。push() 向…

模切ERP和免費OA系統是互相結合提高效率

模切ERP和免費OA系統是互相結合提高效率在模切行業中&#xff0c;模切ERP在管理上的作用占了很大的比重&#xff0c;但是免費OA在管理上的地位都不容忽視的。點晴OA的核心問題是如何提高日常的辦公效率問題。因此點晴OA系統里包含的功能是非常全面&#xff0c;如&#xff1a;辦…

maven知識點

一、Maven簡介 1.1 在項目中如何導入jar包&#xff1f; 下載jar包 &#xff08;mvn&#xff09;將下載的jar包拷貝到項目中&#xff08;WEB-INF/lib&#xff09;選擇jar文件–右鍵–Add as Library 1.2 傳統導入jar包的方式存在什么問題&#xff1f; 步驟多&#xff08;相對…

使用SpringBoot yml配置文件

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 1.上一次我們已經使用SpringBoot實現了一個簡單的HelloWord程序&#xff0c;辣么接下來我們簡單的使用一下他的yml格式的配置文件。 2.在…

軟件行業資訊

為什么只有設計師才能發明流行的新語言 先回顧一下知名編程語言的作者和創造時間&#xff1a;Fortran 語言&#xff0c;50年代&#xff0c;IBM 研究員&#xff1b;Lisp 語言&#xff0c;50年代&#xff0c;MIT 的教授和學生&#xff1b;C語言&#xff0c;70年代&#xff0c;貝爾…

spring知識點

一、Spring概述 1.1 web項目開發中的耦合度問題 在Servlet中需要調用service中的方法&#xff0c;則需要在Servlet類中通過new關鍵字創建Service的實例 public interface ProductService{public List<Product> listProducts(); }public class ProductServiceImpl1 imple…

Linux系統下的權限試題測試

不會做的留言&#xff0c;到時在發布答案&#xff01;一、 有兩個參賽團隊team1、team2&#xff0c;兩個團隊各3人, 這兩個團隊互相競爭&#xff0c;各需提交一份報告&#xff0c;每組成員可以修改自己團隊內的所有文件&#xff0c;且不能讓其他團隊的人修改自己的文件內容&…

電子科大軟件系統架構設計——軟件建模詳細設計

文章目錄 軟件建模詳細設計概述軟件建模詳細設計目標軟件建模詳細設計原則開閉原則里氏 (Liskov) 替換原則依賴倒置原則接口分離原則單一職責原則最少知識原則&#xff08;迪米特法則&#xff09;高內聚原則松耦合原則可重用原則 軟件建模詳細設計內容 UML 軟件靜態結構視圖建模…

YAML文件解析

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 YAML是“另一種標記語言”的外語縮寫,YAML 是一種比JSON&#xff08;json多層次{ 與 [ 會被搞暈的&#xff09;更直觀的表現形式&#xf…

120分鐘React快速掃盲教程

在教程開端先說些題外話&#xff0c;我喜歡在學習一門新技術或讀過一本書后&#xff0c;寫一篇教程或總結&#xff0c;既能幫助消化&#xff0c;也能加深印象和發現自己未注意的細節&#xff0c;寫的過程其實仍然是一個學習的過程。有個記錄的話&#xff0c;在未來需要用到相關…

springmvc知識點

一、SpringMVC概述 Spring MVC 是由Spring官方提供的基于MVC設計理念的web框架。 SpringMVC是基于Servlet封裝的用于實現MVC控制的框架&#xff0c;實現前端和服務端的交互。 1.1 SpringMVC優勢 嚴格遵守了MVC分層思想 采用了松耦合、插件式結構&#xff1b;相比較于我們封裝的…

spring @component的作用

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 1、controller 控制器&#xff08;注入服務&#xff09; 2、service 服務&#xff08;注入dao&#xff09; 3、repository dao&#xff…

微信小程序 懸浮按鈕

2019獨角獸企業重金招聘Python工程師標準>>> 效果視頻 https://pan.baidu.com/s/1yfrDaG9YAX0--v0EA3awZA 布局需要按照圓形排列&#xff0c;所以我們需要計算每個點的坐標 代碼部分 <view styleposition:fixed; wx:for"{{list}}" wx:for-index"i…

C語言const關鍵字—也許該被替換為readolny

const 是constant 的縮寫&#xff0c;是恒定不變的意思&#xff0c;也翻譯為常量、常數等。很不幸&#xff0c;正是因為這一點&#xff0c;很多人都認為被const 修飾的值是常量。這是不精確的&#xff0c;精確的說應該是只讀的變量&#xff0c;其值在編譯時不能被使用&#xff…

dbus服務自啟動方法

Linux 一般發行版上 "/usr/share/dbus-1/services/"目錄就是dbus放service文件的地方。 需要自動啟動的服務器 就在這個目錄放一個 service文件&#xff0c;內容如下&#xff1a; $ cat /usr/share/dbus-1/services/dhcdbd.service [D-BUS Service] Namecom.redhat.…

在Spring Boot中使用 @ConfigurationProperties 注解

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 上一篇博客寫了 如何在Spring Boot application中配置mail . 使用 Value 注解注入屬性. 但 Spring Boot 提供了另一種方式 &#xff0c;能…

Micronaut教程:如何使用基于JVM的框架構建微服務

\本文要點\\Micronaut是一種基于jvm的現代化全棧框架&#xff0c;用于構建模塊化且易于測試的微服務應用程序。\\tMicronaut提供完全的編譯時、反射無關的依賴注入和AOP。\\t該框架的開發團隊和Grails框架的開發團隊是同一個。\\tMicronaut框架集成了云技術&#xff0c;服務發現…

C語言extern關鍵詞—最會帶帽子的關鍵字

extern&#xff0c;外面的、外來的意思。那它有什么作用呢&#xff1f;舉個例子&#xff1a;假設你在大街上看到一個黑皮膚綠眼睛紅頭發的美女&#xff08;外星人&#xff1f;&#xff09;或者帥哥。你的第一反應就是這人不是國產的。extern 就相當于他們的這些區別于中國人的特…

解決Coldfusion連接MySQL數據庫的問題

在連接MySQL時&#xff0c;出現了如下錯誤&#xff1a; Connections to MySQL Community Server are not supported. Please contact MySQL to obtain a MySQL Enterprise or Commercial version. 解決方案&#xff1a; step 1: download the JDBC driver JDBC Driver for MySQ…