?
參閱相關系列文章,
單片機C語言知識點全攻略(一)
第二部分知識點:
第五課 C51變量
第六課 C51運算符和表達式
第七課 運算符和表達式(關系運算符)
第八課 運算符和表達式(位運算符)
第九課 C51運算符和表達式(指針和地址運算符)
第五課、C51變量
上課所提到變量就是一種在程序執行過程中其值能不斷變化的量。要在程序中使用變量必須先用標識符作為變量名,并指出所用的數據類型和存儲模式,這樣編譯系統才能為變量分配相應的存儲空間。定義一個變量的格式如下:
?。鄞鎯ΨN類] 數據類型?。鄞鎯ζ黝愋停荨∽兞棵?/p>
在定義格式中除了數據類型和變量名表是必要的,其它都是可選項。存儲種類有四種:自動(auto),外部(extern),靜態(static)和寄存器(register),缺省類型為自動(auto)。這些存儲種類的具體含義和使用方法,將在第七課《變量的存儲》中進一步進行學習。
而這里的數據類型則是和我們在第四課中學習到的名種數據類型的定義是一樣的。說明了一個變量的數據類型后,還可選擇說明該變量的存儲器類型。存儲器類型的說明就是指定該變量在單片機c語言硬件系統中所使用的存儲區域,并在編譯時準確的定位。表6-1中是KEIL uVision2所能認別的存儲器類型。注意的是在AT89c51芯片中RAM只有低128位,位于80H到FFH的高128位則在52芯片中才有用,并和特殊寄存器地址重疊。特殊寄存器(SFR)的地址表請看附錄二 AT89c51特殊功能寄存器列表
?
?
如果省略存儲器類型,系統則會按編譯模式SMALL,COMPACT或LARGE所規定的默認存儲器類型去指定變量的存儲區域。無論什么存儲模式都能聲明變量在任何的8051存儲區范圍,然而把最常用的命令如循環計數器和隊列索引放在內部數據區能顯著的提高系統性能。還有要指出的就是變量的存儲種類與存儲器類型是完全無關的。
數據存儲模式
存儲模式決定了沒有明確指定存儲類型的變量,函數參數等的缺省存儲區域,共三種:
1. 1. Small模式
所有缺省變量參數均裝入內部RAM,優點是訪問速度快,缺點是空間有限,只適用于小程序。
2. 2. Compact模式
所有缺省變量均位于外部RAM區的一頁(256Bytes),具體哪一頁可由P2口指定,在STARTUP.A51文件中說明,也可用pdata指定,優點是空間較Small為寬裕速度較Small慢,較large要快,是一種中間狀態。
3. 3. large模式
所有缺省變量可放在多達64KB的外部RAM區,優點是空間大,可存變量多,缺點是速度較慢。
提示:存儲模式在單片機c語言編譯器選項中選擇。
之前提到簡單提到sfr,sfr16,sbit定義變量的方法,下面我們再來仔細看看。
sfr和sfr16能直接對51單片機的特殊寄存器進行定義,定義方法如下:
sfr 特殊功能寄存器名= 特殊功能寄存器地址常數;
sfr16 特殊功能寄存器名= 特殊功能寄存器地址常數;
我們能這樣定義AT89c51的P1口
sfr P1 = 0x90; //定義P1 I/O口,其地址90H
sfr關鍵定后面是一個要定義的名字,可任意選取,但要符合標識符的命名規則,名字最好有一定的含義如P1口能用P1為名,這樣程序會變的好讀好多。等號后面必須是常數,不允許有帶運算符的表達式,而且該常數必須在特殊功能寄存器的地址范圍之內(80H-FFH),具體可查看附錄中的相關表。sfr是定義8位的特殊功能寄存器而sfr16則是用來定義16位特殊功能寄存器,如8052的T2定時器,能定義為:
sfr16 T2 = 0xCC; //這里定義8052定時器2,地址為T2L=CCH,T2H=CDH
用sfr16定義16位特殊功能寄存器時,等號后面是它的低位地址,高位地址一定要位于物理低位地址之上。注意的是不能用于定時器0和1的定義。
sbit可定義可位尋址對象。如訪問特殊功能寄存器中的某位。其實這樣應用是經常要用的如要訪問P1口中的第2個引腳P1.1。我們能照以下的方法去定義:
(1)sbit 位變量名=位地址
sbit P1_1 = Ox91;
這樣是把位的絕對地址賦給位變量。同sfr一樣sbit的位地址必須位于80H-FFH之間。
?。?)Sbit 位變量名=特殊功能寄存器名^位位置
sft P1 = 0x90;
sbit P1_1 = P1 ^ 1; //先定義一個特殊功能寄存器名再指定位變量名所在的位置
當可尋址位位于特殊功能寄存器中時可采用這種方法
?。?)sbit 位變量名=字節地址^位位置
sbit P1_1 = 0x90 ^ 1;
這種方法其實和2是一樣的,只是把特殊功能寄存器的位址直接用常數表示。
在單片機c語言存儲器類型中供給有一個bdata的存儲器類型,這個是指可位尋址的數據存儲器,位于單片機的可位尋址區中,能將要求可位錄址的數據定義為bdata,如:
unsigned char bdata ib; //在可位錄址區定義ucsigned char類型的變量ib
int bdata ab[2]; //在可位尋址區定義數組ab[2],這些也稱為可尋址位對象
sbit ib7=ib^7 //用關鍵字sbit定義位變量來獨立訪問可尋址位對象的其中一位
sbit ab12=ab[1]^12;
操作符“^”后面的位位置的最大值取決于指定的基址類型,char0-7,int0-15,long0-31。
下面我們用上一課的電路來實踐一下這一課的知識。同樣是做一下簡單的跑馬燈實驗,項目名為RunLED2。程序如下:
sfr P1 = 0x90; //這里沒有使用預定義文件,
sbit P1_0 = P1 ^ 0; //而是自己定義特殊寄存器
sbit P1_7 = 0x90 ^ 7; //之前我們使用的預定義文件其實就是這個作用
sbit P1_1 = 0x91; //這里分別定義P1端口和P10,P11,P17引腳
void main(void)
{
unsigned int a;
unsigned char b;
do{
for (a=0;a《50000;a++)
P1_0 = 0; //點亮P1_0
for (a=0;a《50000;a++)
P1_7 = 0; //點亮P1_7
for (b=0;b《255;b++)
{
for (a=0;a《10000;a++)
P1 = b; //用b的值來做跑馬燈的花樣
}
P1 = 255; //熄滅P1上的LED
for (b=0;b《255;b++)
{
for (a=0;a《10000;a++) //P1_1閃爍
P1_1 = 0;
for (a=0;a《10000;a++)
P1_1 = 1;
}
}while(1);
}
。 Keil c51指針變量
單片機c語言支持一般指針(Generic Pointer)和存儲器指針(Memory_Specific Pointer)。
1. 1. 一般指針
一般指針的聲明和使用均與標準C相同,不過同時還能說明指針的存儲類型,例如:
long * state;為一個指向long型整數的指針,而state本身則依存儲模式存放。
char * xdata ptr;ptr為一個指向char數據的指針,而ptr本身放于外部RAM區,以上的long,char等指針指向的數據可存放于任何存儲器中。
一般指針本身用3個字節存放,分別為存儲器類型,高位偏移,低位偏移量。
2. 2. 存儲器指針
基于存儲器的指針說明時即指定了存貯類型,例如:
char data * str;str指向data區中char型數據
int xdata * pow; pow指向外部RAM的int型整數。
這種指針存放時,只需一個字節或2個字節就夠了,因為只需存放偏移量。
3. 3. 指針轉換
即指針在上兩種類型之間轉化:
l 當基于存儲器的指針作為一個實參傳遞給需要一般指針的函數時,指針自動轉化。
l 如果不說明外部函數原形,基于存儲器的指針自動轉化為一般指針,導致錯誤,因而請用“#include”說明所有函數原形。
l 能強行改變指針類型。
變量的存儲類別
一、static(靜態局部)變量。
1、靜態局部變量在程序整個運行期間都不會釋放內存。
2、對于靜態局部變量,是在編譯的時候賦初值的,即只賦值一次。如果在程序運行時已經有初值,則以后每次調用的時候不再重新賦值。
3、如果定義局部變量的時候不賦值,則編譯的時候自動賦值為0。而對于自動變量而言,定義的時候不賦值,則是一個不確定的值。
4、雖然靜態變量在函數調用結束后仍然存在,但是其他函數不能引用。
二、用extern聲明外部變量。
用extern聲明外部變量,是為了擴展外部變量的作用范圍。比如一個程序能由多個源程序文件組成。如果一個程序中需要引用另外一個文件中已經定義的外部變量,就需要使用extern來聲明。
正確的做法是在一個文件中定義外部變量,而在另外一個文件中使用extern對該變量作外部變量聲明。
一個文件中: int abc;
另外一個文件中: extern abc;
例子:
用extern將外部變量的作用域擴展到其他文件:
文件1:
//用extern將外部變量的作用域擴展到其他文件中
#include
#include
#include
unsigned int array[10];
void fillarray();
void init_ser()
{
SCON=0X50;
TMOD|=0X20;
TH1=0XF3;
TR1=1;
TI=1;
}
void main()
{
unsigned int i;
init_ser();
fillarray();
for(i=0;i《10;i++)
{
printf(“array[%d]=%d ”,i,array[i]);
}
for(;;){;}
}
文件2:
extern int array[10];
void fillarray()
{
unsigned char i;
for(i=0;i《10;i++)
{
array[i]=i;
}
}
在單片機c語言中變量的空間分配幾個方法
1、 data區空間小,所以只有頻繁用到或對運算速度要求很高的變量才放到data區內,比如for循環中的計數值。
2、 data區內最好放局部變量。
因為局部變量的空間是能覆蓋的某個函數的局部變量空間在退出該函數是就釋放,由別的函數的局部變量覆蓋),能提高內存利用率。當然靜態局部變量除外,其內存使用方式與全局變量相同;
3、 確保你的程序中沒有未調用的函數。
在Keil C里遇到未調用函數,編譯器就將其認為可能是中斷函數。函數里用的局部變量的空間是不釋放,也就是同全局變量一樣處理。這一點Keil C做得很愚蠢,但也沒辦法。
4、 程序中遇到的邏輯標志變量能定義到bdata中,能大大降低內存占用空間。
在51系列芯片中有16個字節位尋址區bdata,其中能定義8*16=128個邏輯變量。定義方法是: bdata bit LedState;但位類型不能用在數組和結構體中。
5、 其他不頻繁用到和對運算速度要求不高的變量都放到xdata區。
6、 如果想節省data空間就必須用large模式,將未定義內存位置的變量全放到xdata區。當然最好對所有變量都要指定內存類型。
7、 當使用到指針時,要指定指針指向的內存類型。
在單片機c51語言中未定義指向內存類型的通用指針占用3個字節;而指定指向data區的指針只占1個字節;指定指向xdata區的指針占2個字節。如指針p是指向data區,則應定義為: char data *p;。還可指定指針本身的存放內存類型,如:char data * xdata p;。其含義是指針p指向data區變量,而其本身存放在xdata區。
第六課、C51運算符和表達式
?
上兩課說了常量和變量,先來補充一個用以重新定義數據類型的的語句吧。這個語句就是 typedef,這是個很好用的語句,但我卻不常用它,通常我定義變量的數據類型時都是使 用標準的關鍵字,這樣別人能很方便的研讀你的程序。如果你是個DELPHI 編程愛好者或是DELPHI程序員,你對變量的定義也許習慣了DELPHI 的關鍵字,如 int 類型常會用關鍵字Integer來定義,在用 單片機c語言時你還想用回這個的話,你能這樣寫:
typedef int integer;
integer a,b;
這兩句在編譯時,其實是先把 integer 定義為 int,在以后的語句中遇到 integer 就用 int 置換,integer 就等于 int,所以 a,b 也就被定義為 int。typedef 不能直接用來定義變量,它 只是對已有的數據類型作一個名字上的置換,并不是產生一個新的數據類型。下面兩句就是一個錯誤的例子:
typedef int integer;
integer = 100;
使用 typedef 能有方便程序的移植和簡化較長的數據類型定義。用 typedef 還能定義結 構類型,這一點在后面詳細解說結構類型時再一并說明。typedef 的語法是
typedef 已有的數據類型 新的數據類型名 運算符就是完成某種特定運算的符號。運算符按其表達式中與運算符的關系可分為單目
運算符,雙目運算符和三目運算符。單目就是指需要有一個運算對象,雙目就要求有兩個運 算對象,三目則要三個運算對象。表達式則是由運算及運算對象所組成的具有特定含義的式 子。C 是一種表達式語言,表達式后面加“;”號就構成了一個表達式語句。
賦值運算符
對于“=”這個符號大家不會陌生的,在 C 中它的功能是給變量賦值,稱之為賦值運算 符。它的作用不用多說大家也明白,就是但數據賦給變量。如,x=10;由此可見利用賦值運 算符將一個變量與一個表達式連接起來的式子為賦值表達式,在表達式后面加“;”便構成 了賦值語句。使用“=”的賦值語句格式如下:
變量 = 表達式; 示例如下
a = 0xFF; //將常數十六進制數 FF 賦于變量 a
b = c = 33; //同時賦值給變量 b,c d = e; //將變量 e 的值賦于變量 d
f = a+b; //將變量 a+b 的值賦于變量 f 由上面的例子能知道賦值語句的意義就是先計算出“=”右邊的表達式的值,然后將得到 的值賦給左邊的變量。而且右邊的表達式能是一個賦值表達式。
在一些朋友的來信中會出現“==”與“=”這兩個符號混淆的錯誤原碼,問為何編譯報 錯,一般就是錯在 if (a=x)之類的語句中,錯將“=”用為“==”。“==”符號是用來進行相 等關系運算。
算術,增減量運算符
對于 a+b,a/b 這樣的表達式大家都很熟悉,用在 C 語言中,+,/,就是算術運算符。單片機c語言 中的算術運算符有如下幾個,其中只有取正值和取負值運算符是單目運算符,其它則都是雙 目運算符:
+ 加或取正值運算符
- 減或取負值運算符
* 乘運算符
/ 除運算符
% 取余運算符 算術表達式的形式:
表達式 1 算術運算符 表達式 2 如:a+b*(10-a), (x+9)/(y-a)
除法運算符和一般的算術運算規則有所不一樣,如是兩浮點數相除,其結果為浮點數,如
10.0/20.0 所得值為 0.5,而兩個整數相除時,所得值就是整數,如 7/3,值為 2。像別的語 言一樣 C 的運算符與有優先級和結合性,同樣可用用括號“()”來改變優先級。這些和我們 小時候學的數學幾乎是一樣的,也不必過多的說明了。
++ 增量運算符
-- 減量運算符
這兩個運算符是 C 語言中特有的一種運算符。在 VB,PASCAL 等都是沒有的。作用就是 對運算對象作加 1 和減 1 運算。要注意的是運算對象在符號前或后,其含義都是不一樣的,雖 然同是加 1 或減 1。如:I++,++I,I--,--I。
I++(或 I--) 是先使用 I 的值,再執行 I+1(或 I-1)
++I(或--I) 是先執行 I+1(或 I-1),再使用 I 的值。增減量運算符只允許用于變量的運算中,不能用于常數或表達式。 先來做一個實驗吧。學習運算符和另外一些知識時,我們還是給我們的實驗板加個串行
接口吧。借助電腦轉件直觀的看單片機的輸出結果,如果你用的是成品實驗板或仿真器,那你就能跳過這一段了。
在制作電路前我們先來看看要用的 MAX232,這里不去具體討論它,只要知道它是 TTL和 RS232 電平相互轉換的芯片和基本的引腳接線功能就行了。通常我會用兩個小功率晶體管加少量的電路去替換MAX232,能省一點,效 果也不錯 (如有興趣能查看 網站中的相關資料)。下圖就是 MAX232 的基本接線圖。
圖 6-1 MAX232
在上兩課的電路的基礎上按圖 6-3 加上 MAX232 就能了。串行口座用 DB9 的母頭,這樣 就能用買來的 PC 串行口延長線進行和電腦相連接,也能直接接到電腦 com 口上。
圖 6-2 DB9 接頭
圖 6-3 加上了 MAX232 的實驗電路 做好后,就先用回前面的“Hello World!”程序,用它來和你的電腦說聲 Hello!把程序
燒到芯片上,把串行口連接好。嘿嘿,這個時候要打開你的串行口調試軟件,沒有就趕快到網上 DOWN 一個了。你會用 Windows 的超級終端也行,不過我從不用它。我用 http://emouze.com 的 comdebug,它是個不錯的軟件,我喜歡它是因為它功能好而且還有“線路狀態”功能,這對
我制作小玩意時很有用。串行口號,波特率調好,打開串行口,單片機上電,就能在接收區看 到不斷出現的“Hello World!”。一定要先打開軟件的串行口,再把單片機上電,不然可能因字符不對齊而看到亂碼哦。

?
圖 6-4 調試結果?
?
第七課、運算符和表達式(關系運算符)?
關系運算符,同樣我們也并不陌生。單片機C語言中有六種關系運算符,這些東西同樣是在我們小時候學算術時就已經學習過了的:
> 大于
?。?小于
?。荆?大于等于
?。迹?小于等于
== 等于
?。。?等于
或者你是個非 C語言 程序員,那么對前四個一定是再熟悉不過的了。而“==”在 VB 或 PASCAL 等中是用“=”,“!=”則是用“not ”。
小學時的數學課就教授過運算符是有優先級別的,計算機的語言也不過是人類語言的一種擴展,這里的運算符同樣有著優先級別。前四個具有相同的優先級,后兩個也具有相同的優先級,但是前四個的優先級要高于后2個的。
當兩個表達式用關系運算符連接起來時,這個時候就是關系表達式。關系表達式通常是用來判別某個條件是否滿足。要注意的是用關系運算符的運算結果只有 0 和 1 兩種,也就是邏輯的真與假,當指定的條件滿足時結果為 1,不滿足時結果為 0。
表達式 1 關系運算符 表達式 2 如:I<J,I==J,(I=4)》(J=3),J+I》J
借助我們在上一課做好的電路和學習了的相關操作。我們來做一個關系運算符相關的實例程序。為了增加學習的趣味性和生動性,不妨我們來假設在做一個會做算術的機器人,當然真正會思考對話的機器,我想我是做不出來的了,這里的程序只是用來學習關系運算符的基本應用。
#include 《AT89X51.H》
#include 《stdio.h》
void main(void)
{
int x,y;
SCON = 0x50; //串行口方式 1,允許接收 TMOD = 0x20; //定時器 1 定時方式 2
TH1 = 0xE8; //11.0592MHz 1200 波特率 TL1 = 0xE8;
TI = 1;
TR1 = 1; //啟動定時器
while(1)
{
printf(“您好!我叫 Robot!我是一個會做算術的機器人! ”); //顯示
printf(“請您輸入兩個 int,X 和 Y ”); //顯示
scanf(“%d%d”,&x,&y); //輸入
if (x 《 y)
printf(“X《Y ”); //當 X 小于 Y 時
else //當 X 不小于 Y 時再作判斷
{
if (x == y)
printf(“X=Y ”); //當 X 等于 Y 時
else
printf(“X》Y ”); //當 X 大于 Y 時
}
}
}
要注意的是,在連接 PC 串行口調試時。發送數字時,發送完一個數字后還要發送一個回車符,以使 scanf 函數確認有數據輸入。
邏輯運算符 關系運算符所能反映的是兩個表達式之間的大小等于關系,那邏輯運算符則是用于求條件式的邏輯值,用邏輯運算符將關系表達式或邏輯量連接起來就是邏輯表達式了。也許你會 對為什么“邏輯運算符將關系表達式連接起來就是邏輯表達式了”這一個描述有疑惑的地方。 其實之前說過“要注意的是用關系運算符的運算結果只有 0 和 1 兩種,也就是邏輯的真與假”, 換句話說也就是邏輯量,而邏輯運算符就用于對邏輯量運算的表達。邏輯表達式的一般形式 為:
邏輯與:條件式 1 && 條件式 2 邏輯或:條件式 1 || 條件式 2 邏輯非: ! 條件式 2
圖 7-1 演示結果
邏輯與,說白了就是當條件式 1“與”條件式 2 都為真時結果為真(非 0 值),不然為 假(0 值)。也就是說運算會先對條件式 1 進行判斷,如果為真(非 0 值),則繼續對條件式
2 進行判斷,當結果為真時,邏輯運算的結果為真(值為 1),如果結果不為真時,邏輯運算 的結果為假(0 值)。如果在判斷條件式 1 時就不為真的話,就不用再判斷條件式 2 了,而 直接給出運算結果為假。
邏輯或,是指只要二個運算條件中有一個為真時,運算結果就為真,只有當條件式都不 為真時,邏輯運算結果才為假。
邏輯非則是把邏輯運算結果值取反,也就是說如果兩個條件式的運算值為真,進行邏輯 非運算后則結果變為假,條件式運算值為假時最后邏輯結果為真。
同樣邏輯運算符也有優先級別,?。ㄟ壿嫹牵?&(邏輯與)→||(邏輯或),邏輯非的 優先值最高。

如有 !True || False && True
按邏輯運算的優先級別來分析則得到(True 代表真,False 代表假)
下面我們來用程序語言去有表達,如下:
#include 《AT89X51.H》
#include 《stdio.h》
void main(void)
{
unsigned char True = 1; //定義
unsigned char False = 0;
SCON = 0x50; //串行口方式 1,允許接收 TMOD = 0x20; //定時器 1 定時方式 2
TH1 = 0xE8; //11.0592MHz 1200 波特率 TL1 = 0xE8;
TI = 1;
TR1 = 1; //啟動定時器
if (!True || False && True)
printf(“True ”); //當結果為真時
else
}
printf(“False ”); //結果為假時
大家能使用以往學習的方法用 keil 或燒到片子上用串行口調試。能更改“!True || False
&& True”這個條件式,以實驗不一樣算法組合來掌握邏輯運算符的使用方法。
第八課、運算符和表達式(位運算符)
學過匯編的朋友都知道匯編對位的處理能力是很強的,但是單片機C語言也能對運算對象進行按位操作,從而使單片機C語言也能具有一定的對硬件直接進行操作的能力。位運算符的作用是按位對變量進行運算,但是并不改變參與運算的變量的值。如果要求按位改變變量的值,則要利用相應的賦值運算。還有就是位運算符是不能用來對浮點型數據進行操作的。單片機c語言中共有6種位運算符。位運算一般的表達形式如下:
變量 1 位運算符 變量 2 位運算符也有優先級,從高到低依次是:“~”(按位取反)→“《《”(左移) →“》》”(右
移) →“&”(按位與)→“^”(按位異或)→“|”(按位或)
表 8-1 是位邏輯運算符的真值表,X 表示變量 1,Y 表示變量 2
表 8-1 按位取反,與,或和異或的邏輯真值表
利用以前建立起來的實驗板,我們來做個實驗驗證一下位運算是否真是不改變參與變量 的值,同時學習位運算的表達形式。程序很簡單,用 P1 口做運算變量,P1.0-P1.7 對應 P1 變量的最低位到最高位,通過連接在 P1 口上的 LED 我們便能直觀看到每個位運算后變量 是否有改變或如何改變。程序如下:
#include 《at89x51.h》
void main(void)
{
unsigned int a;
unsigned int b;
unsigned char temp; //臨時變量
P1 = 0xAA; //點亮 D1,D3,D5,D7 P1 口的二進制為 10101010,為 0 時點亮 LED
for (a=0;a《1000;a++)
for (b=0;b《1000;b++); //延時
temp = P1 & 0x7; //單純的寫 P1|0x7 是沒有意義的,因為沒有變量被影響,不會被編譯
//執行 P1|0x7 后結果存入temp,這個時候改變的是 temp,但 P1 不會被影響。
//這個時候 LED 沒有變化,仍然是 D1,D3,D5,D7 亮
for (a=0;a《1000;a++)
for (b=0;b《1000;b++); //延時 P1 = 0xFF; //熄滅 LED
for (a=0;a《1000;a++)
for (b=0;b《1000;b++); //延時
P1 = 0xAA; //點亮 D1,D3,D5,D7 P1 口的二進制為 10101010,為 0 時點亮 LED
for (a=0;a《1000;a++)
for (b=0;b《1000;b++); //延時
P1 = P1 & 0x7; //這個時候 LED 會變得只有 D2 滅
//因為之前 P1=0xAA=10101010
//與 0x7 位與 0x7=00000111
//結果存入 P1 P1=00000010 //位為 O 時點亮 LED,電路看第三課
for (a=0;a《1000;a++)
for (b=0;b《1000;b++); //延時 P1 = 0xFF; //熄滅 LED
while(1);
//大家能根據上面的程序去做位或,左移,取反等等。
}
復合賦值運算符
復合賦值運算符就是在賦值運算符“=”的前面加上其他運算符。以下是 C 語言中的復 合賦值運算符:

%= 取模賦值 -= 邏輯非賦值
《《= 左移位賦值 復合運算的一般形式為:
變量 復合賦值運算符 表達式 其含義就是變量與表達式先進行運算符所要求的運算,再把運算結果賦值給參與運算的
變量。其實這是 C 語言中一種簡化程序的一種方法,凡是二目運算都能用復合賦值運算符 去簡化表達。例如:
a+=56 等價于 a=a+56
y/=x+9 等價于 y=y/(x+9) 很明顯采用復合賦值運算符會降低程序的可讀性,但這樣卻能使程序代碼簡單化,并
能提高編譯的效率。對于開始學習 C 語言的朋友在編程時最好還是根據自己的理解力和習慣去使 用程序表達的方式,不要一味追求程序代碼的短小。
逗號運算符
如果你有編程的經驗,那么對逗號的作用也不會陌生了。如在 VB 中“Dim a,b,c”的逗 號就是把多個變量定義為同一類型的變量,在 C 也一樣,如“int a,b,c”,這些例子說明逗 號用于分隔表達式用。但在 C 語言中逗號還是一種特殊的運算符,也就是逗號運算符,能 用它將兩個或多個表達式連接起來,形成逗號表達式。逗號表達式的一般形式為:
表達式 1,表達式 2,表達式 3……表達式 n
這樣用逗號運算符組成的表達式在程序運行時,是從左到右計算出各個表達式的值,而 整個用逗號運算符組成的表達式的值等于最右邊表達式的值,就是“表達式 n”的值。在實 際的應用中,大部分情況下,使用逗號表達式的目的只是為了分別得到名個表達式的值,而 并不一定要得到和使用整個逗號表達式的值。要注意的還有,并不是在程序的任何位置出現 的逗號,都能認為是逗號運算符。如函數中的參數,同類型變量的定義中的逗號只是用來 間隔之用而不是逗號運算符。
條件運算符
上面我們說過單片機C語言中有一個三目運算符,它就是“?:”條件運算符,它要求有三個運算對象。它能把三個表達式連接構成一個條件表達式。條件表達式的一般形式如下:
邏輯表達式? 表達式 1 : 表達式 2 條件運算符的作用簡單來說就是根據邏輯表達式的值選擇使用表達式的值。當邏輯表達
式的值為真時(非 0 值)時,整個表達式的值為表達式 1 的值;當邏輯表達式的值為假(值
為 0)時,整個表達式的值為表達式 2 的值。要注意的是條件表達式中邏輯表達式的類型可 以與表達式 1 和表達式 2 的類型不一樣。下面是一個邏輯表達式的例子。
如有 a=1,b=2 這個時候我們要求是取 ab 兩數中的較小的值放入 min 變量中,也許你會這樣 寫:
if (a《b)
min = a;
else
min = b; //這一段的意思是當 a《b 時 min 的值為 a 的值,不然為 b 的值。
用條件運算符去構成條件表達式就變得簡單明了了:
min = (a《b)?a : b 很明顯它的結果和含意都和上面的一段程序是一樣的,但是代碼卻比上一段程序少很多,編譯的效率也相對要高,但有著和復合賦值表達式一樣的缺點就是可讀性相對效差。在實際應 用時根據自己要習慣使用,就我自己來說我喜歡使用較為好讀的方式和加上適當的注解,這 樣能有助于程序的調試和編寫,也便于日后的修改讀寫。
第九課、C51運算符和表達式(指針和地址運算符)
在第 3 課我們學習數據類型時,學習過指針類型,知道它是一種存放指向另一個數據的地址的變量類型。指針是單片機C語言中一個十分重要的概念,也是學習單片機C語言中的一個難點。對于指針將會在第九課中做詳細的講解。在這里我們先來了解一下單片機C語言中供給的兩個專門用于指針和地址的運算符:
* 取內容
& 取地址取內容和地址的一般形式分別為:
變量 = * 指針變量 指針變量 = & 目標變量
取內容運算是將指針變量所指向的目標變量的值賦給左邊的變量;取地址運算是將目標變量的地址賦給左邊的變量。要注意的是:指針變量中只能存放地址(也就是指針型數據), 一般情況下不要將非指針類型的數據賦值給一個指針變量。
下面來看一個例子,并用一個圖表和實例去簡單理解指針的使用方法和含義。
設有兩個 unsigned int 變量 ABC 處 CBA 存放在 0x0028,0x002A 中 另有一個指針變量 portA 存放在 0x002C 中 那么我們寫這樣一段程序去看看*,&的運算結果
unsigned int data ABC _at_ 0x0028; unsigned int data CBA _at_ 0x002A; unsigned int data *Port _at_ 0x002C;
#include 《at89x51.h》
#include 《stdio.h》
void main(void)
{
SCON = 0x50; //串行口方式 1,允許接收 TMOD = 0x20; //定時器 1 定時方式 2
TH1 = 0xE8; //11.0592MHz 1200 波特率 TL1 = 0xE8;
TI = 1;
TR1 = 1; //啟動定時器
ABC = 10; //設初值 CBA = 20;
Port = &CBA; //取 CBA 的地址放到指針變量 Port
*Port = 100; //更改指針變量 Port 所指向的地址的內容
printf(“1: CBA=%d ”,CBA); //顯示此時 CBA 的值
Port = &ABC; //取 ABC 的地址放到指針變量 Port
CBA = *Port; //把當前 Port 所指的地址的內容賦給變量 CBA
printf(“2: CBA=%d ”,CBA); //顯示此時 CBA 的值
printf(“ ABC=%d ”,ABC); //顯示 ABC 的值
}
程序初始時
?
其它的語句也是一樣的道理,大家能用 Keil 的單步執行和打開存儲器查看器一看,這樣
就更不難理解了。
圖 9-1 存儲器查看窗
圖 9-2 在串行調試窗口的最終結果
sizeof 運算符
看上去這確實是個奇怪的運算符,有點像函數,卻又不是。大家看到 size 應該就猜到 是和大小有關的吧?是的,sizeof 是用來求數據類型、變量或是表達式的字節數的一個運 算符,但它并不像“=”之類運算符那樣在程序執行后才能計算出結果,它是直接在編譯時 產生結果的。它的語法如下:
sizeof (數據類型)
sizeof (表達式) 下面是兩句應用例句,程序大家能試著編寫一下。
printf(“char 是多少個字節? ? 字節 ”,sizeof(char));
printf(“long 是多少個字節? ? 字節 ”,sizeof(long));
結果是:
char 是多少個字節? 1 字節
long 是多少個字節? 4 字節
強制類型轉換運算符 不知你們是否有自己去試著編一些程序,從中是否有遇到一些問題?開始學習時我就遇到過
這樣一個問題:兩個不一樣數據類型的數在相互賦值時會出現不對的值。如下面的一段小程序:
void main(void)
{
unsigned char a;
unsigned int b;
b=100*4;
a=b;
while(1);
}
這段小程序并沒有什么實際的應用意義,如果你是細心的朋友定會發現 a 的值是不會等于
100*4 的。是的 a 和 b 一個是 char 類型一個是 int 類型,從以前的學習可知 char 只占一個 字節值最大只能是 255。但編譯時為何不出錯呢?先來看看這程序的運行情況:
圖 9-3 小程序的運行情況
b=100*4 就能得知 b=0x190,這個時候我們能在 Watches 查看 a 的值,對于 watches 窗口我們 在第 5 課時簡單學習過,在這個窗口 Locals 頁里能查看程序運行中的變量的值,也能
在 watch 頁中輸入所要查看的變量名對它的值進行查看。做法是按圖中 1 的 watch#1(或
watch#2),然后光標移到圖中的 2 按 F2 鍵,這樣就能輸入變量名了。在這里我們能查看
到 a 的值為 0x90,也就是 b 的低 8 位。這是因為執行了數據類型的隱式轉換。隱式轉換是 在程序進行編譯時由編譯器自動去處理完成的。所以有必要了解隱式轉換的規則:
1.變量賦值時發生的隱式轉換,“=”號右邊的表達式的數據類型轉換成左邊變量的數
據類型。就如上面例子中的把 INT 賦值給 CHAR 字符型變量,得到的 CHAR 將會是 INT 的低 8 位。如把浮點數賦值給整形變量,小數部分將丟失。
2.所有 char 型的操作數轉換成 int 型。
3.兩個具有不一樣數據類型的操作數用運算符連接時,隱式轉換會按以下次序進行:如 有一操作數是 float 類型,則另一個操作數也會轉換成 float 類型;如果一個操作數為 long 類型,另一個也轉換成 long;如果一個操作數是 unsigned 類型,則另一個操作會被轉換成 unsigned 類型。
從上面的規則能大概知道有那幾種數據類型是能進行隱式轉換的。是的,在 單片機c語言 中只有 char,int,long 及 float 這幾種基本的數據類型能被隱式轉換。而其它的數據類型 就只能用到顯示轉換。要使用強制轉換運算符應遵循以下的表達形式:
?。愋停?表達式 用顯示類型轉換來處理不一樣類型的數據間運算和賦值是十分方便和方便的,特別對指針
變量賦值是很有用的??匆幻嬉欢涡〕绦颍?/p>
#include 《at89x51.h》
#include 《stdio.h》
void main(void)
{
char xdata * XROM;
char a;
int Aa = 0xFB1C;
long Ba = 0x893B7832;
float Ca = 3.4534;
SCON = 0x50; //串行口方式 1,允許接收 TMOD = 0x20; //定時器 1 定時方式 2
TH1 = 0xE8; //11.0592MHz 1200 波特率 TL1 = 0xE8;
TI = 1;
TR1 = 1; //啟動定時器
XROM=(char xdata *) 0xB012; //給指針變量賦 XROM 初值
*XROM = ‘R’; //給 XROM 指向的絕對地址賦值
a = *((char xdata *) 0xB012); //等同于 a = *XROM
printf (“%bx %x %d %c ”,(char) Aa, (int) Ba,(int)Ca, a);//轉換類型并輸出
while(1);
}
程序運行結果:1c 7832 3 R 在上面這段程序中,能很清楚到到各種類型進行強制類型轉換的基本使用方法,程序中先
在外部數據存儲器 XDATA 中定義了一個字符型指針變量 XROM,當用 XROM=(char xdata *)
0xB012 這一語句時,便把 0xB012 這個地址指針賦于了 XROM,如你用 XROM 則會是非法的, 這種方法特別適合于用標識符來存取絕對地址,如在程序前用#define ROM 0xB012 這樣的 語句,在程序中就能用上面的方法用 ROM 對絕對地址 0xB012 進行存取操作了。
?
評論