51單片機基礎剖析(基於C語言)

2020-12-06 電子產品世界
一、51單片機內存剖析

在編寫應用程式時,定義一個變量,一個數組,或是說一個固定表格,到底存儲在什麼地方;當定義變量大小超過MCU的內存範圍時怎麼辦;如何控制變量定義不超過存儲範圍;

本文引用地址:http://www.eepw.com.cn/article/201611/318636.htm

以及如何定義變量才能使得變量訪問速度最快,寫出的程序運行效率最高。以下將一一解答。

1.六類存儲類型 code data idata xdata pdata bdata

code:程序存儲器,也即只讀存儲器,用來保存常量或是程序,採用16位地址線編碼,可以是在片內,或是片外,大小被限制在64KB。

作用:定義常量,如八段數碼錶或是編程使用的常,在定義時加上code或明確指明定義的常量保存到code memory(只讀。)比如:

char code table[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};

此關鍵字的使用方法等同於const。

data:數據存儲區,只能用於聲明變量,不能用來聲明函數,該區域位於片內,採用8位地址線編碼,具有最快的存儲速度,但是數量被限制在128byte或更少。

使用方法:unsigned char data fast_variable=0;

Idata:數據存儲區,只能用於聲明變量,不能用來聲明函數。該區域位於片內,採用8位地址線編碼,內存大小被限制在256byte或更少。該區域的低地址區與data區地址一致,高地址區域是52系列在51系列基礎上擴展的並與特殊功能寄存器具有相同地址編碼的區域。即:data memory是idata memory的一個子集。

xdata:只能用於聲明變量,不能用來聲明函數,該區域位於MCU外部,採用16位地址線進行編碼,存儲大小被限制在64KB以內。如:unsigned char xdata count=0;

pdata:只能用於聲明變量,不能用來聲明函數,該區域位於MCU外部,採用8位地址線進行編碼。存儲大小限制在256byte,是xdata memory的低256byte。為其子集。如:unsigned char pdata count=0;

bdata:只能用於聲明變量,不能用來聲明函數。該區域位於8051內部位數據地址。定義的量保存在內部位地址空間,可用位指令直接讀寫。使用方法:unsigned char bdata varab=0。

註:一般情況下,定義字符型變量時,在預設unsigned的情況下,默認為無符號。但是本人在Keil uV3中遇到並非如此的案例。在預設的情況下默認為有符號。要注意一下,或許不同的編譯器規則不同。所以我們在寫程序的時候,還是最好把unsigned signed加上。

2.函數的參數和局部變量的存儲模式

C51 編譯器允許採用三種存儲器模式:SMALL,COMPACT 和LARGE。一個函數的存儲器模式確定了函數的參數的局部變量在內存中的地址空間。處於SMALL模式下的函數參數和局部變量位於8051單片機內部RAM中,處於COMPACT和LARGE模式下的函數參數和局部變量則使用單片機外部RAM。在定義一個函數時可以明確指定該函數的存儲器模式。方法是在形參表列的後面加上一存儲模式。

示例如下:

#pragma large //此預編譯必須放在所有頭文前面

int func0(char x,y) small;

char func1(int x) large;

int func2(char x);

註:上面例子在第一行用了一個預編譯命令#pragma,它的意思是告訴c51編譯器在對程序進行編譯時,按該預編譯命令後面給出的編譯控制指令LARGE進行編譯,即本例程序編譯時的默認存儲模式為LARGE。隨後定義了三個函數,第一個定義為SMALL存儲模式,第二個函數定義為LARGE第三個函數未指定,在用C51進行編譯時,只有最後一個函數按LARGE存儲器模式處理,其它則分別按它們各自指定的存儲器模式處理。

本例說明,C51編譯器允許採用所謂的存儲器混合模式,即允許在一個程序中將一些函數使用一種存儲模式,而其它一些則按另一種存儲器模式,採用存儲器混合模式編程,可以充分利用8051系列單片機中有限的存儲器空間,同時還可以加快程序的執行速度。

3.絕對地址訪問(頭文件為:absacc.h(相當重要))

#define CBYTE ((unsigned char volatile code *) 0)

#define DBYTE ((unsigned char volatile data *) 0)

#define PBYTE ((unsigned char volatile pdata *) 0)

#define XBYTE ((unsigned char volatile xdata *) 0)

功能:CBYTE尋址CODE區

DBYTE尋址DATA區

PBYTE尋址XDATA(低256)區

XBYTE尋址XDATA區

例:如下指令在對外部存儲器區域訪問地址0x1000

xvar=XBYTE[0x1000];

XBYTE[0x1000]=20;

#define CWORD ((unsigned int volatile code *) 0)

#define DWORD ((unsigned int volatile data *) 0)

#define PWORD ((unsigned int volatile pdata *) 0)

#define XWORD ((unsigned int volatile xdata *) 0)

功能:與前面的一個宏相似,只是它們指定的數據類型為unsigned int。

通過靈活運用不同的數據類型,所有的8051地址空間都是可以進行訪問。例如:

DWORD[0x0004]=0x12F8;// 即內部數據存儲器中(0x08)=0x12; (0x09)=0xF8

註:用以上八個函數,可以完成對單片機內部任意ROM和RAM進行訪問,非常方便。還有一種方法,那就是用指鍾,後面會對C51的指針有詳細的介紹。

4.寄存器變量(register)

為了提高程序的執行效率,C語言允許將一些頻率最高的那些變量,定義為能夠直接使用硬體寄存器的所謂的寄存器變量。定義一個變量時,在變量類型名前冠以「register」 即將該變量定義成為了寄存器變量。寄存器變量可以認為是一自動變量的一種。有效作用範圍也自動變量相同。由於計算機寄存器中寄存器是有限的。不能將所有變量都定義成為寄存器變量,通常在程序中定義寄存器變量時,只是給編譯器一個建議,該變量是否真正成為寄存器變量,要由編譯器根據實際情況來確定。另一方面,C51編譯器能夠識別程序中使用頻率最高的變量,在可能的情況下,即使程序中並未將該變量定義為寄存器變量,編譯器也會自動將其作為寄存器變量處理。被定義的變量是否真正能成為寄存器變量,最終是由編譯器決定的。

5.內存訪問的實現

(1)指鍾

指鍾本身是一個變量,其中存放的內容是變量的地址,也即特定的數據。8051的地址是16位的,所以指針變量本身佔用兩個存儲單元。指針的說明與變量的說明類似,僅在指針名前加上「*」即可。

如: int *int_point; //聲明一個整型指針

char *char_point; //聲明一個字符型指針

利用指針可以間接存取變量。實現這一點要用到兩個特殊運算符

& 取變量地址

* 取指針指向單元的數據

示例一:

int a=15,b;

int *int_point; //定義一個指向整型變量的指針

int_point=&a; //int_point指向 a

*int_point=5; //給int_point指向的變量a 賦值5 等同於a=5;

示例二:

char i,table[6],*char_point;

char_point=table;

for(i=0;i<6;i++)

{

char_point=i;

char_point++;

}

註:指針可以進行運算,它可以與整數進行加減運算(移動指針)。但要注意,移動指針後,其地址的增減量是隨指針類型而異的,如,浮點指針進行自增後,其內部將在原有的基礎上加4,而字符指針當進生自增的時候,其內容將加1。原因是浮點數,佔4個內存單元,而字符佔一個字節。

宏晶科技最新一代STC12C5A360S2系列,每一個單片機出廠時都有全球唯一身份證號碼(ID號),用戶可以在單片機上電後讀取內部RAM單元F1H~F7H的數值,來獲取此單片機的唯一身份證號碼。使用MOV @Ri指令來讀取。下面介紹C51獲取方法:

char id[7]={0};

char i;

char idata *point;

for(i=0;i<7;i++)

{

id[i]=*point;

point++;

}

(此處只是對指針做一個小的介紹,達到訪問內部任何空間的方式,後述有對指針使用的詳細介紹)

(2)對SFR,RAM ,ROM的直接存取

C51提供了一組可以直接對其操作的擴展函數

若源程序中,用#include包含頭文件,io51.h 後,就可以在擴展函數中使用特殊功能寄存器的地址名,以增強程序的可讀性:

注 此方法對SFR,RAM,ROM的直接存取不建議使用.因為,淡io51.h這個頭文件在KEIL中無法打開,可用指針,或是採用absacc.h頭文件,

(3) PWM與PCA

STC12系列有兩路PWM/PCA

PWM:(Pulse Width Modulation)脈寬調製,是一種使用程序來控制波形佔空比,周期,相位波形的技術。

PCA:(Programmable Counter Array)可編程計數陣列,它比通常的定時/計數器的定時能力強,需要CPU的幹預少。其優勢一是軟體簡單,二是精度大有提高。

*6.動態內存分配的實現

在單片機的實際開發中,很多情況下我麼需要開闢一塊內存,但是具體開闢多大,也就是內存的字節數我們還無法確定,比如可能要等到上位機的指令發送下來才能確定,這個時候我們就得動態分配內存。注意,單片機內部存儲資源是極其有限的,不允許開發人員開闢出一塊很大的存儲區來備用。在VC 6.0環境下很容易用malloc()來得到一塊RAM,但是由於單片機內部沒有作業系統(如何在51上跑uC/OS-II我以後會寫出來),所以在51上實現動態內存分配就是個難點也是一個重點問題。下面給出代碼,詳細分析大家可以參考求是科技編的《8051系列單片機C程序設計完全手冊》這本書。

#include

#include //init_mempool()、malloc()、free()函數所在的頭文件

……

void main (void)

{

char *ptr1;

init_mempool (0x1000,0x500); //內存池初始化,0x1000為起始地址,0x500為內存大小

ptr1=malloc(30); /*動態為指針變量分配長度為30位元組的存儲空間*/

……

//此處為你的代碼

……

free(ptr1) ; //注意,動態內存用完之後務必要釋放,否則程序將會出錯

while (1);

}

二、變量類型及其作用域剖析

變量可分為 1.局部變量;2.全局變量(按變量的有效作用範圍劃分)

1.局部變量

是指函數內部(包括main函數)定義的變量,僅在定義它的那個函數範圍內有效,不同函數可使用相同的局部變量名,函數的形式參數也屬於局部變量,在一個函數的內部複合語句中也可以定義局部變量,該局部變量只在該複合語合中有效。

2.全局變量

是指函數外部定義的變量,以稱外部變量。可為多個函數共同使用,其有效作用範圍是從它定義開始到整個程序文件結束。如果全局變量,定義在一個程序文件的開始處,則在整個程序文件範圍都可以使用它,如果一個全局變量不是在程序文件的開始處定義,但又希望在它定義之前的函數中引用該變量,這時應在引用該變量的函數中用關鍵字extern將其聲明為「外部變量」。另個,如果在一個程序模塊文件中引用另一個程序模塊文件中定義的變量時,也必須用extern進行說明。

外部變量的說明與外部變量的定義是不同的,外部變量定義只能有一次,定義的位置在所有函數之外,而同一個程序文件中(不是指模塊文件)的外部變量聲明可以有多次,聲明的置在需要引用該變量的函數之內,外部變量的聲明的作用只是聲明該變量是一個已經在外部定義過了的變量而已。

如在同一個程序文件中,全局變量與局部變量同名,則在局部變量的有效作用範圍之內,全局變量不起作用,也就是說,局部變量的優先級比全局變量高。

在編寫C語言程序時,不是特別必要的地方一般不要使用全局變量,而應當儘可能的使用局部變量。因為局部變量只在使用它的時候,才為其分配內存單元,而全局變量在整個程序的執行過程中都要佔用內存單元,且當全局變量使用過多時,會降低程序的可讀性。

變量的存儲種類

(1).自動變量(auto)

定義變量時,在變量類型名前加上 「auto」 ,自動變量是C語言中使用最為廣泛的一類變量,在函數體內部或是複合語句內部定義的變量,如果省略了存儲種類說明,則該變量默認為自動變量。

例如:

{ 等價於 {

char x; auto char x;

int y; auto int y;

…… ……

} }

註:自動變量的作用範圍在定義它的函數體或是複合語句內部,只有在定義它的函數內被調用,或是定義它的複合語句被執行時,編譯器才會為其分配內存空間,開始其生存期。當函數調用結束返回,或複合語句執行結束,自動變量所佔用的內存空間就被釋放,變量的值當然也就不復存在,其生存期結束。當函數再次調用,或是複合語句被再次執行時,編譯器又會為其內部的自動變量重新分配內存空間。但不會保留上一次運行的值。而必須被重新分配。因此自動變量始終是相對於函數或複合語句的局部變量。

(2).外部變量(extern)

用說明符「extern」定義的變量稱為外部變量。按預設規則,凡是在所有函數之前,在函數外部定義的變量都是外部變量,定義時可以不寫extern說明符,但是一個函數體內說明一個已在該函數體外或別的程序模塊文件中定義過的外部變量時,剛必須要使用extern說明符。外部變量定義後,它就被分配了固定的內存空間。外部變量的生存期為程序的整個執行時間。 外部變量的存儲不會隨函數或複合語句執行完畢而釋放,因此外部變量屬於全局變量。

C語言允許將大型程序分解為若干個獨立的程序模塊文件,各個模塊可分別進行編譯,然後再將它們連接在一起,如果某個變量需要在所有程序模塊文件中使用,只要在一個程序模塊文件中將該變量定義成全局變量,而在其它程序模塊文件中用extern聲明該變量是已被定義過的外部變量就可以了。

函數是可以相互調用的,定義函數時,如果冠以關鍵字extern 即將其明確定義為一個外部函數。例如 extern int func2(char a,b) 。如果在定義函數時省略關鍵字extern,則隱含為外部函數。如果在調用一個在本程序模塊文件以外的其它模塊文件所定義的函數,則必須要用關鍵字extern說明被調用的函數是一個外部函數。對於具有外部函數相互調用的多模塊程序,可用C51編譯器分別對各個模塊文件進行編譯,最後再用L51連接定位器將它們連接成為一個完整的程序。如下為一個多模塊程序:

程序模塊1,文件名為file1.c

#include

int x=5;

void main()

{

extern void fun1( );

extern viod fun2(int y);

fun1( );

fun1( );

fun1( );

printf( 「\n%d %d\n」,x,fun2(x));

}

程序模塊2,文件名為file2.c

#include

extern int x;

void fun1( )

{

static int a=5; //靜態變量只在第一次調用函數時賦值,退出函數時

//會保留上次的值,下次調用不再重新賦值。

int b=5;

printf(「%d %d %d |」,a,b,x);

a-=2;

b-=2

x-=2;

printf(「%d %d %d |」,a,b,x);

}

int fun2(int y)

{

return(35*x*y);

}

程序執行如果如下:

5 5 5 | 3 3 3

3 5 3 | 1 3 1

1 5 1 | -1 3 1

-1 35

註:C語言不允許在一個函數內嵌套定義另一個函數。為了能夠訪問不同文件中各個函數的變量,除了可以採用參數傳遞的方法外,還可以採用外部變量的方法,上面的例子就說了這一點。不過,儘管使用外部變量在不同函數之間傳遞數據有時比使用函數參數傳遞更為方便,不過當外部變量過多時,會增加程序的調試排錯的困難。使得程序不便於維護。別外不通過參數傳遞直接在函數中改變全局變量的值,有時還會發生一些意想不到的副作用。因些最好還是使用函數參數來傳遞數據。

(3).寄存器變量(register)

為了提高程序的執行效率,C語言允許將一些頻率最高的那些變量,定義為能夠直接使用硬體寄存器的所謂的寄存器變量。定義一個變量時,在變量類型名前冠以「register」 即將該變量定義成為了寄存器變量。寄存器變量可以認為是一自動變量的一種。有效作用範圍也自動變量相同。由於計算機寄存器中寄存器是有限的。不能將所有變量都定義成為寄存器變量,通常在程序中定義寄存器變量時,只是給編譯器一個建議,該變量是否真正成為寄存器變量,要由編譯器根據實際情況來確定。另一方面,C51編譯器能夠識別程序中使用頻率最高的變量,在可能的情況下,即使程序中並未將該變量定義為寄存器變量,編譯器也會自動將其作為寄存器變量處理。被定義的變量是否真正能成為寄存器變量,最終是由編譯器決定的。

(4).靜態變量(static)

使用存儲種類說明符「static」定義的變量為靜態變量,在上面模塊2程序文件中使用了一個靜態變量:static int a =5 ;由於這個變量是在函數fun1( )內部定義,因此稱為內部靜態變量或局部靜態變量。局部靜態變量始終都是存在的,但只有在定義它的函數內部進行訪問,退出函數之後,變量的值仍然保持,但不能進行訪問。

還有一種全局靜態變量,它是在函數外部被定義的。作用範圍從它的定義點開始,一直到程序結束,當一個C語言程序由若干個模塊文件所組成時,全局靜態變量始終存在,但它只能在被定義的模塊文件中訪問,其數據值可為該模塊文件內的所有函數共享,退出該文件後,雖然變量的值仍然保持著,但不能被其它模塊文件訪問。在一個較大的程序中,這就方便了多人設計時,各自寫的程序模塊不會被別的模塊文件所引用。全局靜態變量和單純的全局變量,在編譯時就已經為期分配了固定的內存空間,只是他們的作用範圍不同而已。局部靜態變量是一種在兩次函數調用之間仍能保持其值的局部變量。如下,局部變量的使用——計算度輸出1~5的階乘值。

#include

int fac( int n)

{

static int f=1;

f=f*n;

return(f);

}

main( )

{

int i;

for(i=1;i<=5;i++)

printf(「%d!=%d\n」,i,fac(i));

}

程序執行結果

1!=1

2!=2

3!=6

4!=24

5!=120

註:在這個程序中一共調用了5次計算階乘的函數fac(i),每次調用後輸出一個階乘值i!,同時保留了這個i!值,以便下次再乘(i+1).由此可見,如果要保留函數上一次調用結束時的值,或是在初始化之後變量只被引用而不改變其值,則這時使用局部靜態變量;較為方便,以免在每調用時都要重新進行賦值,但是,使用局部靜態變量需要佔用較多的內存空間,而且降低了程序的可讀性,因此並不建議多用局部靜態變量。

靜態函數:

對於函數也可以定義成為具為靜態存儲種類的屬性,定義函數時在函數名前冠以關鍵字static即將其定義為一個靜態函數。例如static int func1(char x, y)函數是外部型的,使用靜態函數可以使該函數隻局限於當前定義它的模塊文件中。其它模塊文件是不能調用它的。換名話說,就是在其它模塊文件中可以定義與靜態函數完全同名的另一個函數。不會因為程序中存在相同的函數名而發生函數調用時的混亂。 這一點對於進行模塊化程序設計是很有用的。

三、中斷淺談

定義中斷函數如下

void timer1() interrupt 3

{

……

……

}

強烈建議:如上所述,定義中斷函數時不要加using n選項。除非你對你的程序以及單片機的工作過程非常熟悉,否則會帶來不必要的麻煩。具體原因由於篇幅的限制暫不討論。

C51中斷程序編寫要求:

1.中斷函數不能進行參數傳遞,否則,將導致編譯出錯

2.中斷中,不能包含任何參數聲明,否則,將導致編譯出錯。

3.中斷函數沒有返回值,如果企圖定義一個返回值將得到不正確的結果,因些建議在定義中斷函數的時將其定義為void 類型,明確說明沒有返回值。

4.任何情況下都不能直接調用中斷函數,否則會主生編譯出錯。

5.如果中斷函數中用到了浮點運算,必須保存浮點寄存器的狀態。當沒有其它的程序執行浮點運算時(即只有中斷中用到浮點運算),可以不用保存。

6.如果中斷函數中調用了其它函數,則被調用的函數所使用的寄存器組必須與中斷函數相同,用戶必須保證按要求使用相同的寄存器組,否則會產生不正確的結果,這一點必須引起足夠的注意,如果定義中斷函數時沒有使用using選項,則由編譯器選擇一個寄存器組作絕對寄存器訪問。另外,不斷的產生不可預測,中斷函數對其它函數的調用可能形成遞規調用,需要時,可將被中斷調用的其它函數定義為再入函數。

淺析函數的遞規調用與再入函數:

函數的遞規調用: 在調用一個函數的過程中雙直接或間接的調用該函數本身;

再入函數:一種可以在函數體內直接或間接調用其自身的一種函數。

C51編譯器採用一個擴展關鍵字reentrant 作為定義函數時的選項,需要將一個函數定義為再入函數時,只要在函數名後加上關鍵字reentrant即可。空不空格以及空幾格都無所謂。

再入函數剖析:

再入函數可被遞歸調用,無論何時,包括中斷服務函數在內的任何函數都可調用再入函數。與非再入函數的參數傳遞和局部就是的存儲分配方法不同,C51編譯器為每個再入函數都生成一個模擬棧。模擬棧所在的存儲器空間根據再入函數的存儲模式的不同,可以分配到DATA,PDATA 或XDATA。

對再入函數有如下規定:

1.再入函數不能傳送bit類型的參數。也不能定義一個局部位變量,再入函數不能包括位操作以及8051系列單片機的可位尋址區。

2.與PL/ M51兼容的函數,不能具有reentrant屬性,也不能調用再入函數。

3.編譯時,在存儲器模式的基礎上,為再入函數在內部或外部存儲中建立一個模擬堆棧區,稱為再入棧,再入函數的局部變量及參數被放在再入棧中,從而使得再入函數可以進行遞規調用。再非再入函數的局部變量被放在再入棧之外的暫存區內,如果對非再入函數進行遞規調用,則上次調用時使用的局部變量數據將被覆蓋。

4.在同一個程序中可以定義和使用不同存儲器模式的再入函數,任意模式的再入函數不能調用不同模式的再入函數,但可以任意調用非再入函數。

5.在參數的傳遞上,實際參數,可以傳遞給間接調用的再入函數,無再入屬性的間接調用函數不能包含調用參數。但是可以使用定義的全局變量來進行參數傳遞。

四、C51指針深度剖析(非常重要,嵌入式系統開發人員必須要掌握的內容)

注意:由於篇幅所限,本人暫時不打算討論抽象指針的內容。但是你必須上網或去圖書館找找關於抽象指針的資料好好看看,抽象指針很有用的。

指針是C語言中的一個重要概念,使用也十分普遍,正確使用指針類型數據可以有效的表示複雜的數據結構,直接處理內存地址,而且可以更為有效的使用數組。

在C語言中,為了能夠實現直接對內存單元的操作,引入了指針類型的數據,指針類型數據是專門用來確定其它數據類型的地址的,因此一個變量的地址就被稱為該變量的指針如: 一個整形變量i 存放在內存單元40H中,則該內存單元地址40H就是變量i 的指針。如果有一個變量專門用來存放另一個變量的地址,則稱之為「指針變量」

變量指針與指針變量

變量的指針: 是指某個變量的地址,而一個指針變量裡面存放的是另一個變量在內存中的地址。擁有這個地址的變量則稱為該指針變量所指向的變量。 所以每個變量都有它自己的指針(地址),而每一個指針變量都是指向另一個變量的。C語言中用符號「*」來表示「指向」,如下:

i=50;

*ip=50;

如果指針ip這個指針變量指向i那麼,兩個賦值表達或同義,第二個表達式可以解釋為「給指針變量ip所指向的變量賦值50」。

(1).指針變量的定義

指針變量的定義與一般變量的定義類似,其一般形式如下:

數據類型 [存儲器類型] * 標識符;

標識符, 是所定義的指針變量名

數據類型, 說明了該指針變量所指向的變量類型

存儲器類型,是可選的,它是C51編譯器的一種擴展,如果帶有此選項,指針被定義為基於存儲器的指針,無此選項時,被定義為一般指針,這兩種指針的區別在於它們的存儲字節不同。

一般指針:佔用三個字節,第一個字節存放該指針存儲器類型的編碼,第二和第三個字節分別存放該指針的高位和低位地址的偏移量

存儲器類型

IDATA

XDATA

PDATA

DATA

CODE

編碼值

1

2

3

4

5

基於存儲器指針:則該指針長度可為一個字節,也可為兩字節

一個字節: (存儲器類型 idata data pdata)

兩個字節: (存儲器類型為code xdata)

註:在定義指針變量時最好指定其為基於存儲器的指針,這個生成的彙編代碼長精 練一些,而且也節省空間(讀者可自行到C51中寫一個程序,查看其反彙編程序)但在一些函數調用的參數中指針需要採用一般指針,為此C51編譯器允許這兩種指針相互轉換,轉換規則如下:

一般指針轉換成基於存儲器指針,採取截斷,基於存儲器類型指針轉換成一般指針採用擴展的。

(2).指針變量的引用

指針變量是含有一個數據對象地址的特殊變量,指針變量中只能存放地址與指針變量有關的兩個運算符:

& 取地址運算符

* 間接訪問運算符

&a為取變量a的地址,*P為指針變量P所指向的變量。如下:

int i , x, y;

int *pi,*px,*py;

pi=&i; //將變量i的地址賦給指針變量pi,也即pi指向i

px=&x;

py=&py;

*pi=0; //等價於i=0

*px+=6; //等價於 i+=6

(*py)++; //等價於 i++

註:指向同類數據的指針之間可以相互賦值。如pi=px;

(3).指針變量作為函數的參數

函數的參數不僅可以是整型,字符型等數據,還可以是指針類型,指針變量作為函數的參數的作用是將一個變量的地址傳到另一個函數中去,地址傳遞是雙向的,即主調用函數不僅可以向被調用函數傳遞參數,而且還可以從被調用函數返回其結果。下面通過一個簡單的示例來進行說明。

#include

swap(int *pi,int *pj)

{

int temp;

temp=*pi;

*pi=*pj; //把指針變量pj所指向的變量的值送給pi所指向的變量

*pj=temp;

}

main( )

{

int a,b;

int *pa, *pb;

a=9;

b=7;

pa=&a;

pb=&b;

if(a

printf(「\n max=%d,min=%d \n」,a,b);

}

上程序上定義了一個swap( )函數,兩個形參為指針變量,在調用函數時,所用的實參也是指針變量,在調用開始,實參變量將它的值傳遞給形參變量,採取的仍然是「值傳遞」方式,但這時傳遞的是指針的值(地址),傳遞後,形參pi的值為&a,pj的值為&b,即指針變量*pi 和*pa都指向了a, *pj和*pb指向了b。接著使*pj與*pi的值互換,從而達到了實現了a,和b值的互換。雖然函數返回時,pi pj被釋放而不存在,但main函數中a 與b的值已經交換。

(4).數組的指針

在C語言中,指針與數組有著十分密切的關係,任何能夠用數組實現的運算都可以通過指針來完成,例如定義一個具有十個元素的整形數據可以寫成:

int a[10];

數組名a表示元素a[0]的地址,而*a 則表示a所代表地址中的內容,即a[0].

如果定義一個指向整形變量的指針pa並賦以數組a中的第一個元素a[0]的地址;

int *pa;

pa=&a[0]; //也可寫成pa=a;

則可通過指針pa來操作數組a了,即可用*pa代表a[0];*(pa+i)代表a[i],也可以上pa[0];pa[1];pa[2]……pa[9]的形式

(5).字符數組的指針

用指針來描述一個字符數組是十分方便的,字符串是以字符數組的形式給出的,並且每個字符數組都是以轉義字符『\0』作為字符串的結束標誌。因此在判斷一個字符數組是否結束時,通常不採用計數的方法,而是以是否讀到轉義字符『\0』來判別。利用這個特點,可以很方便的用指針處理字符數組。

示例如下:

#include

main()

{

char *s1;

char xdata *s2;

char code str[]={「how are you?」};

s1=str;

s2=0x1000;

while((*s2=*s1)!=』\0』)

{

s2++;

s1++;

}

s1=str;

s2=0x1000;

printf(「%s \n,%s\n」,s1,s2);

}

註:任何一個數組及其數組元素都可以用一個指針及其偏移值來表示,但要注意的是,指針是一個變量,因此像上例中的賦值 運算s1=str, s2=0x1000都是合法的。而數組名是一個常量,不能像變量那樣進行運算,即數組的地址是不能改變的。如上面程序中的語句

char code str[]={「how are you?」};

是將字符串「how are you?」置到數組str中作為初值,而語句s1=str則是將數組str的首地址,即指向數組str的指針賦給指針變量s1,如果對數組進行如下的操作:

str=s1;

str++;

都是錯誤的。

(6).指針的地址計算

指針的地址的計算包括以下幾個方面:

1 賦初值

指針變量的初值可以是NULL(零),也可以是變量,數組,結構及函數等的地址,例如

int a[10],b[10];

float fbuf[100];

char *cptr1=NULL;

char *cptr2=&ch;

int *iptrl=&a[5];

int *iptr2=&b;

float *flptr1=fbuf;

2 指針與整數的加減

指針可以與一個整數或整數表達式進行加減運算,從而獲得該指針當前所指位置前面或後面某個數據的地址。假設p為一個指針變量,n為一個整數,則p+n表示離開指針p當前位置的後面第n個數據的地址。

3 指針與指針相減

指針與指針相減的結果為一個整數值,但它並不是地址,而是表示兩個指針之間的距離或元素的個數,注意,這兩個指針必須是指向同一類型的數據。

4 指針與指針的比較

指向同一類型數據的兩個指針可以進行比較運算,從面獲得兩指針所指地址大小的關係,此外,在計算指針地址的同時,還可以進行間接取值運算,不過在這種情況下,間接取值的地址應該是地址計算後的結果,並且還必須注意運算符的優先級和結合規則。如下設p1是一個指針

a= *p1++;

*與++優先級相同,所以上述賦值操作過程是首先將指針p1 所指向的內容賦值給變量a, 然後p1再指向下一個數據,表明是地址增加而不是p1所指向的變量內容增加。

a=* - -p1;

與上例相同,此處是先p1減一,指向前面一個數據,然後再把p1此時所指向的內容賦給變得a

a=(*p2)++;

此處,由於使用了括號,使得結合次序發生了變化,因此首先是將p2所指的內容賦值給變量a,然後再把p2所指向的變量加1,表明是p1所指變量的大小加一,面不是p1指向下一個元素。

(7).函數型指針

函數不是變量,但它在此內存中仍然需要佔據一定的存儲空間,如果將函數的入口地址賦給一個指針,該指針就是函數型指針,由於函數型指針指向的是函數的入口地址,因此可用指向函數的指針代替函數來調用該函數。利用函數指針,可以將函數作為參數傳遞給另一個函數,此處還可以將函數型指針放在一個指針數組中,則該指針的數組中每一個元素都是指向某個函數的指針。

函數型指針定義形式:

數據類型 (*標識符)();

標識符為所定義的函數型指針變量名,數據類型說明了該指針指向函數的返回值類型。例如:

Int ( *func1)( );

註:函數型指針變量是專門用來存放函數入口地址的,在程序中把哪個函數的地址賦給它,它就指向那個函數,在程序中可以對一個函數型指針多次賦值,該指針可以先後指向不同的函數。後面括號中不要加形參表。

函數型指針賦值形式

函數型指針變量名=函數名

注,賦值時不帶形參表,如有一個max(x,y), 可做執行如下操作

func1=max;

引入了函數型指針後,對函數的調用就可以採用如下兩種方法。

A: z=max(x,y);

B: z=( *func1)(x,y);

注意 若採用函數型指針來調用函數,必須預先對該函數指針進行賦值,使之指向所需調用的函數。

函數型指針通常用來將一個函數的地址作為參數傳遞到另一個函數中去,這種方法對於要調用的函數不是某個固定函數的場合特別適用。

作如下示例讓你更加明白

#include

int max( int x,int y)

{

if(x>y)

return(x);

else

return(y);

}

int min(int x,int y)

{

if(x

return(x);

else

return(y);

}

int add(int x,int y)

{

return(x+y);

}

int process(int x,int y, int( * f)( ) )

{

int result=f(x,y );

printf(「%d\n」,result);

}

main()

{

int a,b;

printf(「Please input a and b:\n」);

scanf(「%d %d」,&a,&b);

printf(「max=」);

process(a,b,max);

printf(「min=」);

process(a,b,min);

printf(「sum=」);

process(a,b,add);

}

本例中三個函數max(),min(),add(),在第一次調用process( )函數時,除了將a b作為實參傳遞給了形參,還將函數名max作為實參將其入口地址傳遞給了process( )函數中的形參——指向函數的指針變量*f。process()中的函數調用語句,result=f(x,y)就相當於result=max(x,y),第二次調用時用了min作為實參,第三次用了add.從而實現每次調用process( )函數時完成了不同的功能。

(8).返回指針型數據的函數

在函數的調用過程結束時,被調用的函數可以帶一個整形,字符等到類型的數據,也可以帶一個指針型數據。即地址。這種返回指針型數據的函數又稱為指針函數。

注意區別指針函數與函數指針。

指針函數定義如下:

數據類型 * 函數名(參數表);

其中數據類型說明了所定義的指針函數返回的指針所指向的數據類型。

int *x(a,b);

就定義了一個指針函數*x調用它以後可以得到一個指向整型數據的指針。注意*x 兩則沒有括號。這與函數指針是完全不同的,並且定義函數指針時,後面的括號是不加形參表列的。也很容易混淆。下面分別定義一個指針函數和函數指針。

函數指針: int (*x)( );

指針函數: int *x(char a,b)

如下示例,指針函數的應用

#include

main()

{

Float T[3][4] = {{60.1,70.3,80.5,90.7},

{30.0,40.1,50.2,60.3},

{90.0,80.5,70.4,60.6}};

float * search(float (*pointer)[4],int n);

float *p;

int i, m;

printf(「please enter the number of chanal:」);

scanf(「%d」,&m);

printf(「\n The temperature of chanal %d are: \n」,m);

p=search(T,m);

for(i=0;i<4;i++)

printf(「%5.1f」,*(p+i));

}

float *scarch(float (*pointer)[4],int n)

{

float *pt;

pt=*(pointer+n);

return(pt);

}

上程序中,紅色標出來的那一行是定義了一個指針型函數,它的形參pointer是指向包含4個元素的一維數組的指針變量。於是pointer+i 就是指向二維數組T的第i行,而*(pointer+i)則指向第i行的第一個元素。pt是一個指針變量。調用search( )後,返回了一個指向第m行的首地址,

(9).指針數組

由於指針本身也是一個變量,因此C語言允許定義指針數組,指針數組適合用來指向若干個字符串,使得字符串的處理更加方便。指針數組的定義方法與普通數組完全相同,一般格式如下:

數據類型 * 數組名[數組長度];

例如:

int *x[2];

char *sptr[5];

指針數據在使用之前往往需要先賦初值,方法與一般數組賦初值類似,。使用指針數組最典型的場合就是通過對字符數組賦初值而實現各維長度不一致的多維數組的定義

#include

main( )

{

int i;

char code *season[4]={「spring」,」summer」,」fall」,」winter」};

for(i=0;i<4;i++)

printf(「\n%c--------%s」,*season[i],season[i]);

}

程序執行結果:

s--------spring

s--------summer

f--------fall

w--------winter

在這個例子中,在code區定義了指向char型數據的4個指針,其初值分別為「spring」,」summer」,」fall」和」winter,這樣可以使這四個數組保存在一段地址連續的地址空間裡(此可以通過程序驗證),如果採用二維數組,那麼會造成內存空間的浪費。因為二維數組的長度必須一致,且要等於最大的一列長度。

下面寫一個(經典)示例程序(不做解釋)

#include

char code daytab[2][13]=

{

{0,31,28,31,30,31,30,31,31,30,31,30,31},

{0,31,29,31,30,31,30,31,31,30,31,30,31},

};

char *mname(int n)

{

char code *mn[]=

{

「llligal month」,」january」,」February」,

「March」,」April」,」May」,」June」,

「July」,」August」,」September」,

「October」,」Novenmber」,」December」

};

return((n<1||n>12)?mn[0];mn[n]);

}

monthday( int y,int yd)

{

int i,leap;

leap=y%4==0&&y%100!=0||y%400==0;

for(i=1;yd>daytab[leap][i];i++)

yd-=day[leap][i];

printf(「%s,%d\n」,mname(i),yd);

}

main( )

{

int year,yearday;

printf(「input year and yearday: \n」);

scanf(「%d,%d」,&year,&yearday);

monthday(year,yearday);

}

(10).指針型指針

指針型指針所指向的是另一個指針變量的地址,故有時也稱為多級別指針。

定義指針型指針的一般形式:

數據類型 標識符

標識符: 定義的指針型指針變量。

數據類型: 說明一個被指針型指針所指向的指針變量所指向的變量數據類型。

#include

main( )

{

int x,*p,q;

x=10;

p=&x;

q=&p;

printf(「 %d\n 」,x); //直接取值

printf(「 %d\n 」, *p); //單重間接取址

printf(「 %d\n 」, q); //多重間接取址

}

三行均是列印出10.

一個指針型指針是一種間接取值的形式,而且這種間接取值的方式還可以進一步延伸,故可以將這種多重間接取值的形式看成一個打針鏈

使用指針型指針的例子

#include

main( )

{

char i;

char j;

char *season[4]={ 「spring」,」summer」,」fall」,」winter」} ;

for(i=0;i<4;++i)

{

j=season+i;

printf(「\n%c--------%s」,*season[i] , *j);

}

}

相關焦點

  • 步進電機控制程序(c語言+51單片機)
    #includereg51.h>本文引用地址:http://www.eepw.com.cn/article/162126.htm#define uint unsigned int
  • 單片機C語言程序設計:INT0 中斷計數
    關鍵字:單片機  C語言  程序設計  INT0  中斷計數 編輯:什麼魚 引用地址:http://news.eeworld.com.cn
  • 基於51單片機的康復儀研究
    引言  人類的平衡能力是人能夠進行各種運動的基礎,人的站立、坐臥、行走都需要有平衡能力。一旦人的平衡能力有障礙,則人的行動能力就受制約,給學習、生活帶來極大的不便。在現代化的社會,人口老齡化問題突出,老年人因某種疾病而導致腦損傷引起平衡能力降低,行動能力受阻。因此,在臨床治療中,需要一種能對平衡功能檢驗的設備。
  • 常用單片機優缺點詳細剖析
    推出,由於其典型的結構和完善的總線專用寄存器的集中管理,眾多的邏輯位操作功能及面向控制的豐富的指令系統,堪稱為一代「經典」,為以後的其它單片機的發展奠定了基礎。這點雖不如PIC,但比51系列還是要優秀的…缺點1.是沒有位操作,都是以字節形式來控制和判斷相關寄存器位的2.C語言與51的C語言在寫法上存在很大的差異,這讓從開始學習51單片機的朋友很不習慣3.通用寄存器一共32個(R0~R31),前16個寄存器(R0~R15)都不能直接與立即數打交道,因而通用性有所下降。
  • 基於單片機的溫度傳感器設計
    打開APP 基於單片機的溫度傳感器設計 發表於 2016-12-27 15:07:07 現代工業生產中,溫度的測量和控制極為普遍,單片機的優點十分明細,其具有體積小、功能強大、低功耗、性價比高等諸多優點,其廣泛應用於自動控制領域,單片機的應用可以有效提高產品的控制質量和自動化水平,利用單片機對溫度進行測控的技術,日益得到廣泛應用。
  • 單片機c語言教程:運算符和表達式(關係運算符)
    單片機C語言中有六種關係運算符,這些東西同樣是在我們小時候學算術時就已經學習過了的:本文引用地址:http://www.eepw.com.cn/article/170887.htm> 大於< 小於>= 大於等於<= 小於等於== 等於
  • 51單片機精確控制步進電機(有TB6600驅動)
    > 基於51單片機控溫程序及電路原理圖 所需要 ** 的溫度值。 51單片機控制模擬交通信號燈 採用單片機的I/O口PO口通過上拉電阻和交通燈相連接,P3.0、P3.1口接到數碼管控制位上,控制數碼管的顯示,程序放在STC89C52RC單片機的ROM中,在十字路口的四組紅、黃、綠交通燈中,由單片機的
  • C語言在單片機開發中的應用
    在單片機的開發應用中,已逐漸開始引入高級語言,C語言就是其中的一種。對用慣了彙編的人來說,總覺得高級語言』可控性』不好,不如彙編那樣隨心所欲。但是只要我們掌握了一定的C語言知識,有些東西還是容易做出來的,以下是筆者實際工作中遇到的幾個問題,希望對初學C51者有所幫助。一、C51熱啟動代碼的編制對於工業控制計算機,往往設有有看門狗電路,當看門狗動作,使計算機復位,這就是熱啟動。
  • 基於51單片機的TFT液晶顯示設計
    0 引言本文引用地址:http://www.eepw.com.cn/article/173037.htm  51單片機作為一種常見的通用單片機, 雖然其內部資源
  • 通過51單片機定時器/計數器實現精確延時
    MCS-51每個定時器有4種工作方式:方式0是13位計數結構,計數器由TLi的8位與THi的低5位構成,定時範圍為(1—213)個機器周期;方式1是16位計數結構,定時範圍為(1~216)個機器周期;方式2是8位計數結構,計數器由TLi的8位組成,當定時器溢時.THi的值能自動裝入TLi中,並在此值基礎上自動計數;在方式3下,也是8位的計數器,並且TO的各控制位和引腳歸TLO
  • 單片機c語言教程:C51運算符和表達式
    如果你是個DELPHI 編程愛好者或是DELPHI程式設計師,你對變量的定義也許習慣了DELPHI 的關鍵字,如 int 類型常會用關鍵字Integer來定義,在用 單片機c語言時你還想用回這個的話,你能這樣寫:本文引用地址:http://www.eepw.com.cn/article/170888.htmtypedef int integer;
  • 單片機的C語言中數組的用法
    數組在C51語言的地位舉足輕重,因此深入地了解數組是很有必要的。下面就對數組進行詳細的介紹。(1)一維數組本文引用地址:http://www.eepw.com.cn/article/201611/320327.htm一維數組是最簡單的數組,用來存放類型相同的數據。數據的存放是線性連續的。
  • 基於51單片機的溫室測試系統
    其工作原理是89C51單片機一次查詢各傳感器的輸出信號,然後89C51對輸入信號進行相應處理後通過顯示模塊44780輸出,同時還可輸出各種報警信號。  2 硬體構成  該系統硬體主要包括以下幾個模塊:89C51主控模塊、傳感器模塊、A/D轉換器、擴展、44780顯示模塊等。
  • 一款基於51單片機的高頻頻率計設計
    摘要 基於51單片機設計了一款測試範圍在1 Hz~10 MHz的頻率計。系統通過峰值有效電路和有效值電路將正弦渡、方波和三角波轉化為直流信號送入單片機,通過編寫相應的程序計算出其有效值和峰峰值的比,實現自動檢測的目的,並由顯示電路顯示測量結果。該系統電路簡潔、軟體編寫簡單、調試難度低。
  • 《電子發燒友網51單片機設計方案TOP10》
    【詳情請參閱:基於51單片機的智能壓力傳感器設計】     基於MCS-51單片機的實時在線環境監測系統   1 引言   基於GPRS和MCS-51單片機的數據採集器是一種實時在線
  • 基於單片機的角度測量儀的設計
    本文分析了基於AT89S51 單片機的角度測量的硬體組成、電路設計原理。給出了主函數的工作流程和原始碼。設計樣機已通過多種環境實驗的測試。  角度測量儀是某控制系統中瞄準裝置的關鍵部件。單片機的主要功能是實現角度值數碼管顯示、角度值範圍的發光二極體指示。該系統的難點在於確保角度值轉換成密位值的轉換精度和系統在非常溫環境下的工作可靠性。  硬體分為以下幾個模塊:控制面板、單片機、光電編碼器、電源、數碼顯示器、發光二極體匹配指示裝置、周視鏡與直瞄鏡照明與除霜,如圖1所示。
  • 基於51單片機的光功率計的設計
    摘要:通過光電傳感器將待測光信號變化轉變為模擬信號,對模擬信號進行AD處理分析得到光信號的參數特性並在51單片機上通過串口通訊輸出。
  • 基於51單片機的智能調光防近視檯燈的設計開題報告
    論文(設計)題目 基於51單片機的智能調光防近視檯燈的設計與實現選題目的和意義:檯燈已是千家萬戶的必需生活用品,經常由於忘記關燈而造成巨大的能源浪費。當夜晚來臨時,人們又摸黑去開燈,非常不方便。長時間的學習工作會用眼過度,導致近視。
  • 51單片機復位電路的設計
    以MCS-51單片機為例,復位脈衝的高電平寬度必須大於2個機器周期,若系統選用6MHz晶振,則一個機器周期為2us,那麼復位脈衝寬度最小應為4us。在實際應用系統中,考慮到電源的穩定時間,參數漂移,晶振穩定時間以及復位的可靠性等因素,必須有足夠的餘量。圖1是利用RC充電原理實現上電復位的電路設計。實踐證明,上電瞬間RC電路充電,RESET引腳出現正脈衝。
  • 一種基於單片機的實用在線式UPS電路詳解
    (1)當市電電壓大於170V  51單片機當檢測到4腳電平為高電平,市電工作指示燈亮起,蓄電池處於充電狀態。  (2)當市電電壓小於160V左右  51單片機當檢測到4腳電平為低電平,市電工作指示燈熄滅,蓄電池放電指示燈亮起。控制繼電器,使得市電停止工作。