p1
, p2
相減時,p2-p1
就是從p1
到 p2
,不包含p2
的元素個數,結果的類型是ptrdiff_t
#include <stdio.h> int main() { int a[10] = {1,2,3,4,5,6,7,8,9,0}; int sub; int *p1 = &a[2]; int *p2 = &a[8]; sub=p2-p1; printf("%d\n",sub); // 輸出結果爲 6 return 0; }
先來定義以下的二維數組:數組
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
首先,對於一個數組而言,數組名就是該數組的首地址。函數
首地址:一段存儲空間中的第一個存儲單元的地址指針
因此對於這個二維數組 a[3][4]
,數組名a
指向的就是第一個數組,用以下代碼能夠進行驗證:code
printf("a=%p\n", a); printf("a+1=%p\n", a + 1); printf("a+2=%p\n", a + 2);
輸出以下:對象
a=000000C63835F628 a+1=000000C63835F638 a+2=000000C63835F648
能夠看到,每+1
地址遞增16
。
a
是數組名,是該數組的首地址,
指向該數組的第一個存儲單元(一個一維數組),a類型爲 int(*)[4]
因此a+1
會跳到第二個數組,地址加上16B
內存
printf("*a=%p\n", *a); printf("*a+1=%p\n", *a + 1); printf("*(a+1)=%p\n", *(a + 1));
輸出以下:字符串
*a=000000C63835F628 *a+1=000000C63835F62C *(a+1)=000000C63835F638
能夠看到,每+1
地址遞增4
。
*a
指向以一個一維數組的首地址即原型
*a==a[0]==&a[0][0]
編譯器
因此*a+1
,地址會偏移4B
,即指向下一個數據,it
*a
類型爲int*
*(a+1)
,地址會偏移16B
,即指向下一個一維數組的首地址。
printf("&a=%p\n", &a); printf("&a+1=%p\n", &a + 1); printf("&(a+1)=ERORR\n");
輸出以下:
&a=000000C63835F628 &a+1=000000C63835F658 &(a+1)=ERORR
&a
指向整個二維數組,是取這個二維數組的地址。
&a
類型爲int(*)[3][4]
&a+1
地址偏移了48B
,跳過了整個二維數組
printf("&a[0]=%p\n", &a[0]); printf("&a[0]+1=%p\n", &a[0] + 1); printf("&a[0]+1=%p\n", &a[0]); printf("&(a[0]+1)=ERORR\n");
輸出以下:
&a[0]=000000C63835F628 &a[0]+1=000000C63835F638 &a[0]+1=000000C63835F628 &(a[0]+1)=ERORR
&a[0]
指向第一個數組,是取第一個數組的地址
&a[0]類型爲 int(*)[4]
&a[0]+1
地址偏移了16B,跳過了第一個一維數組
printf("a[0]=%p\n", a[0]); printf("a[0]+1=%p\n", a[0] + 1); printf("&a[0][0]%p\n", &a[0][0]);
a[0]
是第一個數組的數組名,是第一個數組的首地址,即a[0]
指向指向第一個存儲單元a[0][0]
a[0]
類型爲int*
a[0]+1
,指向了第二個存儲單元,地址偏移了4B
&a[0][0]
,是指向a[0][0]
的指針,
&a[0][0]
類型爲int*
int *p[10]
[]
的優先級比 *
高,故 p
先與 []
結合,成爲一個數組 p[]
;再由 int *
指明這是一個 int
的指針。數組的第 i
個元素是 *p[i]
,而 p[i]
是一個指針。
int (*p)[10]
因爲 ()
的優先級最高,因此 p
是一個指針,指向一個 int
類型的一維數組,這個一維數組的長度是 10
,這也是指針 p
的步長。也就是說,執行 p+1
時,p 要跨過10
個 int
型數據的長度。數組指針與二維數組聯繫密切,能夠用數組指針來指向一個二維數組,以下:
#include <stdio.h> int main() { int arr[2][3] = { {1,2,3}, {4,5,6} }; // 定義一個二維數組並初始化 int (*p)[3]; // 指針指向一個含有3個元素的一維數組 p = arr; // p 指向 arr[0]==&arr[0][0] printf("%d\n",(*p)[0]); // 輸出結果爲 1 p++; printf("%d\n",(*p)[1]); // 輸出結果爲5 return 0; }
printf("a[i][j]\n");
printf("a[i][j]\n"); for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) printf("%5d", a[i][j]); printf("\n"); }
printf("1:*(a[i]+j)\n");
for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) printf("%5d", *(a[i] + j)); printf("\n"); }
printf("2:*(*(a+i)+j)\n");
for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) printf("%5d", *(*(a + i) + j)); printf("\n"); }
亦即a[i][j]==*&a[i][j]==*(a[i]+j)==*(*(a+i)+j)
綜合上面的分析,對於二維數組a[3][4]
有以下結論:
表達式 | 數據類型 | 指向 |
---|---|---|
a==&a[0] |
int(*)[4] |
均指向第一個一維數組 |
a[0]==&a[0][0] |
int* |
均指向第一個一維數組的第一個單元 |
&a |
int(*)[3][4] |
指向整個二維數組 |
*(a+i)=a[i]=&a[i][0] |
int* |
指向數組i的第一個存儲單 |
運算 | 意義 |
---|---|
a+1 |
a 指向第一個一維數組,因此a+1 地址偏移4x4=16B |
*a+1 |
*a 指向第一個數組的第一個單元,因此*a+1 地址偏移4B |
*(a+1) |
a+1 指向下一個數組,*a==a[0]---->*(a+1)==a[1] 因此a+1地址偏4x4=16B |
&a+1 |
&a 指向整個二維數組,因此&a+1 地址偏移4x4x3=48B |
&(a+1) |
數組名a 是指針常量,不能更改了,此種寫法錯誤 |
&a[0]+1 |
&a[0] 指向第一個數組,&a[0]+1---->&a[1] ,指向下一個數組,因此地址偏移4x4=16B |
&(a[0]+1) |
此種寫法錯誤 |
a[0]+1 |
a[0] 指向第一個數組的首地址a[0][0] ,因此a[0]+1 指向下一個數據a[0][1] ,地址移4B |
類型 (*指針名)[N]; //N元素個數
數組指針是指向含 N 個元素的一維數組的指針。因爲二維數組每一行均是一維數組,故一般使用指向一維數組的指針指向二維數組的每一行。
[]
運算的優先級高於*
,int *p[N]
爲指針數組,每一個元素類型爲 int*
#include<stdio.h> int main() { int a[3][4]; int(*p)[4]=a;//a是首地址,指向一維數組,類型爲int(*)[4]與p吻合 //第i行首地址 p+i==a+i,其他操做與上文一直 }
指針數組最主要的用途是處理字符串。在 C 語言中,一個字符串常量表明返回該字符串首字符的地址,即指向該字符串首字符的指針常量,而指針數組的每一個元素均是指針變量,故能夠把若干字符串常量做爲字符指針數組的每一個元素。經過操做指針數組的元素間接訪問各個元素對應的字符串。
#include<stdio.h> #include<stdlib.h> //#define NULL ( (void*) 0) int main() { char *c[]={"if","else","for","while",NULL}; for(int i=0;c[i]!=NULL;i++) puts(c[i]); system("pause"); return 0; }
- 首地址:一段存儲空間中的第一個存儲單元的地址
- 分析指針:關鍵不在指針的值,而實指針的類型及其指向
*a==a[0]==&a[0][0]
- 訪問數組元素:
- 下標法
- 指針法
*(a[i]+j)
==*(*(a+i)+j)
結構指針是指向結構的指針,使用 ->
操做符來訪問結構指針的成員。
#include<stdio.h> typedef struct{ char name[10]; int age; int score; }message; int main() { message mess={"elio",18,92}; message *p=&mess; printf("%s\n",p->name);//輸出elio printf("%d\n",p->score);//輸出92 return 0; }
C語言的全部參數均是以「傳值調用」的方式進行傳遞的,這意味着函數將得到參數值的一份拷貝。這樣,函數能夠放心修改這個拷貝值,而沒必要擔憂會修改調用程序實際傳遞給它的參數。
傳值調用:實參爲要處理的數據,函數調用時,把要處理數據(實參)的一個副本複製到對應形參變量中,函數中對形參的全部操做均是對原實參數據副本的操做,沒法影響原實參數據。且當要處理的數據量較大時,複製和傳輸實參的副本可能浪費較多的空間和時間。
傳址調用:顧名思義,實參爲要處理數據的地址,形參爲可以接受地址值的「地址箱」即指針變量。函數調用時,僅是把該地址傳遞給對應的形參變量,在函數體內,可經過該地址(形參變量的值)間接地訪問要處理的數據,因爲並無複製要處理數據的副本,故此種方式能夠大大節省程序執行的時間和空間。
傳值調用的好處是是被調函數不會改變調用函數傳過來的值,能夠放心修改。可是有時候須要被調函數回傳一個值給調用函數,這樣的話,傳值調用就沒法作到。爲了解決這個問題,可使用傳指針調用。指針參數使得被調函數可以訪問和修改主調函數中對象的值。
#include <stdio.h> void swap1(int a,int b) // 參數爲普通的 int 變量 { int temp; temp = a; a = b; b = temp; } void swap2(int *a,int *b) // 參數爲指針,接受調用函數傳遞過來的變量地址做爲參數,對所指地址處的內容進行操做 { int temp; // 地址自己並無改變,地址所對應的內存段中的內容發生了變化 temp = *a; *a = *b; *b = temp; } int main() { int x = 1,y = 2; swap1(x,y); // 將 x,y 的值自己做爲參數傳遞給了被調函數 printf("%d %5d\n",x,y); // 輸出結果爲:1 2 swap(&x,&y); // 將 x,y 的地址做爲參數傳遞給了被調函數,傳遞過去的也是一個值,與傳值調用不衝突 printf("%d %5d\n",x,y); // 輸出結果爲:2 1 return 0; }
有時函數調用結束後,須要函數返回給調用者某個地址即指針類型,以便於後續操做,這種函數返回類型爲指針類型的函數,一般稱爲指針函數。在處理字符串中常見。
#include<stdio.h> #include<stdlib.h> char *link(char*str1,char*str2); int main() { char s1[20]="Chinese"; char s2[10]="Dream"; char *p=link(s1,s2); puts(p); system("pause"); return 0; } char *link(char*str1,char*str2) { char*p1=str1; char*p2=str2; while(*p1!='\0') p1++;//結束時p1指向字符串str1的結尾 *p1=' '; p1++; while(*p2!='\0') { *p1=*p2; p2++; p1++;//*p1++=*p2++ } return str1; }
函數像其餘變量同樣,在內存中也佔用一塊連續的空 間,把該空間的起始地址稱爲函數指針。而函數名就是該空間的首地址,故函數名是常量指針。可把函數指針保存到函數指針變量中。
返回類型(*指針變量名)(函數參數表);
定義中,括號不能省略。
int *p1(int,int)//聲明瞭函數原型,函數名爲p1,含有倆int參數,返回值int* int (*p2)(int,int)//定義了一個函數指針變量p2,p2指向任意含有倆int參數,返回值爲整型的函數
定義以下函數
int f1(int a,int b) { //... } p2=f1//p2=&f1;
在給函數指針變量賦值時,函數名前面的取地址操做符 & 能夠省略。由於在編譯時,C 語言編譯器會隱含完成把函數名轉換成對應指針形式的操做,故加 & 只是爲了顯式說明編譯器隱含執行該轉換操做。
當函數指針變量p2被初始化,指向f1以後,調用f1(),有如下幾種方式
int res; res=f1(a,b); res=p1(a,b); res=(*p1)(a,b)
下面的程序,是一個應用函數指針的例子。
#include<stdio.h> #include<stdlib.h> void cal(void(*ptr)(int,int),int op1,int op2); void add(int a,int b); void sub(int a,int b); void mult(int a,int b); void amult(int a,int b); int main() { int num; int a,b; printf("Operation menu:\n"); printf("1 for add 2 for sub\n"); printf("3 for mult 4 for div\n"); printf("Enter the operator:"); scanf("%d",&num); printf("Input 2 numbers:\n"); scanf("%d %d",&a,&b); switch (num) { case 1: cal(add,a,b); break; case 2: cal(sub,a,b); break; case 3: cal(mult,a,b); break; case 4: cal(amult,a,b); break; default: printf("Input error!"); } system("pause"); return 0; } void cal(void(*ptr)(int a,int b),int op1,int op2) { ptr(op1,op2); }