程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> [C和指針]第一部分,指針第一部分

[C和指針]第一部分,指針第一部分

編輯:關於C語言

[C和指針]第一部分,指針第一部分


聲明:原創作品,轉載時請注明文章來自SAP師太博客,並以超鏈接形式標明文章原始出處,否則將追究法律責任!

第一章     快速上手... 1
第二章     數據... 2
第三章     操作符... 6

第一章     快速上手


要從邏輯上刪除一段C代碼,更好的辦法是使用 #if 指令,這樣即使這段代碼之間原生存在注釋(/**/)也沒問題(/**/塊注釋是不能嵌套的),這是一種安全的方法:
#if 0
         statements
#endif
 
#include <stdio.h>:這是一種預處理指令,由預處理器解釋。預處理器使用stdio.h的庫函數頭文件的內容替換預處理指令,其結果就像stdio.h的內容被逐字寫到源文件的那個位置。另一種預處理指令 #define 像Java中定義的public final static int XX 定義的靜態的final常量。
 
如果你有一些聲明需要被幾個不同的源文件使用,你可以把這些聲明編寫在一個頭文件中,然後在需要使用的源文件使用 #include 預處理指令來包含這些聲明,這樣就只需要一份拷貝,便於後期的維護。
 
函數原型便於編譯器在調用它們進行准確性檢查。
 
假如某個程序的代碼由幾個源文件所組成,那麼使用其他源文件的源文件需要寫明被使用源文件中函數的原型。把原型放在頭文件中並使用#include指令包含它們,可以避免多份拷貝。
 
數組名就是第一個元素的地址,一般不需要在數組名前加上 & ,但你也可以加上也是沒有問題的。
 
scanf是以空白字符為間隔的(除了%c以外),即使字符串也是一樣,所以字符串中不能包含空白字符。
 
標准並未規定編譯器對數組下標的有效性進檢查,所以在使用前一般需要自已進行有效性檢查,否則會破壞數組後面附近的內存數據。
 
scanf函數每次調用時都從標准輸入讀取相應的類型數據(主要是根據格式串來讀取),如果轉換失敗,不管是因為文件已經結尾還是因為下一次輸入的字符無法轉換為整數,函數都會非1。如果正常讀取,則返回個數1。
 
gets函數從標准輸入讀取一行文本並把它存儲在一個字符數組中,它會丟棄換行符,並在該行的結尾加上一個空字符 \0。
 
puts函數是gets函數的輸出版本,它把指定的字符串寫到標准輸出並在末尾添上一個換行符。
 
getchar函數從標准輸入讀取一個字符並返回它的值,如果輸入中不再存在任何字符,函數就會返回常量EOF(-1,在stdio.h中定義),用於提示文件的結尾。getchar函數返回的是字符的ASCII碼整數。
 
putchar將整型變量的內容以字符的形式打印出來,通常顯示在屏幕上
 

C運行過程


編輯 –> 預處理 –> 編譯(編譯成目標代碼) –> 連接(連接目標代碼與庫,生成a.out可執行文件) –> 加載(將程序從磁盤加載到內存) –> 運行
 
連接程序把目標代碼和庫函數的代碼連接起來產生可執行的映象。
 
在基於UNIX的C系統中,編譯和連接程序的命令是cc,如編譯和連接welcome.c的程序,在UNIX提示符下鍵入:
cc welcome.c
就會產生一個名為a.out的文件,該文件是程序welcome.c的可執行映象,在命令提示符中輸入a.out就可以執行程序了。
 
 

第二章     數據


長整型至少應該和整型一樣長,而整型至少應該和短整型一樣長。
 
ANSI標准則加入了一個規范,說明了各種整型值的最小允許范圍:
類型
最小范圍
char
0到127
signed char
-127到127
unsigned char
0到255
short int
-32767到32767
unsigned short int
0到65535
int
-32767到32767
unsigned int
0到65535
long int
-2147483647到2147483647
unsigned long int
0到4294967295
short int至少16位,long int至少32位,至於缺省的int究竟是16位還是32位,或者是其他值,則由編譯器設計都決定。通常這個選擇缺省值是這種機器最為自然高效的位數。同時,ANSI並沒有規定3個值必須不一樣,如果某種機型環境的字長為32位,則完全可能把這3個整型值都設定為32位。
 
limits.h文件中說明了各種不同整數類型特點:
#define CHAR_BIT  8                 //字符的位數(至少8位)
#define SCHAR_MIN (-128)            //signed char最小值
#define SCHAR_MAX 127               //signed char最大值
#define UCHAR_MAX 255               //unsigned char最大值
#if ('\x80' < 0) //看一個字節能否能夠存儲 -128,如果能夠,則缺省的字符就是有符號的,否則就是無符號的,一般缺省的char為有符號字符
#define CHAR_MIN   SCHAR_MIN
#define CHAR_MAX   SCHAR_MAX
#else
#define CHAR_MIN   0
#define CHAR_MAX   UCHAR_MAX
#endif
 
#define SHRT_MAX  32767             //short int最大值
#define SHRT_MIN  (-SHRT_MAX-1)     //short int最小值
#define USHRT_MAX 0xffff            //unsigned short int最大值
 
#define INT_MAX      2147483647    //int最大值
#define INT_MIN      (-INT_MAX-1) //int最大值
#define UINT_MAX  0xffffffff        //unsigned int最大值
 
#define LONG_MAX  2147483647L       //long int最大值
#define LONG_MIN  (-LONG_MAX-1)     //long int最大值
#define ULONG_MAX 0xffffffffUL      //unsignedlong int最大值
 
short int、int、long int缺省就是有符號的,而char缺省是否有符號取決於編譯器,需要根據limits.h頭文件的條件預處理來決定,一般也是有符號的。所以signed關鍵字一般只用於char類型,因為其他整型類型在缺省情況下都是有符號的。
 
當可移植問題比較重要時,字符類型 char 是否為有符號就會帶來問題,最好的方案就是把存儲於char型變量的值限制在signed char 和 unsigned char的交集內,只有位於這個區間的字符才可移值,ASCII字符集中的字符都是位於這個范圍之內,所以是可移植的。
 
當一個程序內出現整型字面值時,它是屬於整型家族9種不同類型中的哪一種呢?答案取決於字面值是如何書寫的,但是你可以在有些字面值的後面添加一個後綴來改變缺省的規則。L為long整型值,U用於把數值指定為unsigned整型值。
 
十進制整型字面值可能是int、long或unsigned long,在缺省情況下,它是最短類型但又能完整容納這個值的類型
 
從技術上講,-275並非字面值常量,而是常量表達式。負號被解釋為單目操作符而不是數值的一部分。但在實踐中,這個歧義性基本沒有什麼意義,這個表達式總是被編譯器按照你所預想的方式計算。
 
八進制和十六進制字面值可能的類型是int、unsigned int、long或unsigned long,在缺省情況下,字面值的類型就是上述類型中最短但足以容納整個值的類型
 
字符常量的類型總是int,你不能在它後面添加U或L後綴。如果一個多字節字符常量的前面有一個L,那麼它就是寬字符常量。如:L'x'
 
ANSI C標准僅僅規定long double 至少和double一樣長,而double至少和float一樣長。標准同時規定了一個最小范圍:所有浮點類型至少能夠容納從10-37到1037之間的任何值。
 
浮點數字面值在缺省情況下都是double類型的,除非它的後面跟一個L表示是一個long double類型值,或跟一個F表示它是一個float類型的值
 
字符串通常存儲在字符數組中,這也是C語言沒有的字符串類型的原因。由於NUL字節是用於終結字符串的,所以在字符串內部不能有NUL字節,不過,在一般情況下,這個限制並不會造成問題,之所以選擇NUL作為字符串的終止符,是因為它不是一個可打印的字符。
 
""表示一個空的字符串常量,即使是空字符串,依然存在作為終止符的NUL字節。
 
NUL是ASCII字符集中 '\0' 字符的名字,它的字節模式為全0,NULL指一個其值為0的指針。符號NULL在頭文件stdio.h中定義,另一方面,並不存在預定義的符號NUL,所以如果你想使用它而不是字符常量'\0',你必須自行定義。
 
在 K&R C中,沒有提及一個字符串常量中的字符是否可以被程序修改,但清楚地表明具有相同的值的不同字符串常量在內存中是分開存儲的。因此,許多編譯器都允許程序修改字符串常量;但ANSI C則聲明如果對一個字符串常量進行修改,其結果是未定義的,它把一個字符串常量存儲於一個地方,即使它在程序中多次出現,所以在程序中最好不要修改字符串常量。
 
int* a, b, c;
人們很自然地以為這條語句把所有三個變量聲明為指向整型的指針,但事實上並非如此,星號實際上是表達 *a 的一部分,只對這個標識符有用,其他兩個變量則只是普通的整型。
 
char *message = "Hello world!";
這條語句把message聲明為一個指向字符的指針,並用字符串常量中第一個字符的地址對該指針進行初始化。這裡看上去初始化似乎是賦給表達式 *message,事實上它是賦給message本身的。換句話說,前面一個聲明相當於:
    char *message;
    message = "Hello world!";
 
隱式聲明:C語言中有幾種聲明,它的類名可以省略。例如,函數如果不地聲明返回類型,它就默認返回整型。使用舊網格聲明函數的形式參數時,如果省略了參數的類型,編譯器就默認它們為整型。最後,如果編譯器可以推斷出一條語句實際上是一個聲明時,如果它缺省類型名,編譯器會假定它為整型:
    int a[10];
    int c;
    b[10];
    d;
f( x) {
    return x + 1;
}
上面只能在 K&R C 標准通過編譯,在ANSI C是非法的。
 
常量定義的兩種方式:
#define MAX_LENGTH 10
intconst max_length = 10;
一旦定義並初始化後,都不能修改其值,但是第一種更好。
 
4種不同的作用域:文件作用域、函數作用域、代碼塊作用域、原型作用域。
 
位於一對花括號之間的所有語句稱為一個代碼塊。任何在代碼塊的開始位置聲明的標識符都具有代碼塊作用域。如果代碼塊處於嵌套狀態,並且內層以代碼塊有一個標識符的名字與外層相同,則內層的那個標識符會隱藏外層的標識符。但是,如果在函數體內部聲明了名字與形參相同的局部變量(在同一層,如果定義在嵌套內層則還是會隱藏),ANSI C不會隱藏,編譯時會出錯:
void func(int i ){
    //!int i;//編譯出錯,因為這兩個i在同一層定義
    {
       int i;//這屬性嵌套定義,會隱藏外層相同定義
    }
}
 
 
任何在所有代碼塊之外的標識符都具有文件作用域,它從聲明之處到源文件結尾處都是可以訪問的。在文件中定義的函數名也具有文件作用域,因為函數名本身並不屬於任何代碼塊。
 
原型作用域只適用於在函數原型中聲明的參數名,由於原型中,參數名並非必須,這與函數的定義不同,如果出現參數名,你可以隨便給它們取任何名字。
 
鏈接時,如果相同的標識符出現在幾個不同的源文件中時,標識符的鏈接屬性決定如何處理在不同文件中出現的相同標識符。標識符的作用域與它的鏈接屬性有關,但這兩個屬性並不相同。鏈接屬性一共有3種:external(外部)、internal(內部)、none(無)。沒有鏈接屬性的標識符(none)總是被當作單獨的個體,也就是說該標識符的多個聲明被當作不同的實體。屬於internal鏈接屬性的標識符在同一個源文件內的所有聲明中都指同一個實體,但位於不同源文件的多個聲明則分屬不同的實體。最後,屬於external鏈接屬性的標識符不論聲明多少次、位於幾個源文件都表示同一個實體:
/*1*/typedefchar *a;
/*2*/int b;
/*3*/int c(int d /*4*/) {
    /*5*/int e;
    /*6*/int f(/*7*/int g);
}
在缺省情況下,標識符b、c和f的鏈接屬性為external(這裡的意義應該是可以被外部其他源文件訪問,而external關鍵本身的意義是引用其他源文件中的變量或函數),其余標識符的鏈接屬性則為none。關鍵字extern和static用於在聲明中修改標識符的鏈接屬性,如果某個聲明在正常情況下具有external鏈接屬性,在它前面加上static 關鍵字可以使它的鏈接屬性變為internal。static只對缺省鏈接屬性為external的聲明才有改變鏈接屬性的效果。例如,盡管你可以在聲明5前面加上static關鍵字,但它的效果完全不一樣,因為e的缺省鏈接屬性並不是external。
 
函數代碼就是存儲在靜態內存中。
 
聲明3為k指定external鏈接屬性,這樣函數就可以訪問在其他源文件聲明的外部變量了:
/*1*/staticint i;
int func() {
    /*2*/int j;
    /*3*/externint k;
    /*4*/externint i;
}
當external關鍵字用於源文件中一個標識符的第一次聲明時,它表示該標識符具有external鏈接屬性,但如果它用於該標識符的第二次或以後的聲明,它並不會更改由第一次聲明所指定的鏈接屬性,上面的聲明4並不修改由聲明1所指定的變量i的鏈接屬性。
 
凡是在任何代碼塊之外聲明的變量總是存儲於靜態內存中,不在堆棧內存,這類變量稱為靜態(static)變量。
在代碼塊內部聲明的變量的缺省存儲類型是自動的,也就是說它存儲於堆棧中,稱為自動(auto)變量。
函數的形式參數不能聲明為靜態的,因為實參總是在堆棧中傳遞給函數。
關鍵字register可以用於自動變量的聲明。
 
static
當它用於函數定義時,或用於代碼塊之外的變量聲明時,static關鍵字用於修改標識符的鏈接屬性,從external改為internal,但標識符的存儲類型和作用域不受影響。用這種方式聲明的函數或變量只能在聲明它們的源文件中訪問。
當它用於代碼塊內部的變量聲明時,static關鍵字用於修改變量的存儲類型,從自動變量修改為靜態變量,但變量的鏈接屬性和作用域不受影響。該變量在運行期一存在。

第三章     操作符


邏輯右移,左邊補零;算術右移,根據左邊符號位來決定
 
標准說明無符號值執行的所有移位操作都是邏輯移位,但對於有符號值,是邏輯還是算術取決於編譯器,需要寫一個程序測試一下。因此,一個程序如果使用了有符號數右移操作,它就是不可移植的。
 
下面這段代碼是錯誤的
char ch;
while((ch=getchar())!=EOF){...}
EOF需要的位數比字符型值所能提供的位數要多,這也是getchar返回一個整型值而不是字符值的原因。然而,把getchar的返回值首先存儲於ch中將導致它被截斷。然後這個被截斷的值被提升為整型並與EOF進行比較。當這段代碼在使用有符號字符集的機器上運行時,如果讀取了一個值為\377的字節時,循環將會終止,因為這個值截斷再提升之後(提升時補符號位)與EOF相等。當這段代碼在使用無符號字符集的機器上運行時(提升時補0),這個循環將永遠不會終止。
 
由於C語言中的char可以是有符號的,所以與Java是不同的,Java中的字符永遠是無符號的,Java中窄的整型轉換成較寬的整型時符號擴展規則:如果最初的數值類型是有符號的,那麼就執行符號擴展(即如果符號位為1,則擴展為1,如果為零,則擴展為0);如果它是char,那麼不管它將要被提升成什麼類型,都執行零擴展。
 
如果函數f沒有副作用,它們是等同的:
a[2 * (y – 6 * f (x) ) ] = a[2 * (y – 6 * f (x) ) ] + 1;
a[2 * (y – 6 * f (x) ) ] += 1;
第一個下標會計算兩次,而第二只會計算一次,第二種效率高。
 
sizeof操作符判斷它的操作數的類型長度,以字節為單位,操作數既可以是個表達式(常常是單個變量),也可以是兩邊加上括號的類型名:
    printf("%d\n",sizeof 1);//4
    printf("%d\n",sizeof (1));//表達式兩邊也可加上括號
    printf("%d\n",sizeof (char));//1
    printf("%d\n",sizeof (short));//2
    printf("%d\n",sizeof (int));//4
    printf("%d\n",sizeof (long));//4
當sizeof的操作數是個數組名時,它返回該數組的長度,以字節為單位。
sizeof(a = b +1)並不會向a賦任何值。
 
Java:
       int a = 1;
       a = a++;
       System.out.println(a);//1
       int b = 1;
       b = ++b;
       System.out.println(b);//2
C:
    int a = 1;
    a = a++;
    printf("%d\n", a);
    int b = 1;
    b = ++b;
    printf("%d\n", b);
 
逗號操作符將兩個或多個表達式分隔開來,這些表達式自左向右逐個進行求值,整個逗號表達式的值就是最後那個表達式的值,如:
if(b + 1, c / 2, d > 0);
如果d的值大於0,那麼整個表達式的值就為真。
 
下標引用和間接訪問表達式是等價的:
arry[下標]
*(arry + (下標))
下標引用實際上是以後面這種形式實現的。
 
.和->操作符用於訪問一個結構的成員。如果s是個結構變量,那s.a就是訪問s中名叫a的成員。當你擁有一個指向結構的指針而不是結構本身,且想訪問它的成員時,就需要使用->操作符而不是.操作符。
 
零是假,任何非零值皆為真。如果flag的值只是0或1,則下面每兩對中的兩個語句是等價的:
#defined FALSE 0
#defined TRUE 1
...
if(flag == FALSE)...
if(!flag)...
...
if(flag == TRUE)...
if(flag)...
但是,如果flag設置為任意的整型值,那麼第2對語句就不是等價的了,可以顯示地對它進行測試來解決這個問題:
if(flag != 0)...
if(flag)...
 
*p即可作為左值,也可以作為右值,如:
int a, *p;
*p = 20;
a = *p;
*p=20賦值左邊顯示是一個表達式,但它卻是一個合法的。因為指針p的值是內存中某個特定位置的地址,*操作符使機器指向那個位置。當它作為左值使用時,這個表達式指定需要進行修改的位置,當它作為右值使用時,它就提取當前存儲於這個位置的值。
 
    int a = 5000;
    int b = 25;
    long c = a * b;
在32位整數的機器上,這段代碼運行起來沒有問題,但在16位整數的機器上,這個乘法運算會產生溢出。解決方案是將其中任何一個或兩個強轉為long:
    long c = a * (long)b;
 
兩個相鄰(不相鄰則不適用此規則操作符的執行順序由它們的優先級決定。如果它們的優先級相同,它們的執行順序由它們的結合性決定。除此之外,編譯器可以自由決定使用任何順序對表達式進行求值,只要它不違背逗號、&&、||和?:操作符所施加的限制。如:a + b * c表達式中,乘法和加法操作符是兩個相鄰的操作符。由於*操作符的優先級比+操作符高,所以乘法運算先於加法運算執行,編譯器在這裡別無選擇,它必須先執行乘法運算。下面是一個更有趣的表達式:a * b + c * d + e * f,如果僅由優先級決定這個表達式的求值順序,那麼所有3個乘法運算將在所有加法運算之前進行,但事實上不是這樣的,實際上只保證每個乘法運算在它相鄰的加法運算之前執行即可這個表達式可能按下面順序進行:
a * b
c * d
(a * b) + (c * d)
e * f
(a * b )+ (c * d) + (e * f)
注意,第1個加法運算在最後一個乘法運算之前執行,但最終的結果是一樣的。所以優先級只對相鄰操作符的執行順序起作用,這裡並沒有任何規則要求所有的乘法運算首先進行,也沒有規則規定這幾個乘法運算之間誰先執行。
 
f() + g() + h(),盡管左邊那個加法運算必須在右邊那個加法運算之前執行,但對於各個函數調用的順序,並沒有規則加以限制。為了避免副作用,請使用臨時變量先調用。
 
c + --c是危險的:操作符的優先級規則要求自減運算在加法運算之前進行,但我們並沒有辦法得知加法操作符的左操作數是在右操作數之前還是之後進行求值。它在這個表達式中將存在區別,因為自減操作符具有副作用。--c在c之前或之後執行,表達式的結果將不同。每種編譯器可能都不同,像這樣的表達式是不可移植的,這是由於表達式本身的缺陷所引起的。
 
a=b=10;
c = ++a;// a增加至11,c得到的值為11
d = b++;//b增加至11,但d得到的值仍為10
前綴和後綴形式的增值操作符都自制一份變量值的拷貝,用於周圍表達式(上面指賦值操作)的值正是這份拷貝。前綴操作符在進行復制之前增加變量的值,後綴操作符在進行復制之後才增加變量的值。這些操作的結果不是被它們所修改的變量的值,而是變量值的拷貝,搞清這一點非常重要。
 
優先級決定表達式中各種不同的運算符起作用的優先次序,而結合性則在相鄰的運算符的具有同等優先級時,決定表達式的結合方向。

操作符

描述

結合性

() [] . ->

():函數調用(也可用來改變表達式的優先級)[]:數組下標

.:通過結構體變量訪問成員變量

->:通過結構體指針訪問成員變量

 

left-to-right

++ -- + - ! ~ (type) * & sizeof

++:前/後自增

--:前/後自減

+:一元正

-:一元負

!:邏輯取反/按位取反

(type):類型轉換

*:間接訪問

&:取地址

sizeof:確定類型或表達式所在字節數

right-to-left

* / %

乘/除/取模

left-to-right

+ -

加/減

left-to-right

<< >>

左移位,右移位

left-to-right

< <= > >=

小於/小於等於/大於/大於等於

left-to-right

== !=

等於/不等於

left-to-right

&

按位與

left-to-right

^

位異或

left-to-right

|

按位或

left-to-right

&&

邏輯與

left-to-right

||

邏輯或

left-to-right

?:

條件操作符

right-to-left

= += -= *= /= %= &= ^= |= <<= >>=

=:賦值

+=:加賦值

-=:減賦值

*=:乘賦值

/=:除賦值

%=:模賦值

&=:位與賦值

^=:位異或賦值

|=:位或賦值

<<=:位左移賦值

>>=:位右移賦值

right-to-left

,

逗號

left-to-right

 

說明,同一行的優先級是相同的
 
int main(int argc, char **argv) {
    int i[] = { 3, 5 };
    int *p = i;
    //先拷貝p,再讓p下移,再取拷貝p中值,
    //值減一後再拷貝,最後將值拷貝賦給j
    int j = --*p++;
    printf("%d\n", j);//2
    printf("%d", *p);//5
}
 
(一)a = b = c;
關於優先級與結合性的經典示例之一就是上面這個“連續賦值”表達式。
b的兩邊都是賦值運算,優先級自然相同。而賦值表達式具有“向右結合”的特性,這就決定了這個表達式的語義結構是“a = (b = c)”,而非“(a = b) = c”。即首先完成c向b的賦值,然後將表達式“b = c”的值再賦向a。
一般來講,對於二元運算符▽來說,如果它是“向左結合”的,那麼“x ▽ y ▽ z”將被解讀為“(x ▽ y) ▽ z”,反之則被解讀為“x ▽ (y ▽ z)”。注意,相鄰的兩個運算符可以不同(如: a.b->c表達式則是 ((a.b)->c),但只要有同等優先級,上面的結論就適用。再比如“a * b / c”將被解讀為“(a * b) / c”,而不是“a * (b / c)”,這可能導致完全不同的結果。
而一元運算符的結合性問題一般會簡單一些,比如“*++p”只可能被解讀為“*(++p)”。三元運算符後面會提到。
(二)*p++;
像下面這樣實現strcpy函數的示例代碼隨處都能見到:
char* strcpy(char* dest, constchar* src) {
    char*p = dest;
    while (*p++ = *src++);
    return dest;
}
理解這一實現的關鍵在於理解“*p++”的含義。
用運算符“*”的優先級低於後自增運算符“++”,所以,這個表達式在語義上等價於“*(p++)”,而不是“(*p)++”。
 
size_t strlen(constchar* str) {
    constchar* p = str;
    while (*p++);
    return p - str - 1;
}
(三)x > y ? 100 : ++y > 2 ? 20 : 30
int x = 3;
int y = 2;
int z = x > y ? 100 : ++y > 2 ? 20 : 30;
這裡面是兩個條件運算符(?:,也叫“三目運算符”)嵌套,許多人會去查條件運算符的特性,得知它是“向右結合”的,於是認為右側的內層條件運算“++y > 2 ? 20 : 30”先求值,這樣y首先被加1,大於2的條件成立,從而使第二個條件運算取得結果“20”;然後再來求值整個條件表達式。這時,由於y已經變成3,“x > y”不再成立。整個結果自然就是剛剛求得的20了。
這種思路是錯誤的。
錯誤的原因在於:它把優先級、結合性跟求值次序完全混為一談了。
首先,在多數情況下,C語言對表達式中各子表達式的求值次序並沒有嚴格規定;其次,即使是求值次序確定的場合,也是要先確定了表達式的語義結構,在獲得確定的語義之後才談得上“求值次序”。
對於上面的例子,條件運算符“向右結合”這一特性,並沒有決定內層的條件表達式先被求值,而是決定了上面表達式的語義結構等價於“x > y ? 100 : (++y > 2 ? 20 : 30)”,而不是等價於“(x > y ? 100 : ++y) > 2 ? 20 : 30”。——這才是“向右結合”的真正含義。
編譯器確定了表達式的結構之後,就可以准確地為它產生運行時的行為了。條件運算符是C語言中為數不多的對求值次序有明確規定的運算符之一(另位還有三位,分別是邏輯與“&&”、邏輯或“||”和逗號運算符“,”)。
C語言規定:條件表達式首先對條件部分求值,若條件部分為真,則對問號之後冒號之前的部分求值,並將求得的結果作為整個表達式的結果值,否則對冒號之後的部分求值並作為結果值。
因此,對於表達式“x > y ? 100 : (++y > 2 ? 20 : 30)”,首先看x大於y是否成立,在本例中它是成立的,因此整個表達式的值即為100。也因此冒號之後的部分得不到求值機會,它的所有副作用也就沒機會起作用。
 
為了可移植性,盡量使用函數庫。
 
 
請編寫函數:
unsignedint reverse_bits(unsignedint value)
這個函數的返回值把value進行翻轉,如在32機器上,25這個數的二進制如下:
00000000 00000000 00000000 00011001
則函數返回的值應該為2550136832,它的二進制位模式為:
10011000 00000000 00000000 00000000
/*
* 將一個無符號整型進行翻轉
*/
unsignedint reverse_bits(unsignedint value) {
    unsignedint answer=0;
    unsignedint i;
    /* 使用位移操作來控制次數,這樣可以避免不可移植性
     * i不是0就繼續進行,這就使用循環與機器字長無
     * 關,從而避免了可移植性問題
     */
    for (i = 1; i != 0; i <<= 1) {
 
       /*
        *把舊的answer左移1位,為下一個位留下空間;
        *如果value的最後一們是1,answer就與1進行or操作;
        *然後將value右移一位
        */
       answer <<= 1;
       if (value & 1) {
           answer |= 1;
       }
       value >>= 1;
    }
    return answer;
}
int main(int argc, char **argv) {
    printf("%u",reverse_bits(25));//2550136832
}
 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved