《C Traps and Pitfalls》 笔记
生活随笔
收集整理的這篇文章主要介紹了
《C Traps and Pitfalls》 笔记
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
這本書短短的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 非數組的指針 - 字符串
字符串常量:代表一塊包含字符串中所有字符加上額外一個空字符('\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 檢查外部變量
--------------------------------------------------------------------
第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 非數組的指針 - 字符串
字符串常量:代表一塊包含字符串中所有字符加上額外一個空字符('\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 檢查外部變量
轉載于:https://www.cnblogs.com/chio/archive/2007/10/26/938064.html
總結
以上是生活随笔為你收集整理的《C Traps and Pitfalls》 笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑C盘清理
- 下一篇: tushare+画图