官术网_书友最值得收藏!

第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)
主站蜘蛛池模板: 新野县| 温宿县| 郧西县| 集贤县| 迁西县| 隆林| 九龙县| 彭山县| 辉南县| 和政县| 大新县| 尼勒克县| 枞阳县| 崇左市| 白河县| 思茅市| 高尔夫| 甘肃省| 曲靖市| 武邑县| 睢宁县| 临猗县| 延津县| 北宁市| 永登县| 开平市| 卢氏县| 石渠县| 北安市| 涿鹿县| 甘德县| 横峰县| 军事| 额济纳旗| 房产| 横峰县| 成都市| 贵德县| 隆化县| 新巴尔虎右旗| 海兴县|