[关闭]
@lishuhuakai 2015-05-17T16:23:26.000000Z 字数 4918 阅读 2461

二级指针的问题

c


如何在被调用函数里面新建数据,然后将数据的地址传递出来呢?

一般来说有两种方法,第一种思路是将数据的首地址以返回值的方法返回,第一种方法如下:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. char *newBuf(int num)
  5. {
  6. char *p;
  7. p = (char *)malloc(num * sizeof(char));
  8. return p;
  9. }
  10. void deleteBuf(char *p)
  11. {
  12. if (p != NULL)
  13. {
  14. free(p);
  15. p = NULL; //垃圾代码,执行了也不会有任何用处
  16. }
  17. }
  18. void main()
  19. {
  20. char *p = NULL;
  21. p = newBuf(100);
  22. printf("p :%x\n", p);
  23. deleteBuf(p);
  24. system("pause");
  25. }

第一种方法虽然可用,但是实际上并不好,要是需要新建多个数据然后返回呢?显然返回值不能有多个。第二种方法是利用指针的间接赋值的性质,下面是第二种方法:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. //间接赋值是指针存在的最大意义
  5. void modifyP(int *p)
  6. {//用一级指针去修改零级指针(通常是实参)的值
  7. *p = 20;
  8. }
  9. void main_01()
  10. {
  11. int a = 0; //定义一个实参变量
  12. int *p = NULL; //定义一个形参
  13. p = &a;
  14. *p = 10; //间接修改a的值*(a的地址)去间接修改a的值
  15. modifyP(&a);
  16. printf("%d\n", a);
  17. system("pause");
  18. }
  19. void getNewBuf(char **p)
  20. {//用二级指针(通常是形参)去修改一级指针(通常是实参)的值
  21. //相当于在被调用函数里面分配内存,把结果给传出来了
  22. //这就是指针做函数参数的精华
  23. *p = 0x33;//间接修改p的值*(p的地址)去间接修改p的值
  24. char *tmp = NULL;
  25. tmp = (char *)malloc(100);
  26. *p = tmp;//可以甩出被调用函数的结果
  27. }
  28. void deleteBuf(char **p)
  29. {
  30. if (p == NULL)
  31. {
  32. return;
  33. }
  34. if (*p != NULL)
  35. {
  36. free(*p);
  37. }
  38. *p = NULL; //这一句话就不是垃圾代码了
  39. }
  40. void main()
  41. {
  42. char *p = NULL;
  43. char **p2 = NULL;
  44. p = 0x11; //直接修改p的值
  45. printf("p : %x \n", p);
  46. p2 = &p;
  47. //*p2 = 0x99;//间接修改p的值*(p的地址)去间接修改p的值
  48. getNewBuf(&p);
  49. printf("p : %x \n", p);
  50. deleteBuf(&p);
  51. system("pause");
  52. }

从上面可以看到,第二种方法远远优于第一种,且第二种方法更加说明了使用者对于指针的理解。

野指针问题

什么是野指针?

指针指向的空间释放之后,指针的值并未有改变,这个指针就成为了野指针

如何避免野指针?

1. 指针定义时初始化NULL;

  1. char **myarray = NULL;

2. 释放的时候需要判断是否为NULL;

  1. if (myarray != NULL)
  2. {
  3. free(myarray);
  4. }

3. 释放完毕之后置成NULL

  1. if (myarray != NULL)
  2. {
  3. free(myarray);
  4. myarray = NULL;
  5. }

二维指针做函数参数

如何使用二级数组做函数参数?
在解决这个问题之前,我们必须先了解这么一些知识:
1. 数组做函数参数,会退化为指针。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. void printfArray(int a[10])
  5. {
  6. printf("----------printfArray----------\n");
  7. printf("a:数组的地址:%x\n", a);
  8. for (int i = 0; i < 10; ++i)
  9. {
  10. printf("%d ", a[i]);
  11. }
  12. printf("\n");
  13. }
  14. void main()
  15. {
  16. int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  17. printf("----------main----------\n");
  18. printf("a:数组的地址:%x\n", a);
  19. for (int i = 0; i < 10; ++i)
  20. {
  21. printf("%d ", a[i]);
  22. }
  23. printf("\n");
  24. printfArray(a);
  25. system("pause");
  26. }

运行上面的代码,我们会发现,在函数printfArray中a的地址和我们传递的a数组的地址是一致的,函数并没有一个一个地拷贝数组的值。因此, 数组做函数参数,会退化为指针。
既然这样,我们可以总结出,下面三种输出函数参数的写法几乎没有区别
第一种:

  1. void printArray(int *a, int size)
  2. {
  3. for (int i = 0; i < size; ++i)
  4. {
  5. printf("%d ", a[i]);
  6. }
  7. printf("\n");
  8. }

第二种:

  1. void printArray(int a[], int size)
  2. {
  3. for (int i = 0; i < size; ++i)
  4. {
  5. printf("%d ", a[i]);
  6. }
  7. printf("\n");
  8. }

第三种:

  1. void printArray(int a[10])
  2. {
  3. for (int i = 0; i < 10; ++i)
  4. {
  5. printf("%d ", a[i]);
  6. }
  7. printf("\n");
  8. }

2. 指针是一种数据类型,是指它指向的内存空间的数据类型
- 指针步长(p++),根据所致内存空间的数据类型来确定
- 指针的步长,根据所指内存空间类型来定。

3. 二维数组也是线性排列的。
看下面的代码:

  1. void printArray(int *a, int size)
  2. {
  3. int i = 0;
  4. printf("printArray: %d\n", sizeof(a));
  5. for(i = 0; i < size; i++)
  6. {
  7. printf("%d\n", a[i]);
  8. }
  9. }
  10. int main()
  11. {
  12. int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
  13. printArray(p, 6);
  14. getchar();
  15. return 0;
  16. }

我们可以看到,函数轻易地输出了二维数组里面的值,也证明了二维数组里的值是线性排列的

利用sizeof可以判断出指针的步长,指针的移动正是以步长为单位。下面的代码值得细细琢磨。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. void printArray(int *a, int size)
  5. {//说明了二维数组里的元素线性排列
  6. //从二维数组指针转换为一维数组指针丢失了步长信息
  7. int i = 0;
  8. printf("-----------printArray函数中----------\n");
  9. printf("sizeof(&a):%d \n", sizeof(&a));
  10. printf("sizeof(a): %d\n", sizeof(a));
  11. printf("sizeof(*a): %d\n", sizeof(*a));
  12. printf("a数组的地址: %d\n", a);
  13. for (i = 0; i < size; i++)
  14. {
  15. printf("%d ", a[i]);
  16. }
  17. printf("\n");
  18. }
  19. //其实下面三种函数形参的写法在编译器看来都一样,不信的话可以运行程序
  20. void printArray01(int (*a)[3], int size)
  21. {//推荐的写法
  22. printf("-----------printArray01函数中----------\n");
  23. printf("sizeof(&a):%d \n", sizeof(&a));
  24. printf("sizeof(a): %d\n", sizeof(a));
  25. printf("sizeof(*a): %d\n", sizeof(*a));
  26. printf("a数组的地址: %d\n", a);
  27. //printf("*a: %d\n", *a);
  28. //printf("*a + 1: %d\n", *a + 1);
  29. for (int i = 0; i < size; ++i)
  30. {
  31. for (int j = 0; j < 3; ++j)
  32. {
  33. printf("%d ", (*(a + i))[j]);
  34. //printf("%x\n", (a + i) + j);
  35. }
  36. }
  37. printf("\n");
  38. }
  39. void printArray02(int a[][3], int size)
  40. {/丢失了一部分的信息,3的存在标志了步长,即*a的步长为12
  41. printf("-----------printArray02函数中----------\n");
  42. printf("sizeof(&a):%d \n", sizeof(&a));
  43. printf("sizeof(a): %d\n", sizeof(a));
  44. printf("sizeof(*a): %d\n", sizeof(*a));
  45. printf("a数组的地址: %d\n", a);
  46. for (int i = 0; i < size; i++)
  47. {
  48. for (int j = 0; j < 3; ++j)
  49. printf("%d ", a[i][j]);
  50. }
  51. printf("\n");
  52. }
  53. void printArray03(int a[2][3])
  54. {//虽然能用,但却是最低级的用法,即使这样写,a的步长也只不过是4
  55. //换句话说,2没起到什么作用
  56. printf("-----------printArray03函数中----------\n");
  57. printf("sizeof(&a):%d \n", sizeof(&a));
  58. printf("sizeof(a): %d\n", sizeof(a));
  59. printf("sizeof(*a): %d\n", sizeof(*a));
  60. printf("a数组的地址: %d\n", a);
  61. for (int i = 0; i < 2; i++)
  62. {
  63. for (int j = 0; j < 3; ++j)
  64. printf("%d ", a[i][j]);
  65. }
  66. printf("\n");
  67. }
  68. int main()
  69. {
  70. int a[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };
  71. char cc[10][30];
  72. printf("-----------main函数中----------\n");
  73. printf("sizeof(&a):%d \n", sizeof(&a));
  74. printf("sizeof(a):%d \n", sizeof(a));
  75. printf("sizeof(*a):%d \n", sizeof(*a));
  76. printf("sizeof(&cc):%d \n", sizeof(&cc));
  77. printf("sizeof(cc):%d \n", sizeof(cc));
  78. printf("sizeof(*cc):%d \n", sizeof(*cc));
  79. int *p = (int *)a;
  80. printArray(p, 6); //即使是用二级指针,也可以输出正确的结果
  81. printArray01(a, 2);
  82. printArray02(a, 2);
  83. printArray03(a);
  84. getchar();
  85. return 0;
  86. }

结论倒是挺简单的:

1.C语言中只会以机械式的值拷贝的方式传递参数(实参把值传给形参)
原因有两个:
* 高效
* C语言处理a[n]的时候,它没有办法知道n是几,它只知道&a[0]是多少,因此把它的值作为参数传递进去了,虽然c语言可以做到直接int fun(char a[20]),然后函数能得到20这个数字,但是,C确实没有这么做。当然,这也提高了效率。

2.二维数组参数同样存在退化的问题。
二维数组可以看做是一维数组-->二维数组中的每个元素是一维数组-->二维数组参数中第一维的参数可以省略.
void f(int a[5]); ---> void f(int a[]); ---> void f(int* a);
void g(int a[3][3]); ---> void g(int a[][3]);---> void g(int (*a)[3]);
3. 等价关系。

数组参数 等效的指针参数
一维数组 char a[30] 指针 char*
指针数组 char *a[30] 指针的指针 char **a
二维数组 char a[10][30] 数组的指针 char(*a)[30]
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注