學習 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 |
經過上面的實例,咱們瞭解了什麼是內存地址以及如何訪問它。接下來讓咱們看看什麼是指針。
指針是一個變量,其值爲另外一個變量的地址,即,內存位置的直接地址。就像其餘變量或常量同樣,您必須在使用指針存儲其餘變量地址以前,對其進行聲明。
指針變量聲明的通常形式爲:type *var-name;
在這裏,type 是指針的基類型,它必須是一個有效的 C 數據類型,var-name 是指針變量的名稱。用來聲明指針的星號 * 與乘法中使用的星號是相同的。可是,在這個語句中,星號是用來指定一個變量是指針。如下是有效的指針聲明:
int *ip; /* 一個整型的指針 */ double *dp; /* 一個 double 型的指針 */ float *fp; /* 一個浮點型的指針 */ char *ch; /* 一個字符型的指針 */ |
全部指針的值的實際數據類型,無論是整型、浮點型、字符型,仍是其餘的數據類型,都是同樣的,都是一個表明內存地址的長的十六進制數。不一樣數據類型的指針之間惟一的不一樣是,指針所指向的變量或常量的數據類型不一樣。
#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; } |
運行結果:
上面變量對應的關係,如圖:
注:定義指針變量時,左側應有類型名,不然就不是定義指針變量。也稱爲基類型
定義指針的通常形式爲:
類型名 *指針變量名;
如:
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是整數,不合法的賦值
(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; } |
運行結果:
指針在過程當中的變化:
例:對輸入的兩個整數按大小順序輸出。現用函數處理,並且用指針類型的數據做函數參數。
#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; } |
運行結果:
數組元素的指針就是素組元素的地址。
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)
在指針已指向一個數組元素時,能夠對指針進行如下運算:
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;} } } |
注:這章節瞭解便可,若是想提高本身,不只須要理解還須要會使用。
理解如下這張圖即可瞭解多維數組:
說明:咱們在這裏說明一下,在計算機底層是不存在二維數組這種存儲方式的,都是以一維數組爲基礎邏輯化出二維數組,而一維數組不只是邏輯化更是物理上也是能夠實現的。總的來講,二維數組是一維數組的邏輯化,本質上二維數組仍是一維數組。只是爲了方便人們理解,才把二維數組分爲行和列的方式顯示。
若是用一個指針變量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"); } } } |
運行結果:
(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; } |
例:用函數調用實現字符串的複製
(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'); } |
|
字符指針做爲函數參數時,實參與形參的類型有如下幾種對應關係:
例:改變指針變量的值。
#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'爲止。而數組名雖然表明地址,但它是常量,它的值是不能改變的。
若是在程序中定義了一個函數,在編譯時會把函數的源代碼轉換爲可執行代碼並分配一段存儲空間。這段內存空間有一個起始地址,也稱爲函數的入口地址。每次調用函數時都從該地址入口開始執行此段函數代碼。
函數名就是函數的指針,它表明函數的起始地址。
能夠定義一個指向函數的指針變量,用來存放某一函數的起始地址,這就意味着此指針變量指向該函數。例如: int (*p)(int,int);
定義p是一個指向函數的指針變量,它能夠指向函數類型爲整型且有兩個整型參數的函數。此時,指針變量p的類型用int (*)(int,int)表示。
例:用函數求整數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); } |
運行結果:
定義指向函數的指針變量的通常形式爲:
類型名 (*指針變量名)(函數參數表列)
如:
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
例:有兩個整數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); //返回值是兩數之和 } |
運行結果:不一樣選擇有不一樣的答案
定義返回指針值的函數的通常形式爲:
類型名 *函數名(參數表列);
解釋:
一個函數能夠返回一個整型值、字符值、實型值等,也能夠返回指針型的數據,即地址。其概念與之前相似,只是返回的值的類型是指針類型而已。如:
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); } |
運行結果:
定義一維指針數組的通常形式爲
類型名 *數組名[數組長度];
如:
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; } |
運行結果:
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; }. |
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頭文件包含到程序文件中。
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"); } |
運行結果:
有關指針的小結