C語言--->指針

指針的運算

  • 當兩個指針p1, p2相減時,p2-p1就是從p1p2,不包含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的意義

首先,對於一個數組而言,數組名就是該數組的首地址。函數

首地址:一段存儲空間中的第一個存儲單元的地址指針

因此對於這個二維數組 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內存

*a的意義

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,即指向下一個一維數組的首地址。

&a的意義

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,跳過了整個二維數組

&a[0]的意義

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,跳過了第一個一維數組

a[0]的意義

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 要跨過10int 型數據的長度。數組指針與二維數組聯繫密切,能夠用數組指針來指向一個二維數組,以下:

#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;	
 	}

訪問數組中的元素

1. 下標法 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");
	}

2. 指針法

  1. 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");
	}
  1. 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;
}

注意:

  1. 首地址:一段存儲空間中的第一個存儲單元的地址
  2. 分析指針:關鍵不在指針的值,而實指針的類型及其指向
  3. *a==a[0]==&a[0][0]
  4. 訪問數組元素:
  • 下標法
  • 指針法
    *(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);
}
相關文章
相關標籤/搜索