C专家编程 总结
1 類型轉換
當執行算術運算時,操作數的類型如果不同,就會發生轉換,數據類型一般朝著浮點精度高、長度更長的方向轉換,整數型如果轉換為signed不會丟失信息,就轉換為signed,否則轉換為unsigned。
K&R C所采用無房戶后保留原著,就是當一個無符號類型與int或更小的整型混合使用時,結果類型是無符號類型。
?
2 C語言中const并不真正表示常量。
?
3 switch語句的缺點
1)switch語句最大的缺點是它不會在每個case標簽后面的語句執行完畢后自動終止。
2)由于break語句事實上跳出的是最近的那層循環語句或switch語句,所以break可能使switch語句提前跳出結束。
?
4 C語言中的符號重載
?
| 符號 | ? ? ? 意義 |
| static | 在函數內部,表示該變量的值在各個調用間一直保持延續性 在函數這一級,表示該函數只對本文件可見 |
| extern | 用于函數定義,表示全局可見(屬于冗余的) 用于變量,表示它在其他地方定義 |
| void | 作為函數的返回類型,表示不返回任何值 在指針聲明中,表示通用指針的類型 位于參數列表中,表示沒有參數 |
| * | 乘法運算符 用于指針,間接引用 在聲明中,表示指針 |
| & | 位的AND操作符 取地址運算符 |
| = | 賦值符 |
| == | 比較運算符 |
| <= <<= | 小于運算符 左移復合賦值運算符 |
| < | 小于運算符 #include指令的左定界符 |
| () | 在函數定義中,包圍形式參數表 調用一個函數 改變表達式的運算次序 將值轉換為其他類型(強制類型轉換) 定義帶參數的宏 包圍sizeof操作符的操作數(如果它是類型名) |
5 C語言中的優先級
| 優先級問題 | 表達式 | 人們可能誤以為的結果 | 實際結果 |
| .的優先級高于*。->操作符 用于消除這個問題 | *p.f | p所指對象的字段f (*p).f | 對p取f偏移,作為左值,然后進行 解除引用操作。*(p.f) |
| []高于* | int *ap[] | ap是個指向int數組的指針 int(*ap)[] | ap是個元素為int左值的數組int *(ap[]) |
| 函數()高于* | int *fp() | fp是個函數指針,所指函數返回int,int(*fp)() | fp是個函數,返回int*,int*(fp()) |
| ==和!=高于位運算符 | (val&mask!=0) | (val&mask)!=0 | val&(mask!=0) |
| ==和!=高于賦值符 | c=getchar()!=EOF | (c=getchar())!=EOF | c=(getchar()!=EOF) |
| 算術運算符高于移位運算符 | msb<<4+lsb | (msb<<4)+lsb | msb<<(4+lsb) |
| 逗號運算符在所有運算符中優先級最低 | i=1,2 | i=(1,2) | (i=1),2 |
結合性只用于表達式中出現兩個以上相同優先級的操作符的情況,用于消除歧義。事實上,你會注意所有優先級相同的操作符,它們的結合性也相同。
?
6 返回局部對象的指針
?
char * localized_time(char* filename) {struct tm *tm_ptr;struct stat stat_block;char buffer[120];stat(filename,&stat_block);tm_ptr=localtime(&stat_block.st_mtime);strftime(buffer,sizeof(buffer,"%a %b %e %T %Y",tm_ptr);return buffer; }問題就出在最后一行,也就是返回buffer的那行。buffer是一個自動分配內存的數組,是該函數的局部變量。當控制流離開聲明自動變量(即局部變量)的范圍時,自動變量就會失效。這就意味著即使返回一個指向局部變量的指針,當函數結束時,由于該變量已被銷毀,誰也不知道這個指針所指向的地址的內容是什么。
在C語言中,自動變量在堆棧中分配內存。當包含自動變量的函數或代碼塊退出時,它們所占用的內存便被回收,它們的內容肯定會被下一個調用的函數覆蓋。這一切取決于堆棧中先前的自動變量位于何處,活動函數聲明了什么變量,寫入了什么內容等。原先自動變量地址的內容可能被立即覆蓋,也可能稍后才被覆蓋。
?
解決這種問題有幾種方案:
1 返回一個指向字符串常量的指針。例如:
char* func() { return "Only works for simple strings";}
2 使用全局聲明的數組。例如:
char *fun() {
...
my_global_array[i] =?
...
return my_global_array;
}
這個適用于自己創建字符串的情況,也很簡單易用。它的缺點在于任何人都有可能在任何時候修改這個全局數組,而且該函數的下一次調用也會覆蓋該數組的內容。
3 使用靜態數組。例如:
char * func()
{
static char buffer[20];
...
return buffer;
}
這就可以防止任何人修改這個數組。只有擁有指向該數組的指針的函數才能修改這個靜態數組。但是,該函數的下一次調用將覆蓋這個數組的內容,所以調用者必須在此之前使用或備份數組的內容。和全局數組一樣,大型緩沖區如果閑置不用是非常浪費內存空間的。
4 顯式分配一些內存,保存返回的值。例如:
char* func(){
char *s=malloc(120);
...
return s;
}
這個方法具有靜態數組的優點,而且在每次調用時都創建一個新的緩沖區,所以該函數以后的調用不會覆蓋以前的返回值。它適用于多線程的代碼。它的缺點在于程序員必須承擔內存管理的責任。根據程序的復雜程度,這項任務可能很容易,也可能很復雜。如果內存尚在使用就釋放或者出現“內存泄露”(不再使用的內存未回收),就會產生令人難以置信的Bug。
5最好使用的解決方案就是要求調用者分配內存來保存函數的返回值。為了提高安全調用者應該同時指定緩沖區的大小。
void func(char* result,int size){
...
strncpy(result,"That't be in the data segment,Bob",size);
}
buffer=malloc(size);
func(buffer,size);
...
free(buffer);
如果程序員可以在同一代碼塊中同時進行“malloc”和“free”操作,內存管理是最為輕松的。這個解決方案就可以實現這一點。
?
7 聲明是如何形成的
不合法的聲明:
- 函數的返回值不能是一個函數,所以像foo()()這樣是非法的。
- 函數的返回值不能是一個數組,所以像foo()[]這樣是非法的。
- 數組里面不能有函數,所以像foo[]()這樣是非法的。
合法的聲明:
- 函數的返回值允許是一個函數指針,如: int(*fun())();
- 函數的返回值允許是一個指向數組的指針,如: int(*foo())[];
- 數組里面允許有函數指針,如int(*foo[])()
- 數組里面允許有其他數組,所以你經??吹絠nt foo[][]
8 typedef的使用
typedef與define的區別:
首先,可以用其他類型說明符對宏類型名進行擴展,但對typedef所定義的類型名不能這樣做。如下所示:
#define peach int?
unsigned peach i; //沒問題
typedef int banana;
unsigned banana i; ?//錯誤! 非法
其次,在連續幾個變量的聲明中,用typedef定義的類型能夠保證聲明中所有的變量均為同一種類型,而用#define定義的類型則無法保證。如下所示:
#define int_ptr int *;
int_ptr chalk,cheese;
經過宏擴展,第二行變為:
int * chalk,cheese;
這使得chalk和cheese成為不同的類型:chalk是一個指向int的指針,而cheese則是一個int。相反,下面的代碼中:
typedef char* char_ptr;
char_ptr Bentley,Rolls_Royce;
Bentley和Rolls_Royce的類型依然相同。雖然前面的類型名變了,但它們的類型相同,都是指向char的指針。
?
//下面兩個聲明具有相似的形式 typedef struct fruit{ int weight, price_per_lb; } fruit; //語句1 struct veg{ int weight,price_per_lb; } veg; //語句2但它們代表的意思卻完全不一樣,語句1聲明了結構表情“fruit”和由“typedef聲明的結構類型”fruit“,其實際效果如下:
struct fruit mandarin; ? //使用結構標簽fruit
fruit mandarin; ? //使用結構類型fruit
語句2聲明了結構標簽veg和變量veg,只有結構標簽能夠在以后的聲明中使用,如
struct veg potato;
如果試圖使用veg ?cabbage這樣的聲明,將是一個錯誤。這有點類似下面的寫法:
int i;
i j;
?
9 聲明和定義的區別
記住,C語言的對象必須有且只有一個定義,但它可以有多個extern聲明。
定義是一種特殊的聲明,它創建了一個對象;聲明簡單地說明了在其他地方創建的對象的名字,它允許你使用這個名字。
?
定義 ? 只能出現在一個地方 ? ? ? ? 確定對象的類型并分配內存,用于創建的對象。例如,int my_array[100];
聲明 ? 可以多次出現 ?? ? 描述對象的類型,用于指代其他地方定義的對象(例如在其他文件里)例:extern int my_array[];
?
區分定義和聲明
只要記住下面的內容即可分清定義和聲明:
聲明相當于普通的聲明:它所聲明的并非自身,而是描述其他地方的創建的對象。
定義相當于特殊的聲明:它為對象分配內存。
?
extern對象聲明告訴編譯器對象的類型和名字,對象的內存分配則在別處進行。由于并未在聲明中為數組分配內存,所以并不需要提供關于數組長度的信息。對于多維數組,需要提供除最左邊一維之外其他維的長度——這就給編譯器足夠的信息產生相應的代碼。
?
10 數組和指針的訪問
C語言引入了”可修改的左值“這個術語,它表示左值允許出現在賦值語句的左邊,這個奇怪的術語是為與數組名區分,數組名也用于確定對象在內存中的位置,也是左值,但它不能作為賦值的對象。因此,數組名是個左值但不是可修改的左值。
?
左值:出現在賦值符左邊的符號有時被稱為左值。
右值:出現在賦值符右邊的符號有時則被稱為右值。
編譯器為每個變量分配一個地址(左值)。這個地址在編譯時可知,而且該變量在運行時一直保存于這個地址。相反,存儲于變量中的值(它的右值)只有在運行時才可知。如果需要用到變量中存儲的值,編譯器就發出指令從指定的地址讀入變量值并將它存于寄存器中。
這里的關鍵之處在于每個符號的地址在編譯時可知。所以,如果編譯器需要一個地址(可能還需要加上偏移量)來執行某種操作,它就可以直接進行操作,并不需要增加指令首先取得具體的地址。相反,對于指針,必須首先在運行時取得它的當前值,然后才能對它進行解除引用的操作(作為以后進行查找的步驟之一)。
相反,如果聲明extern char *p,它將告訴編譯器p是一個指針,它指向的對象是一個字符。為了取得這個字符,必須得到地址p的內容,把它作為字符的地址并從這個地址中取得這個字符。指針的訪問要靈活很多,但需要增加一個額外的提取,如圖所示:
11 數組和指針的其他區別
比較數組和指針的另外一個方法就是對比兩者的特點。
數組和指針的區別
| 指針 | ? ? ? ? ?數組 |
| 保存數據的地址 | 保存數據 ? ? ? ? ? ? ?? |
| 間接訪問數據,首先取得指針的內容,把它作為地址,然后從這個地址提取數據。 如果指針有一個下標[I],就把指針的內容加上I作為地址,從中提取數據 | ?直接訪問,a[I]只是簡單地從a+I為地址取得數據 |
| 通常用于動態數據結構 | 通常用于存儲固定數目且數據類型相同的元素 |
| 相關的函數為malloc() free()? | 隱式分配和刪除 |
| 通常指向匿名數據 | 自身即為數據名 |
定義指針時,編譯器并不為指針所指向的對象分配空間,它只是分配指針本身的空間,除非在定義時賦給指針一個字符串常量進行初始化。例如,下面的定義創建了一個字符串常量(為其分配了內存):
char *p="breadfruit";
注意只有對字符串常量才是如此。不能指望為浮點數之類的常量分配空間,如:
float *pip=3.141; ?//錯誤
初始化指針時所創建的字符串常量被定義為只讀的。如果試圖通過指針修改這個字符串的值,程序就會出現未定義的行為。
?
數組也可以用字符串常量進行初始化:
char a[]="gooseberry";
與指針相反,由字符串常量初始化的數組是可以修改的。
?
?12 UNIX中的堆棧段和MS-DOS中的堆棧段
在UNIX中,當進程需要更多空間時,堆棧會自動生長。程序員可以想象堆棧是無限大的。這是UNIX勝過其他操作系統如MS-DOS的許多優勢之一。在UNIX的實現中一般使用某種形式的虛擬內存。當試圖訪問當前系統分配給堆棧的空間之外時,它將產生一個硬件中斷,稱為頁錯誤。處理頁錯誤的方法有好幾種,取決于對頁的引用是否有效。
在正常情況下,內核通過向違規的進程發送合適的信號(可能是段錯誤)來處理對地址的引用。在堆棧頂部的下端有一個稱為red zone的小型區域,如果對這個區域進行引用,不會產生失敗。相反,操作系統通過一個好的內存塊來增加堆棧段的大小。
在DOS中,在建立可執行文件時,堆棧的大小必須同時確定,而且它不能在運行時增加,如果你猜測錯誤,需要的堆棧空間大于所分配的空間,那么你和程序都會迷失。如果設置了檢查選項,就會收到STACK OVERFLOW(堆棧溢出)消息。
?
13 返回局部指針的兩種不同情況。
?先看下面的程序:
#include<iostream> using namespace std;char* test2() {char p[] = "hello world";return p; } char* test3(){char *p = "hello world";return p; } int main() {test2();test3(); }其中,test2中p是一個數組,分配在棧空間,在??臻g存放字符串。當是數組p,則函數會將字符串常量的字符逐個復制到p數組里面,返回p則是返回數組p,但是調用函數結束后p被銷毀,里面的元素不存在了。
但是在test3中p是一個指針,則p指向存放字符串常量的地址,返回p則是返回字符串常量地址值,調用函數結束字符串常量不會消失(是常量)。所以返回常量的地址不會出錯。
雖然都是返回局部變量的指針,但是test2結果會有問題,但是test3不會。
總結
- 上一篇: 简单网站优化
- 下一篇: Python之禅 by Tim Pete