一、指針的那些事
說起指針,關於指針的四個方面一定要清楚:
- 指針的類型
- 指針指向的類型
- 指針的值
- 指針本身所佔的內存空間
1、指針的類型
<1> int* p; <2> int** p; <3> char* p; <4> double* p[3];
無論上面定義的指針類型你是否認識,有一個判斷指針的萬能公式----把指針名去掉,剩下的就是是指針的類型
比如int** p; 這個指針自身的類型就是 int** (去掉指針名p).。
2、指針指向的類型
這個和上面的判斷方法類似,判斷一個指針的指向類型時,把指針名和前面的一個*去掉,剩下的就是指針指向的類型
比如 int** p; 這個指針指向的類型就是int * 。
作用:決定了指針變量所取空間的寬度,決定了指針變量+1時跳過的單位跨度(★★★)
關於上面的作用可能不是很好理解,等到下面講到指針的運算符會詳細的解釋上面的作用
3、指針的值
在定義指針會申請一塊內存空間在存放這個指針,這個內存空間裏面村的就是指針的值,通過指針的值一般都是第一個地址,這個地址都是指針所指向的的地址。定義指針時,僅僅是申請了一塊內存空間來存放指針的地址,但是這個內存空間並沒有初始化。
4、指針本身所佔的內存空間
在定義指針時,肯定要去內存空間中申請一塊內存空間來存放這個指針。
一開始發現了一個特別有趣的事:32位的系統中,無論什麼類型的指針,都是用4個字節進行存儲,64位的系統都是用8個字節進行存儲。
#include<iostream> using namespace std; int main() { cout << sizeof(int **) << endl; cout << sizeof(char *) << endl; cout << sizeof(double *) << endl; cout << sizeof(long long int*) << endl; return 0; }
上面的代碼在32位的系統中運行,輸出的都是4,在64位的系統中運行,輸出的都是8。
一開始特別困惑,不同的指針類型,它們怎麼可能佔用的內存空間一樣呢?
最後去網上搜了搜,又查了一些書,最後得到了一個說服自己的理由:
假如是32的系統,那麼內存空間的地址編號長度肯定都是一樣長的,而指針的值存放的就是一個個的地址,那麼不同類型指針佔用的內存空間可以是一樣大的,因爲他們存放的地址長度都是一樣的。
總結:一旦遇到了一個指針,自己就應該問問自己?指針的類型是什麼?指針指向的類型是什麼?指針的值是什麼?
二、指針的算術運算
1、關於整形數的存儲
首先看一張圖片
假如我們定義了一個整型變量,又賦予了一個它一個初值(16進制的11223344).那他在系統中的存儲方式如上圖所示(採取的是小端存儲,至於是大端還是小端存儲,這個和操作系統有關,window的系統是小端存儲)。
變量a的首地址都是地址1。當使用到變量a是,他就會拿着a的地址去內存中找,從這個地址一次往後找四個字節(找幾個節和這個變量是什麼類型的有關)。然後根據這四個字節讀出來一個整數(至於怎樣讀的,就不是咱們考慮的,操作系統既然能存進去,它肯定也可以讀出來)。
2、關於指針的運算
首先看 代碼1:
#include<iostream> using namespace std; int main() { int a = 0x11223344; printf("%#x\n", a);//按16進制數字進行輸出 int* p1 = &a; printf("%#x\n", *p1);//按16進制數字進行輸出 short* p2 = (short*)&a; printf("%#x\n", *p2);//按16進制數字進行輸出 char* p3 = (char*)&a; printf("%#x\n", *p3);//按16進制數字進行輸出 return 0; }
代碼1運行結果:
根據整數的存儲,那麼指針的訪問如下圖所示
下面再看 代碼2 :
#include<iostream> using namespace std; int main() { int a = 0x11223344; printf("%#x\n", a);//按16進制數字進行輸出 int* p1 = &a; printf("%#x\n", *p1);//按16進制數字進行輸出 short* p2 = (short*)&a; p2++; printf("%#x\n", *p2);//按16進制數字進行輸出 char* p3 = (char*)&a; p3++; printf("%#x\n", *p3);//按16進制數字進行輸出 return 0; }
代碼2 運行結果:
經過上面的代碼後,指針p2和p3指向的位置都變了,下面通過一張圖片看他們移動後的指向
先說指針p1,他的指向是地址1,指針指向的類型是int(4個字節),所以說指針p1的指向地址是地址1,讀取的寬度是4個字節,因此*p1的值就是0x11223344(因爲採取的小端存儲方式,倒着存倒着取)。
再說指針p2,這個指針一開始的指向的地址1,指針指向的類型是short ( 2個字節 ) ,指針讀取的寬度2個字節,因此指針p2移動前,*p2讀取的數據就是0x3344。採取p2++後,指針p2向後移動一個單位長度(單位長度就是2個字節,這個和指針指向的類型有關)。那麼首地址都變成了地址3,讀取寬度是2個字節,因此讀取的數值就是0x1122
最後說指針p3.這個指針一開始指向的地址1,讀取寬度是1,因此*p3讀取的數值就是0x44。
p3++後,向後移動一個單位長度(一個字節),讀取長度是1,因此 *p3讀取的數值就是0x33.
三、指針的安全行問題
1、空指針
在定義指針時,我們一般都要給指針賦初值,如果還沒有地址給這個指針賦初值,可以賦給指針一個NULL。
上面是一個特別好的習慣,不過有一點需要注意的就是空指針不能別訪問,
原因:NULL其實就是內存空間的0號地址,操作系統中0-255的地址空間都不能被訪問,這個地址只能由操作系統進行管理,程序員不可以進行修改的讀取。
2、野指針
假設我們現在定義了一個指針,同時由賦給了它一個地址(直接進行指定的常量地址),那這個指針就稱爲野指針。
#include<iostream> using namespace std; int main() { int* p = &(0x01020304); cout << *p << endl; return 0; }
就像上面那樣,直接指定了一個地址賦給了指針,這就是野指針,我們在使用指針時千萬不要這樣做。
就像你去酒店開房一樣,只有你去前臺找管理的人,讓他給你選擇一個空的房間,這樣你才能入住,假如少了前面那一步,你可以直接找一個房間進去嗎,說不定裏面就有人在幹啥呢!!!