C語言指針(下)(理解指針)(野指針)(指針運算)(二級指針)【指針】(19)

開篇說明

指針下篇,我想要把指針的基本知識全部說明白,所以本篇博客會比較長,有的部分文字也比較多,文字多有文字多的好處,可以幫助讀者更加清楚的理解指針,篇幅較長,每一個舉例代碼都是在平臺進行測試過的,希望讀者也可以邊閱讀邊測試理解過程。有什麼問題可以在下面評論,多多指教。互相學習。

理解指針

代碼演示:變量的大小/變量地址的大小/取變量的地址

#include <stdio.h>
int main()
{
	char a = 1; 
	short  b = 2;
	int c = 3; 
	double d = 1.234;

	printf("sizeof(a) = %d\n", sizeof(a));
	printf("sizeof(b) = %d\n", sizeof(b));
	printf("sizeof(c) = %d\n", sizeof(c));
	printf("sizeof(d) = %d\n", sizeof(d));

	printf("sizeof(&a) = %d\n", sizeof(&a));
	printf("sizeof(&b) = %d\n", sizeof(&b));
	printf("sizeof(&c) = %d\n", sizeof(&c));
	printf("sizeof(&d) = %d\n", sizeof(&d));

	printf("&a = %p\n", &a);
	printf("&b = %p\n", &b);
	printf("&c = %p\n", &c);
	printf("&d = %p\n", &d);

	return 0;
}

運行結果爲:
運行結果
但是上面a,b,c,d 的地址都是常量。
例如代碼中如果出現:

&a = 0x1234;

編譯就會出錯。
&a 就是物理內存上的一個地址,是一個常量,所以不允許被賦值。

我們已經在指針(中)篇博客中介紹到了指針變量。

凡是一個有類型的地址,都是可賦給同類型的指針變量。類型不同,編譯器則可能報錯。

接下來我們說明需要重要強調的一點,我們已經知道了可以自己定義一個指針變量來存放變量的地址。

但是以下方式是不允許的:

#include <stdio.h>
int main()
{
	char* p = (char*)0x12345678;
	//0x12345678 是數值,(char*)0x123456 是指針 
	printf("%c", *p);
	printf("%d", *p);
	return 0;
}

上面操作是不允許的,當然程序不會有任何結果,結果就是崩掉了,原因就是,我們整個內存中並不是所有的空間在創建變量的時候能夠訪問到的,之後我們會講到C語言內存空間。其中有一部分是操作系統的內核區,內核中的空間是不允許用戶訪問的,而如果你直接給一個指針變量一個內存地址,那麼這個地址很有可能是用戶不允許訪問的內存區域,那麼我們之前已經講到,我們可以定義指針變量對於內存進行操作,那麼如果是不允許訪問的區域使用指針變量進行了修改,可能會帶來致命性的錯誤,所以編譯器直接禁止通過以上方式初始化指針變量(我們的初始化可以直接理解爲賦值,只不過這裏賦值的是一個有類型的地址也就是指針)

所以通常作法是,把一個己經開闢空間的變量的地址賦給指針變量
代碼演示:

#include <stdio.h>
int main()
{
	int a = 100;
	int b = 200;
	int* pa = &a;
	int* pb = &b;
	printf("*pa = %d\t&a = %x\n", *pa, &a);
	*pb = 200;
	printf("*pb = %d\t&b = %x\n", *pb, &b);
	return 0;
}

運行結果爲:

把已經定義變量的地址賦值給指針變量

比較兩個指針是否相同:地址相同並且類型相同才能說兩個指針是同一指針。

指向/ 被指向/ 更改指向

那麼在這裏,我們已經通過以上打印找到了a和b的地址,通過指針用圖解方式給大家演示一下,使得我們的認識更加清楚。
指針的圖解說明
指針變量指向誰就是保存了誰的地址。
例如上面圖解:指針變量pa保存了變量a的地址就是pa指向了a。a被pa所指向。

那麼這裏我們只是標出來了變量的地址,並沒有直接寫出指針變量的地址,這裏的指針變量pa和pb也一定是有地址的,因爲我們說過,指針變量是一個變量,是變量就有地址和大小,指針存放的是其他變量的地址,我們直接說明就是,指針pa指向的是a,a是一個int類型,指針pa是一個int * 類型,那麼int * 表示指針的尋址能力爲int類型大小,所以指針pa能夠尋址到的大小就和a 的類型大小相等,一個是int 一個是int * 。那麼就可以通過 *pa 訪問到變量a的值,*pa就是解引用,也可以理解爲取值,pa保存的是a的地址,所以就是取pa保存變量a的地址。也可以理解爲pa指向變量a。

更改指向:
那麼指針變量保存的是變量地址,那麼我們如果讓指針變量保存的地址發生改變,那麼相應的我們在解引用的時候取到的變量就會改變,也就是指針變量更改指向的過程。

指針變量保存的地址發生改變,是指針變量更改指向的過程。

#include <stdio.h>
int main()
{
    int num = 10;
	int num2 = 20;
	int *p = &num;
	printf("%d\n",*p);
	p = &num2;  //更改指向
	printf("%d\n",*p);
	return 0;
}

打印結果爲:
改變指針變量指向之後的運行結果
只要指針變量保存的地址發生改變,那麼解引用的時候,保存的變量必然也發生改變,指針變量保存的地址是發生指向之後變量的地址,解引用的時候取到就是指針變量指向發生改變之後指向變量的值,這點很容易理解。就像你手機裏面記住的是001房間,如果把001房間改成002房間,那麼你尋找到的時候進入的就是002房間。

NULL

野指針

接下來我們介紹一種很危險的指針,操作不當造成的後果不可估量。
一個指針變量,如果指向一段無效的內存空間,則該指針稱爲野指針,也稱爲無效指針。
常見情況有兩種:
一種是未初化的指針變量,一種是指針變量指向己經被釋放的內存空間。

指針變量指向己經被釋放的內存空間,我們之後在申請內存空間的時候進行說明,這篇博客我們主要說明未初始化的指針。

對野指針的讀操作或許會崩潰尚可忍受,如果對野指針的寫入成功,造成的後果是不可估量的。對野指針的讀寫操作,是危險而且是沒有意義的。世上十之八九最難調的 bug 皆跟它有關係。

#include <stdio.h>
int main()
{
	int *pa;  //未初始化的指針
	printf("%x",*pa);
	return 0;
}

上面操作會直接崩掉,但是造成的損失還不算很嚴重,因爲只是讀取內存數據,讀取的內存地址不確定,但是不會修改內存中的數據。但是很有可能讀取到內核區域不允許用戶讀取的內存區域,操作系統會直接攔截。

#include <stdio.h>
int main()
{
	int *pa;
	*pa = 100;
	printf("%x",*pa);
	return 0;
}

上面操作也會崩掉,這個操作就會直接修改野指針所指向的地址對應內存的值,當定義的指針變量指向的地址不確定的時候,修改內存中的值,這是非常可怕的事情。所以操作系統也會直接攔截。崩潰的情況是正常的,系統會保護,不允許對於指向地址不確定的指針變量進行寫入操作,因爲一旦對於野指針的寫入操作成功了,帶來的損失可能是不可估量的,這就是一個可怕的事情!!!所以操作系統也會直接攔截。

如果一不小心使用了野指針,代碼規模龐大的時候,如果裏面有一個未初始化的變量被操作了,那查找起來就像是大海撈針一樣。

所以我們在定義指針變量的時候要有一個良好的習慣就是在定義的時候一定要把指針變量置爲NULL

NULL 指針(零值無類型指針)

那麼NULL是什麼呢?
我們先定義指針爲空,然後通過編譯器跳轉進行查看:

#include <stdio.h>
int main()
{
	int *p = NULL;
	return 0;
}

指針指向爲空
上面解釋是C++解釋,但是這裏理解不影響

在vs2019我們跳轉到源碼可以看到,NULL是一個宏定義的值爲 0 ,如果現在不知道是宏定義的話,那麼就把NULL理解爲0就可以,這裏的NULL指的是內存的0地址。

NULL 是一個宏,俗稱空指針,等價於指針( void * ) 0。
( void * ) 0 是一個很特別的指針,因爲他是一個計算機黑洞,既讀不出內容,也不寫進東西去。
所以被賦值 NULL的指針變量,進行讀寫操作,是不會有內存數據損壞的。
那麼我們就使用NULL起一個標記的作用,也就是說用 NULL 初始化未使用的指針。

c 標準中是這樣定義的:

define NULL ((void *)0) 

故常用 NULL 來標記未初始化的指針。
或對己經被釋放內存空間的指針變量賦值。

可以理解爲 C 專門拿出了 NULL指針(零值無類型指針),用於作標誌位使用。

void 本質

void叫無類型。

接下來我們說明一下void,我們在見到的或者自己用到的函數在定義返回值的時候,或者參數裏面都會有用到 void,具體的功能我們會在函數部分說明其功能,我們都知道char,int或者其他類型,void叫做無類型,我們首先提出:
①:void*指針可以賦值給任何類型的指針。
②:void代表內存的最小單元,那麼最小單元就是一個字節。

那麼我們爲什麼沒有用char類型來取代呢?
將來有可能char一個字節不夠用了,我們可能就要升級爲2個字節,但是void永遠是一個字節。我們強調一點:void在 32 位機上地位等同於 char。

#include <stdio.h>
int main()
{
	printf("sizeof(char) = %d\n",sizeof(char));	
	printf("sizeof(void) = %d\n",sizeof(void));
	return 0;
}

大家可以通過以上代碼測試 void 的大小,但是並不是所有平臺能都測出來,vs2019編譯不能通過,Qt平臺可以直接打印出來結果。我們在這裏直接給出打印結果:
void在32位機等價於char
在這裏我們需要強調一個小細節:
我們之前定義:

int a,b;

上面代碼定義的是兩個int類型的變量。

如果我們:

int * pa,pb;

那麼這裏,我們是不是定義了兩個指針變量呢?
我們通過測試他們的大小來進行說明。

#include <stdio.h>
int main()
{
	int *p,q;
	printf("sizeof(p) = %d\n",sizeof(p));
	printf("sizeof(q) = %d\n",sizeof(q));
	return 0;
}

打印結果爲:
p和q的區別
兩個類型大小相等,所以是兩個指針變量,真實情況是這樣嗎?
我們改變以下類型:

#include <stdio.h>
int main()
{
	char* p, q;
	printf("sizeof(p) = %d\n", sizeof(p));
	printf("sizeof(q) = %d\n", sizeof(q));
	return 0;
}

打印結果爲:
p和q的區別
前面的 p 是指針變量,後面的 q 是一個 char 類型變量,那麼爲什麼會出現之前的結果呢?因爲我們在32位機上面測試指針大小爲4個字節,int類型的大小也爲4個字節,所以打印出來的結果相同。

如果我們要定義兩個類型相同的指針變量就需要通過一下方式:

#include <stdio.h>
int main()
{
	char * p = NULL, * q = NULL;
	printf("sizeof(p) = %d\n", sizeof(p));
	printf("sizeof(q) = %d\n", sizeof(q));
	return 0;
}

打印結果爲:
p和q的區別

指針運算

接下來我們來說明指針的運算,在說明指針的運算之前我們先複習一下之前運算符的部分內容:
我們已經知道在運算符中例如 i++ 和 ++i 的以及 j-- 以及 --j 的運算(關於這兩個運算符需要注意的點已經在運算符博客中關於用法及注意作以說明。),這兩種運算我們都能夠輕鬆的使用,那麼這裏我們對於上面運算符的使用做一個簡單的說明,對於我們在這裏理解指針的運算有很大的幫助。

在說到i++和++i的使用的時候,我們在這裏再給大家一種比較精簡的驗證方法大家可以自己分析結果,這裏我們不給出解釋,需要理解的話可以去(一維數組)博客有講解。

#include <stdio.h>
int main()
{
	int num = 10;
	int num1 = 10;
	num++;
	printf("%d\t", num);
	printf("%d\t", num1++);
	printf("%d", num1);
	return 0;
}

打印結果爲:
運行結果

如果上面的運行結果,你不能理解的話,請停下來去看運算符篇的博客,然後看懂之後繼續向下看。

賦值運算

不兼容類型賦值會發生類型丟失。爲了避免隱式轉化帶來可能出現的錯誤,最好用強制轉化顯示的區別。

下面我們作以說明 C語言比較靈活。
我們在C語言這裏可以直接這樣寫:

#include <stdio.h>
int main()
{
	int data = 0x12345678;
	char* p = &data;
	printf("%x\n", *p);
	return 0;
}

在C語言中是可以運行的,運行結果爲:
運行結果

上面賦值的過程只是把data的地址賦值給pc,類型還是由char來決定。我們可以認爲在賦值的過程中丟失了類型

但是在C++中必須這樣:

#include <stdio.h>
int main()
{
	int data = 0x12345678;
	char* p = (char *)&data;
	printf("%x\n", *p);
	return 0;
}

打印結果爲:
運行結果

C++中必須使用強制類型轉換,否則編譯不能通過。

算術運算

接下我們介紹指針的算術運算:
指針的乘法運算和除法運算時沒有意義的,有興趣的讀者可以自行測試。
在這裏我們說明指針的加法運算和減法運算,但是在加法運算中,我們指針+指針也是沒有意義的。

指針的算術運算,不是簡單的數值運算,而是一種數值加類型的運算。將指針加上或者減去某個整數值(以 n*sizeof(T)爲單位進行操作的)。

我們給出指針的算數運算符及示例:
指針的算術運算
接下來我們說明指針的加法運算
首先我們給出這樣的代碼:

#include <stdio.h>
int main()
{
	int * p = (int*)0x0001;
	int pData = 0x0001;
	printf("p = %x         p+1 = %x\n", p, p + 1);
	printf("pData = %x  pData+1 = %x", pData, pData + 1);
	return 0;
}

我們在這裏的目的就是定義一個指針變量,然後打印出來指針變量+1的結果進行分析。
打印結果爲:
指針的算術運算
這裏我們對比來看。
我們定義的數值加1是直接+1,指針加1的時候直接加上了4,加上了指針類型的大小,這裏是int類型,佔4個字節,所以+4。

我們已經說過很多次:指針就是類型+地址,地址由地址總線釋放,類型代表了從當前地址的尋址能力,也就是步長(也就是指針一次+1能夠變化的最小單元),那麼不同的指針類型佔幾個字節,指針+1之後就加上幾個字節。我們通過代碼進行解釋:

#include <stdio.h>
int main()
{
	int* p = (int*)0x0001;
	int pData = 0x0001;
	char* p1 = (char*)0x0001;
	short* p2 = (short*)0x0001;
	float* p3 = (float*)0x0001;
	double* p4 = (double *)0x0001;
	long long* p5 = (long long*)0x0001;
	printf("pData = %x  pData+1 = %x\n", pData, pData + 1);
	printf("(char *)p1 = %x         p1+1 = %x\n", p1, p1 + 1);
	printf("(short *)p2 = %x         p2+1 = %x\n", p2, p2 + 1);
	printf("(int *)p = %x             p1+1 = %x\n", p, p + 1);
	printf("(float *)p3 = %x         p3+1 = %x\n", p3, p3 + 1);
	printf("(double *)p4 = %x         p4+1 = %x\n", p4, p4 + 1);
	printf("(long long *)p5 = %x         p5+1 = %x\n", p5, p5 + 1);
	return 0;
}

打印結果爲:
指針的算數運算
pData是一個數字
char * 類型+1 跳躍1個字節
short * 類型+1 跳躍2個字節
int * 類型+1 跳躍4個字節
float * 類型+1 跳躍4個字節
double * 類型+1 跳躍8個字節
long long * 類型+1 跳躍8個字節

我們再通過圖解方式給大家解釋:
圖解指針的算術運算

代碼演示:

#include <stdio.h>
int main()
{
	int* p = (int*)0x0001;
	int pData = 0x0001;
	printf("(double*)p = %x, (double*)p+1 = %x\n",(double*)p,(double*)p + 1);
	printf("(int)pData = %x, (int)pData+1 = %x\n",(int)pData,(int)pData + 1);
	return 0;
}

運行結果爲:
運行結果
上面 p 強轉duoble * 類型之後指針加+1,加的是步長,也就是加上指針類型double所佔字節數的大小爲8個字節,所以結果爲9。

pData 強轉int類型之後加1,也就是數值加1,所以結果爲2。

代碼演示:

#include <stdio.h>
int main()
{
	int* p = (int*)0x0001;
	int pData = 0x0001;

	printf("p = %x, p+1 = %x\n", p,p + 1);
	printf("pData = %x, pData+ 1 = %x\n\n", pData, pData + 1);

	printf("(double*)p = %x, (double*)p+1 = %x\n", (double*)p, (double*)p + 1); //把 p 強制轉換爲double * 步長髮生改變爲8
	printf("(int)p = %x, (int)p + 1 = %x\n\n", (int)p, (int)p + 1);//把 p 強制轉換爲int 步長髮生改變爲1

	printf("++p = %x\n", ++p);
	printf("++pData = %x\n", ++pData);
	return 0;
}

運行結果爲:
運行結果

那麼接下來我們說明指針的減法:
指針-1類似於指針+1,我們給簡單說明:

#include <stdio.h>
int main()
{
	int* p = (int*)10;
	int pData = 10;

	printf("p = %x, p-1 = %x\n", p, p - 1);
	printf("pData = %x, pData - 1 = %x\n\n", pData, pData - 1);

	printf("(double*)p = %d, (double*)p - 1 = %d\n", (double*)p, (double*)p - 1); //把 p 強制轉換爲double * 步長髮生改變爲8
	printf("(int)p = %d, (int)p - 1 = %d\n\n", (int)p, (int)p - 1);//把 p 強制轉換爲int 步長髮生改變爲1

	printf("--p = %d\n", --p);
	printf("--pData = %d\n", --pData);
	return 0;
}

運行結果爲:
運行結果

下面我們看一種新的情況:

指針-指針:表示間隔的單元個數(正,負)
先算出間隔的字節數/sizeof(指針去掉一個 * )

那麼如果來解釋呢?
我們通過一維數組來解決:

#include <stdio.h>
int main()
{
	int arr[10];//x
	int* p = &arr[1];//x+4
	int* q = &arr[9];//x+36
	printf("%d\n", p - q);//-8
	printf("%d\n", q - p);//8
	printf("%d\n", (short*)q - (short*)p);//16
	printf("%d\n", (long long*)q - (long long*)p);//4
	printf("%d\n", (double**)q - (double**)p);//8
	printf("%d\n", (char*)q - (char*)p);//32
	printf("%d\n", (long)q - (long)p);//32
	return 0;
}

我們在後面註釋出來結果和我們打印出來的結果進行對比:
運行結果
我們知道內存的編址順序,當然也就很容易理解會出現負數,我們還是通過上面給出的方法計算和理解,間隔的單元個數,在數組中表示也就是中間間隔了幾個數組。計算方法是,先計算出相差的字節數然後除以指針的類型大小,這裏需要強調,既然是除以指針類型大小,那麼這裏必須首先是指針類型,然後纔可以知道類型佔幾個字節。我們拆分上面代碼逐個分析:
我們首先知道,int類型的數組,第一個元素和第九個元素相差32個字節。也就是8個int類型的變量。

printf("%d\n", p - q);//-8   
printf("%d\n", q - p);//8

那麼這裏爲什麼打印結果是8呢?我們剛纔已經說過字節相差32個字節,當p-q,或者q-p的時候,我們就要用字節數除以去掉 * 之後類型的大小,那麼我們定義指針變量時 int * p 和int * q,去掉 * 之後爲int類型佔4個字節,所以在計算的時候就用 32/4 = 8;所以一個結果爲8,一個結果爲-8。

printf("%d\n", (short*)q - (short*)p);//16
printf("%d\n", (long long*)q - (long long*)p);//4

這裏的我們也很容易解釋:
間隔字節數爲32 short去點* short大小爲2個字節 所以是32/2 = 16
間隔字節數爲32 long long去點* long long大小爲8個字節 所以是32/8 = 4

printf("%d\n", (double**)q - (double**)p);//8
printf("%d\n", (char*)q - (char*)p);//32

這裏我們需要特別注意:double** 去掉一個*之後爲 double * 而 double * 是4個字節
所以結果爲 32/8 =8。
間隔字節數爲32 char去點 * char大小爲1個字節 所以是32/1 = 32

printf("%d\n", (long)q - (long)p);//32

最後一點需要我們更加註意強轉裏面沒有 * 可以去掉,那麼不管裏面是什麼類型全部按照1字節處理所以結果爲32/1 == 32

#include <stdio.h>
int main()
{
	int arr[10];//x
	int* p = &arr[1];//x+4
	int* q = &arr[9];//x+36
	printf("%d\n", (long)q - (long)p);//32
	printf("%d\n", (int)q - (int)p);//32
	printf("%d\n", (short)q - (short)p);//32
	printf("%d\n", (char)q - (char)p);//32
	return 0;
}

打印結果爲:
運行結果
得以證明。
我們這裏幫助理解一下:
指針+i的含義:+i個格子,則爲指針+isizeof(指針去掉一個)個字節
指針-i的含義:-i個格子,則爲指針-isizeof(指針去掉一個)個字節
如果直接沒有*直接當作1個字節

#include <stdio.h>
int main()
{
	int *p = (int *)2000;
	printf("%d\n",p+1);//2004
	printf("%d\n",(char *)p+1);//2001
	printf("%d\n",(long *)p+1);//2004
	printf("%d\n",(float *)p+1);//2004
	printf("%d\n",(double *)p+1);//2008
	printf("%d\n",(short **)p+1);//2004
	printf("%d\n",(char ***)p+1);//2004
	printf("%d\n",(unsigned long long)p+1);//2001
	return 0;
}

打印結果爲:
運行結果
再舉一個例子進行理解:

#include <stdio.h>
int main()
{
	int* p = (int*)0x2010;
	printf("%x\n", p - 2);//2008
	printf("%x\n", (short*)p - 2);//200c
	printf("%x\n", (long*)p - 2);//2008
	printf("%x\n", (float*)p - 2);//2008
	printf("%x\n", (double*)p - 2);//2000
	printf("%x\n", (char**)p - 2);//2008
	printf("%x\n", (char*)p - 2);//200e
	printf("%x\n", (unsigned long)p - 2);//200e
	return 0;
}

打印結果爲:
運行結果
p-1 還是之前的int * 類型, char * * 去掉一個 * 之後還是一個指針類型所以大小爲4字節,最後一行沒有 *,所以不管是什麼類型,都只是一個字節(我們之前有例子專門說明和解釋)

我們已經在這裏用很多例子說明這一點,讀者可以多次複習查看不斷理解。
我們在這裏需要注意很重要的一點就是:
只有當指針指向一串連續的存儲單元時,指針的移動纔有意義。纔可以將一個指針變量與一個整數 n 做加減運算。

關係運算

指針的關係運算
我們先討論一下指針相等的問題
指針相等有兩個條件:
①類型相同
②數值相等

我們通過代碼來驗證:

#include <stdio.h>
int main()
{
	int * p = (int *)0x0001;
	int * q = (int *)0x0005;
	if (p + 1 == q)
	{
		printf("p+1 = q");
	}
	else
	{
		printf("p+1 != q");
	}
	return 0;
}

打印結果爲:
指針的關係運算
說明兩者是相等的。

p+1本質就 p + 1 * sizeof(int) 也就是把*之前的類型大小。

我們再給出一些例子:

#include <stdio.h>
int main()
{
	char * p = (char *)0x0001;
	int * q = (int *)0x0001;
	if (p == q)
	{
		printf(" = q");
	}
	else
	{
		printf(" != q");
	}
	return 0;
}

上面代碼,是寫在C++裏面的,我們這裏只考慮是否相等,上面代碼編譯不能通過,因爲在判斷的時候p和q操作數不兼容。

在C語言中運行下面代碼:

#include <stdio.h>
int main()
{
	char * p = 0x0001;
	int * q = 0x0001;
	if (p == q)
	{
		printf("p = q");
	}
	else
	{
		printf("p != q");
	}
	return 0;
}

運行結果爲:
運行結果
**可以理解爲char * 轉爲 int *

所以我們儘量避免不同類型的之間的比較。

我們再給出下面的例子:

#include <stdio.h>
int main()
{
	int *ptrnum1, *ptrnum2; 
	int value = 1; 
	ptrnum1 = &value; 
	value += 10; 
	ptrnum2 = &value; 
		if (ptrnum1 == ptrnum2) 
			printf("\n 兩個指針指向同一個地址\n"); 
		else 
			printf("\n 兩個指針指向不同的地址\n");
	return 0;
}

打印結果爲:
運行結果
這個對於我們理解的程度來說已經很基礎了,不管數值怎麼變化,只要地址不變,兩個指針指向同一個地址是可以的,就像不同的手機裏面可以存同一個人的電話號碼。但是不允許同一個電話號碼錶示兩個人,否則就會出錯,指針類似,通過一個指針指向不能同時指向兩個地址,這也很容易理解。
指針的大小比較這裏就不多說明,我們已經知道了內存的編址方式,指針的比較就很容易理解了。

小練習字符串迴文

有字符數組如下 char name[5] = {‘M’, ‘A’, ‘D’, ‘A’, ‘M’},判斷其是否是迴文串。

#include <stdio.h>
#include <stdlib.h>



int main()
{
	char name[5] = { 'M', 'A', 'D', 'A', 'M' };
	char* ph = &name[0]; 
	char* pt = &name[4];
	
	int flag = 1;  //設計標誌位
	while (ph<=pt)
	{
		if (*ph == *pt)
		{
			ph++;
			pt--;
		}
		else
		{
			flag = 0;
			break;
		}
	}
	if (flag == 1)
		printf("迴文\n");
	else
		printf("非迴文\n");
	return 0;
}

運行結果爲:
判斷迴文

二級指針

接下來我們瞭解以下二級指針
我們定義一級指針的時候用:

int num = 10;
int * p = &num;
printf(%d”,*p);

我們知道一級指針保存的是變量的地址,我們之前也已經提到過,指針也是變量,也有地址,那麼二級指針就是存儲一級指針的地址,我們通過圖解方式加以理解:
我們先給出代碼:

#include <stdio.h>
int main()
{
    int a = 10;
	int b = 20;
	int* p = &a;
	int* q = &b;
	int** pp = &p;
	int** qq = &q;
	printf("*p = %d\n",*p);
	printf("**pp = %d\n", ** pp);
	printf("*q = %d\n", *q);
	printf("**qq = %d\n",** qq);
	return 0;
}

打印結果爲:

在這裏插入圖片描述
下面我們給出圖解:
在這裏插入圖片描述
在這裏,我們只是提出二級指針,具體的應用會在數據結構的內容說明到,這裏我們首先了解知識作爲補充理解,之後有很多應用,所以我們必須在開始的這一部分就要理解清楚,爲之後更加深入的學習和應用做準備。

需要特變注意的點

1.指針的運算只能發生在同類型或整型之間,否則會報錯或是警告。
2.指針的運算,除了數值以外,還有類型在裏面。

小結

我們通常進行口述形表達時,說誰指向了誰,就是一種描述指針的指向關係。指向誰,即保存了誰的地址。

到這裏所有C語言指針的基礎內容就已經說明完了,整個內容比較多,讀者可以多次閱讀複習,不斷理解。有問題的地方也歡迎大家在評論區留言一起交流學習。

注意:只有當指針指向一串連續的存儲單元時,指針的移動纔有意義。纔可以將一 個指針變量與一個整數 n 做加減運算。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章