指針(I)

基礎(chǔ)知識

物理內(nèi)存

? 維修電腦師傅眼中的內(nèi)存:內(nèi)存在物理上是由一組DRAM芯片組成的.


軟件內(nèi)存

? 操作系統(tǒng)將RAM(分為兩大類:SRAMDRAM)等硬件和軟件結(jié)合起來萝嘁,給程序員提供的一種對內(nèi)存使用的抽象前硫。這種抽象機制使得程序使用的是虛擬存儲器,而不是直接操作和使用真實存在的物理存儲器,所有的虛擬地址形成的集合就是虛擬地址空間。 如下圖:

程序員眼中的內(nèi)存

系統(tǒng)中的實際內(nèi)存排列:


系統(tǒng)中的內(nèi)存排列

? 內(nèi)存是一個很大的命黔,線性的字節(jié)數(shù)組(平坦尋址)澜沟。每一個字節(jié)都是固定的大小够坐,由8個二進制位組成托嚣。最關(guān)鍵的是,每一個字節(jié)都有一個唯一的編號,編號從0開始患久,一直到最后一個字節(jié)椅寺。如上圖中,這是一個256M的內(nèi)存蒋失,他一共有256x1024x1024 = 268435456個字節(jié)返帕,那么它的地址范圍就是 0 ~268435455 。

? 系統(tǒng)中的內(nèi)存每一個字節(jié)都有一個唯一的編號篙挽。因此荆萤,在程序中使用的變量,常量铣卡,甚至函數(shù)等數(shù)據(jù)链韭,當它們被載入到內(nèi)存中后,都有自己唯一的編號煮落,這個編號就是這個數(shù)據(jù)的地址敞峭,指針就是這樣形成的。


  • bit(位): 一個二進制數(shù)據(jù)0或1蝉仇,是1bit旋讹;

  • byte(字節(jié)): 存儲空間的基本計量單位,1byte = 8 bit;

  • 1 字母 = 1 byte = 8 bit;

  • 1 漢字 = 2 byte = 16 bit;

64 位的編譯器各類型字節(jié)數(shù)顯示

//不同的編譯器位數(shù),其字節(jié)數(shù)顯示是不同的量淌。通過在程序中打悠濉:
printf("%lu", sizeof(int));   //在Xcode中輸出:4嫌褪,說明是64位的編譯器


char:      1個字節(jié)

short:     2個字節(jié)

int:       4個字節(jié)

long:      8個字節(jié)

float:      4個字節(jié)

double:    8個字節(jié)

? 查看操作系統(tǒng)位數(shù)呀枢,在終端輸入:uname -a 在末尾有x86_64說明是64位的操作系統(tǒng)。




指針介紹

int a = -12;
char b = M;
float c = 3.14
a笼痛、b裙秋、c內(nèi)存存放示意圖

變量尋址

3.png

編譯器將變量名和地址進行掛鉤琅拌,內(nèi)存中沒有存放變量的名字;

? 指針的值實質(zhì)是內(nèi)存單元(即字節(jié))的編號摘刑,所以指針單獨從數(shù)值上看进宝,也是整數(shù),他們一般用16進制表示枷恕。指針的值(虛擬地址值)使用一個機器字的大小來存儲,也就是說,對于一個機器字為w位的電腦而言,它的虛擬地址空間是0~2w - 1 ,程序最多能訪問2w個字節(jié)党晋。
? 所以可以把指針看做是一個變量,其值為另一個變量的地址徐块,即內(nèi)存位置的直接地址未玻。

//地址是計算機內(nèi)存中的某個位置,而指針是專門用來存放地址的特殊類型變量
type *pointer;

type:      指針的基類型胡控;
*   :      申明指針扳剿;
pointer:   指針變量;//作用:是用來存放地址和對地址進行索引

DEMO圖

指針各類型介紹

? 要掌握指針的四方面的內(nèi)容:指針的類型昼激、指針所指向的類型庇绽、指針的值(或者叫指針所指向的內(nèi)存區(qū))、指針本身所占據(jù)的內(nèi)存區(qū)橙困。

  • 指針的類型: 從語法的角度看瞧掺,把指針聲明語句里的指針名字去掉,剩下的部分就是指針的類型凡傅;

  • 指針所指向的類型:決定了編譯器將把那片內(nèi)存區(qū)里的內(nèi)容當做什么來看待夸盟。從語法上看,你只須把指針聲明語句中的指針名字和名字左邊的指針聲明符*去掉像捶,剩下的就是指針所指向的類型上陕;

  • 指針的值:是指針本身存儲的數(shù)值,這個值被編譯器當作一個地址拓春,而不是一個一般的數(shù)值释簿。在64 位程序里,所有類型指針的值都是一個64 位整數(shù)硼莽,因為64 位程序里內(nèi)存地址全都是64 位長庶溶。指針的值代表那個內(nèi)存地址的開始,長度為sizeof(數(shù)據(jù)類型)的一片內(nèi)存區(qū)懂鸵。以后偏螺,我們說一個指針的值是XX,就相當于說該指針指向了以XX 為首地址的一片內(nèi)存區(qū)域匆光;我們說一個指針指向了某塊內(nèi)存區(qū)域套像,相當于說該指針的值是這塊內(nèi)存區(qū)域的首地址。指針的值指針所指向的類型是兩個完全不同的概念终息。在上圖中夺巩,指針所指向的類型已經(jīng)有了贞让,但由于指針還未初始化,所以它所指向的內(nèi)存區(qū)是不存在的柳譬,或者說是無意義的喳张。

  • 指針本身所占據(jù)的內(nèi)存區(qū):指針本身占用的內(nèi)存可以用函數(shù)sizeof(數(shù)據(jù)類型)測一下就知道了。在64 位平臺里美澳,指針本身占據(jù)了8 個字節(jié)的長度销部。指針本身占據(jù)的內(nèi)存這個概念在判斷一個指針表達式是否是左值時很有用。



//普通的整型變量
int p; 

//首先從P 處開始,先與*結(jié)合,所以說明P 是一個指針,然后再與int 結(jié)合,說明指針所指向的類型為int 型.所以P是一個返回整型數(shù)據(jù)的指針
int *p;  

//首先從P 處開始,先與[]結(jié)合,說明P 是一個數(shù)組,然后與int 結(jié)合,說明數(shù)組里的元素是整型的,所以P 是一個由整型數(shù)據(jù)組成的數(shù)組  
int p[3]; 

//首先從P 處開始,先與[]結(jié)合,因為其優(yōu)先級比*高,所以P 是一個數(shù)組,然后再與*結(jié)合,說明數(shù)組里的元素是指針類型,
//然后再與int 結(jié)合,說明指針所指向的類型是整型的,所以P 是一個返回整型數(shù)據(jù)的指針所組成的數(shù)組  
int *p[3]; 

//首先從P 處開始,先與*結(jié)合,說明P 是一個指針然后再與[]結(jié)合,說明指針所指向的內(nèi)容是一個數(shù)組,
//然后再與int 結(jié)合,說明數(shù)組里的元素是整型的.所以P 是一個指向整型數(shù)據(jù)組成的數(shù)組的指針  
int (*p)[3]; 

//首先從P 開始,先與*結(jié)合,說是P 是一個指針,然后再與*結(jié)合,說明指針所指向的元素是指針,然后再與int 結(jié)合,說明該指針所指向的元素是整型數(shù)據(jù).
//由于二級指針以及更高級的指針極少用在復(fù)雜的類型中,所以后面更復(fù)雜的類型我們就不考慮多級指針了,最多只考慮一級指針.  
int **p; 

//從P 處起,先與()結(jié)合,說明P 是一個函數(shù),然后進入()里分析,說明該函數(shù)有一個整型變量的參數(shù),然后再與外面的int 結(jié)合,說明函數(shù)的返回值是一個整型數(shù)據(jù) 
int p(int); 

//從P 處開始,先與指針結(jié)合,說明P 是一個指針,然后與()結(jié)合,說明指針指向的是一個函數(shù),然后再與()里的int 結(jié)合,說明函數(shù)有一個int 型的參數(shù),
//再與最外層的int 結(jié)合,說明函數(shù)的返回類型是整型,所以P 是一個指向有一個整型參數(shù)且返回類型為整型的函數(shù)的指針  
Int (*p)(int); 

//從P 開始,先與()結(jié)合,說明P 是一個函數(shù),然后進入()里面,與int 結(jié)合,說明函數(shù)有一個整型變量參數(shù),然后再與外面的*結(jié)合,說明函數(shù)返回的是一個指針,
//然后到最外面一層,先與[]結(jié)合,說明返回的指針指向的是一個數(shù)組,然后再與*結(jié)合,說明數(shù)組里的元素是指針,然后再與int 結(jié)合,說明指針指向的內(nèi)容是整型數(shù)據(jù).
//所以P 是一個參數(shù)為一個整數(shù)據(jù)且返回一個指向由整型指針變量組成的數(shù)組的指針變量的函數(shù). 
int *(*p(int))[3]; 




指針的操作

指針的基本使用

打又聘:
a = 10, &a = 0x7ffeefbff4fc
b = 0x7ffeefbff4fc, *b = 10, &b = 0x7ffeefbff4f0
上圖可以看到柴墩,當打印 c 時就會Crash,報錯看圖凫岖。


void contactAddressAndPointer()
{
    int num = 97;
    //定義一個指針pointer
    int *pointer;
    //對  num  取地址(&num, 使用連字號&運算符訪問的地址), 賦值給指針變量pointer
    pointer = #

    /*
      int num=97;
      //在定義指針pointer的同時將num的地址賦給指針pointer
      int *pointer=#
    */

    //pointer:指向的變量num的地址江咳;
    // &pointer:指針pointer的地址,因為指針也是一個變量自己也有地址的哥放;
    //&num:變量 num 的地址歼指;
    printf("地址:\np:%p,  &p:%p,  &num:%p\n\n",pointer, &pointer, &num);

    //*pointer:變量 num 的值;
    //i:變量 i 的值甥雕;
    printf("值:\n指針變量*pointer:%d,  num的值:%d\n",*pointer,num);
}

打印值:
地址: p:0x7ffeefbff57c, &p:0x7ffeefbff570, &num:0x7ffeefbff57c

值: 指針變量*pointer:97, num的值:97

? 用來保存 指針 的變量踩身,就是指針變量。如果指針變量pointer保存了變量 num的地址社露,則就說:pointer指向了變量num挟阻,也可以說pointer指向了num所在的內(nèi)存塊 ,這種指向關(guān)系峭弟,在圖中一般用 箭頭表示附鸽。


1.png

? 指針變量pointer指向了num所在的內(nèi)存塊 ,即從地址0028FF40開始的4個byte 的內(nèi)存塊瞒瘸。



變量和指針

變量和指針的存放區(qū)別


取址

    int pointerFunc(int x, int b){
        return x + b;
    }


    int num = 97;
    float scrore = 10.00F;
    int arr[3] = {1, 2, 3};
    int (*ptr)(int, int);
    
    int *p_num = #
    float *p_scrore = &scrore;
    int (*p_arr)[3] = &arr; //指向含有3個int元素的數(shù)組的指針 
    ptr = *pointerFunc;
    const char *msg = "Hello Pointer";
    
    printf(" p_num: %p, *p_num: %d, &num:%p\n", p_num, *p_num, &num);
    printf(" p_scrore: %p,  *p_scrore: %f, &num:%p\n", p_scrore, *p_scrore, &scrore);
    printf(" p_arr: %p, *p_arr:%p, &arr:%p\n", p_arr, *p_arr, &arr);
    printf(" pointerFunc 地址:%p, &pointerFunc: %p, *pointerFunc:%p\n", pointerFunc, &pointerFunc, *pointerFunc);
    printf(" msg: %p,  *msg:%c,&msg:%p\n", msg, *msg, &msg);
    int n = (*ptr)(10, 5);
    printf(" n = %d", n);

打涌辣浮:
p_num: 0x7ffeefbff534, *p_num: 97, &num:0x7ffeefbff534
p_scrore: 0x7ffeefbff530, *p_scrore: 10.000000, &scrore:0x7ffeefbff530
p_arr: 0x7ffeefbff54c, *p_arr:0x7ffeefbff54c, &arr:0x7ffeefbff54c
pointerFunc 地址:0x100001360, &pointerFunc: 0x100001360, *pointerFunc:0x100001360
msg: 0x100001c60, *msg:H,&msg:0x7ffeefbff508
n = 15

特殊的情況,他們并不一定需要使用&取地址:

  • 數(shù)組名的值就是這個數(shù)組的第一個元素的地址情臭。
  • 函數(shù)名的值就是這個函數(shù)的地址省撑。
  • 字符串字面值常量作為右值時,就是這個字符串對應(yīng)的字符數(shù)組的名稱,也就是這個字符串在內(nèi)存中的地址俯在。


解址

? 實質(zhì)是:從指針指向的內(nèi)存塊中取出這個內(nèi)存數(shù)據(jù)竟秫。
? 對一個指針解地址,就可以取到這個內(nèi)存數(shù)據(jù)跷乐,解地址 的寫法肥败,就是在指針的前面加一個*號。

    int age = 19;
    int*p_age = &age;
    *p_age  = 20;  //通過指針修改指向的內(nèi)存數(shù)據(jù)
    
    printf("age = %d\n",*p_age);   //通過指針讀取指向的內(nèi)存數(shù)據(jù)
    printf("age = %d\n",age);

打印:
age = 20
age = 20

解引用獲取值

注意:

  • 指針所保留的是內(nèi)存中一個地址拙吉,它并不保存指向的數(shù)據(jù)的值本身潮孽。因此揪荣,務(wù)必確保指針對應(yīng)一個已經(jīng)存在的變量或者一塊已經(jīng)分配了的內(nèi)存筷黔;

  • 星號有兩種用途:

    • 用于創(chuàng)建指針:int *myPointer = &myInt;
    • 對指針進行解引用:*myPointer = 3998;


指針之間的賦值

? 指針賦值和int變量賦值一樣,就是將地址的值拷貝給另外一個仗颈。指針之間的賦值是一種淺拷貝佛舱,是在多個編程單元之間共享內(nèi)存數(shù)據(jù)的高效的方法。

//通過指針 p1 挨决、 p3 都可以對內(nèi)存數(shù)據(jù) num 進行讀寫请祖,如果2個函數(shù)分別使用了p1 和p3,那么這2個函數(shù)就共享了數(shù)據(jù)num脖祈。
int num = 10;
int* p1  = & num;
int* p3 = p1;


printf("num值沒改變:num= %d, *p1= %d, *p3= %d", num, *p1, *p3);
    
num = 100;
printf("\n\nnum值改變后:num= %d, *p1= %d, *p3= %d", num, *p1, *p3);

輸出值:
num值沒改變:num= 10, *p1= 10, *p3= 10

num值改變后:num= 100, *p1= 100, *p3= 100

淺拷貝示意圖


指針的屬性

int main(void)
{
    int num = 97;
    int *p1  = #
    char* p2 = (char*)(&num);

    printf("%d\n",*p1);    //輸出  97
    putchar(*p2);          //輸出  a
    return 0;
}

指針的值:很好理解肆捕,如上面的num 變量 ,其地址的值就是0028FF40 盖高,因此 p1的值就是0028FF40慎陵。數(shù)據(jù)的地址用于在內(nèi)存中定位和標識這個數(shù)據(jù),因為任何2個內(nèi)存不重疊的不同數(shù)據(jù)的地址都是不同的喻奥。
指針的類型:指針的類型決定了這個指針指向的內(nèi)存的字節(jié)數(shù)并如何解釋這些字節(jié)信息席纽。一般指針變量的類型要和它指向的數(shù)據(jù)的類型匹配。

由于num的地址是0028FF40撞蚕,因此p1 和 p2的值都是0028FF40
*p1 : 將從地址0028FF40 開始解析润梯,因為p1是int類型指針,int占4字節(jié)甥厦,因此向后連續(xù)取4個字節(jié)纺铭,并將這4個字節(jié)的二進制數(shù)據(jù)解析為一個整數(shù) 97。
*p2 : 將從地址0028FF40 開始解析刀疙,因為p2是char類型指針彤蔽,char占1字節(jié),因此向后連續(xù)取1個字節(jié)庙洼,并將這1個字節(jié)的二進制數(shù)據(jù)解析為一個字符顿痪,即'a'。

同樣的地址油够,因為指針的類型不同蚁袭,對它指向的內(nèi)存的解釋就不同,得到的就是不同的數(shù)據(jù)石咬。





下一篇:指針(II)


參考資料:
C 語言指針詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末揩悄,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鬼悠,更是在濱河造成了極大的恐慌删性,老刑警劉巖亏娜,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蹬挺,居然都是意外死亡维贺,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門巴帮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溯泣,“玉大人,你說我怎么就攤上這事榕茧±伲” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵用押,是天一觀的道長肢簿。 經(jīng)常有香客問我,道長蜻拨,這世上最難降的妖魔是什么池充? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮官觅,結(jié)果婚禮上纵菌,老公的妹妹穿的比我還像新娘。我一直安慰自己休涤,他們只是感情好咱圆,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著功氨,像睡著了一般序苏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捷凄,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天忱详,我揣著相機與錄音,去河邊找鬼跺涤。 笑死匈睁,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的桶错。 我是一名探鬼主播航唆,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼院刁!你這毒婦竟也來了糯钙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎任岸,沒想到半個月后再榄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡享潜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年困鸥,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片米碰。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡窝革,死狀恐怖购城,靈堂內(nèi)的尸體忽然破棺而出吕座,到底是詐尸還是另有隱情,我是刑警寧澤瘪板,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布吴趴,位于F島的核電站,受9級特大地震影響侮攀,放射性物質(zhì)發(fā)生泄漏锣枝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一兰英、第九天 我趴在偏房一處隱蔽的房頂上張望撇叁。 院中可真熱鬧,春花似錦畦贸、人聲如沸陨闹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趋厉。三九已至,卻和暖如春胶坠,著一層夾襖步出監(jiān)牢的瞬間君账,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工沈善, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留乡数,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓闻牡,卻偏偏與公主長得像净赴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子澈侠,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容