C語言的自增++,自減--運算符對於初學者來說一直都是個難題,甚至很多老手也會產生困惑,最近我在網上看到一個問題:
#include <stdio.h>
void main() /*主函數*/
{
int a,b,c,d;
a=5;
b=5;
c=(a++)+(a++)+(a++);
d=(++b)+(++b)+(++b);
printf("a=%d,b=%d,c=%d,d=%d\n",a,b,c,d);
}
結果是什麼?
而後Eric搜了一下後發現,類似的問題很多,也就是說對自增自減運算符感到迷惑是一個普遍存在的問題,基於此,Eric決定對自增自減運算符做個小小的解析,希望能給C語言愛好者們提供參考,解決對此問題的困惑。
自增自減運算符語法
自增運算符 ++ 使操作數的值加1,其操作數必須為(可簡單地理解為變量)。對於自增就是加1這一點,Eric想大家都不會有什麼疑問。
問題在於:++ 可以置於操作數前面,也可以放在後面,如:
++i;
i++ ;
++i表示,i自增1後再參與其它運算;而i++ 則是i參與運算後,i的值再自增1.
自減運算符--與之類似,只不過是變加為減而已,故不重述。
實例剖析
下面我們通過一些實例來深入理解自增運算符的特性,自減運算符同理自悟
例一:
int i=3;
int j=4;
i++;
++j;
printf("%d, %d\n", i, j);
對此,Eric想大家都不會有什麼困惑,結果就是 4,5;下面我們來做一點小改動:
int i=3;
int j=4;
int a = i++;
int b = ++j;
printf("%d, %d\n", a, b);
結果又是多少呢?這裡就開始體現出++前置與後置的區別了,結果是3,5.結合此例,我們回頭再來理解一下"++前置:i自增1後再參與其它運算;++後置:i參與運算後,i的值再自增1".很明顯,a = i++;由於是先執行賦值運算,再自增,所以結果是a=3,i=4;而b = ++j;
則因先自增,然後再賦值,所以b,j均為5.
其實基本道理就這麼簡單了,但在更復雜點的情況下又會如何呢,請看:
例二:
int i=3;
int j=4;
int a = i++ + i++;
int b = ++j + ++j;
printf("%d, %d\n", a, b);
問題又來了,i++ + i++是先自增一次,相加,再自增,然後賦值呢,還是先相加賦值然後自增兩次呢。另外,++j又將如何表現呢?
結果是:6,12
這下明白了,原來 i++的理解應該是執行完整個表達式的其他操作後,然後才自增,所以例子中的a=3+3=6;而後i再自增2次,i=5;相反,++j是先自增然後再參加其它運算,所以b=6+6=12.
到此,是否就徹底明了了呢?然後回到引子中的問題
例三:
int i=3;
int j=4;
int a = i++ + i++ + i++;
int b = ++j + ++j + ++j;
printf("%d, %d\n", a, b);
有人可能會說,這很簡單,我全明白了:a=3+3+3=9,i=6,b=5+5+5=15,j=5.真的是這樣嗎?
結果卻是:9,19
這下可好,又糊塗了。對於a = i++ + i++ + i++;我們已經沒有疑問了,++後置就是執行完整個表達式的其他操作後,然後才自增,上例中也得到了驗證,但 b = ++j + ++j + ++j;又該如何理解呢?
原理表達式中除了預算法本身的優先級外,還有一個結合性問題。在++j + ++j + ++j;中,因為存在兩個同級的+運算,根據+運算符的左結合性,在編譯時,其實是先處理前面的(++j + ++j)這部分,然後再將此結果再和++j相加。具體過程參見匯編代碼:
int b = ++j + ++j + ++j;
0040B7DD mov ecx,dword ptr [ebp-8]
0040B7E0 add ecx,1
0040B7E3 mov dword ptr [ebp-8],ecx // 第一個++j
0040B7E6 mov edx,dword ptr [ebp-8]
0040B7E9 add edx,1
0040B7EC mov dword ptr [ebp-8],edx // 第二個++j
0040B7EF mov eax,dword ptr [ebp-8]
0040B7F2 add eax,dword ptr [ebp-8] // ++j + ++j
0040B7F5 mov ecx,dword ptr [ebp-8]
0040B7F8 add ecx,1
0040B7FB mov dword ptr [ebp-8],ecx // 第三個++j
0040B7FE add eax,dword ptr [ebp-8] // ++j + ++j + ++j
0040B801 mov dword ptr [ebp-10h],eax // 賦值給b
另外我們看看a = i++ + i++ + i++;的匯編代碼:
int a = i++ + i++ + i++;
0040B7B6 mov eax,dword ptr [ebp-4]
0040B7B9 add eax,dword ptr [ebp-4] // i+i
0040B7BC add eax,dword ptr [ebp-4] // i+i+i
0040B7BF mov dword ptr [ebp-0Ch],eax // 賦值給a
0040B7C2 mov ecx,dword ptr [ebp-4]
0040B7C5 add ecx,1
0040B7C8 mov dword ptr [ebp-4],ecx // 第一次i++
0040B7CB mov edx,dword ptr [ebp-4]
0040B7CE add edx,1
0040B7D1 mov dword ptr [ebp-4],edx // 第二次i++
0040B7D4 mov eax,dword ptr [ebp-4]
0040B7D7 add eax,1
0040B7DA mov dword ptr [ebp-4],eax // 第三次i++
果然不出所料。到此,++運算符前置後置的問題應該徹底解決了。
為了驗證一下上述結論,我們再看:
例四:
int i=1;
int j=1;
int a = i++ + i++ + i++ + i++ + i++ + i++ + i++; // 七個
int b = ++j + ++j + ++j + ++j + ++j + ++j + ++j;
printf("%d, %d\n", a, b);
printf("%d, %d\n", i, j);
規則就是規則,咱的計算機可不是黑客帝國的母體,總是要遵循它的
a = 1+1+1+1+1+1+1 = 7, i=8
b = 3+3+4+5+6+7+8 = 36, j=8
不用擔心matrix控制你的思維和生活
注:以上結果及解釋出自VC編譯器,但對於++這個問題是和編譯器的解析有關的,不同廠家可能理解不一致,因手頭沒有其他開發環境,暫無法做全面分析,本文只是為了說明++,--這運算符的一些特性,尤其是前置後置的區
用51操作和檢測PS2鍵盤,接收數據已經搞定了,可是發送數據卻出錯,不知道怎麼回事。給它的命令和它所執行的完全不同,請高手指教下,到底是哪裡錯了,還是沒錯,電路接得不對??接收函數是這樣的。
void send(uchar Data)
{
uchar i,high=0;
EA=0;
EX0=0;//關閉中斷
CLK=0;
delay10us(11);
DATA=0;
delay10us(2);
CLK=1;
while(CLK==1);
//開始發送八位數據
for(i=0;i<=7;i++)
{
if(Data & 0x01)
{
DATA=1;
high++;
}
DATA=(Data>>1);
while(CLK==0);
while(CLK==1);
}
if(high%2==0) DATA=1; //設置奇校驗位
else DATA=0;
while(CLK==0);
while(CLK==1);
DATA=1;
while(DATA==1);
while(CLK==1);
while(DATA==0);
while(CLK==0);
EA=1;
EX0=1;//寫數據程序結束後開中斷
}
全部程序是這樣的:
#include <reg52.H>
#include <intrins.H>
#include <head.h>
#define uchar unsigned char
#define uint unsigned int
#define LCD_DATA P2 //數據口
sbit RS = P1^5; //並行的指令/數據選擇信號, H數據, L命令
sbit RW = P1^7; //並行讀寫選擇信號, H讀, L寫
sbit E = P1^6; //並行使能端, H有效, L無效
sbit PSB = P1^1; //並/串接口選擇, H並,L串
sbit RET = P1^4; //復位, L有效
sbit DATA=P0^2;
sbit CLK=P3^2;
uchar count,ASCII=65,Data;
void trains(uchar Code);
void send (uchar Data);
//檢測LCD是否處於忙狀態, 若忙返回1, 空閒返回0
bit checkBusy()
{
bit busy;
RS = 0;
RW = 1;
E = 1;
delayUs();
busy = (bit)(LCD_DATA&0x80);
E = 0;
return busy;
}
//等待LCD到空閒
void wait()
{
while(checkBusy());
}
//寫命令
void writeCmd(uchar cmd)
{
wait();
RS = 0;
RW = 0;
E = 0;
delayUs();
LCD_DATA = cmd;
delayUs();
E = 1;
delayUs();
E = 0;
}
//寫數據
void writeData(uchar dat)
{
wait();
RS = 1;
RW = 0;
E = 0;
delayUs();
LCD_DATA = dat;
delayUs();
E = 1;
delayUs();
E = 0;
}
//初始化LCD
void init()
{
PSB = 1; //並口方式
writeCmd(0x30); //基本指令, 擴充指令為34H
delayMs(10);
writeCmd(0x0c); //顯示開, 關光標
writeCmd(0x01); //清屏
delayMs(10);
}
void init2()
{
PSB = 1; //並口方式
RET=0; //復位
delayUs(); //延時
RET=1; //復位置高
writeCmd(0x36);
delayMs(10);
writeCmd(0x3E);
delayMs(10);
writeCmd(0x01); //清屏
delayMs(10);
}
void setPosition(uchar x, uchar y)
{
uchar p;
switch(x%4)
{
case 0: p = 0x80; break; //第一行開始地址
case 1: p = 0x90; break; //第二行
case 2: p = 0x88; break; //第三行
case 3: p = 0x98; break; //第四行
}
p += y;
writeCmd(p);
}
void writeString(uchar * str)
{
uchar i = 0;
while(str[i] != '\0')
{
writeData(str[i++]);
}
}
void DisplayGraphic(unsigned char code *adder)
{
int i,j;
//*******顯示上半屏內容設置
for(i=0;i<32;i++) //
{
writeCmd(0x80 + i); //SET 垂直地址 VERTICAL ADD
writeCmd(0x80); //SET 水平地址 HORIZONTAL ADD
for(j=0;j<16;j++)
{
writeData(*adder);
adder++;
}
}
//*******顯示下半屏內容設置
for(i=0;i<32;i++) //
{
writeCmd(0x80 + i); //SET 垂直地址 VERTICAL ADD
writeCmd(0x88); //SET 水平地址 HORIZONTAL ADD
for(j=0;j<16;j++)
{
writeData(0xff);
adder++;
}
}
}
void initkb() //鍵盤初始化函數
{
EA=1;//?
EX0=1;
IT0=1;
count=0;
}
void exter0() interrupt 0
{
count++;
if(count>=2 && count<=9)
{
Data=(Data>>1);
if(DATA==1)
Data|=0x80;
}
}
void trains(uchar scancode)
{
static uchar up=0,shift=0; //up為通、斷碼標志,shift為shift鍵按下標志
uchar i;
if (!up) //已接收的11位數據是通碼(up為0)
{
switch (scancode)//開始翻譯掃描碼
{
case 0xF0: //鍵盤釋放標志(隨後的一個字節是斷碼)
up=1; //設置up為斷碼標志
break;
case 0x12: //左shift鍵按下
shift=1; //設置shift為按下標志
break;
case 0x59: //右shift鍵按下
shift=1; //設置shift為按下標志
break;
default:
if(!shift) //如果shift鍵沒有按下
{ //查找unshifted表,表中左列是掃描碼,右列是對應的ASCII碼
for(i=0;unshifted[i][0]!=scancode&&unshifted[i][0];i++);
if(unshifted[i][0]==scancode)
{
ASCII=unshifted[i][1];
}
}
else //如果shift鍵按下
{ //查找shifted表
for(i=0;shifted[i][0]!=scancode&&shifted[i][0];i++);
if(shifted[i][0]==scancode)
{
ASCII=shifted[i][1];
}
}
break;
}
}
else //已接收的11位數據是斷碼(up為1)
{
up = 0; //將斷碼標志復位
switch (scancode) //檢測shift鍵釋放
{
case 0x12 : //左shift鍵
shift = 0;
break;
case 0x59 : //右shift鍵
shift = 0;
break;
default:
break;
}
}
}
void main()
{
delayMs(500);
init();
initkb();
setPosition(0, 0);
writeString(datas1);
send(0x03);
setPosition(1, 0);
writeString(datas2);
while(1)
{
if(count==11)
{
count=0;
trains(Data);
// send(0xED);
}
setPosition(2, 0);
writeData(ASCII);
if(count==0)
{
setPosition(3, 0);
writeString(datas3);
}
else
{
setPosition(3, 0);
writeString(datas4);
count=0;
}
}
}
//以下是給鍵盤發數據函數
void send(uchar Data)
{
uchar i,high=0;
EA=0;
EX0=0;//?
CLK=0;
delay10us(11);
DATA=0;
delay10us(2);
CLK=1;
while(CLK==1);
//開始發送八位數據
for(i=0;i<=7;i++)
{
if(Data & 0x01)
{
DATA=1;
high++;
}
DATA=(Data>>1);
while(CLK==0);
while(CLK==1);
}
if(high%2==0) DATA=1;
else DATA=0;
while(CLK==0);
while(CLK==1);
DATA=1;
while(DATA==1);
while(CLK==1);
while(DATA==0);
while(CLK==0);
EA=1;
EX0=1;//寫數據程序結束後開中斷
}
head.h頭文件:
#define uchar unsigned char
#define uint unsigned int
uchar code datas1[] = {"PS2 鍵盤檢測實驗"};
uchar code datas2[] = {"檢測的按鍵為"};
uchar code datas3[] = {"按鍵檢測正確"};
uchar code datas4[] = {"檢測有誤 "};
uchar code unshifted[][2]= //shift鍵沒按下譯碼表
{
0x0e,'`',
0x15,'q',
0x16,'1',
0x1a,'z',
0x1b,'s',
0x1c,'a',
0x1d,'w',
0x1e,'2',
0x21,'c',
0x22,'x',
0x23,'d',
0x24,'e',
0x25,'4',
0x26,'3',
0x29,' ',
0x2a,'v',
0x2b,'f',
0x2c,'t',
0x2d,'r',
0x2e,'5',
0x31,'n',
0x32,'b',
0x33,'h',
0x34,'g',
0x35,'y',
0x36,'6',
0x39,',',
0x3a,'m',
0x3b,'j',
0x3c,'u',
0x3d,'7',
0x3e,'8',
0x41,',',
0x42,'k',
0x43,'i',
0x44,'o',
0x45,'0',
0x46,'9',
0x49,'.',
0x4a,'/',
0x4b,'l',
0x4c,';',
0x4d,'p',
0x4e,'-',
0x52,'\'',
0x54,'[',
0x55,'=',
0x5b,']',
0x5d,'\\',
0x61,'<',
0x69,'1',
0x6b,'4',
0x6c,'7',
0x70,'0',
0x71,'.',
0x72,'2',
0x73,'5',
0x74,'6',
0x75,'8',
0x79,'+',
0x7a,'3',
0x7b,'-',
0x7c,'*',
0x7d,'9',
0,0
};
uchar code shifted[][2]= //shift鍵按下譯碼表
{
0x0e,'~',
0x15,'Q',
0x16,'!',
0x1a,'Z',
0x1b,'S',
0x1c,'A',
0x1d,'W',
0x1e,'@',
0x21,'C',
0x22,'X',
0x23,'D',
0x24,'E',
0x25,'$',
0x26,'#',
0x29,' ',
0x2a,'V',
0x2b,'F',
0x2c,'T',
0x2d,'R',
0x2e,'%',
0x31,'N',
0x32,'B',
0x33,'H',
0x34,'G',
0x35,'Y',
0x36,'^',
0x39,'L',
0x3a,'M',
0x3b,'J',
0x3c,'U',
0x3d,'&',
0x3e,'*',
0x41,'<',
0x42,'K',
0x43,'I',
0x44,'O',
0x45,')',
0x46,'(',
0x49,'>',
0x4a,'?',
0x4b,'L',
0x4c,':',
0x4d,'P',
0x4e,'_',
0x52,'"',
0x54,'{',
0x55,'+',
0x5b,'}',
0x5d,'|',
0x61,'>',
0x69,'1',
0x6b,'4',
0x6c,'7',
0x70,'0',
0x71,'.',
0x72,'2',
0x73,'5',
0x74,'6',
0x75,'8',
0x79,'+',
0x7a,'3',
0x7b,'-',
0x7c,'*',
0x7d,'9',
0,0
};
//延時約2us
void delayUs()
{
_nop_();_nop_();
}
//延時 a * 1ms
void delayMs(uint a)
{
uint i, j;
for(i = a; i > 0; i--)
for(j = 100; j > 0; j--);
}
void delay10us(uchar i) //10us延時程序
{ uchar a=14;
uchar b;
for(b=0;b<i;b++)
{ while(a--);a=14;}
}
別這個問題。類似的問題如果有困惑,最好是寫程序做試驗解決,請勿生搬硬套。謝謝!在實際的編程實踐中,類似的問題除了要試驗搞清外,Eric認為應該盡量避免引入環境相關的編程技巧。
*