文章更新,更加詳細的介紹請看這篇:http://www.javashuo.com/article/p-bpizmwmt-go.htmlhtml
不少人不敢講C的指針,有些人講不清,有些人怕講錯。初生牛犢不怕虎,就讓我講講。java
下面開始。程序員
1、指針的定義 數組
指針是內存單元的編號。內存單元是以字節爲單位的。因此指針就是字節的編號。 函數
好比咱們的我的電腦,內存通常4GB吧,那麼一共就有 : 4*1024*1024*1024 = 4294967296 字節,也就是4294967296個編號。一個字節擁有一個編號,spa
範圍從 0 ~ 4294967296-1 。指針
畫個圖表示:(注意字節由8位bit組成,爲了直觀我沒畫出來)code
可是呢,通常咱們是用16進制來表示的這些編號,但效果都同樣。orm
2、變量與內存的關係htm
如今咱們來看C 變量在內存中的樣子。咱們使用自動變量(局部變量)來說解。
int a = 5; //假設a存儲在編號爲0x02開始的位置
說明:
內存能夠存儲數據,因此咱們把每一個字節當作是一個「箱子」。數據存入內存就比如在箱子裏面放數據
可是C語言的不一樣數據類型佔用的字節數是不都同樣的,因此,每種數據類型佔的」箱子」的個數不都同樣。
好比char型,只要一個字節就夠了,因此一個字符只需一個「箱子」。
而int型須要4(通常是4個字節)個「箱子」才放得下。
double型則須要8個「箱子」。
來分析上圖中用橙色框起來的4個字節的內存塊,這裏就存儲了a這個變量。
咱們從4個方面去討論這個內存塊:
一、內存的數據
咱們的變量賦值爲5,因此內存的數據就是 0000 0000 0000 0000 0000 0000 0000 0101 (大端模式)
每一個字節地址: 0x02 0x03 0x04 0x05
二、*內存的名字 (對於咱們的程序使用的內存來講,並非每個內存塊都有名字)
名字就是變量名a
三、內存的地址
變量a佔用了4個字節,那麼,哪個字節的地址,纔是變量a的地址呢?答:第一個低地址字節的地址,也就是0x02
四、內存的寬度
這裏的a變量佔用了4個字節,這就是他的寬度。
這裏提一下,爲後面講指針的寬度作鋪墊 :)
3、定義和使用指針變量
什麼是指針變量。咱們知道int類型變量用來存儲整形值,十二、100三、9823......
那麼,一樣的道理,指針變量就是用來保存地址的變量。
定義指針變量:在指向的變量的類型上加個* ,以下:
int* p_int; //指向int類型變量的指針 double* p_double; //指向idouble類型變量的指針 int(*p_func)(int,int); //指向返回類型爲int,有2個int形參的函數的指針 int(*p_arr)[3]; //指向含有3個int元素的數組的指針 struct Student *p_struct; //結構體類型的指針 int** p_pointer; //指向 一個整形變量指針的指針
既然咱們已經定義指針變量了,那麼接下來就用指針變量來存儲其它變量的地址。
取地址符號 &
#include<stdio.h> int Add(int a,int b); struct Student { int age; double score; char name[31]; }; int main(void) { int val_int =100 ; double val_double = 12.00; int arr[3]={1,2,3}; struct Student stu={ 19, 98.00, "Jack" }; int*p = NULL; /**********************************************************/ int* p_int = &val_int; double* p_double = &val_double; int(*p_func)(int,int) = &Add; //or =Add int(*p_arr)[3] = &arr; struct Student *p_struct = &stu; int** p_pointer = &p; return 0; } int Add(int a,int b) { return a+b; }
4、指針的屬性和使用
我認爲指針有2個屬性: ①指針的值 ②指針的寬度
指針的值很好理解,好比第一個例子 int a = 5; int*p = &a; 那麼p的值就是a的地址0x02
指針的寬度:由指針的類型決定。
若是說指針的值標記了某個數據在內存的起始位置,那麼,指針的寬度就決定了從起始地址
對應的字節開始,日後還有多少個字節,也是屬於這個內存數據的。
生活情景:「老王,去幫我去倉庫拿個貨,個人貨從78號箱開始,而且有2個。」 因而老王取出 78和79號箱子的貨物遞給你
假如你對老王說:「老王,幫我去倉庫取個人貨,個人從78號箱開始,一共
有幾個箱子,我也記不住了」 「那我怎麼取,取少了你就有損失了,取多了別人也不幹了,你不要難爲我啊」
這點個人另外一篇隨筆也講到了 .
#include<stdio.h> int main(void) { int a =256 ; int* p=&a; printf("%d\n", *((unsigned char*)p) ); //讀取內存數據時,取的寬度的比存時的寬度小,數據缺失 return 0; }
#include<stdio.h> int main(void) { int arr[2] = {1,2}; arr[2] = 100; //ops! 訪問越界 return 0; }
前面咱們講了內存塊的4點要素,第4點就是內存塊的寬度,他是和指針的寬度是一一對應的。一個指針變量指向了這個內存塊,
那麼指針的寬度就是這個內存塊的寬度。這也代表,咱們在使用指針的時候,不要越界,也不要取少,取少了取出的數據會不完整,
越界就更嚴重了,程序會掛掉的,由於你訪問了不屬於你的內存。
擴展:有木有 沒有寬度的指針呢? 答:這個能夠有 void* p;
void* 類型的指針對應與C# or java中的Object類型變量,它能夠指向任何類型變量。 不過他只保存了內存的起點,沒有保存寬度信息,若是你想
取出原來的數據,你必須作出正確的強制轉換。
五:一對相反操做的運算符 * 和 &
& 取地址符 &a 就獲取了a的指針,而後咱們就能夠用int*p變量出承接這個地址 p = &a; 咱們就說p指向了a
* 解地址符 *操做符有一個很形象的動詞 :解 。p保存了a的起始地址和延續寬度,那麼,*p則是肯定起始地址,量出寬度,
獲取這個內存塊。 所以咱們能夠經過a 的地址p去操做(讀/寫)這個內存塊了。
p也是一個變量,他本身也有地址,這就長產生了指針的指針。
注意:我所說的指針的寬度並非說指針變量佔用的內存大小,而是經過這個指針指向的內存塊的寬度。指針的佔用的字節數是必定的,通常狀況下,指針變量都佔用4個字節。上面圖中我也畫了4個字節。
6、爲何要使用指針
你可能會問:我有變量名,爲何還要繞一轉,用指針去使用內存數據?
但並非這樣,下面是2個例子。
①經過動態申請的內存是匿名的,沒有名字,只能經過指向這塊內存的指針去使用。
malloc() realloc() calloc() 這裏不擴展了。
②一個經典的題目:用函數交換2個變量的值。
#include<stdio.h> void swap_2b(int a,int b); void swap_hack(int *pa,int *pb); void swap_normal(int*pa,int*pb); int main() { int a = 5; int b = 3; swap_2b(a,b); //Can`t swap; swap_normal(&a,&b); //OK swap_hack(&a,&b); //OK return 0; } //2b青年寫的函數 void swap_2b(int a,int b) { int t; t=a; a=b; b=t; } //程序員寫的函數 void swap_normal(int*pa,int*pb) { int t; t=*pa; *pa=*pb; *pb=t; } //黑客寫的函數 void swap_hack(int *pa,int *pb) { *pa = *pa^*pb; *pb = *pa^*pb; *pa = *pa^*pb; }
咱們發現只有2b青年沒用指針哈。
緣由很簡單,C語言函數是按值傳遞的,2b青年寫的代碼之因此達不到效果是由於它操做的內存塊錯了。
不管他在函數裏面怎麼換a和b的值,main函數中的a和b都不會變。由於swap函數中的a和b是隨函數調用新生成的變量,是副本,而不是源對象。
可是傳遞指針就不同了,由於內存數據的指針是獨一無二的。一我的有惟一的身份證同樣。
7、野指針和NULL指針
指針變量在使用前或者使用完,好的習慣是賦值爲NULL ,NULL 是編號爲0的字節的地址。指向NULL表示不指向任何變量。
NULL就像劍鞘,野指針就像暗箭,若是你不像被暗箭所傷,就讓他歸鞘。
最後:因爲內容比較多,可能有許多地方表達不當,或有疏漏,錯誤,但願你們及時指出,謝謝 : )