深入 char * ,char ** ,char a[ ] ,char *a[] 內核

 C語言中由於指針的靈活性,導致指針能代替數組使用,或者混合使用,這些導致了許多指針和數組的迷惑,因此,刻意再次深入探究了指針和數組這玩意兒,其他類型的數組比較簡單,容易混淆的是字符數組和字符指針這兩個。。。下面就開始剖析一下這兩位的恩怨情仇。。。

 1 數組的本質

   數組是多個元素的集合,在內存中分佈在地址相連的單元中,所以可以通過其下標訪問不同單元的元素。。

 2 指針。

   指針也是一種變量,只不過它的內存單元中保存的是一個標識其他位置的地址。。由於地址也是整數,在32位平臺下,指針默認爲32位。。

 3 指針的指向?

   指向的直接意思就是指針變量所保存的其他的地址單元中所存放的數據類型。

   int  * p ;//p 變量保存的地址所在內存單元中的數據類型爲整型

           float *q;// ........................................浮點型

           不論指向的數據類型爲那種,指針變量其本身永遠爲整型,因爲它保存的地址。

    4  字符數組。。。

        字面意思是數組,數組中的元素是字符。。確實,這就是它的本質意義。

         char  str[10]; 

         定義了一個有十個元素的數組,元素類型爲字符。

         C語言中定義一個變量時可以初始化。

         char  str[10] = {"hello world"};

         當編譯器遇到這句時,會把str數組中從第一個元素把hello world\0 逐個填入。。

         由於C語言中沒有真正的字符串類型,可以通過字符數組表示字符串,因爲它的元素地址是連續的,這就足夠了。

         C語言中規定數組代表數組所在內存位置的首地址,也是 str[0]的地址,即str = &str[0];

         而printf("%s",str); 爲什麼用首地址就可以輸出字符串。。

          因爲還有一個關鍵,在C語言中字符串常量的本質表示其實是一個地址,這是許多初學者比較難理解的問題。。。

          舉例:

          char  *s ;

          s = "China";

          爲什麼可以把一個字符串賦給一個指針變量。。

          這不是類型不一致嗎???

          這就是上面提到的關鍵 。。

          C語言中編譯器會給字符串常量分配地址,如果 "China", 存儲在內存中的 0x3000 0x3001 0x3002 0x3003 0x3004 0x3005 .

          s = "China" ,意識是什麼,對了,地址。

          其實真正的意義是 s ="China" = 0x3000;

          看清楚了吧 ,你把China 看作是字符串,但是編譯器把它看作是地址 0x3000,即字符串常量的本質表現是代表它的第一個字符的地址。。。。。。。。。。

          s = 0x3000

          這樣寫似乎更符合直觀的意思。。。

          搞清楚這個問題。。

          那麼 %s ,它的原理其實也是通過字符串首地址輸出字符串,printf("%s ", s);   傳給它的其實是s所保存的字符串的地址。。。

          比如

        #include <stdio.h>

       int main()
       {

         char *s;
         s = "hello";
         printf("%p\n",s);
         return 0;
      }

                          

          

 

      可以看到 s = 0x00422020 ,這也是"China"的首地址

      所以,printf("%s",0x00422020);也是等效的。。

     

       字符數組:

       char  str[10] = "hello";

       前面已經說了,str = &str[0] , 也等於 "hello"的首地址。。

       所以printf("%s",str); 本質也是 printf("%s", 地址");

        C語言中操作字符串是通過它在內存中的存儲單元的首地址進行的,這是字符串的終極本質。。。

    5  char *  與 char  a[ ];

       char  *s;

       char  a[ ] ;

       前面說到 a代表字符串的首地址,而s 這個指針也保存字符串的地址(其實首地址),即第一個字符的地址,這個地址單元中的數據是一個字符,

   這也與 s 所指向的 char 一致。

      因此可以 s = a;

       但是不能 a = s;

       C語言中數組名可以複製給指針表示地址, 但是卻不能賦給給數組名,它是一個常量類型,所以不能修改。。

       當然也可以這樣:
        char  a [ ] = "hello";

        char *s =a;

        for(int i= 0; i < strlen(a) ; i++)

             printf("%c", s[i]);

         或  printf("%c",*s++);

        字符指針可以用 間接操作符 *取其內容,也可以用數組的下標形式 [ ],數組名也可以用 *操作,因爲它本身表示一個地址 。。

       比如 printf("%c",*a);  將會打印出 'h'

       char * 與 char a[ ] 的本質區別:

       當定義 char a[10 ]  時,編譯器會給數組分配十個單元,每個單元的數據類型爲字符。。

       而定義 char *s 時,  這是個指針變量,只佔四個字節,32位,用來保存一個地址。。

       sizeof(a) = 10 ;

       sizeof(s)  = ?

       當然是4了,編譯器分配4個字節32位的空間,這個空間中將要保存地址。。。

        printf("%p",s);

        這個表示 s 的單元中所保存的地址。。

        printf("%p",&s);

        這個表示變量本身所在內存單元地址。。。。,不要搞混了。。

        用一句話來概括,就是 char *s 只是一個保存字符串首地址的指針變量, char a[ ] 是許多連續的內存單元,單元中的元素爲char ,之所以用 char *能達到

 char a  [ ] 的效果,還是字符串的本質,地址,即給你一個字符串地址,便可以隨心所欲的操所他。。但是,char* 和 char a[ ] 的本質屬性是不一樣的。。

    

     6      char **  與char  * a[ ] ;

            先看 char  *a [ ] ;

            由於[ ] 的優先級高於* 所以a先和 [ ]結合,他還是一個數組,數組中的元素纔是char * ,前面講到char * 是一個變量,保存的地址。。

            所以 char *a[ ] = {"China","French","America","German"};

            同過這句可以看到, 數組中的元素是字符串,那麼sizeof(a) 是多少呢,有人會想到是五個單詞的佔內存中的全部字節數 6+7+8+7 = 28;

            但是其實sizeof(a) = 16;

            爲什麼,前面已經說到, 字符串常量的本質是地址,a 數組中的元素爲char * 指針,指針變量佔四個字節,那麼四個元素就是16個字節了

            看一下實例:

            #include <stdio.h>

   int main()
   {
    char *a [ ] = {"China","French","America","German"};
    printf("%p %p %p %p\n",a[0],a[1],a[2],a[3]);

    return 0;
   }

    

      可以看到數組中的四個元素保存了四個內存地址,這四個地址中就代表了四個字符串的首地址,而不是字符串本身。。。

      因此sizeof(a)當然是16了。。

      注意這四個地址是不連續的,它是編譯器爲"China","French","America","German" 分配的內存空間的地址, 所以,四個地址沒有關聯。

         #include <stdio.h>

   int main()
   {
   char *a [ ] = {"China","French","America","German"};

           printf("%p %p %p %p\n",a[0],a[1],a[2],a[3]); //數組元素中保存的地址
   printf("%p %p %p %p\n",&a[0],&a[1],&a[2],&a[3]);//數組元素單元本身的地址

   return 0;
   }

           

      可以看到 0012FF38 0012FF3C 0012FF40 0012FF44,這四個是元素單元所在的地址,每個地址相差四個字節,這是由於每個元素是一個指針變量佔四個字節。。。

       char **s;

       char **爲二級指針, s保存一級指針 char *的地址,關於二級指針就在這裏不詳細討論了 ,簡單的說一下二級指針的易錯點。  

       舉例:

       char *a [ ] = {"China","French","America","German"};

       char **s =   a;

       爲什麼能把 a賦給s,因爲數組名a代表數組元素內存單元的首地址,即 a = &a[0] = 0012FF38;

       而 0x12FF38即 a[0]中保存的又是 00422FB8 ,這個地址, 00422FB8爲字符串"China"的首地址。

       即 *s = 00422FB8 = "China";

         這樣便可以通過s 操作 a 中的數據

      printf("%s",*s);

      printf("%s",a[0]);

      printf("%s",*a);

      都是一樣的。。。

      但還是要注意,不能a = s,前面已經說到,a 是一個常量。。

      再看一個易錯的點:

      char **s = "hello world";

      這樣是錯誤的,

       因爲  s 的類型是 char **  而 "hello world "的類型是 char *

       雖然都是地址, 但是指向的類型不一樣,因此,不能這樣用。,從其本質來分析,"hello world",代表一個地址,比如0x003001,這個地址中的內容是 'h'

  ,爲 char 型,而 s 也保存一個地址 ,這個地址中的內容(*s) 是char * ,是一個指針類型, 所以兩者類型是不一樣的。 。。

  如果是這樣呢?
  char  **s;

       *s = "hello world";

       貌似是合理的,編譯也沒有問題,但是 printf("%s",*s),就會崩潰

       why??

      咱來慢慢推敲一下。。

       printf("%s",*s); 時,首先得有s 保存的地址,再在這個地址中找到 char *  的地址,即*s;

      舉例:

       s = 0x1000;

      在0x1000所在的內存單元中保存了"hello world"的地址 0x003001 , *s = 0x003001;

      這樣printf("%s",*s);

      這樣會先找到 0x1000,然後找到0x003001;

      如果直接 char  **s;

      *s = "hello world";

       s 變量中保存的是一個無效隨機不可用的地址, 誰也不知道它指向哪裏。。。。,*s 操作會崩潰。。

       所以用 char **s 時,要給它分配一個內存地址。

      char  **s ;

      s = (char **) malloc(sizeof(char**));

      *s =  "hello world";

      這樣 s 給分配了了一個可用的地址,比如 s = 0x412f;

      然後在 0x412f所在的內存中的位置,保存 "hello world"的值。。

    再如:

    #include  <stdio.h>

   void  buf( char **s)

    {

           *s = "message";

    }

    int main()

     {

        char *s ;

        buf(&s);

        printf("%s\n",s);

     }

    二級指針的簡單用法。。。。,說白了,二級指針保存的是一級指針的地址,它的類型是指針變量,而一級指針保存的是指向數據所在的內存單元的地址,雖然都是地址,但是類型是不一樣的。。。

發佈了33 篇原創文章 · 獲贊 2 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章