二维数组指针及二维动态数组的分配问题

在我以前的文章中都有讲过关于数组指针及指针数组的相关问题,但是讲得不够深入,我后来后了别人写的博客后觉得人家的确实写得好,
也学到了不少东西,对以前的问题有深的领悟了,于是准备结合这些博客和文章再稍微深入一点讲讲这些问题。这些指针的问题是C语言中的基础与关键
而且一旦出现这些问题,不太好找bug的来源,有时候不得不借助反汇编。
参考文章:
    http://c.biancheng.net/cpp/html/476.html       C语言指针数组和数组指针
    http://blog.csdn.net/morewindows/article/details/7664479 如何在C/C++中动态分配二维数组

现在我们知道了指针数组与数组指针的区别了嘛。
  1. int *p1[10]; …………………………………………(1)
这个就是指针数了啊,我们主要是看运算符的优先级,因为[   ] 的优先级比 * 的优先级要高,于是知p1首先和[10]结合-----p1[10]表示的是含有10个元素的数组,那么数
组中元素类型呢? 那就是 int *   也就是说 p1 是含有10元素为 int* 的数组名,注意p1是数组名而非什么指针。
  1. int (*p2)[10]; ……………………………………………(2)
而现在就不同了,我们的(*p2)说明p2是指针,而不再像是(1)中的类型名了,嗯好,这个 * 是专门用来修饰p2的。
同样【10】表示含有10个元素啦,而int表示这个数组里面的内容是int 型。则知道我们这个数组是没有数组名的。其实(2)式
组合起来说明p2是一个指针,但不是一个一般的指针,而是一个指向含有10个int型的数组的指针。
那有人问? 
    这个p2指针和(3)
  1. int* p ………………………………………………(3)
中的p(普通的int型指针)有区别吗?
当然是有区别的! 不然这不是脱了裤头放屁-----多此一举。搞得大家对(2)式不太理解。
分析代码:
  1. int a[4]={1,2,3,4};
  2. int (*pp)[4]=&a;
  3. std::cout<<pp[0];
这个输出   
  1. int a[4]={1,2,3,4};
  2. int (*pp)[4]=&a;
  3. std::cout<<pp[0][0];
这个输出
  1. int a[4]={1,2,3,4};
  2. int (*pp)[4]=&a;
  3. std::cout<<*(pp+1);
这个输出  

其实这个数组针一般是用在二维数组中的,因为如果我们声明
  1. int (*pp)[4]
则我们对
  1. pp++;
那么pp指针移动的可不只是一个int型的长度呢,那可是sizeof(int)*4的长度哦。
这里我们须区分下:
  1. int arr[4]={0};
  2. int (*p)[4]=&arr;
  3. int *pp=arr;
在上面的 &arr的值和arr的值是一样的,但是它们的意义可不太一样哦,&a代表的是整个数组的地址,数组指针,是int (*p)[4]类型。
但是arr确代表的是数组中第一个元素的代址是int*类型。
然后我们再来分析下指针的解引用和[]运算符。
看下面
  1. T*p;
  2. T a;
  3. *p=&a;
首先我们定认了一个类型为T的指针,也就是指针 p指向的数据类型是T。大家都明白一个指针的大小是四个字节,也就是任何类型的指针大小是一样的(这个只和寻址的
地址空间有关)。那么为啥又要给指针声明为不同的类型呢?不是反正存的都是一个地址嘛。
其实这个就和那个“解引用”有关。例如你给一个
  1. char *char_ptr;
  2. int *int_ptr;
我们知道char 只占一个字节,而int占4个字节。
如果我们解引用
  1. *char_ptr=a;
  2. *char_int=b;
那么解引用char_ptr和char_int如何知道它所指向的类型有几个字节呢?这就和指针的声明类型有关了嘛。如果char_ptr那么解引用就只读它所指向的那一个字节,
而如果是char_int那么就要读它指向的及后面的三个字节,共四个字节嘛。

我们现在回到原来的问题:
  1. int arr[4]={0};
  2. int (*p)[4]=&arr;
  3. int *pp=arr;
现在我们知道了,p指针在解引用时则会解它 p所指的后面连续sizeof(int)*4大小的空间。
其实*和[ ]运算符的意义是一样的啦 ,都是解析指针所指的空间内容。上面内容的共别自然就知道了。
同样&arr和arr的区别也出来了。

看一个问题:
  1. int main()
  2. {
  3. int a[4]={1,2,3,4};
  4. int *ptr1=(int *)(&a+1);
  5. int *ptr2=(int *)((int)a+1);
  6. printf("%x,%x",ptr1[-1],*ptr2);
  7. return 0;
  8. }
输出结果是多少?
    有了上面的分析,我们知道&a+1就到了数组的未尾了,其实应该是指向数组末尾的下一个节点空间。
     int *ptr1=(int *)(&a+1);也就是说ptr指向数组末尾的一下字节空间。
    那么ptr1[-1]呢?
    然后ptr[-1]=*(ptr-1)嘛,由于ptr是int*类型,于是知向前移动一个int的地址空间,那就是4的地址空间了嘛。于是上ptr[-1]输出4.
    那么
    int *ptr2=(int *)((int)a+1); ……………………………………………………………………(4)
    我们如何分析呢?
    其实上面写得很明白了嘛。
   你看我们将(int)a,也就是我们把a,a的内容就是地址啦 ,我们暂时将 a 的那个指针的“面具”拿下来,将a当作普通的int型的值进行计算,那么对a的
加1运算也就是增加一个byte的地址空间啦 。其实(4)可以转换为等价的
   int *ptr2=(int *)((char*)a+1); ……………………………………………………………………(5)

   



如何动态分配一个二维数组:
    方法1:
  
  1. //列大小固定的二维数组可以申请一维数据并将指针强转成二维数组
  2. #include <iostream>
  3. int main()
  4. {
  5. //列值固定
  6. const int MAXCOL = 3;
  7. int nRow;
  8. std::cin<<nRow;
  9. //申请一维数据并将其转成二维数组指针
  10. int *pp_arr = new int[nRow * MAXCOL];
  11. int (*p)[MAXCOL] = (int(*)[MAXCOL])pp_arr;
  12. //为二维数组赋值
  13. int i, j;
  14. for (i = 0; i < nRow; i++)
  15. for (j = 0; j < MAXCOL; j++)
  16. p[i][j] = i + j;
  17. //释放资源
  18. delete[] pp_arr;
  19. return 0;
  20. }
我们来详细分析下这段代码:
int *pp_arr = new int[nRow * MAXCOL];
首先我们分配了一个一维数组大小为二维数组的行*列。
然后后我们将
int (*p)[MAXCOL] = (int(*)[MAXCOL])pp_arr;
进行强掉转换了。
由于int (*p)[MAXCOL]表示p指向一个大小为MACOL,元素类型为int的数组。也就是数组指针了,。
那么我们对p进行加运算,就会向前移动sizeof(int)*MAXCOL个地址空间。

那么我们对p[i][j]的操作其实就是等价于
  1. *((*int)p+sizeof(int)*MAXCOL*i+sizeof(int)*j)
由于方法1首先必须在编译前知道二维数组的列数(实际情况中可没这种好事给你知道,行数和列数一般在运行的时候才知道,动态变化的);

方法2:
  1. int **dynamicMatrix(int rows,int cols){
  2. int **arr=(int**)malloc(rows*sizeof(int*)+rows*cols*sizeof(int));
  3. int *head=(int*)((char*)arr+sizeof(int*)*rows);
  4. memset(arr,0,rows*sizeof(int*)+rows*cols*sizeof(int));
  5. for(int i=0;i<rows; i++){
  6. arr[i]=(int*)((char*)head+i*cols*sizeof(int));
  7. }
  8. return (int**)arr;
  9. }

我们在数组的前面一段空间存储了每个行的地址。也就是说数组前面一段空间是指针类型啰?why?
不是说了吗,数组的前面一段空间用来记录每个行的首地址,只有指针才是存放地址的啊(这么说有点不太合适,不过知道就好)。
在上面的代码中标红的部分请留意,我就是在那两个地方坏了点小错误,导致结果不正确。其实也可以是:
  1. int **dynamicMatrix(int rows,int cols){
  2. int **arr=(int**)malloc(rows*sizeof(int*)+rows*cols*sizeof(int));
  3. int *head=(int*)((int)arr+sizeof(int*)*rows);
  4. memset(arr,0,rows*sizeof(int*)+rows*cols*sizeof(int));
  5. for(int i=0;i<rows; i++){
  6. arr[i]=(int*)((int)head+i*cols*sizeof(int));
  7. }
  8. return (int**)arr;
  9. }
至于原因前面解释过,不再缀述。
于是当我们使用时:
    
  1. int **p=dynamicMatrix(2,3);
  2. p[0][2]=1;
  3. std::cout<<p[1][1];
  4. p[1][2]=1;
  5. std::cout<<p[1][2];
  6. p[1][1]=2;
  7. std::cout<<p[1][1];
那么我们对于p进行加1时,那么和以前的方法1就不同了。
  1. p++;
那么p移动的地址大小就是只是一个sizeof(int*) 也就是4个字节的空间大小。因为p是指针的指针,因为数组的开始一段的类型是指针类型嘛,
于是我们就是能是用指针的指针啰。
因为p所指向的类型是指针,于是加1向后移就是 sizeof(int*)大小啰。
由于
p[i][j]现在相当于
  1. *(*((int*)p+sizeof(int*)*i)+sizeof(int)*j)







原文地址:https://www.cnblogs.com/yml435/p/4668474.html