指針常量
常量指針是有類型的地址常量
我們首先提出一個概念:
常量指針是有類型的地址常量,對變量取地址,取出的地址就是一個指針,且是常量指針
對於變量使用取地址符號 & 取到了變量的地址,再對取到的地址進行解引用,也就是取內容 * 操作,得到了變量的數據,可以理解爲,先找到變量地址,再通過變量的地址訪問到變量所保存的數據。
代碼演示:
#include <stdio.h>
int main()
{
char a = 1;
short b = 2;
int c = 10;
double d = 123.45;
//打印變量的地址
printf("%x\t%d\n", &a,&a);
printf("%x\t%d\n", &b,&b);
printf("%x\t%d\n", &c,&c);
printf("%x\t%d\n", &d,&d);
//通過變量的地址,解引用取到變量的內容
printf("a = %d\n",*(&a));
printf("b = %d\n",*(&b));
printf("c = %d\n",*(&c));
printf("d = %f\n",*(&d));
return 0;
}
運行結果爲:
上面執行代碼中對變量取地址,取出來的地址就是常量指針。
那麼既然我們之前說到了指針就是地址,現在我們知道了a,b,c,d的地址,那我們能不能直接通過16進制的地址訪問到變量呢?
我們通過獲得的16進制地址數據通過解引用獲得指針指向的數據,查看是否能取到地址所對應的數據。
#include <stdio.h>
int main()
{
char a = 1;
short b = 2;
int c = 10;
double d = 123.45;
printf("%x\t%d\n", &a,&a);
printf("%x\t%d\n", &b,&b);
printf("%x\t%d\n", &c,&c);
printf("%x\t%d\n", &d,&d);
printf("a = %d\n",*(&a));
printf("b = %d\n",*(&b));
printf("c = %d\n",*(&c));
printf("d = %f\n",*(&d));
printf("a = %d\n",*(0x62fe1f));
printf("b = %d\n",*(0x62fe1c));
printf("c = %d\n",*(0x62fe18));
printf("d = %f\n",*(0x62fe10));
上面最後一種方式是否都能打印出來變量的值呢?
我們在編譯的時候直接出現錯誤:
說明是不允許的。
我們得出常量指針和地址是不等價的,也就是說常量指針不僅僅只是地址。
我們換一種方式:
把其中:
#include <stdio.h>
int main()
{
printf("a = %d\n",*(0x62fe1f));
printf("b = %d\n",*(0x62fe1c));
printf("c = %d\n",*(0x62fe18));
printf("d = %f\n",*(0x62fe10));
return 0;
}
換成:
#include <stdio.h>
int main()
{
printf("a = %d\n",*((char*)0x62fe1f));
printf("b = %d\n",*((short*)0x62fe1c));
printf("c = %d\n",*((int*)0x62fe18));
printf("d = %f\n",*((double*)0x62fe10));
return 0;
}
打印結果爲:
我們可以看到:
我們通過以上方式證明:
①常量指針並不直接等價於地址
②說明十六進制地址的時候必須要說明指針的類型。
同時也就證明了我們剛開始提到的:
常量指針不是一個單純的地址,而是有類型的。
也就是說常量指針是有類型的地址常量
常量指針 = 地址 + 類型
地址是 0000 0000 到 1111 1111 一共4G的範圍對應的內存編址單元的數值,
那麼我們如何理解類型呢?
類型對於指針有什麼意義呢?
接下來我們通過圖解進行說明:
如果我們只是給了一個地址,那麼我們從這個地址開始到底取到多少個字節,我們是沒有辦法確定的,這也就是上面沒有給指針類型編譯出錯的原因。
其他類型變量證明類似。
常量指針不是一個單純的地址,而是有類型的。(重點理解)
結論:
指針的類型,決定了該指針的尋址能力。即從指針所代表的地址處的尋址範圍。
小端序和大端序
那麼我們在這裏就類型展開討論:
在這裏我們提到一個小端序和大端序的問題:
小端序就是內存中的小地址存儲的是小數據,大地址存儲的是大數據。
大端序就是內存中小地址存儲的的是大數據,大地之存儲的是小數據。
例如:
int data = 0x12345678;
那麼在內存中的存儲爲:
也就是說,最小的78存儲在最低位,最大的12存儲在了最高位,這就是我們的小端序,我們的計算機內存存儲就是這種方式,手機的內存存儲方式爲大端序。
接下來我們通過代碼進行驗證:
#include <stdio.h>
int main()
{
int data = 0x12345678;
printf("%x\n",&data);
printf("%x\n",*((char*)0x62fe1c));//起始地址相同
printf("%x\n",*((short*)0x62fe1c));//類型決定大小
printf("%x\n",*((int*)0x62fe1c));//同樣的地址類型不同獲取的長度也不同
return 0;
}
當我們取到變量data的地址之後,我們可以通過指針類型限定我們訪問的字節數。
例如我們通過char * 訪問一個字節,short * 訪問兩個字節,並且進行打印。
打印結果爲:
我們通過圖解方式進行說明:
上面的三行打印結果都是依託於地址:0x62fe1c 求得,只不過地址類型不同,所以尋址的範圍不同。同一個地址,char * 類型尋址範圍只有1個字節,short * 類型尋址範圍只有4個字節,double * 類型尋址範圍只有8個字節。
所以同樣是一個地址,類型不同,尋址能力就不同,那麼取到變量的數值就不同。
以上也就證明了打印出來:
78
5678
12345678
的原因,
所以我們最終對於指針的結論就是:
指針的本質是一個有類型的地址。
地址我們已經早都知道了,32位機能夠放出的地址總數,類型決定了從這個地址開始的尋址能力。
(也就是從一個地址開始能夠尋址多少個字節)。
那麼我們說明指針常量,既然是常量,也就是說指針常量的地址是不允許被賦值修改的:
#include <stdio.h>
int main()
{
char a;
short b;
int c;
double d;
&a = 0x12345678;
return 0;
}
出現錯誤:不允許a的地址被改變
我們接下來說明指針變量。
指針變量
指針是一個有類型地址,是一個有類型的常量。用以存放指針的變量,我們叫作指針變量。一個指針變量卻可以被賦予不同的指針值,把不同的指針值存儲在指針變量中,可以能過指針變量改變指向和間接操作。
嚴格意義上來說,指針即是指的指針常量,而我們通常意義上所說的指針,多指的指針變量。
我們前面知道常量是有類型的。
#include <stdio.h>
int main()
{
int a = 12;
char b = 'c';
float d = 1.234f;
return 0;
}
上面的 12 ,c ,1.234 ,我們認爲是輸入的常量數據,那麼輸入的常量數據,我們就需要找一個合適的變量進行存儲。
對於一個指針來說:
#include <stdio.h>
int main()
{
char a;
short b;
float f;
double d;
//取地址的時候取出來是常量地址
//常量地址不允許被賦值
&a = (char *)0x12345678;
&b;
&f;
&d;
return 0;
}
上面代碼編譯就會出現錯誤:
&a 的地位就等價於 12,c,1.234 ,&a是一個常量指針,常量不允許被賦值。
那麼最終我們需要找一個變量把 a 的地址存起來,那麼就需要聲明一個指針變量,下面我們給出指針變量的定義。
定義
聲明一個指針變量必須保存:①地址數據,②類型
因爲要完整的表達一個指針就必須要有地址和類型。
指針變量的定義:
解析
- 表明該變量是一個指針變量。
type 決定該指針變量中存儲的地址的尋址能力。
指針變量大小
指針 = 地址 + 類型
聲明一個指針變量用於存放常量指針的時候, 定義帶 * 的變量決定了該變量是一個指針類型,我們都知道類型決定了變量所佔字節的大小,那麼指針類型也就決定了指針變量變量所佔字節的大小。
定義帶有 * 的變量都是指針類型變量,對於32位機來說總共釋放2^32個地址,所以32位二進制數就可以尋址到內存地址 從 0000 0000 到 1111 1111 中的每一個字節,那麼對於32位機來說所有指針類型所佔內存的大小都是32位也就是4個字節。那麼同理在64位機的情況下,所有指針類型所佔內存的大小相等且佔8個字節,我們下面代碼在64位機下面進行打印,證明指針的大小是一樣的。
#include <stdio.h>
int main()
{
char a;
short b;
int c;
double d;
char* aa;
short* bb;
int* cc;
double* dd;
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(aa) = %d\n", sizeof(aa));
printf("sizeof(bb) = %d\n", sizeof(bb));
printf("sizeof(cc) = %d\n", sizeof(cc));
printf("sizeof(dd) = %d\n", sizeof(dd));
printf("sizeof(char *) = %d\n", sizeof(char*));
printf("sizeof(short *) = %d\n", sizeof(short*));
printf("sizeof(int *) = %d\n", sizeof(int*));
printf("sizeof(double *) = %d\n", sizeof(double*));
return 0;
}
這裏我們在64位平臺下驗證,64位平臺下,每個指針佔8個字節。
打印結果爲:
有興趣的讀者可以把上面代碼放在32位機下面打印,所有的打印結果大小都爲4。
之前我們在訪問地址指向的變量時候是通過下面形式訪問的:
#include <stdio.h>
int main()
{
char a = 1;
short b = 2;
int c = 3;
double d = 5.6;
printf("%d\t",*(&a));
printf("%d\t",*(&b));
printf("%d\t",*(&c));
printf("%f\t",*(&d));
return 0;
}
打印結果爲:
初始化及間接訪問
那麼在定義了指針變量之後,我們可以換一種方式訪問變量。
#include <stdio.h>
int main()
{
char a = 1;
short b = 2;
int c = 3;
double d = 5.6;
char*aa = &a;
short*bb = &b;
int*cc = &c;
double*dd = &d;
printf("%d\t",*(&a));
printf("%d\t",*(&b));
printf("%d\t",*(&c));
printf("%f\n",*(&d));
printf("%d\t",*aa);
printf("%d\t",*bb);
printf("%d\t",*cc);
printf("%f\n",*dd);
return 0
}
打印結果爲:
我們可以看出兩種方式打印出來完全相同。
那麼接下來我們要區分一下不同位置 * 的作用:
我們在定義的時候使用的 * 只是聲明變量是指針變量,例如:int * p;
printf("%d\t", * aa); 這裏的*表示取值,也就是我們理解的解引用。
那麼之後我們就可以這樣使用:
例如:
#include <stdio.h>
int main()
{
int data = 0x12345678;
int *pa = &data;
printf("%x\n",&data);
printf("%x\n",*(int *)pa);
printf("%x\n",*(short*)pa);
printf("%x\n",*(char*)pa);
//上面方法就不需要使用地址打印輸出
printf("%x\n",*(int *)0x62fe14);
printf("%x\n",*(short*)0x62fe14);
printf("%x\n",*(char*)0x62fe14);
//使用地址進行打印
return 0;
}
打印結果爲:
通過使用指針變量存儲變量的地址,我們就不用直接看見變量的地址,就可以對於變量進行操作。
把變量的地址放在指針變量裏面,就可以對於變量的地址進行操作,這就是爲什麼所有數據類型能夠和指針產生關係的重要紐帶。
把變量的地址取出來放在指針變量裏面,就可以通過地址操縱內存。
小結
強調:
有了指針變量之後,我們就可以不需要知道變量的地址,直接對於指針變量存放的變量地址進行訪問和操作。
我們可以定義一個指針變量把變量的地址存放在指針變量裏面,就可以通過指針變量對於地址進行操作。並且可以通過地址操縱內存。
我們強調:指針的本質是一個有類型的地址。
單純的說指針只是一個地址是沒有意義的,說明指針時必須要有類型,才能確定在從這個地址開始尋址的內存空間大小也就是尋址能力。 也就是從指針所代表的地址處開始尋址的尋址範圍。