C語言指標
1 指標
指標是什麼?與記憶體地址的關係是什麼?
下圖是 4G 記憶體中每個位元組單元和它的編號:
- 每個位元組單元可以儲存8個二進位制位,即1個位元組大小的內容;
- 位元組單元也被稱為記憶體單元;
- 為了方便定址,每個記憶體單元都有唯一的編號,這個編號就是記憶體地址(Address)或者指標(Address)
- 地址從 0 開始依次增加,對於 32 位環境,程式能夠使用的記憶體為 4GB,最小的地址為
0x00000000
,最大的地址為0xFFFFFFFF
。
【答】
- 指標是記憶體單元的編號;
- 指標變數的值是一個記憶體地址。用
*
取指標變數的值得到的就是該指標變數指向的記憶體地址儲存的值。
1.1 如何建立指標
#include <stdio.h> int main() { int a = 10; int* p1 = &a; int *p2 = &a; int** pp = &p1; printf("%p\n", &a); printf("%p\n", p1); printf("%p\n", p2); printf("%p\n", pp); system("pause"); return 0; }
一次執行中,執行結果如下:
我們畫圖來表示:
-
但在編寫程式碼的過程中,我們認為變數名錶示的是資料本身;例如,a 是 int 型,表示的是 4位元組的整數。
-
a 是一個變數,用來存放整數,需要在前面加&來獲得它的地址;
&a
表示就是該變數的首地址。 -
指標可以認為是一種資料型別,
int*
就可以視為一個整體。p1 就是一個指標,這個指標指向變數 a 的首地址。 -
int* p = &a;
和int *p = &a;
寫法略有差異,但是實際功能是相同的。因此,&a
,p1
,p2
表示的是同一個記憶體單元。 -
int** pp
就等同於(int*)* pp
int*
佔4個位元組,所以 pp 儲存的值等於0x0133F7D4
,這個值又是一個記憶體地址,或者說指標。
【答】:定義指標用 int* p;
或者 int *p
,個人更推薦前者。
1.2 指標佔多少位元組
我們先來看一個程式:
#include <stdio.h> int main() { char* p1; short* p2; int* p3; long* p4; float* p5; double* p6; printf("%d\n", sizeof(p1)); printf("%d\n", sizeof(p2)); printf("%d\n", sizeof(p3)); printf("%d\n", sizeof(p4)); printf("%d\n", sizeof(p5)); printf("%d\n", sizeof(p6)); system("pause"); return 0; }
我們來看一下執行結果:
- 我們發現任何型別的指標變數都是佔用4個位元組。
- 因為我們的程式是用32位編譯的,所以總是4個位元組
【答】
指標的大小是固定的;指標所佔的位元組數跟編譯的程式位數有關。如果是32位程式,指標的寬度就是4位元組,如果是64位程式,指標的寬度就是8位元組。
1.3 指標變數的運算
我們再來看一段程式:
#include <stdio.h>
int main()
{
char c = 'C';
short s = 1;
int i = 100;
double d = 99.9;
char* p1 = &c;
short* p2 = &s;
int* p3 = &i;
double* p4 = &d;
printf("size char=%6d short=%5d int=%7d double=%4d\n", sizeof(char), sizeof(short), sizeof(int), sizeof(double));
printf("Init p1=%8p p2=%8p p3=%8p p4=%8p\n", p1, p2, p3, p4);
// 加法運算
p1++;p2++;p3++;p4++;
printf("p++ p1=%8p p2=%8p p3=%8p p4=%8p\n", p1, p2, p3, p4);
// 減法運算
p1-=2;p2-=2;p3-=2;p4-=2;
printf("p-=2 p1=%8p p2=%8p p3=%8p p4=%8p\n", p1, p2, p3, p4);
system("pause");
return 0;
}
執行結果如下:
- 自增:p1,p2,p3,p4每次加1,他們的地址分別增加1、2、4、8個位元組,這個增量正好等於 char,short,int,double 型別的長度。
- 減法:p1,p2,p3,p4每次減2,他們的地址分別減少2、4、8、16個位元組,這個減少量正好等於 char,short,int,double 型別的長度的2倍。
1.4 單目操作符&
和*
首先,定義指標變數void* p
或者 void *p
,以及定義引用變數 int& a
或者 int &a
;在定義變數時,會比 &
和 *
單純作為單目運算子要多一個型別宣告。
首先,作為單目運算子,它的特點就是隻有一個運算元:
int a = 1;
int* p = &a;
*p = 10;
- 首先定義了一個變數a
- 接著,定義了指標變數p,併為它賦值。賦值時,用上了單目運算子
&
來取得變數a的首地址,並賦值給指標p。 - 最後,再次使用了單目運算子
*
來獲取指標p指向的資料,並修改了資料內容。
&
作為 單目運算子 使用時,是取地址符。
*
作為 單目運算子 使用時,是解引用符。
2 指標常量和常量指標
我之前在學習指標的時候,總是會把指標常量和常量指標混淆起來。最主要的原因是,按照一般的習慣,我們是從左向右讀,所以我們會理所當然地認為const int* p
是常量指標。但是實際上你倒過來念就是正確的。
2.1 指標常量
int a = 0;
int b = 1;
// 指標常量
const int* p = &a;
// *p = 10; // 錯誤,內容不可以修改
p = &b; // 正確,指標可以修改
記憶方法:按照英文反著念 int*
,const
: 指標常量。中文的習慣,最終定性為“常量”,所以指標指向的資料是個常量,不可以被修改。
2.2 常量指標
int a = 0;
int b = 1;
// 常量指標
int* const p = &a;
*p = 10; // 正確,指標指向的資料可以修改
// p = &b; // 錯誤,指標地址不可以修改
記憶方法:反著念定義關鍵字 const
,int*
:常量指標。中文的習慣,最終定性為“指標”,常量修飾“指標”一詞,所以指標地址不可以被修改。但是,指標指向的資料可以被修改。
2.3 地址和資料都不許改
int a = 0;
int b = 1;
const int* const p = &a;
// *p = 10; // 錯誤,指標指向的資料不可以修改
// p = &b; // 錯誤,指標地址不可以修改
3 指標與陣列
3.1 訪問陣列中的元素
int arr[] = {1,2,3,4,5};
int* start = arr;
- 首先,初始化了整型陣列 arr;
- 接著,把陣列名賦值給了指標start;
函式名、字串名和陣列名錶示的是程式碼塊或資料塊的首地址
我們想要遍歷陣列,首先需要計算陣列的元素個數:
int len = sizeof(arr) / 4;
然後,不使用指標時,我們訪問陣列的是這樣:
for (int i = 0; i < len; i++) {
printf("%d\n", arr[i]);
}
使用指標訪問陣列是這樣的:
for (int i = 0; i < len; i++) {
printf("%d\n", *(start+i));
}
3.2 通過char型指標訪問short陣列會發生什麼?
short arr[] = {1, 2, 3};
char* p = reinterpret_cast<char *>(arr);
for (int i = 0; i < 3; i++) {
printf("%d\n", *(p+i));
}
執行結果:
參考文件
《C語言指標是什麼?1分鐘徹底理解C語言指標的概念》閱讀