掌握C語言指標,輕鬆解鎖程式碼高效性與靈活性(中)

Betty’sSweet發表於2024-03-02

pFp8UCq.jpg

✨✨ 歡迎大家來到貝蒂大講堂✨✨

🎈🎈養成好習慣,先贊後看哦~🎈🎈

所屬專欄:C語言學習
貝蒂的主頁:Betty‘s blog

1. 引言

前面給大家介紹了一些指標的基本概念,今天就讓我們繼續深入指標的世界,和貝蒂一起打敗指標大魔王吧

2. 二級指標

指標變數也是變數,是變數就有地址,那我們就把存放指標變數地址的指標稱為二級指標。

可能理解起來有點繞,我們可以透過下面示意圖演示一下

程式碼如下:

	int a = 10;
	int* pa = &a;//一級指標,存放a的地址
	int** ppa = &a;//二級指標,存放指標變數p的地址
  • 不能直接把&&a賦值給ppa哦,因為&&在C語言中是且的意思”

img

(1)對ppa解引用,找到pa,也就是說*ppa==pa

(2)對pa解引用,找到a,也就是說**ppa==a

	int* b = *ppa;//找到a的地址
	int c = **ppa;//找到a
  • 依次內推我們可以衍生出三級指標,四級指標。

3. 陣列與指標的關係

3.1 陣列名的理解

我們在前面學習陣列時就明白,陣列名是首元素地址,但是講解的不夠深入,今天就讓我們深入瞭解一下吧~

首先讓我們觀察一下如下程式碼

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//&arr[0],arr,&arr的區別
	printf("&arr[0] = %p\n", &arr[0]);//首元素地址
	printf("arr = %p\n", arr);//一維陣列陣列名
	printf("&arr = %p\n", &arr);//對整個陣列取地址
	return 0;
}

從結果來說&arr[0],arr,&arr到底有什麼區別呢?

讓我們再看看下面這段程式碼

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("&arr[0] = %p\n", &arr[0]);
	printf("&arr[0]+1 = %p\n", &arr[0] + 1);
	printf("arr = %p\n", arr);
	printf("arr+1 = %p\n", arr + 1);
	printf("&arr = %p\n", &arr);
	printf("&arr+1 = %p\n", &arr + 1);
	return 0;
}

輸出結果:

  1. &arr[0]與arr+1都是跳過4個位元組,相當於跳過1個整型元素。

  2. &arr+1跳過40個位元組,相當於10個整型,也就是整個陣列。

總結:arr與&arr[0]都是首元素地址,指向陣列第一個元素。&arr以首元素地址表示,但是指向的是整個陣列。

3.2 sizeof與陣列名

我們知道sizeof實際上是獲取了資料在記憶體中所佔用的儲存空間,單位是位元組

讓我們看看下面這段程式碼吧

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%d\n", sizeof(arr));//計算大小
	return 0;
}

輸出結果:40

不知道大家有沒有疑惑?如果陣列名是首元素地址的話,我們知道在32位機器上大小為4,在64位機器上大小為8。那為什麼是40呢?

其實陣列名就是陣列⾸元素(第⼀個元素)的地址,但是有兩個例外
• sizeof(陣列名),sizeof中單獨放陣列名,這⾥的陣列名錶⽰整個陣列,計算的是整個陣列的⼤⼩,單位是位元組
• &陣列名,這⾥的陣列名錶⽰整個陣列,取出的是整個陣列的地址(整個陣列的地址和陣列⾸元素的地址是有區別的)

3.3 陣列與指標等價關係

假設有一個一維陣列和一個二維陣列

int arr[5]={1,2,3,4,5};
int arr[3][3]={{1,2,3},{4,5,6},{7,8,9}}

我們要訪問它的每個元素,有哪些方法呢~

  1. 陣列訪問
	int arr1[5] = { 1,2,3,4,5 };
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", arr1[i]);
	}
int arr2[3][3] = { {1,2,3},{4,5,6},{7,8,9} };
for (int i = 0; i < 3; i++)
{
	for (int j = 0; j < 3; j++)
	{
		printf("%d ", arr2[i][j]);
	}
	printf("\n");
}
  1. 指標訪問
for (int i = 0; i < 5; i++)
{
	printf("%d ", *(arr1+i));
}
for (int i = 0; i < 3; i++)
{
	for (int j = 0; j < 3; j++)
	{
		printf("%d ", *(*(arr2 + i) + j));
	}
}

透過對上面程式碼的觀察,我們可以總結如下規律

  1. arr[i]與*(arr+i)等價。

  2. arr[i][j]與((arr+i)+j)等價。

3.4 指標陣列

(1) 指標陣列的概念

指標陣列顧名思義就是存放指標的陣列 ,陣列中每個元素都是指標,存放的都是地址

int*parr1[5];//存放五個整型指標變數
char*parr2[5];//存放五個字元指標變數
float*parr3[5];//存放五個浮點數指標變數

程式碼示例

	int arr1[] = { 1,2,3 };
	int arr2[] = { 4,5,6 };
	int arr3[] = { 7,8,9 };
	//將每個陣列的首元素地址都存進去
	int* parr[3] = { arr1,arr2,arr3 };

示意圖:

img

(2) 指標陣列的理解

int main()
{
	int arr1[] = { 1,2,3 };
	int arr2[] = { 4,5,6 };
	int arr3[] = { 7,8,9 };
	int* parr[3] = { arr1,arr2,arr3 };
	printf("%p\n", parr);//列印指標陣列首元素地址,也就是列印存放arr1空間的地址
	printf("%p\n", parr[0]);//arr1陣列首元素地址
	printf("%p\n", *parr);//arr1首元素地址
	printf("%d\n", **parr);//相當於對arr1首元素地址解引用,指的的是1
	printf("%d\n", *parr[0]);//也相當於對arr1首元素地址解引用,為1
	printf("%d\n", *parr[1]);//相當於對arr2首元素地址解引用,為4
	return 0;
}

輸出結果:

012FFE30
012FFE6C
012FFE6C
1
1
4

(3) 模擬二維陣列

透過上述我們對指標陣列的理解,我們可以間接來模擬出二維陣列。

程式碼如下:

int main()
{
	int arr1[] = { 1,2,3 };
	int arr2[] = { 4,5,6 };
	int arr3[] = { 7,8,9 };
	//將每個陣列的首元素地址都存進去
	int* parr[3] = { arr1,arr2,arr3 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 3; j++)
		{
			printf("%d ", parr[i][j]);
		}
		printf("\n");//換行)
	}
	return 0;
}
  • 模擬的二維陣列並不是真正的二維陣列,因為二維陣列在記憶體中是連續儲存的,而模擬出來的陣列記憶體儲存並不連續

3.5 陣列指標

(1) 陣列指標的概念

同理,指標陣列的本質是一個陣列;那麼陣列指標的本質就是個指標,指向一個陣列的指標。

int(*parr1)[5];//指向一個有五個元素的整型陣列
char(*parr2)[5];//指向一個有五個元素的字元陣列
float(*parr3)[5];//指向一個有五個元素的浮點數陣列

(2) 陣列指標的理解

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int(*parr)[5] = &arr;
	//對陣列名取地址代表整個陣列的地址
	printf("%p\n", parr);//整個陣列的地址一般用陣列首元素地址表示
	printf("%p\n", parr[0]);//相當於*(parr+0)==arr,首元素地址
	printf("%p\n", *parr);//首元素地址
	printf("%d\n", **parr);//相當於對首元素地址解引用,指的的是1
	printf("%d\n", *parr[0]);//也相當於對首元素地址解引用,為1
	printf("%d\n", *parr[1]);//等價於*(*(parr+1)),parr+1跳過一個陣列大小的地址,越界訪問
	return 0;
}

輸出結果:

012FF6F0
012FF6F0
012FF6F0
1
1
-858993460(越界訪問,隨機數)

示意圖:

img

3.6 指標陣列與陣列指標的區別

可能有許多小夥伴區別不清楚指標陣列與陣列指標,但是如果寫成指標的陣列,陣列的指標,可能更好理解。接下來讓我們具體分析一下吧?

首先我們要清楚一個優先順序順序:()>[]>*

  1. 在int*parr[]中,parr先與[]結合(陣列),而parr前面宣告的變數型別是int*。所以這是一個陣列,陣列中每個元素的型別是int*的指標,這一類我們統稱為指標陣列

  2. 在int(*parr)[]中,parr先與結合(指標),而後除開(parr)是一個int []的陣列型別。所以這是一個指標,這個指標指向的是一個陣列,這一類我們稱為陣列指標。

3.7 字串

我們先看一下下面這段程式碼

char arr1[]="im betty";
char arr2[]={'a','b','c','\0'};

這是一種常見的字串的表示形式,以'\0'作為其結尾標誌。

但是還有另外一種表示形式,程式碼如下

	//const可以省略
    const char* p1 = "im betty";
    const char* p2 = "abc";

我們知道const修飾在*前,不能改變指標變數所指向的值,所以這個字串是不能改變的,這種字串我們稱為常量字串

  • 本質上是將字串中的首元素地址存放進指標變數。

知道這些之後,讓我們來看一道題吧

輸出什麼?

#include <stdio.h>
int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	const char* str3 = "hello bit.";
	const char* str4 = "hello bit.";
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");
	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");
	return 0;
}

輸出結果:

為什麼會出現這種結果呢,那是因為這⾥str3和str4指向的是⼀個同⼀個常量字串。C/C++會把常量字串儲存到單獨的⼀個記憶體區域(常量區),當⼏個指標指向同⼀個字串的時候,他們實際會指向同⼀塊記憶體。但是⽤相同的常量字串去初始化不同的陣列的時候就會開闢出不同的記憶體塊,每個陣列地址就會不同。所以str1和str2不同,str3和str4相同。

3.8 陣列傳參

(1) 一維陣列傳參

我們在之前學習函式時候就講過一維陣列傳參,讓我們來複習一下吧。

程式碼如下

void print(int arr[])//寫成陣列的形式
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	print(arr);//將陣列傳遞給print函式
	return 0;
}

我們傳參是傳的陣列名,我們知道陣列名是首元素的地址,既然是地址,自然就能用指標來接受,所以就有了另外一種寫法。

void print(int*p)//用指標來接收
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ",*(p+i));
	}
}

(2) 二維陣列的傳參

先讓我們看看一般二維陣列是如何傳參的吧

void print(int arr[][3])//行可以省略,列不可以
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 3; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][3] = {{1, 2, 3}, { 4,5,6 }, { 7,8,9 }};
	print(arr);//將陣列傳遞給print函式
	return 0;
}

那麼指標接收如何寫呢,還是int*p嗎,我們知道二維陣列可以看成把每一行當做一個元素的一維陣列,陣列名首元素地址自然是第一行元素的地址,所以要用陣列指標來接收哦~

程式碼如下:

void print(int(*p)[3])//明確元素個數
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 3; j++)
		{
			printf("%d ",*(*(p+i)+j));
		}
		printf("\n");
	}
}

本文由部落格一文多發平臺 OpenWrite 釋出!

相關文章