第8章 善於利用指針

學習 C 語言的指針既簡單又有趣。經過指針,能夠簡化一些 C 編程任務的執行,還有一些任務,如動態內存分配,沒有指針是沒法執行的。因此,想要成爲一名優秀的 C 程序員,學習指針是頗有必要的。程序員

正如您所知道的,每個變量都有一個內存位置,每個內存位置都定義了可以使用連字號(&)運算符訪問的地址,它表示了在內存中的一個地址。請看下面的實例,它將輸出定義的變量地址:編程

 

#include <stdio.h>數組

 

int main ()app

{ide

   int  var1;函數

   char var2[10];學習

 

   printf("var1 變量的地址: %p\n", &var1  );url

   printf("var2 變量的地址: %p\n", &var2  );spa

 

   return 0;設計

}

當上面的代碼被編譯和執行時,它會產生下列結果:

var1 變量的地址: 0x7fff5cc109d4

var2 變量的地址: 0x7fff5cc109de

經過上面的實例,咱們瞭解了什麼是內存地址以及如何訪問它。接下來讓咱們看看什麼是指針。

8.1什麼是指針?

指針是一個變量,其值爲另外一個變量的地址,即,內存位置的直接地址。就像其餘變量或常量同樣,您必須在使用指針存儲其餘變量地址以前,對其進行聲明。

指針變量聲明的通常形式爲:type *var-name;

在這裏,type 是指針的基類型,它必須是一個有效的 C 數據類型,var-name 是指針變量的名稱。用來聲明指針的星號 * 與乘法中使用的星號是相同的。可是,在這個語句中,星號是用來指定一個變量是指針。如下是有效的指針聲明:

int    *ip;    /* 一個整型的指針 */

double *dp;    /* 一個 double 型的指針 */

float  *fp;    /* 一個浮點型的指針 */

char   *ch;     /* 一個字符型的指針 */

全部指針的值的實際數據類型,無論是整型、浮點型、字符型,仍是其餘的數據類型,都是同樣的,都是一個表明內存地址的長的十六進制數。不一樣數據類型的指針之間惟一的不一樣是,指針所指向的變量或常量的數據類型不一樣。

8.2指針變量

8.2.1 使用指針變量的例子

#include <stdio.h>

int main()

{ int a=100,b=10;

//定義整型變量a,b,並初始化

int *pointer_1,*pointer_2;

//定義指向整型數據的指針變量pointer_1, pointer_2

pointer_1=&a; //把變量a的地址賦給指針變量pointer_1

       pointer_2=&b;   //把變量b的地址賦給指針變量pointer_2

printf("a=%d,b=%d\n",a,b); //輸出變量a和b的值

printf("*pointer_1=%d,*pointer_2=%d\n",*pointer_1,*pointer_2);

//輸出變量a和b的值

return 0;

}

運行結果:

 

上面變量對應的關係,如圖:

 

 

注:定義指針變量時,左側應有類型名,不然就不是定義指針變量。也稱爲基類型

8.2.2 怎樣定義指針變量

定義指針的通常形式爲:

類型名 *指針變量名;

如:

int *pointer_1;

注意:左端的int是在定義指針變量時必須指定的"基類型".換個說法就是類型名必定要有,不然是錯誤的定義。

如:

*pointer_1; //企圖定義pointer_1爲指針變量。出錯

int *pointer_1; //正確,必須指定指針變量的基類型

 

說明:在定義指針變量時要注意一下幾點:

(1) 指針變量前面的"*"表示該變量的類型爲指針型變量。

(2) 在定義指針變量時必須制定基類型(即類型名)

一個變量的指針的含義包括兩個方面,一是以存儲單元編號表示的地址(如編號爲2000的字節),一是它指向的存儲單元的數據類型(如int,char,float等)。

(3)如何表示指針類型

指向整型數據的指針類型表示爲"int*",讀做"指向int的指針"或簡稱"int指針"

(4) 指針變量中只能存放地址(地址),不要講一個整數賦給一個指針變量。如:

*pointer_1 = 100; //pointer_1是指針變量,100是整數,不合法的賦值

8.2.3 怎樣引用指針變量

(1) 給指針變量賦值。如:

p = &a; //把a的地址賦給指針變量a

(2) 引用指針變量指向的變量

若是已執行"p = &a;",即指針變量p指向了整型變量a,則

printf("%d",*p);

做用:以整數形式輸出指針變量p所指向的變量的值,即變量a的值

注:必定要帶'*',不然輸出地址

也能夠用指針的形式對a從新賦值

*p = 1;

表示將1賦值給p當前p指向的變量,由於p指向變量a,則至關於把1賦值給a,即和"a = 1"等價。

(3) 引用指針變量的值。如:

printf("%o",p);

做用:以八進制數形式輸出指針變量p的值,若是p指向了a,就是輸出了a的地址,即&a.

printf("%o",&a);   <==> printf("%o",p); //二者等價

例:

#include <stdio.h>

int main()

{

int *p,a;

a = 100;

p = &a;

printf("%o\n",p);

printf("%o\n",&a);

 

}

 

要熟練掌握兩個有關的運算符:

(1) &取地址運算符。&a是變量a的地址。

(2) * 指針運算符(或稱「間接訪問」運算符),*p表明指針變量p指向的對象。

 

#include <stdio.h>

int main()

{ int *p1,*p2,*p,a,b;     //p1,p2的類型是int *類型

printf("please enter two integer numbers:");

       scanf("%d,%d",&a,&b);                       //輸入兩個整數

p1=&a;       //使p1指向變量a

p2=&b;       //使p2指向變量b

if(a<b)       //若是a<b

{ p=p1;p1=p2;p2=p;}   //使p1與p2的值互換

printf("a=%d,b=%d\n",a,b);   //輸出a,b

printf("max=%d,min=%d\n",*p1,*p2); //輸出p1和p2所指向的變量的值

return 0;

}

運行結果:

 

指針在過程當中的變化:

 

 

8.2.4 指針變量做爲函數參數

例:對輸入的兩個整數按大小順序輸出。現用函數處理,並且用指針類型的數據做函數參數。

#include <stdio.h>

void swap(int *p1,int *p2);    //對swap函數的聲明

int main()

{

int a,b;

int *pointer_1,*pointer_2; //定義兩個int *型的指針變量

printf("please enter a and b:");

scanf("%d,%d",&a,&b);  //輸入兩個整數

pointer_1=&a;   //使pointer_1指向a

       pointer_2=&b;                 //使pointer_2指向b

if(a<b) swap(pointer_1,pointer_2); //若是a<b,調用swap函數

printf("max=%d,min=%d\n",a,b);  //輸出結果

return 0;

}

 

void swap(int *p1,int *p2)   //定義swap函數

{ int temp;

temp=*p1;     //使*p1和*p2互換

*p1=*p2;

*p2=temp;

}

運行結果:

 

指針變量pointer_1 pointer_2的變量過程

 

 

注:要注意區分如下三種形式,可本身調試,增長理解

void swap(int *p1,int *p2)//定義swap函數

{ int temp;

temp=*p1;//使*p1和*p2互換

*p1=*p2;

*p2=temp;

}

void swap(int *p1,int *p2)

{ int *temp;

*temp=*p1;

      *p1=*p2;

*p2=*temp;

}

void swap(int x,int y)

{ int temp;

temp=x;

      x=y;

y=temp;

}

下面有一個問題須要注意,也能夠直接跳過,若是想提高能夠理解,以理解爲主,不須要死記硬背。

指針變量做爲函數參數

函數的調用能夠(並且只能夠)獲得一個返回值(即函數值),而使用指針變量做參數,能夠獲得多個變化了的值。若是不用指針變量是難以作到這一點的。要善於利用指針法。

若是想經過函數調用獲得n個要改變的值,能夠這樣作:

 

1.在主調函數中設n個變量,用n個指針變量指向它們;

 

2.設計一個函數,有n個指針形參。在這個函數中改變這n個形參的值;

 

3.在主調函數中調用這個函數,在調用時將這n個指針變量做實參,將它們的值,也就是相關變量的地址傳給該函數的形參;

 

3.在執行該函數的過程當中,經過形參指針變量,改變它們所指向的n個變量的值;

 

5.主調函數中就可使用這些改變了值的變量。

例:輸入3個整數a,b,c,要求按由大到小的順序將它們輸出。用函數實現。

#include <stdio.h>

int main()

{ void exchange(int *q1, int *q2, int *q3); //函數聲明

int a,b,c,*p1,*p2,*p3;

printf("please enter three numbers:");

scanf("%d,%d,%d",&a,&b,&c);

p1=&a;p2=&b;p3=&c;

exchange(p1,p2,p3);

printf("The order is:%d,%d,%d\n",a,b,c);

return 0;

}

 

void exchange(int *q1, int *q2, int *q3) //將3個變量的值交換的函數

{ void swap(int *pt1, int *pt2);  //函數聲明

if(*q1<*q2) swap(q1,q2);  //若是a<b,交換a和b的值

if(*q1<*q3) swap(q1,q3);  //若是a<c,交換a和c的值

if(*q2<*q3) swap(q2,q3);  //若是b<c,交換b和c的值

}

 

void swap(int *pt1, int *pt2)   //交換2個變量的值的函數

{ int temp;

temp=*pt1;    //交換*pt1和*pt2變量的值

*pt1=*pt2;

*pt2=temp;

}

運行結果:

 

 

8.3 經過指針引用數組

8.3.1 數組元素的指針

數組元素的指針就是素組元素的地址。

int a[10]={1,3,5,7,9,11,13,15,17,19};  //定義a爲包含10個整型數據的數組

int *p;         //定義p爲指向整型變量的指針變量

p=&a[0];        //把a[0]元素的地址賦給指針變量p

引用數組元素的方式

(1) 下標法

(2) 指針法

p=&a[0]; //p的值是a[0]的地址

等價於

p=a; //p的值是數組a首元素(即a[0])的地址

注:程序中的數組名不表明整個數組,只表明數組首元素的地址。

如下是指針變量的初始化方式:

(1) 在定義後再單獨進行初始化

int *p;

p=&a[0]; //不該寫成*p=&a[0];

(2) 在定義時直接進行初始化

int *p=&a[0];

等價於

int *p=a;

以上三種的做用:將a數組首元素(即a[0])的地址賦值給指針變量p(而不是賦給*p)

8.3.2 在引用數組元素時指針的運算

在指針已指向一個數組元素時,能夠對指針進行如下運算:

1.加一個整數(用+或+=),如p+1,表示指向同一數組中的下一個元素;

 

2.減一個整數(用-或-=),如p-1,表示指向同一數組中的上一個元素;

 

3.自加運算,如p++,++p;

 

4.自減運算,如p--,--p。

 

兩個指針相減,如p1-p2(p1和p2都指向同一數組中的元素時纔有意義),結果爲兩個地址之差除以數組元素的長度。

注:兩個地址不能相加,如p1+p2是無實際意義的。

 

說明:

*(p+i)或*(a+i)是p+i或a+i所指向的數組元素,即a[i]。

p的初值爲&a[0],則p+i和a+i就是數組元素a[i]的地址

[]其實是變址運算符,即將a[i]按a+i計算地址,而後找出此地址單元中的值。

例:有一個整型數組a,有10個元素,要求輸出數組中的所有元素。

//下標法

#include <stdio.h>

int main()

{ int a[10];

int i;

printf("please enter 10 integer numbers:");

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

scanf("%d",&a[i]);

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

printf("%d ",a[i]);

//數組元素用數組名和下標表示

printf("%\n");

return 0;

}

//經過數組名計算數組元素地址,找出元素的值

#include <stdio.h>

int main()

{ int a[10];

int i;

printf("please enter 10 integer numbers:");

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

scanf("%d",&a[i]);

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

printf("%d ",*(a+i));

//經過數組名和元素序號計算元素地址找到該元素

printf("\n");

return 0;

}

//用指針變量指向數組元素

#include <stdio.h>

int main()

{ int a[10];

int *p,i;

printf("please enter 10 integer numbers:");

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

scanf("%d",&a[i]);

for(p=a;p<(a+10);p++)

printf("%d ",*p);

//用指針指向當前的數組元素

printf("\n");

return 0;

}

說明:

第(1)和第(2)種方法執行效率是相同的。C編譯系統是將a[i]轉換爲*(a+i)處理的,即先計算元素地址。所以用第(1)和第(2)種方法找數組元素費時較多。

 

第(3)種方法比第(1)、第(2)種方法快,用指針變量直接指向元素,沒必要每次都從新計算地址,像p++這樣的自加操做是比較快的。這種有規律地改變地址值(p++)能大大提升執行效率。

 

例:經過指針變量輸出整型數組a的10個元素。

#include <stdio.h>

int main()

{ int *p,i,a[10];

p=a;    //p指向a[0]  ①

printf("please enter 10 integer numbers:");

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

scanf("%d",p++); //輸入10個整數給a[0]~a[9]

for(i=0;i<10;i++,p++)

printf("%d ",*p); //想輸出a[0]~a[9] ②

printf("\n");

return 0;

}

#include <stdio.h>

int main()

{ int i,a[10],*p=a; //p的初值是a,p指向a[0]

printf("please enter 10 integer numbers:");

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

scanf("%d",p++);

p=a;    //從新使p指向a[0]

for(i=0;i<10;i++,p++)

printf("%d ",*p);

printf("\n");

return 0;

}

運行上面兩個代碼看看有何區別,形成這種區別的緣由是什麼?

答:第一種是由於指針已經到了數組的末尾,繼續循環則超出了數組的範圍,則會直接輸出P對應的地址值。如圖所示:

 

 

技巧:

設p開始時指向數組a的首元素(即p=a)

(1)

p++; //使p指向下一元素a[1]

*p; //獲得下一個元素a[1]的值

(2)

*p++; /*因爲++和*同優先級,結合方向自右而左,所以它等價於*(p++)。先引用p的值,實現*p的運算,而後再使p自增1*/

(3)

*(p++); //先取*p值,而後使p加1

*(++p); //先使p加1,再取*p

(4)

++(*p); /*表示p所指向的元素值加1,若是p=a, 則至關於++a[0],若a[0]的值爲3,則a[0]的值爲4。注意: 是元素a[0]的值加1,而不是指針p的值加1*/

(5)

若是p當前指向a數組中第i個元素a[i],則:

*(p--) //至關於a[i--],先對p進行「*」運算,再使p自減

*(++p) //至關於a[++i],先使p自加,再進行「*」運算

*(--p) //至關於a[--i],先使p自減,再進行「*」運算

8.3.4 用數組名做函數參數

以變量名和數組名做爲函數參數的比較

 

 

C語言調用函數時虛實結合的方法都是採用「值傳遞」方式,當用變量名做爲函數參數時傳遞的是變量的值,當用數組名做爲函數參數時,因爲數組名錶明的是數組首元素地址,所以傳遞的值是地址,因此要求形參爲指針變量。

 

注意:實參數組名錶明一個固定的地址,或者說是指針常量,但形參數組名並非一個固定的地址,而是按指針變量處理。

 

例:將數組a中n個整數按相反順序存放

#include <stdio.h>

int main()

{ void inv(int x[],int n); //inv函數聲明

int i,a[10]={3,7,9,11,0,6,7,5,4,2};

printf("The original array:\n");

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

printf("%d ",a[i]); //輸出未交換時數組各元素的值

printf("\n");

inv(a,10);    //調用inv函數,進行交換

printf("The array has been inverted:\n");

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

printf("%d ",a[i]); //輸出交換後數組各元素的值

printf("\n");

return 0;

}

void inv(int x[],int n)  //形參x是數組名

{ int temp,i,j,m=(n-1)/2;

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

{ j=n-1-i;

temp=x[i]; x[i]=x[j]; x[j]=temp; //把x[i]和x[j]交換

}

return;

}

#include <stdio.h>

int main()

{ void inv(int *x,int n);

int i,a[10]={3,7,9,11,0,6,7,5,4,2};

printf("The original array:\n");

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

printf("%d ",a[i]);

printf("\n");

inv(a,10);

printf("The array has been inverted:\n");

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

printf("%d ",a[i]);

printf("\n");

return 0;

}

 

void inv(int *x,int n)   //形參x是指針變量

{ int *p,temp,*i,*j,m=(n-1)/2;

i=x; j=x+n-1; p=x+m;

for(;i<=p;i++,j--)

{ temp=*i; *i=*j; *j=temp;} //*i與*j交換

return;

}

以上兩種方式所得結果是一致的,只是實現的形式有區別。所得結果都爲下圖所示:

說明:如下的形式要認識,如下四種都是等價的

 

 

例:將數組a中n個整數按相反順序存放,用指針變量做實參。

#include <stdio.h>

int main()

{ void inv(int *x,int n); //inv函數聲明

int i,arr[10],*p=arr;  //指針變量p指向arr[0]

printf("The original array:\n");

for(i=0;i<10;i++,p++)

scanf("%d",p);  //輸入arr數組的元素

printf("\n");

p=arr;    //指針變量p從新指向arr[0]

inv(p,10);    //調用inv函數,實參p是指針變量

printf("The array has been inverted:\n");

for(p=arr;p<arr+10;p++)

printf("%d ",*p);

printf("\n");

return 0;

}

 

void inv(int *x,int n)         //定義inv函數,形參x是指針變量

{ int *p,m,temp,*i,*j;

m=(n-1)/2;

i=x;j=x+n-1;p=x+m;

for(;i<=p;i++,j--)

{ temp=*i;*i=*j;*j=temp;}

return;

}

注:若是用指針變量做實參,必須先使指針變量有肯定值,指向一個已定義的對象。

 

例:用指針方法對10個整數按由大到小順序排序。(選擇排序法)

//形參是數組

#include <stdio.h>

int main()

{ void sort(int x[],int n); //sort函數聲明

int i,*p,a[10];

p=a;     //指針變量p指向a[0]

printf("please enter 10 integer numbers:");

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

scanf("%d",p++); //輸入10個整數

p=a; //指針變量p從新指向a[0]

sort(p,10);   //調用sort函數

for(p=a,i=0;i<10;i++)

{ printf("%d ",*p); //輸出排序後的10個數組元素

p++;

}

printf("\n");

return 0;

}

 

void sort(int x[],int n)//x是形參數組名

{ int i,j,k,t;

for(i=0;i<n-1;i++)

{ k=i;

for(j=i+1;j<n;j++)

if(x[j]>x[k]) k=j;

if(k!=i)

{ t=x[i]; x[i]=x[k]; x[k]=t;}

}

}

//形參是指針

#include <stdio.h>

int main()

{ void sort(int x[],int n); //sort函數聲明

int i,*p,a[10];

p=a;     //指針變量p指向a[0]

printf("please enter 10 integer numbers:");

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

scanf("%d",p++); //輸入10個整數

p=a; //指針變量p從新指向a[0]

sort(p,10);   //調用sort函數

for(p=a,i=0;i<10;i++)

{ printf("%d ",*p); //輸出排序後的10個數組元素

p++;

}

printf("\n");

return 0;

}

 

void sort(int *x,int n) //形參x是指針變量

{ int i,j,k,t;

for(i=0;i<n-1;i++)

{ k=i;

for(j=i+1;j<n;j++)

if(*(x+j)>*(x+k)) k=j; //*(x+j)就是x[j],其餘亦然

if(k!=i)

{ t=*(x+i); *(x+i)=*(x+k); *(x+k)=t;}

}

}

 

8.3.5 經過指針引用多維數組

注:這章節瞭解便可,若是想提高本身,不只須要理解還須要會使用。

理解如下這張圖即可瞭解多維數組:

 

 

說明:咱們在這裏說明一下,在計算機底層是不存在二維數組這種存儲方式的,都是以一維數組爲基礎邏輯化出二維數組,而一維數組不只是邏輯化更是物理上也是能夠實現的。總的來講,二維數組是一維數組的邏輯化,本質上二維數組仍是一維數組。只是爲了方便人們理解,才把二維數組分爲行和列的方式顯示。

 

若是用一個指針變量pt來指向此一維數組:

int (*pt)[4];

//表示pt指向由4個整型元素組成的一維數組,此時指針變量pt的基類型是由4個整型元素組成的一維數組

 

例:輸出二維數組的有關數據(地址和元素的值)。

#include <stdio.h>

int main()

{ int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};

printf("%d,%d\n",a,*a);    //0行起始地址和0行0列元素地址

printf("%d,%d\n",a[0],*(a+0));   //0行0列元素地址

printf("%d,%d\n",&a[0],&a[0][0]);  //0行起始地址和0行0列元素地址

printf("%d,%d\n",a[1],a+1);   //1行0列元素地址和1行起始地址

printf("%d,%d\n",&a[1][0],*(a+1)+0); //1行0列元素地址

printf("%d,%d\n",a[2],*(a+2));   //2行0列元素地址

printf("%d,%d\n",&a[2],a+2);   //2行起始地址

printf("%d,%d\n",a[1][0],*(*(a+1)+0)); //1行0列元素的值

printf("%d,%d\n",*a[2],*(*(a+2)+0)); //2行0列元素的值

return 0;

}

結果以下:

 

 

說明:要注意看懂以上的格式,區分各類形式的聯繫和區別。如a和*a在輸出上是等價的

 

例:有一個3×4的二維數組,要求用指向元素的指針變量輸出二維數組各元素的值。

#include <stdio.h>

int main()

{ int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};

int *p;       //p是int *型指針變量

for(p=a[0];p<a[0]+12;p++)   //使p依次指向下一個元素

{ if((p-a[0])%4==0) printf("\n"); //p移動4次後換行

              printf("%4d",*p);                           //輸出p指向的元素的值

}

printf("\n");

return 0;

}

運行結果:

 

例:輸出二維數組任一行任一列元素的值。

 

#include <stdio.h>

int main()

{ int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};  //定義二維數組a並初始化

int (*p)[4],i,j;   //指針變量p指向包含4個整型元素的一維數組

p=a;     //p指向二維數組的0行

printf("please enter row and colum:");

scanf("%d,%d",&i,&j); //輸入要求輸出的元素的行列號

printf("a[%d,%d]=%d\n",i,j,*(*(p+i)+j));   //輸出a[i][j]的值

return 0;

}

 

#include <stdio.h>

int main()

{ int a[4]={1,3,5,7};  //定義一維數組a,包含4個元素

int (*p)[4];   //定義指向包含4個元素的一維數組的指針變量中

p=&a;    //使p指向一維數組

printf("%d\n",(*p)[3]); //輸出a[3],輸出整數7

return 0;

}

 

比較:

① int a[4];(a有4個元素,每一個元素爲整型)

② int (*p)[4];

第②種形式表示(*p)有4個元素,每一個元素爲整型。也就是p所指的對象是有4個整型元素的數組,即p是指向一維數組的指針,見圖8.24。應該記住,此時p只能指向一個包含4個元素的一維數組,不能指向一維數組中的某一元素。p的值是該一維數組的起始地址。雖然這個地址(指純地址)與該一維數組首元素的地址相同,但它們的基類型是不一樣的。

 

指向由m個元素組成的一維數組的指針變量

要注意指針變量的類型,從「int (*p)[4];」能夠看到,p的類型不是int *型,而是int (*)[4]型,p被定義爲指向一維整型數組的指針變量,一維數組有4個元素,所以p的基類型是一維數組,其長度是16字節。「*(p+2)+3」括號中的2是以p的基類型(一維整型數組)的長度爲單位的,即p每加1,地址就增長16個字節(4個元素,每一個元素4個字節),而「*(p+2)+3」括號外的數字3,不是以p的基類型的長度爲單位的。因爲通過*(p+2)的運算,獲得a[2],即&a[2][0],它已經轉化爲指向列元素的指針了,所以加3是以元素的長度爲單位的,加3就是加(3×4)個字節。雖然p+2和*(p+2)具備相同的值,但因爲它們所指向的對象的長度不一樣,所以(p+2)+3和*(p+2)+3的值就不相同了。

一維數組名能夠做爲函數參數,多維數組名也可做函數參數。

用指針變量做形參,以接受實參數組名傳遞來的地址。能夠有兩種方法:

① 用指向變量的指針變量;

② 用指向一維數組的指針變量。

例8:有一個班,3個學生,各學4門課,計算總平均分數以及第n個學生的成績。

#include <stdio.h>

int main()

{ void average(float *p,int n);

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

float score[3][4]={{65,67,70,60},{80,87,90,81},{90,99,100,98}};

average(*score,12);  //求12個分數的平均分

search(score,2);   //求序號爲2的學生的成績

return 0;

}

 

void average(float *p,int n)  //定義求平均成績的函數

{ float *p_end;

float sum=0,aver;

p_end=p+n-1;

//n的值爲12時,p_end的值是p+11,指向最後一個元素

for(;p<=p_end;p++)

sum=sum+(*p);

aver=sum/n;

printf("average=%5.2f\n",aver);

}

 

void search(float (*p)[4],int n)

//p是指向具備4個元素的一維數組的指針

{ int i;

printf("The score of No.%d are:\n",n);

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

              printf("%5.2f ",*(*(p+n)+i));

printf("\n");

}

運行結果:

 

 

注:實參與形參若是是指針類型,應當注意它們的基類型必須一致。不該把int *型的指針(即數組元素的地址)傳給int (*)[4] 型(指向一維數組)的指針變量,反之亦然。

例:在上例的基礎上,查找有一門以上課程不及格的學生,輸出他們的所有課程的成績。

#include <stdio.h>

int main()

{ void search(float (*p)[4],int n); //函數聲明

float score[3][4]={{65,57,70,60},{58,87,90,81},{90,99,100,98}};

//定義二維數組函數score

search(score,3);    //調用search函數

return 0;

}

 

void search(float (*p)[4],int n)

//形參p是指向包含4個float型元素的一維數組的指針變量

{ int i,j,flag;

for(j=0;j<n;j++)

{ flag=0;

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

if(*(*(p+j)+i)<60) flag=1;

//*(*(p+j)+i)就是score[j][i]

if(flag==1)

{ printf("No.%d fails,his scores are:\n",j+1);

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

printf("%5.1f ",*(*(p+j)+i));

//輸出*(*(p+j)+i)就是輸出score[j][i]的值

printf("\n");

}

}

}

運行結果:

 

 

 

8.4 經過指針引用字符串

8.4.1 字符串的引用方式

(1)用字符數組存放一個字符串,能夠經過數組名和下標引用字符串中一個字符,也能夠經過數組名和格式聲明「%s」輸出該字符串。

(2)用字符指針變量指向一個字符串常量,經過字符指針變量引用字符串常量。

 

例:定義一個字符數組,在其中存放字符串″I love China!″,輸出該字符串和第8個字符。

#include <stdio.h>

int main()

{ char string[]="I love China!"; //定義字符數組sting

printf("%s\n",string);  //用%s格式聲明輸出string,能夠輸出整個字符串

       printf("%c\n",string[7]);         //用%c格式輸出一個字符數組元素

return 0;

}

運行結果:

 

 

例:經過字符指針變量輸出一個字符串。

#include <stdio.h>

int main()

{ char *string="I love China!"; //定義字符指針變量string並初始化

printf("%s\n",string);  //輸出字符串

return 0;

}

運行結果:

 

 

在C語言中只有字符變量,沒有字符串變量。

char *string="I love China!";

等價於

char *string;  //定義一個char *型變量

string=″I love China!″;

//把字符串第1個元素的地址賦給字符指針變量string

注:string被定義爲一個指針變量,基類型爲字符型。它只能指向一個字符類型數據,而不能同時指向多個字符數據,更不是把″I love China!″這些字符存放到string中(指針變量只能存放地址),也不是把字符串賦給*string。只是把″I love China!″的第1個字符的地址賦給指針變量string。

 

能夠對指針變量進行再賦值,string=″I am a student.″;  //對指針變量string從新賦值

能夠經過字符指針變量輸出它所指向的字符串,printf(″%s\n″,string); //%s可對字符串進行總體的輸入輸出

 

說明:%s是輸出字符串時所用的格式符,在輸出項中給出字符指針變量名string,則系統會輸出string所指向的字符串第1個字符,而後自動使string加1,使之指向下一個字符,再輸出該字符……如此直到遇到字符串結束標誌′\0′爲止。注意,在內存中,字符串的最後被自動加了一個′\0′。

 

例:將字符串a複製爲字符串b,而後輸出字符串b。

//用數組的方式輸出

#include <stdio.h>

int main()

{ char a[]="I am a student.",b[20]; //定義字符數組

int i;

for(i=0;*(a+i)!='\0';i++)

*(b+i)=*(a+i);  //將a[i]的值賦給b[i]

*(b+i)='\0';    //在b數組的有效字符以後加'\0'

printf("string a is:%s\n",a);//輸出a數組中所有有效字符

printf("string b is:");

for(i=0;b[i]!='\0';i++)

printf("%c",b[i]); //逐個輸出b數組中所有有效字符

printf("\n");

return 0;

}

 

//用指針變量

#include <stdio.h>

int main()

{ char a[]="I am a boy.",b[20],*p1,*p2;

p1=a;p2=b;

//p1,p2分別指向a數組和b數組中的第一個元素

for(;*p1!='\0';p1++,p2++)  //p1,p2每次自加1

*p2=*p1;

//將p1所指向的元素的值賦給p2所指向的元素

*p2='\0';   //在複製徹底部有效字符後加'\0'

printf("string a is:%s\n",a); //輸出a數組中的字符

printf("string b is:%s\n",b); //輸出b數組中的字符

return 0;

}

 

8.4.2 字符指針做函數參數

例:用函數調用實現字符串的複製

(1) 用字符數組名做爲函數參數

#include <stdio.h>

int main()

{ void copy_string(char from[], char to[]);

char a[]="I am a teacher.";

char b[]="You are a student.";

printf("string a=%s\nstring b=%s\n",a,b);

printf("copy string a to string b:\n");

copy_string(a,b);  //用字符數組名做爲函數實參

printf("\nstring a=%s\nstring b=%s\n",a,b);

return 0;

}

 

void copy_string(char from[], char to[])    //形參爲字符數組

{ int i=0;

while(from[i]!='\0')

{ to[i]=from[i]; i++;}

to[i]='\0';

}

運行結果:

 

 

(2) 用字符型指針變量做實參

#include <stdio.h>

int main()

{ void copy_string(char from[], char to[]); //函數聲明

char a[]="I am a teacher.";  //定義字符數組a並初始化

char b[]="You are a student."; //定義字符數組b並初始化

       char *from=a,*to=b; //from指向a數組首元素,to指向b數組首元素

printf("string a=%s\nstring b=%s\n",a,b);

printf("copy string a to string b:\n");

copy_string(from,to); //實參爲字符指針變量

printf("\nstring a=%s\nstring b=%s\n",a,b);

return 0;

}

void copy_string(char from[], char to[])   //形參爲字符數組

{ int i=0;

while(from[i]!='\0')

{ to[i]=from[i]; i++;}

to[i]='\0';

}

運行結果:

 

 

說明:指針變量from的值是a數組首元素的地址,指針變量to的值是b數組首元素的地址。它們做爲實參,把a數組首元素的地址和b數組首元素的地址傳遞給形參數組名from和to(它們實質上也是指針變量)。其餘與程序(1)相同。

(3) 用字符指針變量做形參和實參

#include <stdio.h>

int main()

{ void copy_string(char *from, char *to);

char *a="I am a teacher.";  //a是char*型指針變量

char b[]="You are a student."; //b是字符數組

char *p=b;  //使指針變量p指向b數組首元素

printf("string a=%s\nstring b=%s\n",a,b); //輸出a串和b串

printf("copy string a to string b:\n");

copy_string(a,p); //調用copy_string函數,實參爲指針變量

printf("\nstring a=%s\nstring b=%s\n",a,b); //輸出改變後的a串和b串

return 0;

}

 

void copy_string(char *from, char *to) //定義函數,形參爲字符指針變量

{ for(;*from!='\0';from++,to++)

{ *to=*from;}

*to='\0';

}

運行結果:

 

 

函數void copy_string(char *from, char *to)還有如下等價幾種,只需記住一種便可,其它作了解。最好的狀況固然是全都會用。

void copy_string(char *from, char *to)

{ for(;(*to++=* from++)!='\0';);

//或for(;*to++=* from++;);

}

void copy_string(char *from, char *to)

{ while((*to=*from)!='\0')

//或while(*to=*from)

{ to++; from++;}

}

void copy_string(char *from, char *to)

{ while(*from!='\0')

//或while(*from) ,由於'\0'的ASCII碼爲0

*to++=*from++;

*to='\0';

}

void copy_string(char *from, char *to)

{ while((*to++=*from++)!='\0');

//或while(*to++=*from++)

}

void copy_string(char from[],char to[])

{ char *p1, *p2;

p1=from;p2=to;

while((*p2++=*p1++)!='\0');

}

 

字符指針做爲函數參數時,實參與形參的類型有如下幾種對應關係:

 

 

8.4.3 使用字符指針變量和字符數組的比較

 

 

例:改變指針變量的值。

#include <stdio.h>

int main()

{ char *a="I love China!";

a=a+7;   //改變指針變量的值,即改變指針變量的指向

printf("%s\n",a); //輸出從a指向的字符開始的字符串

return 0;

}

運行結果:

 

 

 

#include <stdio.h>

int main()

{ char str[]={"I love China!"};

str=str+7;

printf("%s\n",str);

return 0;

}

說明:

(1)指針變量的值是能夠改變的,而字符數組名錶明一個固定的值(數組首元素的地址),不能改變。

(2)指針變量a的值是能夠變化的。printf函數輸出字符串時,從指針變量a當時所指向的元素開始,逐個輸出各個字符,直到遇'\0'爲止。而數組名雖然表明地址,但它是常量,它的值是不能改變的。

8.5 指向函數的指針

8.5.1 什麼是函數指針

若是在程序中定義了一個函數,在編譯時會把函數的源代碼轉換爲可執行代碼並分配一段存儲空間。這段內存空間有一個起始地址,也稱爲函數的入口地址。每次調用函數時都從該地址入口開始執行此段函數代碼。

函數名就是函數的指針,它表明函數的起始地址。

能夠定義一個指向函數的指針變量,用來存放某一函數的起始地址,這就意味着此指針變量指向該函數。例如: int (*p)(int,int);

定義p是一個指向函數的指針變量,它能夠指向函數類型爲整型且有兩個整型參數的函數。此時,指針變量p的類型用int (*)(int,int)表示。

8.5.2 用函數指針變量調用函數

例:用函數求整數a和b中的大者

//(1)經過函數名調用函數

#include <stdio.h>

int main()

{ int max(int,int); //函數聲明

int a,b,c;

printf("please enter a and b:");

scanf("%d,%d",&a,&b);

c=max(a,b);  //經過函數名調用max函數

printf("a=%d\nb=%d\nmax=%d\n",a,b,c);

return 0;

}

 

int max(int x,int y)  //定義max函數

{ int z;

if(x>y) z=x;

else z=y;

return(z);

}

 

(2) 經過指針變量調用它所指向的函數

#include <stdio.h>

int main()

{ int max(int,int); //函數聲明

int (*p)(int,int); //定義指向函數的指針變量p

int a,b,c;

p=max;   //使p指向max函數

printf("please enter a and b:");

scanf("%d,%d",&a,&b);

c=(*p)(a,b);  //經過指針變量調用max函數

printf("a=%d\nb=%d\nmax=%d\n",a,b,c);

return 0;

}

int max(int x,int y)  //定義max函數

{ int z;

if(x>y)z=x;

else z=y;

return(z);

}

運行結果:

 

 

8.5.3 怎樣定義和使用指向函數的指針變量

定義指向函數的指針變量的通常形式爲:

類型名 (*指針變量名)(函數參數表列)

如:

int (*p)(int,int);

說明:

(1) 定義指向函數的指針變量,並不意味着這個指針變量能夠指向任何函數,它只能指向在定義時指定的類型的函數。

(2)  若是要用指針調用函數,必須先使指針變量指向該函數。

(3) 在給函數指針變量賦值時,只須給出函數名而沒必要給出參數。

(4) 用函數指針變量調用函數時,只須將(*p)代替函數名便可(p爲指針變量名),在(*p)以後的括號中根據須要寫上實參。

(5) 對指向函數的指針變量不能進行算術運算,如p+n,p++,p--等運算是無心義的。

(6) 用函數名調用函數,只能調用所指定的一個函數,而經過指針變量調用函數比較靈活,能夠根據不一樣狀況前後調用不一樣的函數。

例:輸入兩個整數,而後讓用戶選擇1或2,選1時調用max函數,輸出兩者中的大數,選2時調用min函數,輸出兩者中的小數。

#include <stdio.h>

int main()

{ int max(int,int); //函數聲明

int min(int x,int y); //函數聲明

int (*p)(int,int); //定義指向函數的指針變量

int a,b,c,n;

printf("please enter a and b:");

scanf("%d,%d",&a,&b);

printf("please choose 1 or 2:");

scanf("%d",&n); //輸入1戓2

if(n==1) p=max; //如輸入1,使p指向max函數

else if (n==2) p=min; //如輸入2,使p指向min函數

c=(*p)(a,b);  //調用p指向的函數

printf("a=%d,b=%d\n",a,b);

if(n==1) printf("max=%d\n",c);

else printf("min=%d\n",c);

return 0;

}

 

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);

}

運行結果:

 

OR

 

8.5.4 用指向函數的指針做函數參數

 

 

例:有兩個整數a和b,由用戶輸入1,2或3。如輸入1,程序就給出a和b中的大者,輸入2,就給出a和b中的小者,輸入3,則求a與b之和。

#include <stdio.h>

int main()

{ int fun(int x,int y, int (*p)(int,int)); //fun函數聲明

       int max(int,int);                //max函數聲明

int min(int,int);   //min函數聲明

int add(int,int);   //add函數聲明

int a=34,b=-21,n;

printf("please choose 1,2 or 3:");

scanf("%d",&n);   //輸入1,2或3之一

if(n==1) fun(a,b,max);  //輸入1時調用max函數

else if(n==2) fun(a,b,min); //輸入2時調用min函數

else if(n==3) fun(a,b,add); //輸入3時調用add函數

return 0;

}

 

int fun(int x,int y,int (*p)(int,int)) //定義fun函數

{      int result;

result=(*p)(x,y);

       printf("%d\n",result);             //輸出結果

}

 

 

int max(int x,int y) //定義max函數

{ int z;

if(x>y) z=x;

else z=y;

printf("max=" );

       return(z);            //返回值是兩數中的大者

}

 

int min(int x,int y) //定義min函數

{ int z;

if(x<y) z=x;

else z=y;

printf("min=");

return(z);  //返回值是兩數中的小者

}

 

int add(int x,int y) //定義add函數

{ int z;

z=x+y;

printf("sum=");

return(z);  //返回值是兩數之和

}

運行結果:不一樣選擇有不一樣的答案

 

 

 

 

 

 

 

 

8.6 返回指針值的函數

定義返回指針值的函數的通常形式爲:

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

解釋:

一個函數能夠返回一個整型值、字符值、實型值等,也能夠返回指針型的數據,即地址。其概念與之前相似,只是返回的值的類型是指針類型而已。如:

int *a(int x,int y);

a是函數名,調用它之後能獲得一個int*型(指向整型數據)的指針,即整型數據的地址。x和y是函數a的形參,爲整型。

注:在「*a」兩側沒有括號,在a的兩側分別爲*運算符和()運算符。而()優先級高於*,所以a先與()結合,顯然這是函數形式。這個函數前面有一個*,表示此函數是指針型函數(函數值是指針)。最前面的int表示返回的指針指向整型變量。

例:有a個學生,每一個學生有b門課程的成績。要求在用戶輸入學生序號之後,能輸出該學生的所有成績。用指針函數來實現。

#include <stdio.h>

int main()

{ float score[][4]={{60,70,80,90},{56,89,67,88},{34,78,90,66}};

//定義數組,存放成績

float *search(float (*pointer)[4],int n); //函數聲明

float *p;

int i,k;

printf("enter the number of student:");

scanf("%d",&k); //輸入要找的學生的序號

printf("The scores of No.%d are:\n",k);

p=search(score,k); //調用search函數,返回score[k][0]的地址

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

printf("%5.2f\t",*(p+i)); //輸出score[k][0]~score[k][3]的值

printf("\n");

return 0;

}

 

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

//形參pointer是指向一維數組的指針變量

{ float *pt;

pt=*(pointer+n); //pt的值是&score[k][0]

return(pt);

}

運行結果:

 

 

例:對上例題,找出其中有不及格的課程的學生及其學生號。

#include <stdio.h>

int main()

{ float score[][4]={{60,70,80,90},{56,89,67,88},{34,78,90,66}};

//定義數組,存放成績

float *search(float (*pointer)[4]); //函數聲明

float *p;

int i,j;

for(i=0;i<3;i++)    //循環3次

{ p=search(score+i);

//調用search函數,若有不及格返回score[i][0]的地址,不然返回NULL

if(p==*(score+i))

//若是返回的是score[i][0]的地址,表示p的值不是NULL

{ printf("No.%d score:",i);

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

printf("%5.2f  ",*(p+j));

//輸出score[i][0]~score[i][3]的值

printf("\n");

              }

}

       return 0;

}

 

float *search(float (*pointer)[4])

//定義函數,形參pointer是指向一維數組的指針變量

{ int i=0;

float *pt;

pt=NULL; //先使pt的值爲NULL

for(;i<4;i++)

if(*(*pointer+i)<60) pt=*pointer;

              //若是有不及格課程,使pt指向score[i][0]

return(pt);

}

運行結果:

 

8.7 指針數組和多重指針

8.7.1 什麼是指針數組

定義一維指針數組的通常形式爲

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

如:

int *p[4];

一個數組,若其元素均爲指針類型數據,稱爲指針數組,也就是說,指針數組中的每個元素都存放一個地址,至關於一個指針變量。

指針數組比較適合用來指向若干個字符串,使字符串處理更加方便靈活。

 

例:將若干字符串按字母順序(由小到大)輸出。

 

 

 

#include <stdio.h>

#include <string.h>

int main()

{ void sort(char *name[],int n);  //函數聲明

void print(char *name[],int n); //函數聲明

char *name[]={"Follow me","BASIC",

       "Great Wall","FORTRAN","Computer design"};

//定義指針數組,它的元素分別指向5個字符串

int n=5;

       sort(name,n);     //調用sort函數,對字符串排序

print(name,n); //調用print函數,輸出字符串

return 0;

}

 

void sort(char *name[],int n)   //定義sort函數

{ char *temp;

int i,j,k;

for(i=0;i<n-1;i++)   //用選擇法排序

{ k=i;

for(j=i+1;j<n;j++)

if(strcmp(name[k],name[j])>0) k=j;

if(k!=i)

{ temp=name[i]; name[i]=name[k]; name[k]=temp;}

}

}

 

void print(char *name[],int n) //定義print函數

{ int i;

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

printf("%s\n",name[i]);

//按指針數組元素的順序輸出它們所指向的字符串

}

運行結果:

 

 

在瞭解了指針數組的基礎上,須要瞭解指向指針數據的指針變量,簡稱爲指向指針的指針。

 

 

例:使用指向指針數據的指針變量。

#include <stdio.h>

int main()

{ char *name[]={"Follow me","BASIC","Great Wall","FORTRAN","Computer design"};

char **p;

int i;

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

{ p=name+i;

printf("%s\n",*p);

}

return 0;

}

運行結果:

 

 

說明:

(1) p是指向char*型數據的指針變量,即指向指針的指針。在第1次執行for循環體時,賦值語句「p=name+i;」使p指向name數組的0號元素name[0],*p是name[0]的值,即第1個字符串首字符的地址,用printf函數輸出第1個字符串(格式符爲%s)。執行5次循環體,依次輸出5個字符串。

 

(2) 指針數組的元素也能夠不指向字符串,而指向整型數據或實型數據等。

 

例:有一個指針數組,其元素分別指向一個整型數組的元素,用指向指針數據的指針變量,輸出整型數組各元素的值。

#include <stdio.h>

int main()

{ int a[5]={1,3,5,7,9};

int *num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};

int **p,i;    //p是指向指針型數據的指針變量

p=num;    //使p指向num[0]

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

{ printf("%d ",**p);

p++;

}

printf("\n");

return 0;

}

運行結果:

 

8.7.3 指針數組做main函數的形參

 

 

 

 

int main(int argc,char *argv[])

{ while(argc>1)

{ ++argv;

printf("%s\n", *argv);

--argc;

}

return 0;

}

int main(int argc,char *argv[])

{ while(argc-->1)

printf("%s\n", *++argv);

return 0;

}.

 

 

 

 

8.8 動態內存分配與指向它的指針變量

8.8.1 什麼是內存的動態分配

 

 

8.8.2 怎樣創建內存的動態分配

1. 用malloc函數開闢動態存儲區

函數原型爲

void *malloc(unsigned int size);

做用:在內存的動態存儲區中分配一個長度爲size的連續空間。形參size的類型定爲無符號整型(不容許爲負數)。此函數的值(即「返回值」)是所分配區域的第一個字節的地址,或者說,此函數是一個指針型函數,返回的指針指向該分配域的第一個字節。如:

malloc(100);              //開闢100字節的臨時分配域,函數值爲其第1個字節的地址

指針的基類型爲void,即不指向任何類型的數據,只提供一個純地址。若是此函數未能成功地執行(例如內存空間不足),則返回空指針(NULL)。

 

2.用calloc函數開闢動態存儲區

函數原型爲

void *calloc(unsigned n, unsigned size);

做用:在內存的動態存儲區中分配n個長度爲size的連續空間,這個空間通常比較大,足以保存一個數組。

p=calloc(50,4);  //開闢50×4個字節的臨時分配域,把首地址賦給指針變量p

用calloc函數能夠爲一維數組開闢動態存儲空間,n爲數組元素個數,每一個元素長度爲size。這就是動態數組。函數返回指向所分配域的第一個字節的指針;若是分配不成功,返回NULL。

 

3.用realloc函數從新分配動態存儲區

函數原型爲

void *realloc(void *p,unsigned int size);

做用:若是已經經過malloc函數或calloc函數得到了動態空間,想改變其大小,能夠用realloc函數從新分配。

realloc(p,50); //將p所指向的已分配的動態空間改成50字節

 

用realloc函數將p所指向的動態空間的大小改變爲size。p的值不變。若是重分配不成功,返回NULL。

 

4.用free函數釋放動態存儲區

函數原型爲

void free(void *p);

做用:釋放指針變量p所指向的動態空間,使這部分空間能從新被其餘變量使用。p應是最近一次調用calloc或malloc函數時獲得的函數返回值。

free(p);  //釋放指針變量p所指向的已分配的動態空間

free函數無返回值。

 

說明:以上4個函數的聲明在stdlib.h頭文件中,在用到這些函數時應當用「#include <stdlib.h>」指令把stdlib.h頭文件包含到程序文件中。

 

8.8.3 void指針類型

C 99容許使用基類型爲void的指針類型。能夠定義一個基類型爲void的指針變量(即void*型變量),它不指向任何類型的數據。在將它的值賦給另外一指針變量時由系統對它進行類型轉換,使之適合於被賦值的變量的類型。

int *pt;

pt=(int *)mcaloc(100); //mcaloc(100)是void *型,把它轉換爲int *型

注:不要把「指向void類型」理解爲能指向「任何的類型」的數據,而應理解爲「指向空類型」或「不指向肯定的類型」的數據。

例:創建動態數組,輸入5個學生的成績,另外用一個函放數檢查其中有無低於60分的,輸出不合格的成績。

#include <stdio.h>

#include <stdlib.h>    //程序中用了malloc函數,應包含stdlib.h

int main()

{ void check(int *);    //函數聲明

int *p1,i;      //p1是int型指針

p1=(int *)malloc(5*sizeof(int)); //開闢動態內存區,將地址轉換成int *型,而後放在p1中

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

              scanf("%d",p1+i);                   //輸入5個學生的成績

check(p1);     //調用check函數

return 0;

}

 

void check(int *p)     //定義check函數,形參是int*指針

{ int i;

printf("They are fail:");

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

              if(p[i]<60) printf("%d ",p[i]);        //輸出不合格的成績

printf("\n");

}

運行結果:

 

 

有關指針的小結

 

 

 

相關文章
相關標籤/搜索