1.關(guān)于+=以及-=
這是兩個運算符,但你否有過這種經(jīng)歷:
[cpp]view plaincopy
inttemp;
chari
for(i=0;i
{
...
temp=+2;//這里本意是每次循環(huán),temp都自增2,但是卻將'+='寫成了'=+',按照這種寫法,每次循環(huán)都為temp賦值正數(shù)2,與本意相差甚遠(yuǎn)
}
2. 關(guān)于意想不到的死循環(huán)
[cpp]view plaincopy
unsignedchari;
for(i=0;i<256;i++)??
{
//something
}
當(dāng)我們用上述代碼想實現(xiàn)一個小循環(huán)時,結(jié)果卻事與愿違,這其實是死循環(huán)的另一種寫法,因為無符號變量i最大只有255,要命的是,編譯器并不會指出這個錯誤。
與之相類似的代碼是:
[cpp]view plaincopy
unsignedchari;
for(i=10;i>=0;i--)
{
//something
}
這也是一個死循環(huán),你看出什么原因了嗎?無論i如何減,i都是大于等于0的。
這就告訴我們對于每個變量類型的取值范圍要由清醒的認(rèn)識。值得注意的是相同的變量類型對于不同的CPU構(gòu)架和不同的編譯器會有不同的結(jié)果。比如int類型在大多數(shù)16位CPU構(gòu)架中占用兩個字節(jié),但在32位CPU中卻往往占用4個字節(jié);char類型在絕大多數(shù)編譯器中都是有符號數(shù),但在keilMDK中卻是無符號數(shù),若是要在keilMDK下定義有符號char類型變量,必須用signed顯式聲明。我曾讀過一本書,其中有一句話:“signed關(guān)鍵字也是很寬宏大量,你也可以完全當(dāng)它不存在,在缺省狀態(tài)下,編譯器默認(rèn)數(shù)據(jù)位signed類型”,這句話便是有異議的,我們應(yīng)該對自己所用的CPU構(gòu)架以及編譯器熟練掌握。
3. 關(guān)于'='和'=='
[cpp]view plaincopy
if(Value=0x01)
{
//something
}
當(dāng)我們判斷一個變量是否等于0x01時,你是否也寫過類似上面的代碼?C語言的創(chuàng)造者認(rèn)為賦值運算符"="出現(xiàn)的概率要遠(yuǎn)遠(yuǎn)大于等于運算符"==",因此,我們正常邏輯中的"等于"符號(=)在C語言中成了賦值運算符,而C語言的"等于"運算符卻被兩個等于號(==)所代替。我之所以對這個事件耿耿于懷是因為我在大二的時候參加的C++二級上機考試,當(dāng)我感覺很輕松的做完最后一道題后,卻發(fā)現(xiàn)運算的結(jié)果卻與邏輯相悖,經(jīng)過調(diào)試發(fā)現(xiàn),有一個條件一直為真,我檢查了很多遍才發(fā)現(xiàn)出問題的邏輯將等于運算符寫成了賦值運算符。在if語句中給變量賦一個非零值,也難怪這個邏輯總是為真。
編譯器同樣不對這個問題做出指導(dǎo)性建議,值得一提的是,如果你在Keil的if語句中使用了賦值運算符,編譯器會給出警告。
避免這個問題的一個很好的辦法是使用良好編程習(xí)慣,比如上面的代碼可寫為:
[cpp]view plaincopy
if(0x01==Value)
{
//something
}
將常量值放到變量的前面,即使將等于運算符寫成賦值運算符,編譯器也能產(chǎn)生一個語法錯誤,因為將一個變量賦值給一個常量是非法的。
4.error:#7:unrecognizedtoken
我在剛使用C語言以及Keil編譯器時,對于這個編譯器錯誤,有很深的印象。出現(xiàn)這個編譯錯誤的典型代表是在敲代碼的時候輸入了中文標(biāo)點??!
真是讓人感慨萬分的錯誤!我們這些與硬件打交道的程序員,為模數(shù)電生,為PCB死,為Debug奮斗一輩子,吃需求的虧,上大小寫的當(dāng),最后死在標(biāo)點上??!
5.關(guān)于字母'O'和數(shù)字'0',以及字母'l'和數(shù)字'1',在嵌入式編程中很容易和寄存器打交道,一個CPU如果有兩個相同模塊時,這些模塊寄存器,往往使用數(shù)字0和數(shù)字1來區(qū)分模塊0和模塊1,比如,NXP的ARM7串口模塊的兩個接收緩沖寄存器分別為:U0RBR和U1RBR,要命的是在鍵盤上字母O和數(shù)字0相距的還那么近,你是否也有將上述寄存器寫成UORBR和UlRBR的經(jīng)歷,我是曾經(jīng)在這方面糾結(jié)過一次,好在編譯器能指出這個未定義的字符串。
6.sizeof()
不知道有多少人和我曾經(jīng)一樣,將這個關(guān)鍵字認(rèn)為是一個庫函數(shù)。
[cpp]view plaincopy
inti,j;
j=sizeof(i);//對于這一句,當(dāng)初壓根沒把它往關(guān)鍵字上想,這家伙偽裝的實在夠好。
既然提到它,不如多說一下,sizeof在計算變量所占空間大小時,括號可以省略,而計算類型大小時,不能省略。什么意思呢?還是上面的變量聲明,可以寫成j=sizeof(i)也可以寫成j=sizeofi,因為這是計算變量所占空間大小;可以寫成j=sizeof(int),但不可以寫成j=sizeofint,因為這是計算數(shù)據(jù)類型大小。
總體來說,關(guān)鍵字sizeof的具有一定的變態(tài)基礎(chǔ)的,在我還是小白的時候,曾經(jīng)為下面的一道題傷過腦袋:
下面代碼里,假設(shè)在32位系統(tǒng)下,個sizeof計算的結(jié)果分別是多少?
int*p=NULL;
sizeof(p)的值是:
sizeof(*p)的值是:
inta[100]
sizeof(a)的值是:
sizeof(a[100])的值是:
sizeof(&a)的值是:
sizeof(&a[0])的值是:
intb[100];
voidfun(intb[100])
{
sizeof(b);
}
sizeof(b)的值為:
7 關(guān)于數(shù)組越界
[cpp]view plaincopy
inta[30];
for(i=30;i>0;i--)
{
a[i]=something;
}
這是個典型的數(shù)組越界例子,最近我同事的一個程序中便出現(xiàn)了。不知道有多少同學(xué)遇到或?qū)⒁龅綌?shù)組越界問題,即便你定義了30個數(shù)組a[30],你也不可以為a[30]賦值,因為下標(biāo)為30的元素已經(jīng)越界了。所以說數(shù)組下標(biāo)定義的很奇特,它是從0開始的。但當(dāng)我們還是新手的時候,最容易忽視這一點。幸好現(xiàn)在的有些編譯器會對這個越界產(chǎn)生警告信息。
8. 關(guān)于宏
[cpp]view plaincopy
#defineMAX_TAST4;
這個錯誤編譯器會指出的,即便這樣,相信很多同學(xué)在最初的時候也不會在第一時間發(fā)現(xiàn)這句代碼的最后多了一個分號。這個分號會導(dǎo)致一些編譯器報錯,因為宏定義的結(jié)尾并不需要分號。
同樣與define有關(guān)的是這樣一句:#define"config.h",我便吃過類似暗虧,在編譯器的提示之下,看了幾遍才發(fā)現(xiàn)頭文件包含應(yīng)該是#include"config.h"。
既然提到#define,還是說說它需要注意的幾個點,也是經(jīng)常在資料上被提及的。
a.使用#define時,括號一定要足夠。比如定義一個宏函數(shù),求x的平方:
[cpp]view plaincopy
#defineSQR(x)x*x..............1
或者這樣寫:
[cpp]view plaincopy
#defineSQR(x)(x)*(x)...............2
上面兩種都是有風(fēng)險的,對于第一種定義,SQR(10+1)就會得到和我們的設(shè)想不一致的結(jié)果;第二種SQR(5*3)*SQR(5*3)也會得到和我們設(shè)想不一致的結(jié)果,因此更安全的定義方法是:
[cpp]view plaincopy
#defineSQR(x)((x)*(x))
b.使用#define的時候,,意空格的使用。比如下面的例子:
[cpp]view plaincopy
#defineSQR(x)((x)*(x))
這已經(jīng)不是SQR(x)函數(shù)了,編譯器會把認(rèn)為定義了一個宏SQR,代表(x)((x)*(x)),因為SQR與(x)之間有空格。這點需要注意。
c.使用'#'在字符串中包含宏參數(shù)。比如下面的例子:
[cpp]view plaincopy
#defineSQR(x)printf("Thesquareofxis%d.\n",((x)*(x))")
[cpp]view plaincopy
如果這樣使用宏:
[cpp]view plaincopy
SQR(8)
則輸出為:
Thesquareofxis64.
這個時候引號中的x被當(dāng)做字符串來處理了,而不是一個可以被宏參數(shù)替換的符號.如果你想在字符中的x也被宏參數(shù)替換,可以這么來定義宏:
[cpp]view plaincopy
#defineSQR(x)printf("Thesquareof"#x"is%d.\n",((x)*(x))")
這樣得到的結(jié)果為:
Thesquareof8is64.
上面的這些例子,恐怕是網(wǎng)上隨處可見的,但真的會這么用卻有待考證。下面給出一個我自己遇到的不加括號產(chǎn)生錯誤的例子。在嵌入式編程中,遇到讀取IO端口某一位的電平狀態(tài)的場合是在平常不過的了,比如在NXP的ARM7中,讀取端口P0.11的電平狀態(tài)并判斷是否為高電平,代碼如下:
[cpp]view plaincopy
#defineREADSDAIO0PIN&(1<<11)????????????//定義宏,讀IO口p0.11的端口狀態(tài),但并未使用足夠多的括號??
//判斷p0.11端口是否為高電平,使用下述語句就是錯誤的:
if(READSDA==(1<<11))??
{
//是高電平,處理高電平的問題
}
編譯器在編譯后將宏帶入,原if語句變?yōu)椋?/p>
[cpp]view plaincopy
if(IO0PIN&(1<<11)?==(1<<11))??
{
//是高電平,處理高電平的問題
}
這樣的話,運算符'=='的優(yōu)先級是大于'&'的,從而IO0PIN&(1<<11)?==(1<<11))語句等效為IO0PIN&0x00000001,相當(dāng)于判斷P0.1是否為高電平,與原意相差甚遠(yuǎn).
9.數(shù)組和指針
在32位系統(tǒng)下,
定義一個數(shù)組:
[cpp]view plaincopy
inta[10]={1,2,3,4,5,6,7,8,9,0};
定義一個指針:
[cpp]view plaincopy
int*p;
那么,a、a[0]、&a、&a[0]各表示什么意思?
那么,sizeof(a)、sizeof(a[0])、sizeof(&a)、sizeof(&a[0])的值各是什么?
如果,對指針p賦值:
p=a;
并且通過編譯器仿真,得知現(xiàn)在p等于a等于0x00000200,
那么,a+1=?
&a+1=?
p+1=?
p[2]=?
*(p+2)=?
*(a+2)=?
再如果
[cpp]view plaincopy
int*ptr=(int*)(&a+1);
那,*(ptr-1)=?
世上最曖昧、最糾纏不清的,莫過于數(shù)組名和指針。這一方面源于大學(xué)的教材并沒有重視這一塊,也源于教學(xué)時硬生生的將C語言和硬件分開。一方面,教材和教這一門的老師在開始時便向我們灌輸了“數(shù)組名和指針很像,可以等同”的思想;另一方面,在學(xué)C語言的時候,并沒有系統(tǒng)的學(xué)過計算機硬件(尋址、存儲、匯編),C語言是一個很接近硬件的高級語言,如果沒有處理器(包括單片機等微處理器)的基礎(chǔ)知識,會導(dǎo)致非常多的同學(xué)怎么都理解不透C語言的指針和數(shù)組。
當(dāng)我們定義一個數(shù)組inta[10]時,編譯器會分配一塊內(nèi)存,這塊內(nèi)存的名字命名為a,這個a只是一個名字,只是方便編譯器和編程者使用,編譯器并不為這個名字分配空間來存儲它。我們可以用a[0]、a[1]來訪問數(shù)組內(nèi)的元素。a作為右值(位于等號的右邊)時,表示的是數(shù)組第一個元素的地址(意義與&a[0]一樣,但&a[0]是一個指針變量,編譯器會為他分配空間,a卻不一樣,編譯器并不為它分配什么空間),而并非數(shù)組首地址,&a才表示數(shù)組的首地址。
所以,第一個問題,a是這個數(shù)組所在的內(nèi)存的名字,當(dāng)它為右值時,表示數(shù)組首元素的地址,a[0]是數(shù)組的第一個元素,其值等于1,&a是整個數(shù)組的首地址,它是一個指針;&a[0]是數(shù)組首元素的地址,它的值和a做右值時一樣,但意義不同,因為&a[0]是一個指針,編譯器要為它分配存儲空間,但a卻不會被分配存儲空間,a也不是指針型變量。
明白了上面那些,關(guān)于sizeof的計算也就不會困難了:
sizeof(a)=4*10=40,因為a代表的是整塊數(shù)組內(nèi)存;
sizeof(a[0])=4,這相當(dāng)于計算int的大小,在32位系統(tǒng)下,int占4個字節(jié)。
sizeof(&a)和sizeof(&a[0])都是計算指針變量的大小,在32位系統(tǒng)下,指針變量占4個字節(jié)。
對于最后一個問題,涉及到指針的加減。
指針的加減中有一個重要的原則就是它的加減跟我們普通意義上的加減不是一個概念,它是按指針?biāo)割愋偷膬?nèi)存大小來進行加減的。當(dāng)我還是一個新手的時候,對于p++、p+1這類指針運算的含義超出了我的意料之外,在上例中,若是p=0x00000200,那么p++運算之后的p值應(yīng)該為0x00000204。有多少同學(xué),曾經(jīng)把它計算成0x00000201!
數(shù)組名a在做計算的時候表示數(shù)組首元素的地址,這時候a等于0x00000200,所以a+1等于0x00000200+4=0x00000204,因為一個int型在32位系統(tǒng)下占用4個字節(jié)。&a是整個數(shù)組的首地址,&a+1=0x00000200+4*10=0x00000228。
其它的也都比較好理解,p+1=0x00000200+04=0x00000204、p[2]=3、*(p+2)=3、*(ptr-1)=0.
10.3/(-3)=?
3%(-2)=?
(-3)%2=?
拋開它是否有實際的意義,這個看似簡單的語句,不知道有多少同學(xué)不確定結(jié)果到底是什么。
其實大多數(shù)的編譯器遵循這樣一個規(guī)定:余數(shù)與被除數(shù)的正負(fù)號相同,被除數(shù)等于結(jié)果乘以除數(shù)加上余數(shù)。所以,以上的三個結(jié)果分別為-1、1、-1。
11.指針數(shù)組與數(shù)組指針
有一段時間,我怎么都不能區(qū)分指針數(shù)組和數(shù)組指針,就像下面的聲明:
[cpp]view plaincopy
int*p1[10];
int(*p2)[10];
首先,要來解釋一下什么是指針數(shù)組,什么是數(shù)組指針:指針數(shù)組首先是一個數(shù)組,它的成員都是指針型變量;數(shù)組指針首先是一個指針,這個指針指向一個數(shù)組(它的值和數(shù)組名表示的值一樣,只是數(shù)組指針是一個變量,編譯器要為它分配存儲空間,但數(shù)組名類似于一個常亮,是編譯器在編譯階段就確定好的一個值,編譯器不會為它分配存儲空間)。
對于p1,由于中括號的優(yōu)先級(關(guān)于優(yōu)先級,后面會專門提起)是大于*的,所以p1首先與'[]'相結(jié)合,構(gòu)成一個數(shù)組,在這個數(shù)組之前又有一個'*'運算符,說明這是定義一個指針數(shù)組(int*a:定義一個指針a,這里可以將p1[10]替換成a,就不難理解了),數(shù)組的元素都是指向int型的指針。
對于p2,'()'雖然與'[]'為同一優(yōu)先級,但卻是表達(dá)式結(jié)合方向從左到右結(jié)合的,所以編譯器會先處理(*p2),這是典型的定義一個指針,只不過這個指針指向一個包含10個int型數(shù)據(jù)的內(nèi)存塊。為了加強理解,這里給出兩個相同原理的函數(shù)聲明:
void*p1(void);----------------------聲明1,定義一個返回值是void類型指針的函數(shù)p1
void(*p2)(void);-----------------------聲明2,定義一個函數(shù)指針,該函數(shù)不返回任何值
有了上面的鋪墊,現(xiàn)在定義一個高級C語言編程技巧中常用的函數(shù)指針數(shù)組應(yīng)該很容易了吧!首先這是一個數(shù)組,數(shù)組的元素是指向一個函數(shù)的指針,以定義一個參數(shù)為空,返回值為int類型的函數(shù)指針數(shù)組p1為例:
[cpp]view plaincopy
int(*p1[5])(void);
分析如下:
定義一個返回值為int類型的函數(shù)指針p1應(yīng)該是:
[cpp]view plaincopy
int(*p1)(void);
那么將這類指針放到一個數(shù)組中不正是我們需要的定義嗎,套用指針數(shù)組的定義方法,返回值為int類型函數(shù)指針數(shù)組定義為:int(*p1[5])(void);
12.運算符的優(yōu)先級
C語言有32個關(guān)鍵字卻有44個運算符!運算符之間有固定的優(yōu)先級,雖然它們可以分成15類優(yōu)先級,但如果讓一個程序員完全記住這些運算符之間的優(yōu)先級關(guān)系,怕是老手也是不容易的吧。如果你的程序只是語法錯誤,這類錯誤是最容易解決的,編譯器就會幫你檢測出來;如果是你自己的邏輯出現(xiàn)錯誤,那么根據(jù)運行結(jié)果仔細(xì)檢查一下代碼可能也不難發(fā)現(xiàn);但若是你的邏輯正確卻記錯了運算符的優(yōu)先級關(guān)系,導(dǎo)致程序運行結(jié)果和你設(shè)想的不同,這種錯誤就很難查出了,因為在你的潛意識里,已經(jīng)把這種錯誤點當(dāng)成理所當(dāng)然不用關(guān)注的。
請看下面一句代碼代表什么意思:
[cpp]view plaincopy
*string++;
由于*和++但是單目運算符,優(yōu)先級相同,但結(jié)合方向卻是自右向左,那么*string++應(yīng)該就是*(string++),取出當(dāng)前字符后將指針后移。不知道有沒有人把它認(rèn)為是(*string)++,即取指針string所指向的對象,然后將該對象增1.
我曾經(jīng)在代碼中不止一次的出現(xiàn)過因為優(yōu)先級問題而導(dǎo)致的程序邏輯錯誤,那個時候我并沒有完整的記過優(yōu)先級,二十使用了一種“偷巧”的方法:只是簡單記住前幾級優(yōu)先級,其它自己沒把握的一律使用括號。這種方法我現(xiàn)在是不推薦的,一是因為大量的括號影響代碼閱讀和程序的簡潔,二是總有時候我們稍微一松懈,就忘記了加括號,而后一種情況,正是很多人可能會遇到的。比如下面一句代碼,無符號8位變量ucTimeValue中存放十進制編碼的數(shù)據(jù)23,我想將十進制編碼轉(zhuǎn)成16進制編碼,代碼為:
[cpp]view plaincopy
temp8=(ucTimeValue>>4)*10+ucTimeValue&0x0F;//十進制轉(zhuǎn)化為16進制,但忽略了運算符'+'的優(yōu)先級是大于運算符'&'的
像這類代碼編譯肯定可以通過,但運行的結(jié)果卻出乎我的意料,而且由于我先入為主的錯誤思想,要在一大段代碼中發(fā)現(xiàn)這個錯誤著實要花費一番功夫。
再例如,如果我想判斷一個寄存器的某一位是否為零,假如是判斷寄存器IO0SET的bit17是否為零,但代碼卻寫成了這樣:
[cpp]view plaincopy
if(IO0SET&(1<<17)==0)???
這樣寫其實是得不到正確的結(jié)果的,因為我忽略了"=="的優(yōu)先級是大于"&"的.按照上面的代碼分析:因為"=="的優(yōu)先級大于"&",所以程序先判斷(1<<17)是否等于0?發(fā)現(xiàn)這是不相等的,所以(1<<17)==0表達(dá)式的值為假,即為0,0與(&)上任何一個數(shù)都是0,所以IO0SET&(1<<17))==0整個表達(dá)式的值永遠(yuǎn)為0,這與原意相差甚遠(yuǎn)。?
按照原意,應(yīng)該這樣寫:
[cpp]view plaincopy
if((IO0SET&(1<<17)))==0)??
其實,運算符的優(yōu)先級是有一定的規(guī)律可循的,下面給出優(yōu)先級口訣(注:口訣來源于互聯(lián)網(wǎng))
優(yōu)先級口訣
括號成員第一; 括號運算符[]()成員運算符.->
全體單目第二; 所有的單目運算符比如++--+(正)-(負(fù))指針運算*&
乘除余三,加減四; 這個"余"是指取余運算即%
移位五,關(guān)系六; 移位運算符:<>>,關(guān)系:>>=<=?等
等于(與)不等排第七; 即==!=
位與異或和位或; 這幾個都是位運算:位與(&)異或(^)位或(|)
"三分天下"八九十;
邏輯或跟與; 邏輯運算符:||和&&
十二和十一; 注意順序:優(yōu)先級(||)底于優(yōu)先級(&&)
條件高于賦值, 三目運算符優(yōu)先級排到13位只比賦值運算符和","高
逗號運算級最低! 逗號運算符優(yōu)先級最低
-
C語言
+關(guān)注
關(guān)注
180文章
7604瀏覽量
136864 -
編譯器
+關(guān)注
關(guān)注
1文章
1634瀏覽量
49134
原文標(biāo)題:曾讓我哭笑不得抓狂的C語言
文章出處:【微信號:wujianying_danpianji,微信公眾號:單片機精講吳鑒鷹】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論