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


    在我以前的文章中都有讲过关于数组指针及指针数组的相关问题,但是讲得不够深入,我后来后了别人写的博客后觉得人家的确实写得好,
    也学到了不少东西,对以前的问题有深的领悟了,于是准备结合这些博客和文章再稍微深入一点讲讲这些问题。这些指针的问题是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)







  • 相关阅读:
    Scala基础(1)
    简单模拟flume
    朴素贝叶斯
    关于hive的优化
    Hive的一些理解
    Flume的简单理解
    tiny-Spring【2】逐步step分析-新加入特性
    前、中、后缀表达式【待完成】
    奇妙的算法【9】YC每个小孩的糖果数,找公约数,最少硬币数
    奇妙的算法【8】筹钱种数、定时找出最高频次的数据、三子棋落点判断
  • 原文地址:https://www.cnblogs.com/yml435/p/4668474.html
Copyright © 2020-2023  润新知