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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章