《C Traps and Pitfalls》 筆記

來源:互聯網
上載者:User
這本書短短的100多頁,很象是一篇文章。但是指出的很多問題的確容易出現在筆試的改錯題中

--------------------------------------------------------------------
第1章 詞法陷阱

1.1 = 和 ==

1.3 詞法分析的"貪進法則"
編譯器從左至右讀入字元,每個符號包含儘可能多的字元,直到不是字元為止

如:
a---b  等價於  a-- - b

a/*b   並不是 a/(*b), 而是/*當作注釋符

1.4 整型常量

0開頭的整數為8進位

如:
014  為8進位, 不要誤看作10進位

1.5 字元和字串

單引號 - ASCII字元,實際上是一個整數
雙引號 - 指向匿名的字元數組起始字元的指標,該數組以引用的字元和額外的'\0'初始化

另外,大多數編譯器允許一個字元常量中包含多個字元。Borland C++中只取第一個字元,而VC 6.0和GCC中則後面的字元依次覆蓋前面的字元,最後得到的是最後一個字元。

如:(GCC 3.5)
char ch='yes';
cout << ch;//output: s, but with warning

練習題:
1-1。某些C編譯器允許嵌套注釋。請寫一個測試程式,要求:無論對是否允許嵌套注釋的編譯器,該程式都能正常通過編譯(無錯誤訊息),但是這兩種情況下程式執行的結果卻不同。

1-3. n-->0的含義?
(n--) > 0     貪進法則

1-4 a+++++b的含義?
((a++) ++) + b
但是值得一提的是:現代編譯器中,此式子是非法的

為什嗎?
你可以查看operator++(int)的原型: const int operator++(int)
返回的為const型,且為a的臨時拷貝。之所以返回為const型,就是為了將返回作為左值,也就防止出現這類式子
總結:a++不能做左值

---------------------------------------------------------------------------------------
第2章 文法陷阱

2.1 理解函式宣告
換個角度理解聲明語句(declaration)
聲明語句構成:類型 + 一組類似運算式的聲明符(declarator)

float f, g; //運算式f, g求值為浮點數,即f, g為浮點型
float ff(); //運算式ff()求值是一個浮點數,即ff是一個傳回型別為浮點數的函數
float *pf; //*pf求值是一個浮點數,即pf是指向浮點型的指標
更複雜的,
float *g(), (*h)();
依據上述並且()優先順序大於*,很容易知道g是一個函數,傳回型別為浮點指標(float *)
h為一個函數指標,函數傳回型別為float
(float (*h)()) 是一個類型轉換符

再看:
(*(void(*)())0)()
實質上是:
void (*fp)(); //declare a function pointer
typedef void (*fp_type)();// for simplifying, otherwise always need void(*)()
conv_0 = (fp_type)0; // converse function 0 into function conv_o
(*conv_0)();//using the conversed function

再讓我們看看<signal.h>中聲明的signal函數
void (*signal(int, void(*)(int)))(int)
首先,用typedef簡化,
typedef void (*handler_type)(int)
得,void (*signal(int, handler_type))(int)
進一步 handler_type signal(int, handler_type);

2.3 作為語句結束的分號
1)多寫了分號
if(x[i] > big);
   big = x[i]
這還是很容易辨識
再看
2)漏寫了分號if(n < 3)
    return
logrec.date = x[0];
logrec.time = x[1];
logrec.code = x[2];

看出問題來了沒?

繼續看下面一個經典的:struct logrec
{
    int date;
    int time;
    int code;
}

main()
{
    //
}

註:這個問題筆試題已經出現過

2.4 swith語句
這個估計是老生常談了

也就是case後的break有無的問題了

首先要搞清楚一件事:你可以把(case:)當作語句的標號,就好像彙編中的標號一樣。switch之後徑直跳到匹配的case處順序執行下去,以後再碰到case則無視

當然,程式設計中有意不要break的除外

2.6 “空懸”else引發的問題

看下面代碼:if(x == 0)
    if(y == 0) error();
else{
    z = x + y;
    f(&z);
}

這段代碼可能與你的本意大相徑庭,因為else與最近的if匹配

防止這類問題很簡單,只要每次使用if,else都用{}

---------------------------------------------------------
第3章 語義陷阱

3.1 指標和數組

C語言數組需要注意:
1)C語言只有一維數組,數組大小必須在編譯期確定為常數。二維數組是通過數組元素也為數組的一維數組實現
2)對於一個數組,只能做2件事情:確定數組大小,取得指向數組首元素的指標。其他相關操作,如下標運算, 都是通過指標進行

int a[3]; //數組元素為int型
struct
{
    int p[4];
    double x;
}b[17]; //數組元素為結構體
int calendar[12][31];
//12個元素的數組,每個元素又是31元素的數組
//並非:31個元素的數組,每個元素是12個元素的數組

記住:數組名是指向該數組首元素的指標
如,int a[11];  //那麼a的類型為 (int *)
int *ptr;
ptr = a;
但是ptr = &a;是非法的,這裡&a的類型為int (*)[],即指向數組的指標,大多數編譯期對這種操作,或者視為非法,或者讓其等於a

在C中,a+i和i+a的含義是一樣的,但後者不推薦

下面看多維陣列:
int calendar[12][31];
int *p;
int i;

我們很容易知道calendar[4]表示什麼含義:calendar[4]表示calendar數組的第5個元素,是12個有31個元素的數組之一。
sizeof(calendar[4])結果為31×sizeof(int)

此例中,calendar名字轉換為一個指向數組的指標,其類型為int (*)[31]
於是p=calendar; 是非法的

int (*monthp)[31];
monthp = calendar;//OK

calendar[month][day] = 0;
等價於
*(*(calendar+month)+day) = 0;
怎樣分析這個呢?
首先,calendar+month是指向12個元素之一的指標,對其解引用得到就是其元素(而元素是數組),所以*(calendar+month)是指向含31個元素的數組首元素的指標,再位移然後解引用即得到最終的int型元素

總結:
1)數組名表示指向首元素的指標,類型為元素類型的指標
2) 對數組名取地址,為指向數組的指標,類型為數組的指標

3.2 非數組的指標 - 字串

字串常量:代表一塊包含字串中所有字元加上額外一個Null 字元('\0')的記憶體區的地址。

一般字串常量用字元數組儲存的,且是唯讀。

字串操作函數:
size_t strlen(char *);//計算字串長度,直到遇到'\0'.且不包括'\0'
int strcpy(char * dest, const char *src);
int strcat(char *dest, char *src);

注意其中的輸出參數dest必須是預先分配好,且有足夠的空間能容納

3.3 數組作為函數參數

自動轉換成指標

3.6 邊界計算與不對稱邊界

這個主題值得探討

3.7 求值順序

C中只有四個運算子(&&, ||, ? :和,)規定了求值順序,對於其他運算子不要錯誤的假設求值順序,他們求值順序是未定義的。

如:
i = 0;
while(i < n)
    y[i] = x[i++];

這裡y[i]的地址在i自增前被求值是沒有任何保證的

3.9 整數溢出

C語言中存在2類整數算術運算:有符號運算與無符號運算。

兩個無符號數運算不存在溢出。

算術運算中一個是有符號數,另一個是無符號數,則有符號數會轉換為無符號數,運算時溢出也不可能發生。

兩個有符號數運算,溢出有可能發生。並且溢出發生時,溢出結果是未定義的。

那麼如何檢測是否發生溢出呢?
看下面的方式:
int a, b;
if(a + b < 0)
    //do something

這種方式是不可靠的,因為對溢出結果做的任何假設都是不可靠的

正確的方式:
#include <limits.h>
int a, b;
if((unsigned)a + (unsigned)b > INT_MAX)
    //...
或者
if(a > INT_MAX - b)
    //...

--------------------------------------------------------------------------------------
第4章 串連

4.2 聲明與定義

下面聲明語句:
int a;
如果出現在所有函數體(包括main函數)之外, 它被成為外部對象a的定義,並且其初始值預設為0

下面聲明語句:
int a = 7;
定義a的同時指定了初始值

下面聲明語句:
extern int a;
並不是a的定義,說明a是一個外部整型變數,它的儲存空間在程式的其他地方分配

典型情況:
//file1.c
int a = 7;

//file2.c
int a = 9;

這種情況一般在串連時會報錯,因為定義只能一次,聲明卻可以很多

4.3 命名衝突與static修飾符

static將變數或函數的範圍限定在一個源檔案中了

4.5 檢查外部變數

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.