C提高(5)/二級指針

  • 基本概念
    如果一個指針變量存放的又是另一個指針變量的地址,則稱這個指針變量爲指向指針的指針變量。也稱爲“二級指針”。
    二級指針

    
    #define  _CRT_SECURE_NO_WARNINGS
    
    
    #include <stdio.h>
    
    
    #include <string.h>
    
    
    #include <stdlib.h>
    
    
    int main(void)
    {
        int a = 10;
        int *p = &a;
        int **pp = &p;
    
        printf("&a : %p\n", &a);
        printf("p : %p\n", p);
        printf("&p: %p\n", &p);
        printf("pp: %p\n", pp);
        printf("&pp: %p\n", &pp);
        printf("*p: %d\n", *p);
        printf("*pp:%p\n", *pp);
        printf("**pp:%d\n", **pp);
    
        return 0;
    }
  • 用處
    我們知道,指針往往作爲函數參數才能發揮最大作用。而指針作爲函數參數具有輸入輸出特性。
    二級指針也是指針,同樣具有上述屬性。

    • 二級指針作爲輸出參數
      最典型的例子就是跨函數開闢堆內存空間。(分配,初始化,賦值,釋放)

      
      #define  _CRT_SECURE_NO_WARNINGS 
      
      
      #include <stdio.h>
      
      
      #include <string.h>
      
      
      #include <stdlib.h>
      
      
      
      //二級指針作爲輸出參數
      //在函數內部 在堆上開闢空間  傳出去。
      int get_mem(/*out */char **mem1, int *mem_len1, char **mem2, int *mem_len2)
      {
          char *temp_p1 = NULL;
          char *temp_p2 = NULL;
          int len1 = 0;
          int len2 = 0;
      
          if (mem1 == NULL || mem2 == NULL || mem_len1 == NULL || mem_len2 == NULL) {
              fprintf(stderr, " (mem1 == NULL || mem2 == NULL || mem_len1 == NULL || mem_len2 == NULL) \n");
              return -1;
          }
      
          temp_p1 = (char *)malloc(4096);
          if (temp_p1 == NULL) {
              return -1;
          }
          memset(temp_p1, 0, 4096);//在堆上開闢4096個BYTE的內存空間,然後判斷指針是否爲空,最後初始化
      
          strcpy(temp_p1, "12345678");
          len1 = strlen(temp_p1);
      
          temp_p2 = (char*)malloc(4096);
          if (temp_p2 == NULL) {
              return -1;
          }
          memset(temp_p2, 0, 4096);
      
          strcpy(temp_p2, "abcdefg");
          len2 = strlen(temp_p2);
      
      
          //以上開闢完空間
      
          *mem1 = temp_p1;
          *mem2 = temp_p2;
          *mem_len1 = len1;
          *mem_len2 = len2;
      
          return 0;
      }
      
      void free_mem(char **mem1, char **mem2)
      {
          char *temp_mem1 = *mem1;
          char *temp_mem2 = *mem2;
      
          if (mem1 != NULL) {
              free(temp_mem1);
          }
          if (mem2 != NULL) {
              free(temp_mem2);
          }
      
          *mem1 = NULL;
          *mem2 = NULL;
      }
      
      int main(void)
      {
          char *buf1 = NULL;
          char *buf2 = NULL;
          int len1 = 0;
          int len2 = 0;
      
      
          if (get_mem(&buf1, &len1, &buf2, &len2) < 0) {
              return -1;
          }
      
          printf("buf1: %s, buf2:%s\n", buf1, buf2);
      
      
          free_mem(&buf1, &buf2);
      
          return 0;
      }
    • 二級指針做輸入參數
      二級指針做輸出參數,本質上操作的還是一級指針,只是披着二級指針的外衣。
      而二級指針做輸入參數則是真正的對二級指針進行操作。

      • 二級指針做輸入參數的三個模型

        • char * Array[num]
        • char Array[][num]或者 char (*Array)[num]
        • char ** Array 或者 char Array[num1][num2]

        首先我們來看一看這三種輸入參數的區別。

        對char * Array[num]。我們可以理解爲char * Array和[num]的組合。對前者我們再熟悉不過了,是一個字符指針,對後者我們可以理解爲字符指針的個數。那麼我們就可以這樣理解char * Array[num]。定義了一個字符指針的數組,簡稱爲指針數組。數組中每一個元素存放的都是各指針指向字符串首元素的地址。所以每個元素佔4個字節,指針的步長爲4。
        任務:畫出下列程序的內存四區圖

        
        #define  _CRT_SECURE_NO_WARNINGS 
        
        
        #include <stdio.h>
        
        
        #include <string.h>
        
        
        #include <stdlib.h>
        
        
        //等價於int print_array(char*  array[], int len)
        int print_array(char*  *array, int len)
        {
            int i = 0;
            for (i = 0; i < len; i++) {
                //操作字符串的兩種方法printf("%s\n", array[i]);
                printf("%s\n", *(array + i));
            }
            return 0;
        }
        
        int sort_array(char *array[], int len)
        {
            int i = 0;
            int j = 0;
            char *temp = NULL;
        
            for (i = 0; i < len; i++) {
                for (j = i; j < len; j++) {
                    if (strcmp(array[i], array[j])  > 0) {
                        temp = array[i];
                        array[i] = array[j];
                        array[j] = temp;
                    }
                }
            }
        
            return 0;
        }
        
        int main(void)
        {
            char    * myArray[] = { "aaaaaa", "ccccc", "bbbbbb", "111111" };
            int len = 0;
        
        
            len = sizeof(myArray) / sizeof(myArray[0]);  // 16 / 4 = 4個
        
            printf("排序之前\n");
            print_array(myArray, len);
        
            //排序
            sort_array(myArray, len);
        
            printf("排序之後\n");
            print_array(myArray, len);
        
        
        
            return 0;
        }

        對char Array[][num]或者 char (*Array)[num]來說,二級指針的傳參就又有不同了。
        我們在上一程序中,訪問指針數組時使用的是array[i],但是這是怎麼實現的呢?我們知道指針變量在內存中佔4個字節。指針數組中存放的元素是指向字符的指針。所以指向指針數組的指針的步長是4個字節。在上述程序中myArray+1實際上移動了4個字節。
        而對於數組myArray[5][6]來說,myArray+1移動的是4個字節嗎?答案很明顯不是。這是一個5行6列的數組,也就是說一行有6個元素,如果數組是char 類型,那麼一行就佔有6個字節。我們知道,字符數組中一行存儲一個字符串,那麼我如果從一個字符串移動到下一個字符串,按照以上的設定,就會移動6個字節。
        所以,對於二維數組的參數傳遞,不能使用上述程序的方法。下述程序解決了這一問題:

        
        #define  _CRT_SECURE_NO_WARNINGS 
        
        
        #include <stdio.h>
        
        
        #include <string.h>
        
        
        #include <stdlib.h>
        
        
        
        //int print_array(char *  *array, int num)
        //int print_array(char array[5][6],  int num)
        //int print_array(char[6]* array, int num) 這三種指針傳遞方法都是錯誤的
        int print_array(char array[][6], int num)      //int print_array(char (*array)[6], int num)這兩種方法是等價的
        {
            int i = 0;
        
            for (i = 0; i < num; i++) {
                //printf("%s\n", array[i]); //my_array[0]; my_array[0]--->"aaa"  printf(%s, myArray[0]);
                printf("%s\n", *(array + i)); /// ===>array 應該是一個指向 char[6]的指針
            }
        
            return 0;
        }
        
        
        int sort_array(char array[][6], int num)
        {
            char buf[6] = { 0 };
            int i = 0;
            int j = 0;
        
            for (i = 0; i < num; i++) {
                for (j = i; j< num; j++) {
                    if (strcmp(array[i], array[j]) > 0) {
                        strcpy(buf, array[i]);
                        strcpy(array[i], array[j]);
                        strcpy(array[j], buf);
                    }
                }
            }
        
            return 0;
        }
        
        int main(void)
        {
            char my_array[5][6] = { "aaa", "ccc", "bbb", "111" };
            int num = 0;
            int i = 0;
        
        
            for (i = 0; i < 5; i++) {
                if (strlen(my_array[i]) != 0) {
                    num++;
                }
            }
            printf("num : %d\n", num);
        
            printf("排序之前\n");
            print_array(my_array, num);
        
            sort_array(my_array, num);
        
            printf("排序之後\n");
            print_array(my_array, num);
        
            return 0;
        }

        我們再來思考一下以上兩種二級指針做輸入參數傳遞指針的不同。第一種二級指針,我們稱之爲指針數組。第二種二級指針,我們稱之爲數組指針。
        指針數組存放的元素是指針,即地址,用4個字節就能表示所有的地址。所以對char Myarray[5][6],char **Myarray, char * Myarray[]來說,他們在編譯器中都會被翻譯成char **Myarray,他們的步長均爲4個Byte。指針數組的數據存放在全局區中的常量區。Myarray[i]的值可以改變(因爲其爲指針變量)
        而數組指針中的元素卻是字符,不是地址。字符的長度不固定,所以我們不能確定步長到底是多少。既然不能確定,那麼傳參數的時候就應該告訴編譯器,這裏的步長是多少。那問題來了,怎麼“告訴”編譯器呢?那就是char Myarray[][6]或者char (*Myarray)[6]。這個6就代表了列數就是一行有多少個元素。數組指針的值存放在棧區。Myarray[i]的值不能改變(因爲是數組名,內存塊的別名)。

        我們最後再來看看char **Array的應用。

        現在我們有一個需求,主函數中有一個二級指針p。現在需要跨函數在堆內存中開闢一個指針數組,指針數組中每個字符指針都指向一個長爲64字節的內存空間。我們應該怎麼做?

        該需求有兩種方法實現。第一種方法是採用三級指針做輸出參數,在子函數中動態分配內存空間。該方法的問題是使用了三級指針;一般來說程序使用二級指針基本可以完成各種需求了,沒有必要使用更高級的指針了。第二種方法使用函數的返回值來傳遞二級指針。下面使用第一種方法來實現,第二種方法讀者感興趣可以自己編寫相關代碼。


        #define  _CRT_SECURE_NO_WARNINGS 
        #include <stdio.h>
        #include <string.h>
        #include <stdlib.h>


        int get_mem(char ***array_p, int num)
        {
            char **array = NULL;
            int i = 0;

            array = (char**)malloc(sizeof(char*)* num);//在堆上開闢num個 char*指針
            if (array == NULL) {
                fprintf(stderr, "malloc char **array error\n");
                return -1;
            }
            memset(array, 0, sizeof(char*)*num);

            for (i = 0; i < num; i++) {
                array[i] = (char*)malloc(64);
                if (array[i] == NULL) {
                    fprintf(stderr, "maloc array[%d] error\n", i);
                    return -1;
                }
                memset(array[i], 0, 64);

                //賦值
                sprintf(array[i], "%d%d%d%d", 9 - i, 9 - i, 9 - i, 9 - i);
            }

            *array_p = array;

            return 0;
        }

        void free_mem(char ***array_p, int num)
        {
            int i = 0;

            if (array_p == NULL) {
                return;
            }
            char **array = *array_p;


            for (i = 0; i < num; i++) {
                if (array[i] != NULL) {
                    free(array[i]);
                    array[i] = NULL;
                }
            }

            free(array);

            *array_p = NULL;
        }

        int main(void)
        {
            char **my_array = NULL;
            int num = 4;

            get_mem(&my_array, num);
            printf("-----\n");
            free_mem(&my_array, num);

            if (my_array == NULL) {
                printf("kong\n");
            }



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