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

在我以前的文章中都有讲过关于数组指针及指针数组的相关问题,但是讲得不够深入,我后来后了别人写的博客后觉得人家的确实写得好,

也学到了不少东西,对以前的问题有深的领悟了,于是准备结合这些博客和文章再稍微深入一点讲讲这些问题。这些指针的问题是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)

来自为知笔记(Wiz)

时间: 2024-10-11 17:19:45

二维数组指针及二维动态数组的分配问题的相关文章

数组指针与二维数组的寻址

引例:已知如下程序 1 #include <stdio.h> 2 main() 3 { 4 int x[3][4] = {1,3,5,7,9,11,2,4,6,8,10,12} ; 5 int (*p)[4] = x, k = 1, m, n = 0; 6 for(m=0; m < 2; m++) 7 n += *(*(p+m)+k); 8 printf("%d\n",n); 9 } 试写出程序的输出值.(虽然我很讨厌做这种笔头功夫的题,我也坚信编程语言是在实践中练出

C++中关于[]静态数组和new分配的动态数组的区别分析

这篇文章主要介绍了C++中关于[]静态数组和new分配的动态数组的区别分析,很重要的概念,需要的朋友可以参考下 本文以实例分析了C++语言中关于[]静态数组和new分配的动态数组的区别,可以帮助大家加深对C++语言数组的理解.具体区别如下: 一.对静态数组名进行sizeof运算时,结果是整个数组占用空间的大小:因此可以用sizeof(数组名)/sizeof(*数组名)来获取数组的长度.int a[5]; 则sizeof(a)=20,sizeof(*a)=4.因为整个数组共占20字节,首个元素(i

C++ delete []p 数组指针,如何知道该数组大小的

本来只是一时兴起,想动手整整大学学的很渣的C++,可是一段小小的代码缺牵扯出一堆问题来,好有趣. 来看一段代码: void main() { char p[6]; char *buf = new char[4]; strcpy(buf,"012356789");//这里越界不会报错 strcpy(p,"Hello"); //若此处越界立即报错 cout<<p<<endl; cout<<buf<<endl; delete

指针与数组--指针与二维数组2

指向一维数组的指针   char (*p)[10] ;指向一维数组的指针类型 typedef  char(*TYPE_P2ARRAY)[10]  ;   该指针可以指向数组 ,且使用起来效果节本相同,但指针与数组并不等价.{sizeof结果不同  , 且该指针还可以指向除此之外其他类型的数据. } #include <stdio.h> typedef int (*TP_PARRY1)[3] ; #define Uart_Printf printf void f1(void) { int a[2

C++C++ 指针(二)--c++ 指针(二)--c++

一.内存管理:new和delete 1.new操作符:从操作系统获得内存块,并返回该内存块的首地址. delete操作符:将new申请的内存返还给操作系统. 开始一个简单的例子: #include <iostream> #include<cstring> using namespace std; int main() {     char* str="it is a good job!";     int len=strlen(str);     char* p

C#有关数组内存的释放及动态数组问题

一.数组内存释放问题 数组内存的释放可以按照如下语句实现: string [] aa=new string[2]; aa[0]="A"; aa[1]="B"; aa=null; 其中最后一句:aa=null就是释放内存的. 二.关于动态数组 动态数组可以按以下方式申明: string[,] aa; 然后在任何要使用该数组的地方,都可以重新定义数组的大小: aa=new string[2,3];

Powershell 数组的使用(一)【动态数组】

在上文中我们介绍了如何使用[静态数组],内容涉及到数组的创建.元素的添加删除等操作,很显然,通过该种方式去操作一个数组是非常麻烦的,静态数组的目的在于让大家了解数组的基本概念和操作,在实际使用数组的脚本中,我们会创建动态数组来更好的提高脚本运算速度和易编写性. 动态数组.使用.Net的类来进行定义,它除了像静态数组一样可以进行比较,索引查找外还可以很方便的进行元素变更,在循环添加元素时它也是自由的去添加单个元素而不是重新定义整个数组,因而极大的提高了脚本的运算速度. 一. 动态数组的定义,动态数

数组指针(二维数组的指针)

1 #include <iostream> 2 3 using namespace std; 4 5 6 int main() 7 { 8 int v[2][2]={{1,2},{3,4}}; 9 cout<<"v = "<<v<<endl; 10 cout<<"*v = "<<*v<<" &v = "<<&v<<endl;

关于返回二维数组指针问题

所谓的二维数组指针,是指针的指针,指的就是二维数组在内存中的存储地址. 二维数组的地址与一维数组的地址的不同点是:它除了有元素地址外,还有标识各行起始位置的行首地址(称为行的首地址).行的首地址和行的首元素的地址具有相同的地址值,但是它们是两种不同的地址:若有定义int a[5][5]:则a[0][0]是a数组 首行首列元素(代表该元素的值).而&a[0][0]是首行首元素的地址.&&a[0][0]则是首行的首地址.从这个意义上讲,可以说行的首地址是一种二重地址,即指针的指针. 废