【C語言】玩轉指標——關於指標,你需要掌握的基礎知識!

2021-09-25 10:00:01


前言

指標是C語言中的一個重要概念。正確而靈活的運用指標,可以使程式間接、緊湊、高效。每一個學習和使用C語言的人,都應當深入地學習和掌握指標。


提示:以下是本篇文章正文內容,下面案例可供參考

一、指標是什麼?

指標是包含記憶體地址的變數,這個地址是記憶體中另一個物件(通常是另一個變數)的位置。例如如果一個變數包含另一個變數的地址,我們說第一個變數指向第二個變數。

相信大家看到上面這段話,可能有點懵,不急,我稍後再給大家解釋。在這裡,我先給大家講述一下,資料在記憶體中是如何儲存和讀取的?

1.資料在記憶體中的儲存

如果在程式中定義了一個變數,在對程式進行編譯的時候,系統就會給這個變數分配記憶體單元。編譯系統根據程式中的定義的變數型別,分配一定長度的空間

在這裡插入圖片描述那麼,這些位元組在記憶體中被分配到哪裡?我們如何找到呢?
為了解決這個問題,我們就給記憶體區的每一個位元組一個編號,這個就是它們的「地址」。它相當於旅館中的房間號,在地址所標誌的記憶體單元中存放的資料則相當於旅館房間中居住的旅客。

在這裡插入圖片描述

所以指標是個變數,存放記憶體單元的地址(編號)

2.一個小的單元到底是多大?

1、對於32位元的機器,假設有32根地址線,那麼假設每根地址線在定址的時候產生高電平(高電壓)和低電
平(低電壓)就是(1或者0);

2根地址線上的電訊號轉換成數位訊號用(1/0)表示,所以可能性
00000000000000000000000000000000–11111111111111111111111111111111
也就是有2^32 編號,說明可以管理2的32次方個單元
這裡就有2的32次方個地址。

每個地址標識一個位元組,那我們就可以給
(2^32Byte == 2^32/1024KB == 2^32 /1024/1024MB==2^32/1024/1024/1024GB == 4GB) 4G的空閒進行編址。

按照同樣的方法,我們可以計算出64四位機器,下面就直接給結論了。

1、在32位元的機器上,地址是32個0或者1組成二進位制序列,那地址就得用4個位元組的空間來儲存,所 以一個指標變數的大小就應該是4個位元組。
2、在64位元機器上,如果有64個地址線,那一個指標變數的大小是8個位元組,才能存放一個地址

指標的大小在32位元平臺是4個位元組,在64位元平臺是8個位元組。

二、指標變數

1.什麼是指標變數

思考一個問題,在編譯器中,如何把3賦值給i這個變數中?

第一種作法,把3直接送到i所表示的單元中,例如「i=3」;

int main()
{
	int i=3;
	return 0;
}

第二種方法,把3送到變數p所指向的單元(即變數i的儲存單元,也就是地址,如p=3,其中i表示p指向的物件)

int main()
{
	int i;
	//int i = 3;//第一種方法
	int *p = &i;//第二種方法
	//這裡我們對變數a,取出它的地址,可以使用&操作符。
   //將i的地址存放在p變數中,p就是一個指標變數。
	*p = 3;
	printf("%d\n", i);
	return 0;
}

在這裡插入圖片描述

2.指標型別

思考一個問題:

把int型變數a和float型變數b先後分配到2000開始的儲存單元中,&a和&b的資訊完全相同嗎?

答案是不相同的,因為雖然儲存單元的編號相同,但他們的資料型別不同。

此外,還因為資料型別的不同,無法確定是從一個位元組中取資訊(字元資料),還是從兩個位元組取資訊(短整型),抑或是從四個位元組取資訊(整型),不同的型別,儲存方式是不一樣的。

如果我們要將&num(num的地址)儲存到p中,我們需要我們給指標變數相應的型別。

如下:

char  *pc = NULL;//har* 型別的指標是為了存放 char 型別變數的地址。
int   *pi = NULL;//int* 型別的指標是為了存放 int 型別變數的地址。
short *ps = NULL;//short* 型別的指標是為了存放 short 型別變數的地址
long  *pl = NULL;
float *pf = NULL;
double *pd = NULL;

這裡可以看到,指標的定義方式是: 型別名 * 指標變數名

【總結】

C語言中的地址包括位置資訊(記憶體編號,或稱純地址)和它所指向的資料的型別資訊,或者說它是「帶型別的地址」,如&a,一般稱它位「變數a的地址」,但是確切地說,它是「整型變數a的地址」

3.指標型別的作用

作用一:

指標型別決定了指標解除參照操作的時候,一次存取幾個位元組(存取記憶體的大小)

int main()

{	int a = 0x11223344;
	int* pa = &a;
	*pa = 0;
	return 0;
}

在這裡插入圖片描述

int main()
{
	int a = 0x11223344;
/*	int* pa = &a;
	*pa = 0;*/
	char* pc = &a;//int*
	*pc = 0;
	return 0;
}

在這裡插入圖片描述
指標型別的意義1

指標型別決定了指標解除參照操作的時候,一次存取幾個位元組(存取記憶體的大小)
char* 指標解除參照存取1個位元組
int* 指標解除參照存取4個位元組

作用二:

指標型別決定了,指標±整數的時候的步長(指標±整數的時候,跳過幾個位元組)

int main()
{
	int a = 10;
	int * pa=&a;
	char *pc = &a;
	printf("%p\n", pa);
	printf("%p\n", pc);

	printf("%p\n", pa+1);//如果是整型指標int*,+1則跳過4個位元組、
	printf("%p\n", pc+1);//char* 指標+1,跳過1個位元組
	return 0;
}

在這裡插入圖片描述

三、野指標

1.什麼是野指標

概念: 野指標就是指標指向的位置是不可知的(隨機的、不正確的、沒有明確限制的)

什麼意思?舉個例子
就是你撿到一把鑰匙,但是不知道它可以開那道門。

2.野指標成因

2.1. 指標未初始化

指標沒有初始化,裡面放的是隨機值

#include <stdio.h>
int main()
{ 
 	int *p;//區域性變數指標未初始化,預設為隨機值
    *p = 20;//通過p中存的隨機值作為地址,找到一個空間,這個空間不屬於我們當前的程式,就造成了非法存取
//如果非法存取了,p就是野指標
 return 0; }

2.2指標越界存取

指標越界造成野指標問題

int main()
{
	int arr[10] = 0;
	int i = 0;
	int * p = arr;

	for (i = 0; i <= 10; i++)//這裡迴圈了11次,當指標指向的範圍超出陣列arr的範圍時,p就是野指標
	{
		*p = 1;
		p++;
	}
	return 0;
}

2.3指標指向的空間釋放

當一個指標指向的空間釋放了,這個指標就變成野指標了


int* test()
{
	int a = 10;
	return &a;  //int *,生命週期,出來就銷燬了
}
int main()
{
	int *p = test();
	//printf("不愧是你\n");//加入這裡加了一條語句,下面的值就變了
	printf("%d\n", *p);//編譯出10是因為編譯器會對值做一次保留。所以能存取到上面函數不一定是對的
	return 0;
}

3.如何規避野指標

  1. 指標初始化
  2. 小心指標越界
  3. 指標指向空間釋放即使置NULL
  4. 避免返回區域性變數的地址
  5. 指標使用之前檢查有效性
//規避野指標
int main()
{
	int a = 10;
	int * p = &a;//1、明確初始化,確定指向

	int * p2 = NULL;//NULL本質是0,2、不知道一個指標當前應該指向哪裡是,可以初始化位NULL
	//*p2 = 100;//err,對於空指標,是不能直接解除參照的

	//如何規避? 
	if (p2 != NULL)//先判斷是不是空指標
	{
		*p2 = 100;//這樣才對
	}

}

四、指標運算

1.指標±整數

int main()
{
	float arr[5];
	float *p;
	for (p = &arr[0]; p < &arr[5];)
	{
		*p++ = 0;//對一個指標加1使它指向陣列中的下一個元素,把指標指向的內容全部賦值給0
	}
	return 0;
}

在這裡插入圖片描述

也就說,如果加2使它向右移動2個元素的位置,依次類推。把一個指標減去2使它向左移動2個元素的位置。

2.指標-指標

1、指標減去指標的前提,是兩個指標指向同一塊區域
2、指標減去指標,得到數位的絕對值,是指標和指標之間元素的個數


int main()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	char ch[5] = { 0 };
	//printf("%d\n", &arr[9] - &ch[0]);//這種演演算法是錯誤的

	printf("%d\n", &arr[9] - &arr[0]);//算出的是元素的個數
	printf("%d\n", &arr[0] - &arr[9]);//
	//指標減去指標的前提,是兩個指標指向同一塊區域
	//指標減去指標,得到數位的絕對值,是指標和指標之間元素的個數

	return 0;
}

在這裡插入圖片描述【注意】
指標與指標之間不能進行加法運算,因為進行加法後,得到的結果指向一個不知所向的地方,沒有實際意義

什麼意思,舉個例子。
在這裡插入圖片描述

五、指標和陣列

1.陣列元素的指標

一個變數有地址,一個陣列包含若干元素,每個陣列元素都在記憶體中佔用儲存單元,它們都有相應的地址。

指標變數既然可以指向變數,當然也可以指向陣列元素,也就是把某一元素地址放到一個指標變數中。

所謂陣列元素的指標就是陣列元素的地址

(1)用一個指標變數指向一個陣列元素

int main()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	int* p;//定義p位指向整型變數的指標變數
	p = &arr[0];//把a[0]元素的地址賦給指標變數p
	return 0;
}

以上是使指標變數p指向a陣列的第0號元素

在這裡插入圖片描述

2.通過指標參照陣列元素

(1)下標法,如a[i]形式
(2)指標法,如*(a+i)

下標法:

int main()
{
	int arr[10];
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", sz);
	printf("%p\n", arr);//陣列名就是首元素地址
	//下標法
	printf("%p\n", &arr[0]);
	int* p=&arr[5];//整型地址放在整型指標上,從而讓指標跟陣列建立聯絡

	//陣列名確實是首元素地址,
	//但是有兩個例外
	//1.sizeof(陣列名),這裡的陣列名不是首元素地址,是表示整個陣列,計算的是整個陣列的大小,單位是位元組
	//2.&陣列名,拿到的是整個陣列的地址

	return 0; 
}

在這裡插入圖片描述指標法:

int main()
{
	int arr[10];
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);

	int* p=&arr[0];//整型地址放在整型指標上,從而讓指標跟陣列建立聯絡
	//指標法
	for (i = 0; i < sz; i++)
	{
		*(p + i) = i;// p+i 其實計算的是陣列 arr 下標為i的地址。
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0; 
}

在這裡插入圖片描述一個小知識:

int main()
{

	int arr[10] = { 0 };
	arr;//陣列名
	&arr[0];//取出首元素地址
	&arr;//取出整個陣列的地址
	printf("%d\n", &arr[0]);
	printf("%d\n", &arr);
	return 0;
}

在這裡插入圖片描述

六、二級指標

指標變數的地址二級指標

什麼意思?舉個例子

int main()
{
	int a = 10;//4byte,向記憶體申請4個位元組
	int* p=&a;//p指向a,稱為一級指標

	int* *pp=&p;//pp就是二級指標,pp存放的是一級指標的地址
	* *pp = 20;//需兩層解除參照	
	printf("%d\n", a);
	//int** * ppp = &pp;//ppp就是三級指標
	return 0;
}

七、指標陣列

存放指標的陣列就是指標陣列

int main()
{
	int arr[10];//整型陣列,存放整型的陣列就是整型陣列
	char ch[5];//字元陣列,存放字元的陣列就是字元陣列
	//指標陣列,存放指標的陣列就是指標陣列
	//int*  整型指標的陣列
	//char* 字元指標的陣列

	int* parr[5];//整型指標的陣列,存放的型別都是int*
	char* pc[6];//字元指標的陣列
	return 0;
}

我們也可以用同樣的方式來存取指標陣列。
如下

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;

	int * arr[3] = { &a, &b, &c };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d\n",*(arr[i]));
	}

	int *pa = &a;
	int *pb = &b;
	int *pc = &c;
	return 0;
}

在這裡插入圖片描述


最後

本文介紹的是指標的基礎知識,往後還會繼續深入講解指標更深入的知識。此外,本文參考了譚浩強《C語言設計》(第五版),以及網上的部分資料,加之自己在學習聽課時的筆記,梳理而成,花費了我很多心思。當文章寫成之時,時間已過去4個多小時!

希望能對看到的大家有所幫助!