- Keil Cx51 V7.0單片機(jī)高級語言編程與μVision2應(yīng)用實(shí)踐
- 徐愛鈞 彭秀華編著
- 197字
- 2018-12-29 19:18:33
第4章 數(shù)組與指針
在第2章中介紹了C語言的基本數(shù)據(jù)類型,如整型、字符型、浮點(diǎn)型等,除此之外C語言還提供一種構(gòu)造類型的數(shù)據(jù)。構(gòu)造類型數(shù)據(jù)是由基本類型數(shù)據(jù)按一定規(guī)則組合而成的,又稱為導(dǎo)出類型數(shù)據(jù)。C語言中的構(gòu)造類型數(shù)據(jù)有數(shù)組類型、結(jié)構(gòu)類型以及聯(lián)合類型等。本章介紹數(shù)組類型的數(shù)據(jù),結(jié)構(gòu)和聯(lián)合類型數(shù)據(jù)將在第5章介紹。由于數(shù)組和指針有著十分密切的聯(lián)系,因此本章還將介紹在C語言中用途極為廣泛的指針類型數(shù)據(jù)。
4.1 數(shù)組的定義與引用
數(shù)組是一組有序數(shù)據(jù)的集合,數(shù)組中的每一個(gè)數(shù)據(jù)都屬于同一種數(shù)據(jù)類型。數(shù)組中的各個(gè)元素可以用數(shù)組名和下標(biāo)來唯一地確定。一維數(shù)組只有一個(gè)下標(biāo),多維數(shù)組有兩個(gè)以上的下標(biāo)。在C語言中數(shù)組必須先定義,然后才能使用。一維數(shù)組的定義形式如下:
數(shù)據(jù)類型 數(shù)組名[常量表達(dá)式];
其中,“數(shù)據(jù)類型”說明了數(shù)組中各個(gè)元素的類型。
“數(shù)組名”是整個(gè)數(shù)組的標(biāo)識符,它的定名方法與變量的定名方法一樣。
“常量表達(dá)式”說明了該數(shù)組的長度,即該數(shù)組中的元素個(gè)數(shù)。常量表達(dá)式必須用方括號“[ ]”括起來,而且其中不能含有變量。下面是幾個(gè)定義一維數(shù)組的例子:
char x[5]; /* 定義字符型數(shù)組x,它具有5個(gè)元素 */ int y[10]; /* 定義整型數(shù)組y,它具有10個(gè)元素 */ float z[15]; /* 定義浮點(diǎn)型數(shù)組z,它具有15個(gè)元素 */
定義多維數(shù)組時(shí),只要在數(shù)組名后面增加相應(yīng)于維數(shù)的常量表達(dá)式即可。對于二維數(shù)組的定義形式為:
數(shù)據(jù)類型 數(shù)組名[常量表達(dá)式1][常量表達(dá)式2];
例如,要定義一個(gè)10×10的整數(shù)矩陣A,可以采用如下的定義方法:
int A[10][10];
需要指出的是,C語言中數(shù)組的下標(biāo)是從0開始的,因此對于數(shù)組char x[5]來說,其中的5個(gè)元素是x[0]~x[4],不存在元素x[5],這一點(diǎn)在引用數(shù)組元素時(shí)是應(yīng)當(dāng)加以注意的。C語言規(guī)定在引用數(shù)值數(shù)組時(shí),只能逐個(gè)引用數(shù)組中的各個(gè)元素而不能一次引用整個(gè)數(shù)組;但如果是字符數(shù)組則可以一次引用整個(gè)數(shù)組。
例4.1:用數(shù)組計(jì)算并輸出Fibonacci數(shù)列的前20項(xiàng)。
Fibonacci數(shù)列在數(shù)學(xué)和計(jì)算機(jī)算法研究中是十分有用的。Fibonacci數(shù)列是這樣的一組數(shù):第一個(gè)數(shù)字為0,第二個(gè)數(shù)字為1,之后每個(gè)數(shù)字都是前兩個(gè)數(shù)字之和。
#include<stdio.h> main() { int f[20], i; f[0]=0; f[1]=1; for (i=2;i<20; i++) f[i]=f[i-2]+f[i-1]; for(i=0; i<20; i++) { if(i%5==0) printf("\n"); printf("%10d",f[i]); } while(1); }
程序執(zhí)行結(jié)果:
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181
4.2 字符數(shù)組
用來存放字符數(shù)據(jù)的數(shù)組稱為字符數(shù)組,它是C語言中常用的一種數(shù)組。字符數(shù)組中的每個(gè)元素都是一個(gè)字符,因此可用字符數(shù)組來存放不同長度的字符串。字符數(shù)組的定義方法與一般數(shù)組相同,下面是兩個(gè)定義字符數(shù)組的例子:
char menu[20]; char string[50];
在C語言中字符串是作為字符數(shù)組來處理的。一個(gè)一維的字符數(shù)組可以存放一個(gè)字符串,這個(gè)字符串的長度應(yīng)小于或等于字符數(shù)組的長度。為了測定字符串的實(shí)際長度,C語言規(guī)定以“\0”作為字符串結(jié)束標(biāo)志,對字符串常量也自動加一個(gè)“\0”作為結(jié)束符。因此字符數(shù)組char menu[20]可存儲一個(gè)長度≤19的不同長度的字符串。
在訪問字符數(shù)組時(shí),遇到“\0”就表示字符串結(jié)束,因此在定義字符數(shù)組時(shí),應(yīng)使數(shù)組長度大于它允許存放的最大字符串的長度。另外,符號“\0”是一個(gè)表示ASCII碼值為0的字符,它不是一個(gè)可顯示字符,而是一個(gè)“空操作符”,在這里僅僅起一個(gè)結(jié)束標(biāo)志的作用。
對于字符數(shù)組的訪問可以通過數(shù)組中的元素逐個(gè)進(jìn)行訪問,也可以對整個(gè)數(shù)組進(jìn)行訪問。
例4.2:對字符數(shù)組進(jìn)行輸入和輸出。
#include<stdio.h> main() { char c[10]; scanf("%s",c); printf("%s\n",c); while(1); }
程序中用“%s”格式控制輸入輸出字符串,這里的輸入輸出操作是對整個(gè)字符數(shù)組進(jìn)行的,因此輸入項(xiàng)必須是數(shù)組名c,而不能用數(shù)組元素名c[i]。在μVision2環(huán)境下對例4.2程序編譯連接通過后進(jìn)行仿真調(diào)試,啟動程序全速運(yùn)行,將光標(biāo)移到串行窗口,從鍵盤輸入HELLO并回車,系統(tǒng)會自動在輸入的字符串后面加一個(gè)結(jié)束符“\0”,然后輸出HELLO,如果輸入的字符數(shù)大于10,則只取前10個(gè)字符作為有效字符輸出。
前面介紹了數(shù)組的定義方法,可以在內(nèi)存中開辟一個(gè)相應(yīng)于數(shù)組元素個(gè)數(shù)的存儲空間,數(shù)組中各個(gè)元素的賦值是在程序運(yùn)行過程中進(jìn)行的。如果希望在定義數(shù)組的同時(shí)給數(shù)組中各個(gè)元素賦以初值,可以采用如下方法:
數(shù)據(jù)類型 [存儲器類型] 數(shù)組名[常量表達(dá)式]={常量表達(dá)式表};
其中,“數(shù)據(jù)類型”指出數(shù)組元素的數(shù)據(jù)類型。
“存儲器類型”是可選項(xiàng),指出定義數(shù)組所在的存儲器空間。
“常量表達(dá)式表”中給出各個(gè)數(shù)組元素的初值。
需要注意的是,在定義數(shù)組的同時(shí)對數(shù)組元素賦初值時(shí),初值的個(gè)數(shù)必須小于或等于數(shù)組中元素的個(gè)數(shù)(即數(shù)組長度),否則在程序編譯時(shí)作為出錯(cuò)處理。賦初值時(shí)可以不指定數(shù)組的長度,編譯器會根據(jù)初值的個(gè)數(shù)自動計(jì)算出該數(shù)組的長度。因此數(shù)組名后面的“常量表達(dá)式”為可選項(xiàng),省略該選項(xiàng)時(shí)數(shù)組的長度由實(shí)際初值的個(gè)數(shù)決定。例如:
unsigned char a[5]={0x11,0x22,0x33,0x44,0x55} unsigned char xdata a[]={0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99}
對于多維數(shù)組可以采用同樣的方法來賦值,例如,可用下面的方法在定義一個(gè)二維數(shù)組的同時(shí)賦以初值:
int MAX[4][3]= {{1,4,7}, {2,5,8}, {3,6,9}, {0, 0, 0}};
例4.3:用冒泡法對一組數(shù)據(jù)進(jìn)行排序。
#include <reg51.h> #include <stdio.h> void main() { unsigned char xdata a[]= {0x3f,0x44,0x32,0x54,0x66,0x56,0x99,0x88,0x77,0x11,0x34}; unsigned char i,j,t; printf("the unsorted numbers : \n"); for (i=0;i<=9;i++) printf("%bx ",a[i]); printf("\n"); for (j=0;j<=8;j++) for (i=0;i<=9-j;i++) if (a[i]>a[i+1]) {t=a[i];a[i]=a[i+1];a[i+1]=t;} printf("the sorted numbers : \n"); for (i=0;i<=10;i++) printf("%bx ",a[i]); while(1); }
程序執(zhí)行結(jié)果:
the unsorted numbers : 0x3f,0x44,0x32,0x54,0x66,0x56,0x99,0x88,0x77,0x11,0x34 the sorted numbers : 0x11,0x32,0x34,0x3f,0x44,0x54,0x56,0x66,0x77,0x88,0x99
給字符數(shù)組賦初值對于在程序存儲器ROM中制作一些常數(shù)表格特別有用,例如,可以采用如下方法在ROM中制作一張共陰極LED的顯示字符段碼表:
char code SEG[11]= {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
利用字符數(shù)組可以很方便地實(shí)現(xiàn)LED段碼的查表顯示。圖4.1是利用8031單片機(jī)串行口實(shí)現(xiàn)的動態(tài)LED掃描顯示接口電路,在串行口上擴(kuò)展一片移位寄存器74LS164作為共陰極7段LED的段碼數(shù)據(jù)口,8031的P1.0~P1.3作為LED顯示器的位掃描信號,串行口工作于移位寄存器方式(方式0)。執(zhí)行下面的程序后可在LED上顯示出“8031”這幾個(gè)數(shù)字。
例4.4:利用字符數(shù)組實(shí)現(xiàn)LED數(shù)字顯示。
#include <reg51.h> char code seg[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; void dsp(char digt, j) { unsigned char tmp; SBUF=seg[digt]; P1=j; tmp=0x7f; while(tmp--); } void main() { SCON=0x00; while(1) { dsp(0x08, 1); dsp(0x00, 2); dsp(0x03, 4); dsp(0x01, 8); } }

圖4.1 用串行口實(shí)現(xiàn)的LED動態(tài)顯示電路
4.3 數(shù)組作為函數(shù)的參數(shù)
除了可以用變量作為函數(shù)的參數(shù)之外,還可以用數(shù)組名作為函數(shù)的參數(shù)。一個(gè)數(shù)組的數(shù)組名表示該數(shù)組的首地址。用一個(gè)數(shù)組名作為函數(shù)的參數(shù)時(shí),在進(jìn)行函數(shù)調(diào)用的過程中參數(shù)傳遞方式采用的是地址傳遞。將實(shí)際參數(shù)數(shù)組的首地址傳遞給被調(diào)函數(shù)中的形式參數(shù)數(shù)組,這樣一來兩個(gè)數(shù)組就占用同一段內(nèi)存單元。如圖4.2所示,若數(shù)組a的起始地址為0x1000,則數(shù)組b的起始地址也是0x1000,顯然數(shù)組a和b占用同一段內(nèi)存單元。

圖4.2 兩個(gè)數(shù)組占用同一段內(nèi)存單元
用數(shù)組名作為函數(shù)的參數(shù),應(yīng)該在主調(diào)函數(shù)和被調(diào)函數(shù)中分別進(jìn)行數(shù)組定義,而不能只在一方定義數(shù)組。而且在兩個(gè)函數(shù)中定義的數(shù)組類型必須一致,如果類型不一致將導(dǎo)致編譯出錯(cuò)。實(shí)參數(shù)組和形參數(shù)組的長度可以一致也可以不一致,編譯器對形參數(shù)組的長度不作檢查,只是將實(shí)參數(shù)組的首地址傳遞給形參數(shù)組。如果希望形參數(shù)組能得到實(shí)參數(shù)組的全部元素,則應(yīng)使兩個(gè)數(shù)組的長度一致。定義形參數(shù)組時(shí)可以不指定長度,只在數(shù)組名后面跟一個(gè)空的方括號[]。這時(shí)為了在被調(diào)函數(shù)中處理數(shù)組元素的需要,應(yīng)另外設(shè)置一個(gè)參數(shù)來傳遞數(shù)組元素的個(gè)數(shù)。
例4.5:用數(shù)組作為函數(shù)的參數(shù),計(jì)算兩個(gè)不同長度的數(shù)組中所有元素的平均值。
#include<stdio.h> float average(array, n) int n; float array[]; { int i; float aver, sum=array[0]; for (i=1; i<n; i++) sum=sum+array[i]; aver=sum/n; return(aver); } main() { float pot_1[5]={99.9,88.8,77.7,66.6,0}; float pot_2[10]={11.1,22.2,33.3,44.4,55.5,99.9,88.8,77.7,66.6,0}; printf("the average of A is %6.2f\n", average(pot_1,5)); printf("the average of B is %6.2f\n", average(pot_2,10)); while(1); }
程序執(zhí)行結(jié)果:
the average of A is 66.60 the average of B is 49.95
在這個(gè)程序中定義了一個(gè)求平均值的函數(shù)average(),它有兩個(gè)形式參數(shù)array和n。array是一個(gè)長度不定的float類型的數(shù)組,n是int類型的變量。在主函數(shù)main()中定義了兩個(gè)長度確定的float類型的數(shù)組pot_1[5]和pot_2[10]。通過嵌套函數(shù)調(diào)用實(shí)現(xiàn)求數(shù)組元素平均值的運(yùn)算并輸出。可以看到,兩次調(diào)用average()函數(shù)時(shí)數(shù)組的長度是不同的,在調(diào)用時(shí)通過一個(gè)實(shí)際參數(shù)(5或10)將數(shù)組的長度傳遞給形式參數(shù)n,從而使average()函數(shù)能處理數(shù)組pot_1和pot_2中的所有元素。
用數(shù)組名作為函數(shù)的參數(shù),參數(shù)的傳遞過程采用的是地址傳遞。地址傳遞方式具有雙向傳遞的性質(zhì),即形式參數(shù)的變化將導(dǎo)致實(shí)際參數(shù)也發(fā)生變化,這種性質(zhì)在程序設(shè)計(jì)中有時(shí)是很有用的。下面的例子就是利用這一性質(zhì)來對數(shù)組a中的10個(gè)整數(shù)按從小到大的順序排序。
例4.6:采用選擇法對數(shù)組元素進(jìn)行排序。
#include<stdio.h> void sort (array, n) int array[]; int n; { int i, j, k, t; for (i=0; i<n-1; i++) { k=i; for (j=i+1; j<n; j++) if (array[j]<array[k]) k=j; t=array[k]; array[k]=array[i]; array[i]=t; } } main() { int a[10], i; printf("please enter the array\n"); for (i=0; i<10; i++) scanf("%d", &a[i]); sort(a, 10); printf("the sorted array is: \n"); for (i=0; i<10; i++) printf("%d ", a[i]); printf("\n"); while(1); }
程序執(zhí)行結(jié)果:
please enter the array 9 7 5 3 1 8 6 4 2 0 the sorted array is: 0 1 2 3 4 5 6 7 8 9
在這個(gè)例子中,主程序在執(zhí)行函數(shù)調(diào)用語句sort(a, 10)的前后,數(shù)組a中各個(gè)元素的值是不同的。執(zhí)行函數(shù)調(diào)用語句之前,數(shù)組a中的元素是無序的,執(zhí)行函數(shù)調(diào)用語句之后,數(shù)組a中的元素已經(jīng)按大小排序。這是因?yàn)樵趫?zhí)行被調(diào)用函數(shù)時(shí),形式參數(shù)數(shù)組已經(jīng)進(jìn)行了排序,而形式參數(shù)數(shù)組的改變會使實(shí)際參數(shù)數(shù)組隨之改變,從而使主函數(shù)中的數(shù)組a也進(jìn)行了排序。
對于多維數(shù)組作為函數(shù)的參數(shù)與一維數(shù)組的情形類似。可以用多維數(shù)組名作為函數(shù)的實(shí)際參數(shù)和形式參數(shù),在被調(diào)用函數(shù)中對形式參數(shù)說明時(shí)可以指定每一維的長度,也可以省略數(shù)組第一維的長度說明,但是絕不能省略第二維以及其他高維的長度說明。因?yàn)閺膶?shí)際參數(shù)傳送過來的是數(shù)組的起始地址,在內(nèi)存中數(shù)組是按行存放的,而并不區(qū)分?jǐn)?shù)組的行和列。如果在形式參數(shù)中不說明列數(shù),編譯器就無法確定該數(shù)組有幾行幾列。
例4.7:求一個(gè)3×4矩陣中的最大元素。
#include<stdio.h> max(int array[][4]) { int i, j, max; max=array[0][0]; for (i=0; i<3; i++) for (j=0; j<4; j++) if (array[i][j]>max) max=array[i][j]; return(max); } main() { int a[3][4]={{1,3,5,7},{2,4,6,8},{15,17,34,12}}; printf("max value is %d\n", max(a)); while(1); }
程序執(zhí)行結(jié)果:
max value is 34
4.4 指針
指針是C語言中的一個(gè)重要概念,指針類型數(shù)據(jù)在C語言程序中的使用十分普遍。正確地使用指針類型數(shù)據(jù),可以有效地表示復(fù)雜的數(shù)據(jù)結(jié)構(gòu),直接處理內(nèi)存地址,而且可以更為有效地使用數(shù)組。
4.4.1 指針與地址
眾所周知,一個(gè)程序的指令、常量和變量等都要存放在機(jī)器的內(nèi)存單元中,而機(jī)器的內(nèi)存是按字節(jié)來劃分存儲單元的。給內(nèi)存中每個(gè)字節(jié)都賦予一個(gè)編號,這就是存儲單元的地址。各個(gè)存儲單元中所存放的數(shù)據(jù),稱為該存儲單元的內(nèi)容。計(jì)算機(jī)在執(zhí)行任何一個(gè)程序時(shí)都要涉及許多的尋址操作。所謂尋址,就是按照內(nèi)存單元的地址來訪問該存儲單元中的內(nèi)容,即按地址來讀或?qū)懺搯卧械臄?shù)據(jù)。由于通過地址可以找到所需要的存儲單元,因此可以說地址是指向存儲單元的。
在C語言中為了能夠?qū)崿F(xiàn)直接對內(nèi)存單元進(jìn)行操作,引入了指針類型的數(shù)據(jù)。指針類型數(shù)據(jù)是專門用來確定其他類型數(shù)據(jù)地址的,因此一個(gè)變量的地址就稱為該變量的指針。例如,有一個(gè)整型變量i存放在內(nèi)存單元40H中,則該內(nèi)存單元地址40H就是變量i的指針。如果有一個(gè)變量專門用來存放另一個(gè)變量的地址,則稱之為“指針變量”,例如,如果用另一個(gè)變量ip來存放整型變量i的地址40H,則ip即為一個(gè)指針變量。
變量的指針和指針變量是兩個(gè)不同的概念。變量的指針就是該變量的地址,而一個(gè)指針變量里面存放的內(nèi)容是另一個(gè)變量在內(nèi)存中的地址,擁有這個(gè)地址的變量則稱為該指針變量所指向的變量。每一個(gè)變量都有它自己的指針(即地址),而每一個(gè)指針變量都是指向另一個(gè)變量的。
為了表示指針變量和它所指向的變量之間的關(guān)系,C語言中用符號“*”來表示“指向”。例如,整型變量i的地址40H存放在指針變量ip中,則可用*ip來表示指針變量ip所指向的變量,即*ip也表示變量i,下面兩個(gè)賦值語句:
i=0x50; *ip=0x50;
都是給同一個(gè)變量賦值0x50。圖4.3形象地說明了指針變量ip和它所指向的變量i之間的關(guān)系。

圖4.3 指針變量和它所指向的變量
從圖4.3可以看到,對于同一個(gè)變量i,可以通過變量名i來訪問它,也可以通過指向它的指針變量ip,用*ip來訪問它。前者稱為直接訪問,后者稱為間接訪問。符號“*”稱為指針運(yùn)算符,它只能與指針變量一起聯(lián)用,運(yùn)算的結(jié)果是得到該指針變量所指向的變量的值。
在2.2.5節(jié)中介紹了一個(gè)取地址運(yùn)算符“&”,它可以與一個(gè)變量聯(lián)用,其作用是求取該變量的地址。通過運(yùn)算符“&”可以將一個(gè)變量的地址賦值給一個(gè)指針變量。例如:賦值語句ip=&I;它的作用是取得變量i的地址并賦給指針變量ip。通過這種賦值后即可以說指針變量ip指向了變量i。不要將符號“&”和“*”弄混淆,&i是取變量i的地址,*ip是取指針變量ip所指向的變量的值。
4.4.2 指針變量的定義
指針變量的定義與一般變量的定義類似,其一般形式如下:
數(shù)據(jù)類型 [存儲器類型1]* [存儲器類型2]標(biāo)識符;
其中“標(biāo)識符”是所定義的指針變量名。
“數(shù)據(jù)類型”說明了該指針變量所指向的變量的類型。
“存儲器類型1”和“存儲器類型2”是可選項(xiàng),它是Keil Cx51編譯器的一種擴(kuò)展,如果帶有“存儲器類型1”選項(xiàng),指針被定義為基于存儲器的指針,無此選項(xiàng)時(shí),被定義為一般指針。這兩種指針的區(qū)別在于它們的存儲字節(jié)不同。一般指針在內(nèi)存中占用3個(gè)字節(jié),第一個(gè)字節(jié)存放該指針存儲器類型的編碼(由編譯時(shí)編譯模式的默認(rèn)值確定),第二個(gè)和第三個(gè)字節(jié)分別存放該指針的高位和低位地址偏移量。存儲器類型的編碼值如下:

“存儲器類型2”選項(xiàng)用于指定指針本身的存儲器空間。
一般指針可用于存取任何變量而不必考慮變量在8051單片機(jī)存儲器空間的位置,許多C51庫函數(shù)采用了一般指針,函數(shù)可以利用一般指針來存取位于任何存儲器空間的數(shù)據(jù)。下面是一個(gè)一般指針定義的例子,同時(shí)給出了匯編語言代碼,請注意匯編語言代碼中指針第一個(gè)字節(jié)是存儲器類型編碼值,第二、三字節(jié)是地址偏移量。
例4.8:定義一般指針。
3stmt level source 1 char *c_ptr; /* char ptr */ 2 int *i_ptr; /* int ptr */ 3 long *l_ptr; /* long ptr */ 4 5 void main (void) 6 { 7 1 char data dj; /* data vars */ 8 1 int data dk; 9 1 long data dl; 10 1 11 1 char xdata xj; /* xdata vars */ 12 1 int xdata xk; 13 1 long xdata xl; 14 1 15 1 char code cj = 9; /* code vars */ 16 1 int code ck = 357; 17 1 long code cl = 123456789; 18 1 19 1 20 1 c_ptr = &dj; /* data ptrs */ 21 1 i_ptr = &dk; 22 1 l_ptr = &dl; 23 1 24 1 c_ptr = &xj; /* xdata ptrs */ 25 1 i_ptr = &xk; 26 1 l_ptr = &xl; 27 1 28 1 c_ptr = &cj; /* code ptrs */ 29 1 i_ptr = &ck; 30 1 l_ptr = &cl; 31 1 } 32 ASSEMBLY LISTING OF GENERATED OBJECT CODE ;FUNCTION main (BEGIN) ;SOURCE LINE # 5 ;SOURCE LINE # 6 ;SOURCE LINE # 20 0000750000 R MOV c_ptr,#00H 0003750000 R MOV c_ptr+01H,#HIGH dj 0006750000 R MOV c_ptr+02H,#LOW dj ;SOURCE LINE # 21 0009750000 R MOV i_ptr,#00H 000C 750000 R MOV i_ptr+01H,#HIGH dk 000F 750000 R MOV i_ptr+02H,#LOW dk ;SOURCE LINE # 22 0012750000 R MOV l_ptr,#00H 0015750000 R MOV l_ptr+01H,#HIGH dl 0018750000 R MOV l_ptr+02H,#LOW dl ;SOURCE LINE # 24 001B 750001 R MOV c_ptr,#01H 001E 750000 R MOV c_ptr+01H,#HIGH xj 0021750000 R MOV c_ptr+02H,#LOW xj ;SOURCE LINE # 25 0024750001 R MOV i_ptr,#01H 0027750000 R MOV i_ptr+01H,#HIGH xk 002A 750000 R MOV i_ptr+02H,#LOW xk ;SOURCE LINE # 26 002D 750001 R MOV l_ptr,#01H 0030750000 R MOV l_ptr+01H,#HIGH xl 0033750000 R MOV l_ptr+02H,#LOW xl ;SOURCE LINE # 28 00367500FF R MOV c_ptr,#0FFH 0039750000 R MOV c_ptr+01H,#HIGH cj 003C 750000 R MOV c_ptr+02H,#LOW cj ;SOURCE LINE # 29 003F 7500FF R MOV i_ptr,#0FFH 0042750000 R MOV i_ptr+01H,#HIGH ck 0045750000 R MOV i_ptr+02H,#LOW ck ;SOURCE LINE # 30 00487500FF R MOV l_ptr,#0FFH 004B 750000 R MOV l_ptr+01H,#HIGH cl 004E 750000 R MOV l_ptr+02H,#LOW cl ;SOURCE LINE # 31 0051 22 RET ;FUNCTION main (END)
上例中一般指針c_ptr、i_ptr、l_ptr全部位于8051單片機(jī)的片內(nèi)數(shù)據(jù)存儲器中,如果在定義一般指針時(shí)帶有“存儲器類型2”選項(xiàng),則可指定一般指針本身的存儲器空間位置,例如:
char * xdata strptr; /* 位于xdata空間的一般指針 */ int * data numptr; /* 位于data空間的一般指針 */ long * idata varptr; /* 位于idata空間的一般指針 */
由于一般指針?biāo)笇ο蟮拇鎯ζ骺臻g位置只有在運(yùn)行期間才能確定,編譯器在編譯期間無法優(yōu)化存儲方式,必須生成一般代碼以保證能對任意空間的對象進(jìn)行存取,因此一般指針?biāo)a(chǎn)生的代碼運(yùn)行速度較慢,如果希望加快運(yùn)行速度則應(yīng)采用基于存儲器的指針。基于存儲器的指針?biāo)笇ο缶哂忻鞔_的存儲器空間,長度可為1個(gè)字節(jié)(存儲器類型為IDATA、DATA、PDATA)或2個(gè)字節(jié)(存儲器類型為CODE、XDATA),例如:
char data * str; /* 指向DATA空間char型數(shù)據(jù)的指針 */ int xdata *numtab; /* 指向XDATA空間int型數(shù)據(jù)的指針 */ long code *powtab; /* 指向CODE空間long型數(shù)據(jù)的指針 */
與一般指針類似,若定義時(shí)帶有“存儲器類型2”選項(xiàng),則可指定基于存儲器的指針本身的存儲器空間位置,例如:
char data * xdata str; int xdata * data numtab; long code * idata powtab;
基于存儲器的指針長度比一般指針短,可以節(jié)省存儲器空間,運(yùn)行速度快,但它所指對象具有確定的存儲器空間,缺乏靈活性。下面是一個(gè)基于存儲器的指針定義例子,其匯編代碼長度明顯小于一般指針。
例4.9:基于存儲器的指針定義。
stmt level source 1 char data *c_ptr; /* memory-specific char ptr */ 2 int xdata *i_ptr; /* memory-specific int ptr */ 3 long code *l_ptr; /* memory-specific long ptr */ 4 5 long code powers_of_ten []= 6 { 7 1L, 8 10L, 9 100L, 10 1000L, 11 10000L, 12 100000L, 13 1000000L, 14 10000000L, 15 100000000L, 16 }; 17 18 void main (void) { 19 1 char data strbuf [10]; 20 1 int xdata ringbuf [1000]; 21 1 22 1 c_ptr = &strbuf [0]; 23 1 i_ptr = &ringbuf [0]; 24 1 l_ptr = &powers_of_ten [0]; 25 1 } 26 ASSEMBLY LISTING OF GENERATED OBJECT CODE ;FUNCTION main (BEGIN) ;SOURCE LINE # 18 ;SOURCE LINE # 22 0000750000 R MOV c_ptr,#LOW strbuf ;SOURCE LINE # 23 0003750000 R MOV i_ptr,#HIGH ringbuf 0006750000 R MOV i_ptr+01H,#LOW ringbuf ;SOURCE LINE # 24 0009750000 R MOV l_ptr,#HIGH powers_of_ten 000C 750000 R MOV l_ptr+01H,#LOW powers_of_ten ;SOURCE LINE # 25 000F 22 RET ;FUNCTION main (END)
在一些函數(shù)調(diào)用中進(jìn)行參數(shù)傳遞時(shí)需要采用一般指針,例如,Cx51的庫函數(shù)printf()、sprintf()、gets()等便是如此。當(dāng)傳遞的參數(shù)是基于存儲器的指針時(shí),若不特別指明,Keil Cx51編譯器會自動將其轉(zhuǎn)換為一般指針,如果被調(diào)用函數(shù)的參數(shù)應(yīng)該為某種較短長度指針,則會產(chǎn)生程序出錯(cuò),為避免此類錯(cuò)誤,應(yīng)采用#include預(yù)處理器命令將函數(shù)的說明文件包含到C語言源程序中去。
一般指針與基于存儲器的指針轉(zhuǎn)換規(guī)則如下(一般指針用GP表示)。
GP→xdata:使用GP的偏移部分(2字節(jié))。
GP→code:同上。
GP→idata:使用GP偏移部分的低字節(jié),高字節(jié)不用。
GP→data:同上。
GP→pdata:同上。
xdata→GP:一般指針的存儲器類型編碼被設(shè)定為0x01,使用xdata *的雙字節(jié)偏移量。
code→GP:一般指針的存儲器類型編碼被設(shè)定為0xFF,使用code *的雙字節(jié)偏移量。
idata→GP:一般指針的存儲器類型編碼被設(shè)定為0x00,指針的一字節(jié)偏移量被轉(zhuǎn)換為unsigned int類型。
data→GP:同上。
pdata→GP:一般指針的存儲器類型編碼被設(shè)定為0xFE,指針的一字節(jié)偏移量被轉(zhuǎn)換為unsigned int類型。
例4.10:指針轉(zhuǎn)換及其所生成的代碼。
stmt level source 1 int *p1; /* 一般指針,3字節(jié) */ 2 int xdata *p2; /* xdata指針,2字節(jié) */ 3 int idata *p3; /* idata指針,1字節(jié) */ 4 int code *p4; /* code指針,2字節(jié) */ 5 6 void pconvert (void) { 7 1 p1 = p2; /* xdata指針轉(zhuǎn)換為一般指針 */ 8 1 p1 = p3; /* idata指針轉(zhuǎn)換為一般指針 */ 9 1 p1 = p4; /* code指針轉(zhuǎn)換為一般指針 */ 10 1 11 1 p4 = p1; /* 一般指針轉(zhuǎn)換為code指針 */ 12 1 p3 = p1; /* 一般指針轉(zhuǎn)換為idata指針 */ 13 1 p2 = p1; /* 一般指針轉(zhuǎn)換為xdata指針 */ 14 1 15 1 p2 = p3; /* idata指針轉(zhuǎn)換為xdata指針 (警告錯(cuò)誤) */ 16 1 p3 = p4; /* code指針轉(zhuǎn)換為idata指針 (警告錯(cuò)誤) */ 17 1 } 18 ASSEMBLY LISTING OF GENERATED OBJECT CODE ;FUNCTION pconvert (BEGIN) ;SOURCE LINE # 6 ;SOURCE LINE # 7 0000750001 R MOV p1,#01H 0003850000 R MOV p1+01H,p2 0006850000 R MOV p1+02H,p2+01H ;SOURCE LINE # 8 0009750000 R MOV p1,#00H 000C 750000 R MOV p1+01H,#00H 000F 850000 R MOV p1+02H,p3 ;SOURCE LINE # 9 0012 AA00 R MOV R2,p4 0014 A900 R MOV R1,p4+01H 0016 7BFF MOV R3,#0FFH 0018 8B00 R MOV p1,R3 001A 8A00 R MOV p1+01H,R2 001C 8900 R MOV p1+02H,R1 ;SOURCE LINE # 11 001E AE02 MOV R6,AR2 0020 AF01 MOV R7,AR1 0022 8E00 R MOV p4,R6 0024 8F00 R MOV p4+01H,R7 ;SOURCE LINE # 12 0026 AF01 MOV R7,AR1 0028 8F00 R MOV p3,R7 ;SOURCE LINE # 13 002A AE02 MOV R6,AR2 002C 8E00 R MOV p2,R6 002E 8F00 R MOV p2+01H,R7 ;SOURCE LINE # 15 0030750000 R MOV p2,#00H 0033 8F00 R MOV p2+01H,R7 ;SOURCE LINE # 16 0035850000 R MOV p3,p4+01H ;SOURCE LINE # 17 0038 22 RET ;FUNCTION pconvert (END)
4.4.3 指針變量的引用
指針變量是含有一個(gè)數(shù)據(jù)對象地址的特殊變量,指針變量中只能存放地址。與指針變量有關(guān)的運(yùn)算符有兩個(gè),它們是取地址運(yùn)算符&和間接訪問運(yùn)算符*。例如:&a為取變量a的地址,* p為指針變量p所指向的變量。
指針變量經(jīng)過定義之后可以像其他基本類型變量一樣引用。例如:
● 變量定義
int i, x, y, *pi, *px, *py;
● 指針賦值
pi=&i; /* 將變量i的地址賦給指針變量pi,使pi指向i */ px=&x; /* px指向x */ py=&y; /* py指向y */
● 指針變量引用
*pi=0; /* 等價(jià)于i=0; */ *pi+=1; /* 等價(jià)于i+=1; */ (*pi)++; /* 等價(jià)于i++; */
指向相同類型數(shù)據(jù)的指針之間可以相互賦值。例如:
px=py;
原來指針px指向x,py指向y,經(jīng)上述賦值之后,px和py都指向y。
例4.11:輸入兩個(gè)整數(shù)x和y,經(jīng)比較后按大小順序輸出。
#include<stdio.h> main() { int x, y; int *p, *p1, *p2; printf("Input x and y: \n"); scanf("%d %d", &x, &y); p1=&x; p2=&y; if(x<y) { p=p1; p1=p2; p2=p; } printf("max=%d, min=%d, \n", *p1, *p2); while(1); }
程序執(zhí)行結(jié)果:
Input x and y: 4 8 回車 max=8, min=4
這個(gè)程序中定義了三個(gè)指針變量*p、*p1和*p2,它們都指向整型變量。經(jīng)過賦值之后,p1指向x,p2指向y。然后比較變量x和y的大小,若x<y,則將p1和p2交換,使p1指向y,p2指向x;若x>y則不交換。最后的結(jié)果必然使指針p1指向較大的數(shù),p2指向較小的數(shù),按順序輸出*p1和*p2的值,就可得到正確的結(jié)果。值得注意的是在程序執(zhí)行過程中,變量x和y的值并未交換,所交換的只是它們的指針。由于指針p、p1和p2都是指向int類型數(shù)據(jù)的指針,故可以相互賦值,實(shí)現(xiàn)指針p1和p2的交換。
4.4.4 指針變量作為函數(shù)的參數(shù)
函數(shù)的參數(shù)不僅可以是整型、實(shí)型、字符型等數(shù)據(jù),還可以是指針類型的數(shù)據(jù)。指針變量作為函數(shù)的參數(shù)的作用是將一個(gè)變量的地址傳送到另一個(gè)函數(shù)中去,地址傳遞是雙向的,即主調(diào)用函數(shù)不僅可以向被調(diào)用函數(shù)傳遞參數(shù),而且還可以從被調(diào)用函數(shù)返回其結(jié)果。下面通過一個(gè)例子來進(jìn)行說明。
例4.12:利用指針變量作為函數(shù)的參數(shù)實(shí)現(xiàn)兩個(gè)元素的相互交換。
#include <stdio.h> swap(int * pi, int * pj) { int temp; temp = * pi; * pi = * pj; * pj = temp; } main() { int a,b; int *pa, *pb; printf("Please input a and b: \n"); scanf("%d %d", &a, &b); pa = &a; pb = &b; if(a<b) swap(pa, pb); printf("\n max=%d, min=%d \n", a, b); while(1); }
程序執(zhí)行結(jié)果:
Please input a and b: 1234 5678 回車 max=5678, min=1234
程序中自定義函數(shù)swap()的功能是交換兩個(gè)變量a和b的值,swap()函數(shù)的兩個(gè)形式參數(shù)p1和p2是指針變量。程序開始執(zhí)行時(shí),先輸入a和b的值,然后將a和b的地址分別賦給指針變量pa和pb,使pa指向a,pb指向b。接著執(zhí)行if語句,如果a<b則調(diào)用swap()函數(shù)。
注意函數(shù)調(diào)用時(shí)的實(shí)參pa和pb也是指針變量,在調(diào)用開始時(shí),實(shí)參變量將它的值傳遞給形參變量,采取的仍然是“值傳遞”方式,但這時(shí)傳遞的是指針的值(即地址)。參數(shù)傳遞完成后,形參pi的值為&a,pj的值為&b,即指針變量*pi和*pa都指向了變量a,*pj和*pb都指向了變量b。接著執(zhí)行swap()函數(shù)體,使*pi和*pj的值互換,從而實(shí)現(xiàn)了a和b的值的互換。函數(shù)返回時(shí),雖然pi和pj被釋放而不再存在,但main()函數(shù)中a和b的值已經(jīng)被交換,因此最后能輸出正確的結(jié)果。
由此可見以指針變量作為函數(shù)的參數(shù),被調(diào)用函數(shù)在執(zhí)行過程中使指針變量所指向的變量值發(fā)生變化,函數(shù)調(diào)用結(jié)束后,這些變量值的變化將被保留下來,從而可以實(shí)現(xiàn)“在被調(diào)用函數(shù)中改變變量的值,在主調(diào)用函數(shù)中使用這些被改變了的值”。
如果希望通過函數(shù)調(diào)用得到n個(gè)要改變的值,可以在主調(diào)用函數(shù)中設(shè)置n個(gè)變量,再用n個(gè)指針變量來指向它們,然后將指針變量作為實(shí)參將這n個(gè)變量的地址傳遞給被調(diào)用函數(shù)中的形參,通過形參指針變量來改變這n個(gè)變量的值,被改變了值將被保留下來,最后在主調(diào)用函數(shù)中就可以使用這些被改變了的值。
4.5 數(shù)組的指針
4.5.1 用指針引用數(shù)組元素
在C語言中指針與數(shù)組有著十分密切的關(guān)系,任何能夠用數(shù)組實(shí)現(xiàn)的運(yùn)算都可以通過指針來完成。例如,定義一個(gè)具有10個(gè)元素的整型數(shù)組可以寫成:
int a[10];
數(shù)組a中各個(gè)元素分別為a[0],a[1],…,a[9]。數(shù)組名a表示元素a[0]的地址,而*a則表示a所代表的地址中的內(nèi)容,即a[0]。
如果定義一個(gè)指向整型變量的指針pa并賦以數(shù)組a中第一個(gè)元素a[0]的地址:
int *pa; pa=&a[0];
則可以通過指針pa來操作數(shù)組a了。即可用*pa代表a[0],*(pa+i)代表a[i]等,也可以使用pa[0],pa[1],…,pa[9]的形式。
例4.13:使用指針與數(shù)組的例子——計(jì)算質(zhì)數(shù)。
#include <stdio.h> #define MAX 20 main() { int i, j, n, p, r, primes[MAX]; int *pntw, *pntr; long q; pntw = primes; n = 2; *pntw++ = 2; *pntw++ = 3; i = 5; do { pntr = primes; do { p = *pntr++; q = i / p; r = i - q * p; } while( r && i < q*q ); if( r ) { *pntw++ = i; n++; } i += 2; } while( n < MAX ); j = 0; pntr = primes; for( i=0; i<MAX; ++i ) { printf("%4d",*pntr++); if( ++j == 10 ) { printf("\n"); j = 0; } } while(1); }
程序執(zhí)行結(jié)果:
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71
4.5.2 字符數(shù)組指針
用指針來描述一個(gè)字符數(shù)組是十分方便的。前面已經(jīng)講過,字符串是以字符數(shù)組的形式給出的,并且每個(gè)字符數(shù)組都以轉(zhuǎn)義字符“\0”作為字符串的結(jié)束標(biāo)志。因此在判別一個(gè)字符數(shù)組是否結(jié)束時(shí),通常不采用計(jì)數(shù)的方法,而是以是否讀到轉(zhuǎn)義字符“\0”來判斷。利用這個(gè)特點(diǎn),可以很方便地用指針來處理字符數(shù)組。
例4.14:利用指針將一個(gè)字符數(shù)組中的字符串復(fù)制到另一個(gè)字符數(shù)組中去。
#include<stdio.h> 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); while(1); }
程序執(zhí)行結(jié)果:
How are you ? How are you ?
任何一個(gè)數(shù)組及其數(shù)組元素都可以用一個(gè)指針及其偏移值來表示,但要注意的是,指針是一個(gè)變量,因此像上例中的賦值運(yùn)算s1=str、s2=0x1000等都是合法的;而數(shù)組名是一個(gè)常量,不能像變量那樣進(jìn)行運(yùn)算,即數(shù)組的地址是不能改變的。例如上面程序中的語句:
char code str[]={"How are you ? "};
是將字符串“How are you ?”置到數(shù)組str[]中作為初值,而語句:
s1=str;
則是將數(shù)組str[]的首地址,即指向數(shù)組str的指針賦值給指針變量s1。如果對數(shù)組名進(jìn)行如下的操作:
str=s1; str++;
則都是錯(cuò)誤的。
4.5.3 指針的地址計(jì)算
指針的地址計(jì)算包括以下幾個(gè)方面的內(nèi)容。
(1)賦初值
指針變量的初值可以是NULL(0),也可以是變量、數(shù)組、結(jié)構(gòu)以及函數(shù)等的地址。例如:
int a[10], b[10]; float fbuf[100]; char *cptr1=NULL; char *cptr2=&ch; int *iptr1=&a[5]; int *iptr2=b; float *fptr1=fbuf;
(2)指針與整數(shù)的加減
指針可以與一個(gè)整數(shù)或整數(shù)表達(dá)式進(jìn)行加減運(yùn)算,從而獲得該指針當(dāng)前所指位置前面或后面某個(gè)數(shù)據(jù)的地址。假設(shè)p為一個(gè)指針變量,n為一個(gè)整數(shù),則p±n表示離指針p當(dāng)前位置的前面或后面第n個(gè)數(shù)據(jù)的地址。
(3)指針與指針相減
指針與指針相減的結(jié)果為一整數(shù)值,但它并不是地址,而是表示兩個(gè)指針之間的距離或元素的個(gè)數(shù)。注意,這兩個(gè)指針必須指向同一類型的數(shù)據(jù)。
(4)指針與指針的比較
指向同一類型數(shù)據(jù)的兩個(gè)指針可以進(jìn)行比較運(yùn)算,從而獲得兩指針?biāo)傅刂返拇笮£P(guān)系。
此外,在計(jì)算指針地址的同時(shí),還可進(jìn)行間接取值運(yùn)算。不過在這種情況下,間接取值的地址應(yīng)該是地址計(jì)算后的結(jié)果,并且還必須注意運(yùn)算符的優(yōu)先級和結(jié)合規(guī)則。設(shè)p1、p2都是指針,對于
a=*p1++;
由于運(yùn)算符*和++具有相同的優(yōu)先級而指針運(yùn)算具有右結(jié)合性,按右結(jié)合規(guī)則,有++、*的運(yùn)算次序,而運(yùn)算符++在p1的后面,因此上述賦值運(yùn)算的過程是首先將指針p1所指的內(nèi)容賦值給變量a,然后p1再指向下一個(gè)數(shù)據(jù),表明是地址增加而不是內(nèi)容增加。對于
a=*--p1;
與上例相同,按右結(jié)合規(guī)則有--、*的運(yùn)算次序,而運(yùn)算符--在p1的前面,因此首先將p1減去1,即指向前面一個(gè)數(shù)據(jù),然后再把p1此時(shí)所指的內(nèi)容賦值給變量a。對于
a=(*p2)++;
由于使用括號()使結(jié)合次序變?yōu)?、++,因此首先將p2所指的內(nèi)容賦值給變量a,然后再把p2所指的內(nèi)容加1,表明是內(nèi)容增加而不是地址增加。
例4.15:指針運(yùn)算的例子。
#include <stdio.h> main() { char data *p1, *p2, *p3, *px; char x[]={1,2,3,4}; char px1, px2, px3; px=p1=p2=p3=x; px1=*p1++; p2=p1+2; px2=*--p2; px3=(*p3)++; printf("The start address of ARRAY x[]is %P\n",px); printf("px1 = %bd, p1 = %P\n",px1, p1); printf("px2 = %bd, p2 = %P\n",px2, p2); printf("px3 = %bd, p3 = %P\n",px3, p3); while(1); }
程序執(zhí)行結(jié)果:
The start address of ARRAY x[]is D:002C px1=1, p1=D:002D px2=3, p2=D:002E px3=1, p3=D:002C
例4.16:兩指針相減——計(jì)算字符串長度的函數(shù)。
#include <stdio.h> main() { char *s = "abcdef"; int strlen(char *s); printf("\n length of '%s' = %d\n",s,strlen(s)); while(1); } int strlen(char *s) { char *p = s; while( *p != '\0' ) p++; return( p - s ); }
程序執(zhí)行結(jié)果:
lenth of 'abcdef' = 6
需要指出的是,指針的運(yùn)算是很有限的,它只能進(jìn)行如上所述的四種運(yùn)算操作,除此之外所有其他的指針運(yùn)算都是非法的。特別強(qiáng)調(diào)一點(diǎn),不允許對兩個(gè)指針進(jìn)行加、乘、除、移位或屏蔽運(yùn)算,也不允許用float類型數(shù)據(jù)與指針作加減運(yùn)算。
4.6 函數(shù)型指針
函數(shù)不是變量,但它在內(nèi)存中仍然需要占據(jù)一定的存儲空間,如果將函數(shù)的入口地址賦給一個(gè)指針,該指針就是函數(shù)型指針。由于函數(shù)型指針指向的是函數(shù)的入口地址,因此在進(jìn)行函數(shù)調(diào)用時(shí)可用指向函數(shù)的指針代替被調(diào)用的函數(shù)名。在C語言中函數(shù)與變量不同,函數(shù)名不能作為參數(shù)直接傳遞給另一個(gè)函數(shù)。但是利用函數(shù)型指針,可以將函數(shù)作為參數(shù)傳遞給另一個(gè)函數(shù)。此外還可以將函數(shù)型指針放在一個(gè)指針數(shù)組中,則該指針數(shù)組的每一個(gè)元素都是指向某個(gè)函數(shù)的指針。
定義一個(gè)函數(shù)型指針的一般形式為:
數(shù)據(jù)類型 (* 標(biāo)識符)()
其中,“標(biāo)識符”就是所定義的函數(shù)型指針變量名。
“數(shù)據(jù)類型”說明了該指針?biāo)赶虻暮瘮?shù)返回值的類型。例如:
int (* func1)();
定義了一個(gè)函數(shù)型指針變量func1,它所指向的函數(shù)返回值為整型數(shù)據(jù)。函數(shù)型指針變量是專門用來存放函數(shù)入口地址的,在程序中把哪個(gè)函數(shù)的地址賦給它,它就指向那個(gè)函數(shù)。在程序中可以對一個(gè)函數(shù)型指針多次賦值,該指針可以先后指向不同的函數(shù)。
給函數(shù)型指針賦值的一般形式為:
函數(shù)型指針變量名 = 函數(shù)名
如果有一個(gè)函數(shù)max(x,y),則可用如下的賦值語句將該函數(shù)的地址賦給函數(shù)型指針func1,使func1指向函數(shù)max:
func1=max;
引入了函數(shù)型指針之后,對于函數(shù)的調(diào)用可以采用兩種方法。例如,程序中要求將函數(shù)max(x,y)的值賦給變量z,可采用如下方法:
z=max(x,y); 或z=(* func1)(x, y);
用這兩種方法實(shí)現(xiàn)函數(shù)調(diào)用的結(jié)果是完全一樣的。但需要注意的是,若采用函數(shù)型指針來調(diào)用函數(shù),必須先對該函數(shù)指針進(jìn)行賦值,使之指向所需調(diào)用的函數(shù)。
函數(shù)型指針通常用來將一個(gè)函數(shù)的地址作為參數(shù)傳遞到另一個(gè)函數(shù)中去。這種方法對于要調(diào)用某個(gè)非固定函數(shù)的場合特別適用。下面通過一個(gè)例子來說明函數(shù)型指針的這種應(yīng)用。
例4.17:函數(shù)型指針作為函數(shù)的參數(shù)。
設(shè)置一個(gè)函數(shù)process(),每次調(diào)用它時(shí)完成不同的功能。輸入兩個(gè)整型數(shù)a和b,第一次調(diào)用時(shí)找出a和b中較大者,第二次調(diào)用時(shí)找出較小者,第三次調(diào)用時(shí)求出a與b的和。
#include <stdio.h> int max(int x, int y) { int z; if (x>y) z=x; else z=y; return(z); } int min(int x, int y) { int z; if (x<y) z=x; else z=y; return(z); } int add(int x, int y) { int z; z=x+y; return(z); } int process(int x, int y,int (*f)()) { int result; result=f(); 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); while(1); }
程序執(zhí)行結(jié)果:
Please input a and b: 1234 5678 回車 max=5678 min=1234 sum=6912
本例中的三個(gè)函數(shù)max()、min()和add()分別用來實(shí)現(xiàn)求較大數(shù)、求較小數(shù)和求和的功能。在主函數(shù)main()中第一次調(diào)用process()函數(shù)時(shí),除了將a和b作為實(shí)參將兩個(gè)數(shù)傳遞給process()的形參x、y之外,還將函數(shù)名max作為實(shí)參將其入口地址傳遞給process()函數(shù)中的形參——指向函數(shù)的指針變量*f。這樣一來,process()函數(shù)中的函數(shù)調(diào)用語句result=f();就相當(dāng)于result=max(x, y);因此,執(zhí)行process()函數(shù)即可求出a與b中的較大者。第二次調(diào)用process()函數(shù)時(shí)改用函數(shù)名min作為實(shí)參,第三次調(diào)用process()函數(shù)時(shí)改用函數(shù)名add作為實(shí)參,從而實(shí)現(xiàn)每次調(diào)用process()函數(shù)時(shí)完成不同的功能。
4.7 返回指針型數(shù)據(jù)的函數(shù)
上一節(jié)中介紹了函數(shù)型指針的概念,在使用過程中要注意函數(shù)型指針與返回指針型數(shù)據(jù)的函數(shù)的區(qū)別。在函數(shù)的調(diào)用過程結(jié)束時(shí),被調(diào)用的函數(shù)可以帶回一個(gè)整型數(shù)據(jù)、字符型數(shù)據(jù)、實(shí)型數(shù)據(jù)等,也可以帶回一個(gè)指針型數(shù)據(jù),即地址。這種返回指針型數(shù)據(jù)的函數(shù)又稱為指針函數(shù),它的一般定義形式為:
數(shù)據(jù)類型 * 函數(shù)名(參數(shù)表);
其中,數(shù)據(jù)類型說明了所定義的指針函數(shù)在返回時(shí)帶回的指針?biāo)赶虻臄?shù)據(jù)類型。例如:
int * x(a, b);
定義了一個(gè)指針函數(shù)* x,調(diào)用它以后可以得到一個(gè)指向整型數(shù)據(jù)的指針,即地址。請讀者注意,在指針函數(shù)* x的兩側(cè)沒有括號(),這是與函數(shù)型指針完全不同的,也是容易搞混的,實(shí)際使用時(shí)一定要注意。
例4.18:指針函數(shù)應(yīng)用。
內(nèi)存中存有巡回檢測3個(gè)通道的溫度值(每個(gè)通道有4個(gè)點(diǎn)),要求用戶在輸入通道號以后,能立即輸出該通道所有點(diǎn)的溫度值。
#include<stdio.h> main() { float T[3][4]= {{60.1,70.3,80.5,90.7},{30.0,40.1,50.2,60.3}, {90.0,80.2,70.4,60.6}}; float * search(float (* pointer)[4], int n); float * p; int i, m; printf("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)); while(1); } float * search (float (* pointer)[4], int n) { float *pt; pt= * (pointer+n); return(pt); }
程序執(zhí)行結(jié)果:
Enter the number of chanal: 2 回車 The temperature of chanal are: 90.0 80.2 70.4 60.6
程序中將巡回檢測得到的某個(gè)通道各點(diǎn)的溫度值存放在一個(gè)二維數(shù)組T[3][4]中,通道號作為數(shù)組的行,各點(diǎn)溫度值作為數(shù)組的列。在輸入通道號時(shí)要注意,通道號是從0算起的。函數(shù)search被定義為指針型函數(shù),它的形式參數(shù)pointer是指向包含4個(gè)元素的一維數(shù)組的指針變量。pointer+1指向二維數(shù)組T的第0行,而*(pointer+1)則指向第0行第0列元素。
pt是一個(gè)指針變量,它指向?qū)嵭妥兞浚ǘ皇侵赶蛞痪S數(shù)組)。main()函數(shù)調(diào)用search()函數(shù),將T數(shù)組的首地址傳遞給pointer(注意T是指向數(shù)組行的指針,而不是指向數(shù)組列元素的指針)。m是要查找的通道號。調(diào)用search()函數(shù)后,得到一個(gè)地址(指向第m個(gè)通道第0點(diǎn)溫度值),并將這個(gè)地址賦給變量p。*(p+i)表示該通道第i點(diǎn)的溫度值,從而可將該通道4個(gè)點(diǎn)的溫度值輸出來。讀者可參考圖4.4來加深對這個(gè)例子的理解。

圖4.4 數(shù)組與指針的關(guān)系
4.8 指針數(shù)組與指針型指針
4.8.1 指針數(shù)組
由于指針本身也是一個(gè)變量,因此C語言允許定義指針的數(shù)組,指針數(shù)組適合于用來指向若干個(gè)字符串,使字符串的處理更為方便。指針數(shù)組的定義方法與普通數(shù)組完全相同,一般格式為:
數(shù)據(jù)類型 * 數(shù)組名[數(shù)組長度]
例如:
int * x[2]; /* 指向整型數(shù)據(jù)的2個(gè)指針 */ char * sptr[5]; /* 指向字符型數(shù)據(jù)的5個(gè)指針 */
指針數(shù)組在使用之前往往需要先賦初值,方法與一般數(shù)組賦初值類似。使用指針數(shù)組最典型的場合是通過對字符數(shù)組賦初值而實(shí)現(xiàn)各維長度不一致的多維數(shù)組的定義,下面通過兩個(gè)例子來進(jìn)一步說明指針數(shù)組賦初值的問題。
例4.19:使用指針數(shù)組的例子。
#include<stdio.h> main() { int i; char code *season[4]= { "spring", "summer", "fall", "winter" }; for(i=0; i<4; ++i) printf("\n%c---%s", *second[i], second[i]); while(1); }
程序執(zhí)行結(jié)果:
s---spring s---summer f---fall w---winter
這個(gè)例子中在code區(qū)定義了指向char型數(shù)據(jù)的4個(gè)指針,其初值分別為“spring”、“summer”、“fall”和“winter”。它們可用圖4.5直觀地表示。

圖4.5 指針數(shù)組與所賦初值的關(guān)系
從圖4.5中可以看到各個(gè)指針數(shù)組的長度可以不同,實(shí)際的存儲空間分配情況如圖4.6所示,它們是各行首尾相接的連續(xù)區(qū)域,這樣可以更為有效地利用內(nèi)存空間。如果不使用指針數(shù)組而采用二維字符數(shù)組,則該二維數(shù)組各列的長度必須相等,并且要等于最大一列的長度,這樣會造成內(nèi)存空間的浪費(fèi)。

圖4.6 指針數(shù)組的初值在內(nèi)存中的存放格式
例4.20:指針數(shù)組的應(yīng)用。
日期轉(zhuǎn)換程序,輸入年份和天數(shù),輸出這一天所在的月份及該月天數(shù)。
#include<stdio.h> 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[]= { "illegal 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-=daytab[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); while(1); }
程序執(zhí)行結(jié)果:
Input year and yearday: 1996, 60 回車 February, 29
這個(gè)程序由三個(gè)函數(shù)組成:主函數(shù)main()、求月份名函數(shù)mname()和求月天數(shù)函數(shù)monthday()。在主函數(shù)main()中輸入年份year和天數(shù)yearday,然后調(diào)用求月天數(shù)函數(shù)將該年的這一天轉(zhuǎn)換為該年的某月某日。
求月天數(shù)函數(shù)monthday()中定義了一張每月的天數(shù)表。由于二月的天數(shù)因閏年和平年而不同,因此將天數(shù)表設(shè)計(jì)為二維數(shù)組,用變量leap來判別輸入的年份是否為閏年,閏年leap為1,平年leap為0。for語句的作用是控制當(dāng)輸入天數(shù)大于每月的天數(shù)時(shí),用輸入的天數(shù)減去該月的天數(shù),并使月數(shù)i+1,再進(jìn)入下一個(gè)循環(huán),直到天數(shù)不大于第i月的天數(shù)時(shí),退出循環(huán)。此時(shí)的i值即為所求的月數(shù),而天數(shù)則為該月的天數(shù)。例如,在程序執(zhí)行中,輸入1996,60,經(jīng)過上述轉(zhuǎn)換得到i=2,yd=29,即1996年的第60天為2月29日。
求月份名函數(shù)mname()的作用是將由函數(shù)monthday() 所求得的月數(shù)能用相應(yīng)的月份名來表示。即要求對于給出的月份數(shù)n,能返回一個(gè)指向n月名字符串的指針。函數(shù)中定義了一個(gè)指針數(shù)組mn并給它賦了初值,mn中共有13個(gè)元素,它們都是指針,指向字符類型數(shù)據(jù),其初值分別是字符串“illegal month”、“January”、…、“December”的首地址。函數(shù)monthday()調(diào)用mname(i)后,返回一個(gè)指向第i月的月名字符串指針mn[i],然后用格式“%s”輸出,就能得到該指針?biāo)赶虻脑旅址?/p>
如果一個(gè)指針數(shù)組中的元素都是函數(shù)指針,則稱之為函數(shù)指針數(shù)組。利用函數(shù)指針數(shù)組可以很方便地實(shí)現(xiàn)散轉(zhuǎn)處理。在設(shè)計(jì)一個(gè)單片機(jī)應(yīng)用系統(tǒng)時(shí),鍵盤管理程序是整個(gè)系統(tǒng)監(jiān)控程序的一個(gè)重要組成部分。為了實(shí)現(xiàn)各個(gè)不同按鍵的功能,通常是根據(jù)不同的鍵值進(jìn)行散轉(zhuǎn)。下面是一個(gè)鍵值散轉(zhuǎn)處理的例子,根據(jù)從鍵盤輸入不同的數(shù)字鍵進(jìn)行不同的處理。
例4.21:鍵值散轉(zhuǎn)處理。
#include <stdio.h> void Key_0() { /* "0" 鍵處理 */ } void Key_1() { /* "1" 鍵處理 */ } void Key_2() { /* "2" 鍵處理 */ } /* k3, k4, ... 其他鍵處理 */ /* 函數(shù)指針數(shù)組定義 */ code void (code * KeyProcTab[])()= {Key_0, Key_1, Key_2/*k2,...,k9 */ }; void main() { unsigned char key,num; while(1){ scanf ("%c", &key); /* 等待按鍵 */ num=key-0x30; /* 計(jì)算鍵值 */ (*KeyProcTab[num])(); /* 按鍵值散轉(zhuǎn) */ } }
4.8.2 指針型指針
在掌握了指針數(shù)組的基礎(chǔ)上,再來介紹一種指針型的指針,這種指針?biāo)赶虻氖橇硪粋€(gè)指針變量的地址,故有時(shí)也稱之為多級指針。從前面的圖4.5中可以看到,指針數(shù)組season中的每一個(gè)元素都是一個(gè)指針型數(shù)據(jù)。既然season是一個(gè)數(shù)組,那么它的每一個(gè)元素都有相應(yīng)的地址,而數(shù)組名season則代表了該指針數(shù)組的首地址。因此我們可以定義一個(gè)指向指針數(shù)組中元素的指針變量,這就是指向指針型數(shù)據(jù)的指針變量,即指針型指針變量。
定義一個(gè)指針型指針變量的一般形式為:
數(shù)據(jù)類型 ** 標(biāo)識符
其中,“標(biāo)識符”就是所定義的指針型指針變量名,而“數(shù)據(jù)類型”則說明一個(gè)被指針型指針?biāo)赶虻闹羔樧兞克赶虻淖兞繑?shù)據(jù)類型。
例4.22:指針型指針變量的應(yīng)用。
#include<stdio.h> main() { int x, *p, ** q; x=10; p=&x; q=&p; printf("%d\n:, x); printf("%d\n:, * p); printf("%d\n:, ** q); while(1); }
程序執(zhí)行結(jié)果:
10 10 10
本例程序中定義了一個(gè)指向整型數(shù)據(jù)的指針變量p,又定義了一個(gè)指向整型數(shù)據(jù)的指針型指針q。經(jīng)過賦值之后,指針p指向整型變量x,而指針型指針q則指向指針變量p。換句話說,在q中存放的是p的地址,在p中存放的是x的地址,在x中存放的才是整型數(shù)據(jù)10。若要將這個(gè)整型數(shù)據(jù)的值輸出,可以通過變量名x直接輸出:
printf("%d\n:, x);
這種方法稱為直接取值。也可以先通過指針變量p找到變量x的地址,然后再從這個(gè)地址中將整型數(shù)據(jù)的值輸出:
printf("%d\n:, * p);
這種方法稱為單重間接取值。還可以通過指針型指針變量q先找到指針p的地址,再通過指針p找到x的地址,最后從x的地址中將整型數(shù)據(jù)的值輸出:
printf("%d\n:, ** q);
這種方法稱為多重間接取值。從程序的執(zhí)行結(jié)果可以看到,這三種方法得到的結(jié)果是完全一樣的。由此可知,一個(gè)指針型指針是一種間接取值的形式,而且這種間接取值的方式還可以進(jìn)一步延伸,故可以將這種多重間接取值的形式看成為一個(gè)指針鏈。
指針型指針常用來作為指向指針數(shù)組的指針變量。例如:
int * x[5]; int ** y;
其中,第一條語句定義了一個(gè)指向整型數(shù)據(jù)的指針數(shù)組x[5],它由5個(gè)元素組成,各個(gè)元素x[0],x[1],…,x[4]均為指針變量,它們都指向整型數(shù)據(jù);第二條語句定義了一個(gè)指針型指針y。如果經(jīng)過如下賦值:
y=x;
則y就成了指向指針數(shù)組x的多級指針了。
例4.23:使用指針型指針的例子。
#include<stdio.h> 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); } while(1); }
程序執(zhí)行結(jié)果:
s---spring s---summer f---fall w---winter
這個(gè)程序的執(zhí)行結(jié)果與例4.19程序是完全一樣的。在這個(gè)程序中先定義了一個(gè)指針數(shù)組season,并給它賦以字符串初值。在指針數(shù)組season中有4個(gè)元素,它們分別指向4個(gè)字符串的首地址。程序中還定義了一個(gè)指針型指針j,并進(jìn)行了賦值:
j=season+i;
當(dāng)i為0時(shí),*j為season[0]的值,即第一個(gè)字符串的首地址,用字符串格式“%s”輸出*j就可以得到第一個(gè)字符串,以后循環(huán)i為1、2、3時(shí),j分別為j[1]、j[2]和j[3],用“%s”輸出即可依次得到各個(gè)字符串。
4.8.3 抽象型指針
ANSI新標(biāo)準(zhǔn)增加了一種“void *”指針類型,這是一種抽象型指針,即可以定義一個(gè)指針變量,但不指定該指針是指向哪一種類型數(shù)據(jù)的。對于這種抽象型指針在給另一個(gè)指針變量賦值時(shí),需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換,使之適合于被賦值的變量的類型。例如:
char *p1; void *p2; p1=(char *)p2;
當(dāng)然也可以用(void *)p1將p1的地址轉(zhuǎn)換成void *類型之后賦值給p2:
p2=(void *)p1;
函數(shù)也可以定義為void *類型,例如:
void * fun(x, y)
表示函數(shù)fun返回的是一個(gè)地址,它指向“空類型”。
抽象型指針可以用來在每個(gè)存儲區(qū)內(nèi)訪問任意絕對地址,或者用來產(chǎn)生絕對調(diào)用。下面是一個(gè)使用Keil Cx51編譯器對抽象型指針形成匯編碼的例子,讀者可以從這個(gè)例子中進(jìn)一步了解如何正確使用這種抽象類型的指針。
例4.24:抽象指針的使用。
stmt level source 1 char xdata * px; 2 char idata * pi; 3 char code * pc; 4 char c; 5 int i; 6 void main(void){ 7 1 pc=(void *) main; 8 1 pi=(char idata *)&i; 9 1 pi=(char idata *)px; 10 1 pc=(char code *)0x7788; 11 1 12 1 i=((int (code *)(void))0xff00)(); /* LCALL 0FF00H */ 13 1 c=*((char code *)0x8000); /* char [code[0x8000]]*/ 14 1 c+=*((char xdata *)0xff00); /* char [xdata[0xFF00]]*/ 15 1 c+=*((char idata *)240); /* char [idata[0xF0]]*/ 16 1 c+=*((char pdata *)240); /* char [pdata[0xF0]]*/ 17 1 18 1 i=*((int code *)0x1200); /* int from code[0x1200]*/ 19 1 px=*((char xdata *xdata *)0x4000);/* x-ptr from xdata[0x4000]*/ 20 1 px=((char xdata *xdata *)0x4000)[0];/* same as before */ 21 1 px=((char xdata*xdata*)0x4000)[1];/*x-ptr from xdata[0x4002]*/ 22 1 } 23 24 ASSEMBLY LISTING OF GENERATED OBJECT CODE ;FUNCTION main (BEGIN) ;SOURCE LINE # 6 ;SOURCE LINE # 7 0000750000 R MOV pc,#HIGH main 0003750000 R MOV pc+01H,#LOW main ;SOURCE LINE # 8 0006750000 R MOV pi,#LOW i ;SOURCE LINE # 9 0009850000 R MOV pi,px+01H ;SOURCE LINE # 10 000C 750077 R MOV pc,#077H 000F 750088 R MOV pc+01H,#088H ;SOURCE LINE # 12 0012 12FF00 LCALL 0FF00H 0015 8E00 R MOV i,R6 0017 8F00 R MOV i+01H,R7 ;SOURCE LINE # 13 0019908000 MOV DPTR,#08000H 001C E4 CLR A 001D 93 MOVC A,@A+DPTR 001E F500 R MOV c,A ;SOURCE LINE # 14 0020 90FF00 MOV DPTR,#0FF00H 0023 E0 MOVX A,@DPTR 00242500 R ADD A,c 0026 F500 R MOV c,A ;SOURCE LINE # 15 0028 78F0 MOV R0,#0F0H 002A E6 MOV A,@R0 002B 2500 R ADD A,c 002D F500 R MOV c,A ;SOURCE LINE # 16 002F E2 MOVX A,@R0 00302500 R ADD A,c 0032 F500 R MOV c,A ;SOURCE LINE # 18 0034901200 MOV DPTR,#01200H 0037 E4 CLR A 0038 93 MOVC A,@A+DPTR 0039 F500 R MOV i,A 003B 7401 MOV A,#01H 003D 93 MOVC A,@A+DPTR 003E F500 R MOV i+01H,A ;SOURCE LINE # 19 0040904000 MOV DPTR,#04000H 0043 E0 MOVX A,@DPTR 0044 FE MOV R6,A 0045 A3 INC DPTR 0046 E0 MOVX A,@DPTR 0047 8E00 R MOV px,R6 0049 F500 R MOV px+01H,A ;SOURCE LINE # 20 004B 8E00 R MOV px,R6 004D F500 R MOV px+01H,A ;SOURCE LINE # 21 004F A3 INC DPTR 0050 E0 MOVX A,@DPTR 0051 FE MOV R6,A 0052 A3 INC DPTR 0053 E0 MOVX A,@DPTR 0054 8E00 R MOV px,R6 0056 F500 R MOV px+01H,A ;SOURCE LINE # 22 0058 22 RET ;FUNCTION main (END)
- Advanced Quantitative Finance with C++
- 簡單高效LATEX
- Responsive Web Design with HTML5 and CSS3
- 深入理解Java7:核心技術(shù)與最佳實(shí)踐
- Python數(shù)據(jù)挖掘與機(jī)器學(xué)習(xí)實(shí)戰(zhàn)
- C語言開發(fā)基礎(chǔ)教程(Dev-C++)(第2版)
- Python深度學(xué)習(xí)原理、算法與案例
- Web性能實(shí)戰(zhàn)
- Machine Learning for OpenCV
- Hacking Android
- Neo4j 3.x入門經(jīng)典
- HTML+CSS+JavaScript前端開發(fā)(慕課版)
- WordPress Responsive Theme Design
- 設(shè)計(jì)模式的藝術(shù)
- C編程從入門到實(shí)踐