帶你重新認識指針(下)


目錄

    真的猛士,敢於直面慘淡的人生,敢於正視淋漓的鮮血。這是怎樣的哀痛者和幸福者?然而造化又常常為庸人設計,以時間的流駛,來洗滌舊跡,僅使留下淡紅的血色和微漠的悲哀。在這淡紅的血色和微漠的悲哀中,又給人暫得偷生,維持着這似人非人的世界。我不知道這樣的世界何時是一個盡頭!我們還在這樣的世上活着;我也早覺得有寫一點東西的必要了。 ——魯迅

    本文已經收錄至我的GitHub,歡迎大家踴躍star 和 issues。

    https://github.com/midou-tech/articles

    點關注,不迷路!!!

    看完我上一篇指針的講解之后很多同學反饋很不錯,有網友給私信說之前在大學里面一直搞不懂指針的問題,說到指針都是雲里霧里,老師講的也是很難聽懂 ,點擊即可進入 指針(上)。也有很多網友表示非常期待指針下的文章,所以我就馬不停蹄的繼續寫,下 主要講解指針的特性以及指針安全問題。

    指針的特性

    指針和常量

     先說下什么是常量,常量就是不可變的量,一旦定義該常量,其值在整個程序生命周期都是不可變的,常量存放在虛擬地址空間的常量區。

     在C語言里面有兩種定義常量的方法。

    • 使用const關鍵字 ,const 定義的是變量不是常量,只是這個變量的值不允許改變是常變量,帶有類型。編譯運行的時候起作用存在類型檢查。

    • 使用#define預處理器, define 定義的是不帶類型的常數,只進行簡單的字符替換。在預編譯的時候起作用,不存在類型檢查。

     其實很多時候我們錯誤的以為常量就是const 修飾的變量,這個說法其實是有瑕疵的。

    指針常量

     很多網友在學習指針和指針的特性等問題上總是會繞進去,其實不要繞進去最重要的一點是 要把握住核心本質

     本質上是一個常量,指針用來說明常量的類型,表示該常量是一個指針類型的常量。在指針常量中,指針自身的值是一個常量,不可改變,始終指向同一個地址。在定義的同時必須初始化

    1int num = 5;
    2int *const p = #  // p為一個常量,擁有常量的屬性。
    3*p = 70;
    4int snum = 100;
    5int *sp = &snum;
    6p = sp;

     聰明的你一定看出上面代碼有個地方會報錯,是的 p 被我們聲明為一個指針常量,此時指針p具有了常量的屬性,其不能在改變指向,但是其指向的值是可以改變的。所以報錯的代碼是p = sp這句。

    常量指針

     常量指針本質上是一個指針,常量表示指針指向的內容,說明該指針指向一個“常量”。在常量指針中,指針指向的內容是不可改變的,指針看起來好像指向了一個常量。

    1int num = 5;
    2int const *p = #   //常量指針
    3const int *sp = #  //常量指針
    4*p = 20
    5int snum = 100;
    6p = &snum;   //改變指向
    7sp = &snum;

     是不是又發現上面的代碼有一處報錯,你太聰明了,基本搞懂了常量和指針的本質。指針p和sp只是申明格式不同,本質完全一樣。p被聲明為一個指針,指向一個常量。換句話說就是一個常量的地址存放在指針p中。此時報錯的就是*p = 20,因為常量是不可變的。

     到這里你基本掌握了常量和指針的關系,其實還是很簡單的,也沒大家在學校學的那么繞。接下來給大家在介紹一個進階的關系。

    常量指針常量

     本質上是一個常量,該常量被一個常量指針指向。也就是說一個常量指針里面放置一個常量的地址,千萬不要多看一眼這句話,你會被繞進去。

    1const int num = 5;   //一個不可變的常量
    2const int * const p = #  //一個存放常量地址的常量指針

     千萬不要繞進去了,其實認真理解了上面的指針常量和常量指針的問題,這個問題看起來會簡單很多,就是一個常量,和一個常量指針。num是一個不可改變的常量,p只一個指針,該指針也是不可改變指向的。

     指針和常量這個問題在面試中會被問到,好好理解下,同時有助於你更好的理解指針。

    指針和函數

    函數指針

    什么是函數指針

     如果在程序中定義了一個函數,那么在編譯時系統就會為這個函數代碼分配一段存儲空間,這段存儲空間的首地址稱為這個函數的地址。而且函數名表示的就是這個地址。既然是地址我們就可以定義一個指針變量來存放,這個指針變量就叫作函數指針變量,簡稱函數指針。

     函數指針的定義和普通指針不太一樣。函數返回值類型 (* 指針變量名) (函數參數列表);

    1bool(*p)(charint); 

     還是很簡單的,這就知道怎么定義一個函數指針變量了,當然也有很復雜的函數指針變量,面試的時候面試官可能會問一些變態的面試題,比如:

    1int (*(void (*)())0)();
    2void (*signal(int , void(*)(int)))(int

     不過還是那句話,要把握核心本質,函數指針的核心本質是:函數返回值類型 (* 指針變量名) (函數參數列表);

    函數指針使用

     很多人會說,搞這么難干嘛,平時有使用么?哈哈,還真的經常用到,尤其是標准庫中用的那叫一個多,比如sort中的比較函數就是一個函數指針。

    指針作為函數參數

     用指針變量作函數參數可以將函數外部的地址傳遞到函數內部,使得在函數內部可以操作函數外部的數據,並且這些數據不會隨着函數的結束而被銷毀。

     這不得不使我想起一個經典案例,大學老師一定會講的,而且當時也是很多同學一直半解的。

     1void swap(int a,int b){
    2  int tmp = a;
    3  a = b;
    4  b = tmp;
    5}
    6int main(){
    7  int x = 10;
    8  int x = 20;
    9  printf("swap before:%d,%d",x,y);
    10  swap(a,b);
    11  printf("swap after:%d,%d",x,y);
    12  return 0;
    13}

     是不是歷歷在目。。。

     這個簡單的問題,要搞明白可以學到好幾個知識點。第一個,函數棧問題;第二個,函數的參數傳遞是值傳遞還是地址傳遞;第三個,指針作為函數參數。不過我這里就不講前面兩個了,相信大家能來看指針問題說明前面基礎知識都差不多了,要是你真的不會的話,你可以找龍叔我,我一定把你整明白,微信搜索公眾號 龍躍十二 即可找到龍叔微信,同時有機會加入龍叔技術交流群,千萬別錯過喔。

    求點贊👍 求關注❤️

     交換兩個數值問題,使用指針傳遞可以很輕松實現交換,原理如圖。

     1void swap(int *pa,int *pb){
    2  int tmp = *pa;
    3  *pa = *pb;
    4  *pb = tmp;
    5}
    6int main(){
    7  int x = 10;
    8  int x = 20;
    9  printf("swap before:%d,%d",x,y);
    10  swap(&a,&b);
    11  printf("swap after:%d,%d",x,y);
    12  return 0;
    13}

     指針作為函數參數並不簡單是這點用處,更大的用處在於傳遞復雜的結構體或者大容量的數組,減少數據拷貝產生的零時變量。舉個例子

     1struct Person{
    2  string name;
    3  string addr;
    4  string number;
    5  int age;
    6  string hobby;
    7  ...
    8};
    9//方案一
    10int Fun(struct Person person){
    11  //TODO
    12}
    13//方案二
    14int Fun(struct Person *person){
    15  //TODO
    16}

     此時足以見得用指針的好處,可以減少零時變量的產生。有一個問題必須說一下 指針作為函數參數依然是值傳遞。

    指針作為函數返回值

     函數的返回值是一個指針(地址),我們將這樣的函數稱為指針函數。舉個例子

    1char *strlong(char *str1, char *str2){
    2    if(strlen(str1) >= strlen(str2)){
    3        return str1;
    4    }else{
    5        return str2;
    6    }
    7}

     用指針作為函數返回值時需要注意的一點是,函數運行結束后會銷毀在它內部定義的所有局部數據,包括局部變量、局部數組和形式參數,函數返回的指針請盡量不要指向這些數據,C語言沒有任何機制來保證這些數據會一直有效,它們在后續使用過程中可能會引發運行時錯誤。總結一句話 不要讓返回的指針指向一個局部性的對象

    指針和C語言的內存管理

     C語言的動態內存分配使用的是malloc系列函數,看下庫函數的聲明。

    1void    *malloc(size_t __size) __result_use_check __alloc_size(1);
    2void    *calloc(size_t __count, size_t __size) __result_use_check __alloc_size(1,2);

    malloc系列函數返回值都是一個指針,而且是void*類型的,所以用malloc系列函數分配的內存必須用一個指針指向該內存,而且指針類型自己一定要強制轉換。分配的內存是一個內存塊,返回的是內存的首地址,指針存儲的也是首地址。這一點內容較為簡單,主要還是把握住指針的核心本質。

    求點贊👍 求關注❤️

    指針安全問題

     說到這里指針的問題基本告一段落了,當然還有一個最重要的問題,那就是指針的安全問題。不可忽略,必須學懂,否則就不要把指針用在工程代碼里面。

    數組越界訪問

    1int main(){
    2    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    3    int *p = arr;
    4    printf("value:%d,%d,%d,%d\n",p[0],p[-2],p[16],p[100]);
    5}

     把數組轉為指針訪問的時候很容易出現這樣的錯誤,但是你要是拿着數組下表訪問,這段代碼編譯會報warning。這個錯誤也是天知道結果會是怎樣,反正程序可以正常跑着,結果就是不多。

    不要隨便強轉指針的類型

     先看段簡單的代碼

    1int main(){
    2  char c = 'a';
    3  int *p = (int *)&c;
    4  *p = 1314;
    5  printf("value:%d\n",*p);
    6}

     這段代碼有多恐怖,我真的難以想象他的恐怖程度。

    如果你在工程里面這樣寫了這樣的代碼,天知道會出現什么樣的結果。p指針指向了一個不屬於自己的空間地址,那片地址有可能是別的程序或者其他代碼正在使用,你就這樣改了別人的地址上的內容,天知道會出現什么。。。

    重點來了 不要隨便強制轉換指針的數據類型,一定要清楚轉類型之后會不會越界訪問到其他內容。

    迷途指針

    1int *p = (int *) malloc(sizeof(int));
    2*p = 100;
    3free(p);
    4*p = 200;

     從內存中刪除一個對象或者返回時刪除棧幀后,並不會改變相關的指針的值。該指針仍然指向原來的內存地址,即使引用已經刪除,現在也可能已經被其它進程使用了。

    解決迷途指針的方法就是,我們釋放指針對應的內存之后切記一定要把指針置為NULL,置空之后對指針使用會造成 segmentation fault error ,程序會崩潰。

    解引用空指針

    1int *p = (int *) malloc(sizeof(int)*1000);
    2*p = 100;
    3free(p);
    4p = NULL;

     這段代碼看起來沒啥問題,仔細看看也沒啥問題。但是這段代碼不知道會在線上崩潰到那一天,malloc返回的地址不是一定保證正確的,萬一內存分配不出來或者分配失敗了,你的程序瞬間就崩掉了。

    總結

     指針有很多好處,同時也有很多壞處。怎樣去平衡好處與壞處,我們一定要規范我們使用指針的姿勢,防止因為我們使用姿勢的問題導致線上崩潰。把握指針的本質,了解內存的原理,掌握這兩個重要的點能減少你平時在工作中的很多錯誤。

    求點贊👍 求關注❤️

    「轉發」是明目張膽的喜歡,「在看」是偷偷摸摸的愛。

    如果有人想發文章,我這里提供有償征文(具體細則微信聯系),歡迎投稿或推薦你的項目。提供以下幾種投稿方式:

    • 去我的github提交 issue: https://github.com/midou-tech/articles

    • 發送到郵箱: 2507367760@qq.com 或者 longyueshier@163.com 或者 longyueshier@gmail.com

    • 微信發送: 掃描下面二維碼,公眾號里面有作者微信號。

    精選文章都同步在公眾號里面,公眾號看起會更方便,隨時隨地想看就看。微信搜索 龍躍十二 或者掃碼即可訂閱。


    免責聲明!

    本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



     
    粵ICP備18138465號   © 2018-2025 CODEPRJ.COM