**貳**
**幾個關鍵字**
sizeof
1、sizeof是關鍵字而不是函數。
例:int i=3;?? sizeof(i) ;? 與? sizeof i ; 是完全等同。
sizeof在計算變量所占空間大小時,括號可以省略,而計算類型大小時不能省略。(所以我們常在其后加上括號)
2、sizeof的作用域是緊跟它后面的一個變量或類型。
故:sizeof(int)*p;//p為指針,則此表達式會報錯。此表達式的意思不是計算 *p強制轉換成int類型之后值的存儲大小,而意為sizeof(int)的值4 然后是*p的值 這兩個值中間沒有操作符故會報錯。同理:sizeof i*i ;//int i=3 此表達式值為12
1、?★sizeof的值在編譯時就已經確定,故在程序執行時不會執行sizeof括號內的表達式。
~~~
#include <stdio.h>
int main(void)
{
int i;
i = 10;
printf("sizeof(i++)is: %d\n",sizeof(i++));
printf("i :%d\n",i);
return 0;
}//最后打印i的值為10,因為程序不執行sizeof里的表達式i++
~~~
switch、case組合
注意:
1、每個case語句的結尾不要忘了加break,若是為了實現某功能故意不加,應該注釋清楚
2、最后必須使用default分支。即使程序真的不需要default處理,也應該保留語句
default :?? break;
可以避免讓人誤認為你忘了default處理。
2、?case關鍵字后面只能是整型或字符型常量(小整型)或常量表達式。
3、?★switch塊中的case是一個標簽,switch根據值來選擇執行哪個case標簽的語句。故編譯器只對case和default標簽里的語句編譯,而寫在而在case塊之前的語句則忽視掉。
~~~
#include <stdio.h>
int main(void)
{
inta=1;
switch(a)
{
intb=20; //此語句不被編譯,C編譯器會產生警告(C++編譯器會報錯)
case1:
printf("bis %d\n",b); //故此輸出隨機值
break;
default:
printf("bis %d\n",b);
break;
}
return 0;
}
~~~
void
void的字面意思是“空類型”,void * 則為“空指針類型”任何類型的指針都可以直接賦值給它,無需進行強制類型轉換。但:void * 不可以不經強制類型轉換地賦值給其它類型的指針。因為“空類型”可以包容“有類型”,而有類型則不能包容“空類型”。
如果函數的參數可以是任意類型的指針,那么應聲明其參數為void *
void不能代表一個真實的變量。(因為定義變量時必須分配內存空間,定義void類型編譯器不知道分配多少空間)故:void a ;//錯誤
void只是為了一種抽象的需要。
const
在C語言中,const修飾的值是只讀變量,其值在編譯的時候不能被使用,因為編譯器在編譯的時候不知道其存儲的內容。
例:const intmax=100 ;? int Array[max] ;
在C編譯環境下,編譯器會報錯。因為在C中,const修飾的仍然是變量,只不過是只讀屬性罷了。
而在C++中,const則修飾的值是常量,完全可以取代宏定義,其效果和宏定義相同。故上面的代碼在C++中是合法的。
volatile
volatile修飾的量就是很容易變化,不穩定的量,它可能被其它線程,操作系統,硬件等等在未知的時間改變,所以它被存儲在內存中,每次取用它的時候都只能在內存中去讀取,它不能被編譯器優化放在內部寄存器中。
★const和volatile在類型聲明中的位置
兩者的規則一樣。以const為例:
類型聲明中const用來修飾一個常量,我們一般這樣使用:
①const在前面
const int? ;?????????? //int是const
const char* ;???????? //char是const
char* const ;???????? //*(指針)是const
const char* const ; //char和*都是const
【const所修飾的類型是 它后面的那一個】
②const在后面( 與上面的聲明對等)
int const ;???????????????????? //int是const
char const* ;???????? //char是const
char* const ;???????? //*(指針)是const
char const* const ; //char和*都是const
**建議const寫在后面:**
A. const所修飾的類型是正好在它前面的那一個。
B. 我們很多時候會用到typedef的類型別名定義。比如typedef char* pchar,如果用const來修飾的話,當const在前面的時候,就是const pchar,你會以為它就是const char* ,但是你錯了,它的真實含義是char* const。是不是讓你大吃一驚!但如果你采用const在后面的寫法,意義就怎么也不會變,不信你試試!
***幾個運算符***
邏輯運算符:**(一定要注意表達式的順序)**
&&?||? 有“短路”現象??? (有好處也有壞處)
**/*利用短路現象*/**
例:if( x>=0&& x<MAX && array[x]==0 ) ……….//可用
(首先檢查x值是否在數組下標范圍內。如果不是,代碼中的下標引用表達式便忽略。此可防止下標值錯誤導致引用失敗)
例:if(p==NULL|| *p==’\0’) ………….//可用
【總結:**使用&&時,把大前提條件放前面,小條件放后面**
**使用&&時,是第一個表達式成立才繼續執行(有點像if-else 故我們常利用&&的短路規則)**
**使用||時,是第一個表達式不成立才繼續執行**】
條件運算符:(可替換if-else)
例:a>5 ? b-6: c/2? ; //可讀作“a是不是大于5?如果是,就執行b-6,否則執行c/2”
~~~
if(a>5)
b[2*c+d(e/5)]= 3 ;
else
b[2*c+d(e/5)]= -20 ;
//用條件運算符可寫作:b[2*c+d(e/5)]= a>5 ? 3 : -20 ;
~~~
【在某些復雜的表達式寫相似的兩次時,用條件運算符更簡潔,易修改會產生較小的目標代碼, 可以簡化表達式】
逗號運算符:
【**可用于循環判斷中(多用于while),使多次重復的語句只寫一次**】
~~~
a=get_value();
count_value(a);
while(a>0)
{ ……….
a=get_value();
count_value(a);
}
~~~
則可簡化為: while(a=get_value() , count_value(a) , a>0){……}
(這些表達式自左向右逐個求值,整個逗號表達式的值就是最后那個表達式的值)
***隱式轉換 / 溢出***
例1:char? ch ;
????? ?While( (ch=getchar()) != EOF ) …..//錯誤!
注意:**getchar()返回一個整型值而不是字符值!**
若把getchar返回值存儲于ch中,將導致它被截斷!然后這個被截斷的值被提升為整形并與EOF比較,循環會出錯。
【用整形來定義一個字符變量更好!**字符就是一個小整數**】?sizeof(‘a’) ; 的值為4。
~~~
char a,b,c ;
a = b+c ; //b+c的值超過或等于128就會出錯。
~~~
注意:C的整型算數運算總是至少以缺省整型類型的精度來進行的。為了獲得這個精度,表達式中的字符型和短整型操作數在使用之前被轉換為普通整型。即:**整型提升**
(上面b和c的值被提升為普通整型,然后再執行加法運算。加法運算的結果被截斷再存儲于a中)
【所以:**最好把字符型定義為int 尤其是涉及運算時**】
//WHY?在整數運算時,操作數是放到兩個寄存器中進行的(32位計算機寄存器是32位 故字符型變量被提升為整型,計算的結果又被傳回到內存中的字符型存儲位置中故被截斷)
~~~
int a=5000 ;
int b=25 ;
long c=a*b ;
~~~
表達式a*b以整型進行計算,若在16位的計算機上,這個乘法可能會產生溢出!
故應該顯式轉換: longc=(long)a*b ;
【在進行算術運算時一定要警惕乘法加法的可能溢出,尤其**注意賦值號兩邊的數據類型保持一致**】
在計算類似a*b/c 這樣的表達式的時候,一些中間計算結果可能會溢出。
【**對于算術運算可能的溢出一定要保持警惕!!!**】
***對無符號類型的建議:***
1、盡量不要在你的代碼中使用無符號類型,以免增加不必要的復雜性。
2、盡量使用像int那樣的有符號類型,這樣在涉及升級混合類型的復雜細節時,不必擔心邊界情況。
3、只有在使用位段和二進制掩碼時,才可以使用無符號數。
***表達式求值的順序***
***兩個相鄰操作符的執行順序由它們的優先級決定。如果它們的優先級相同,它們的執行順序由它們的結合性決定。(即從左還是從右運算),此外編譯器可自由決定順序對表達式求值!
~~~
a*b + c*d +e*f ;
//可能按下列順序運行:
c*d
e*f
a*b
(a*b)+(c*d)
(a*b)+(c*d)+(e*f)
//加法運算的結合性要求兩個加法運算按先左后右執行,但對表達式剩余部分執行順序沒有限制。 沒有規則要求所有的乘法首先進行,沒有規定其誰先執行。
~~~
【**優先級只對相鄰的操作符的執行順序起作用**】
【在C語言的眾多運算符中,**ANSI標準只規定了4種運算符的操作數的求值順序: &&? || 逗號運算符 和 ?:**】
警示:f( ) + g( )+ h( ) ; //應該避免!
如果函數執行有副作用,不同的結合順序會產生不同的結果!
故 最好使用臨時變量,讓每個函數調用都在單獨的語句中進行。
~~~
tmp = f( ) ;
tmp += g( ) ;
tmp += h( ) ; //良好的編程風格
~~~
【**避免編寫 結果依賴于求值順序的表達式**】
例:a[i] = i++;? //應該避免!
//i在同一表達式的其它地方被引用無從判斷該應用是舊值還是新值。
【**確保一個表達式只修改一個對象,如果要修改多個對象,要確保修改的對象互不相同**!(在一個表達式中,一個對象只能修改一次)】
~~~
printf(“%d%d”,f1(), f2() ) ; //應該避免!
//若函數f2( )的結果依賴于函數f1( ) 則printf的結果是不確定的!
//用逗號分隔的函數參數不是逗號運算符。
~~~
【**函數調用的參數的求值順序是不確定的! 不要讓有副作用的函數作為參數!**】
***左值和右值***
左值就是那些能夠出現在賦值號左邊的東西,右值就是那些可以出現在賦值號右邊的東西。
【編譯原理:編譯器會把賦值號左邊的部分解釋成一個存儲位置,把賦值號右邊的部分解釋成一個值。在編譯過程中,編譯器會維護一張變量表,其中是我們定義過的變量名及其在內存中分配的地址。 當編譯器遇到一個變量名,若此變量名在賦值號的左邊,則編譯器查變量表得到其在內存中的地址;若此變量名在賦值號的右邊,則編譯器先查得其在內存中地址,再把地址中的內容提取出來。(不準確,但初學者可以這么理解)】
**變量作為右值時,編譯器只是取變量的值。**
**左值標志了一個特定的位置來存儲結果。**
例: a =b+25? ; // 正確
????? ?b+25 = a?;// 錯誤
當計算機計算b+25時,它的結果只是一個映像(暫存寄存器中),不在內存區中,我們無法得到它的地址,故這個表達式不是一個左值。
同理:*cp + 1 也不可做左值。(因為其值不存儲于內存區中的一個特定的位置)
而字符串字面量雖然存儲于內存區中,但其存儲于內存的無名常量區,這個位置不是由我們設定的特定位置,而是由編譯器隨機分配的,故其也不能作為左值。
**表達式也可以是左值。**
例:int? a[30] ; ……….a[b+10]=0 ; // 正確
下標引用實際是一個操作符,故左邊實際上是個表達式,但它是一個合法的左值,因為它標志了一個特定的位置。
同理: int? a, *p ; ……..p=&a; *p=20 ; // 正確
(含操作符的就是表達式。 sizeof(int) ; 也是表達式)
【**左值意味著一個位置,而右值意味著一個值(左值就是存儲右值的位置)**】