在我以前的文章中都有讲过关于数组指针及指针数组的相关问题,但是讲得不够深入,我后来后了别人写的博客后觉得人家的确实写得好,
也学到了不少东西,对以前的问题有深的领悟了,于是准备结合这些博客和文章再稍微深入一点讲讲这些问题。这些指针的问题是C语言中的基础与关键
而且一旦出现这些问题,不太好找bug的来源,有时候不得不借助反汇编。
参考文章:
http://c.biancheng.net/cpp/html/476.html C语言指针数组和数组指针
http://blog.csdn.net/morewindows/article/details/7664479 如何在C/C++中动态分配二维数组
现在我们知道了指针数组与数组指针的区别了嘛。
int *p1[10]; …………………………………………(1)
这个就是指针数了啊,我们主要是看运算符的优先级,因为[ ] 的优先级比 * 的优先级要高,于是知p1首先和[10]结合-----p1[10]表示的是含有10个元素的数组,那么数
组中元素类型呢? 那就是 int * 也就是说 p1 是含有10元素为 int* 的数组名,注意p1是数组名而非什么指针。
int (*p2)[10]; ……………………………………………(2)
而现在就不同了,我们的(*p2)说明p2是指针,而不再像是(1)中的类型名了,嗯好,这个 * 是专门用来修饰p2的。
同样【10】表示含有10个元素啦,而int表示这个数组里面的内容是int 型。则知道我们这个数组是没有数组名的。其实(2)式
组合起来说明p2是一个指针,但不是一个一般的指针,而是一个指向含有10个int型的数组的指针。
那有人问?
这个p2指针和(3)
int* p ………………………………………………(3)
中的p(普通的int型指针)有区别吗?
当然是有区别的! 不然这不是脱了裤头放屁-----多此一举。搞得大家对(2)式不太理解。
分析代码:
int a[4]={1,2,3,4};
int (*pp)[4]=&a;
std::cout<<pp[0];
这个输出
int a[4]={1,2,3,4};
int (*pp)[4]=&a;
std::cout<<pp[0][0];
这个输出
int a[4]={1,2,3,4};
int (*pp)[4]=&a;
std::cout<<*(pp+1);
这个输出
其实这个数组针一般是用在二维数组中的,因为如果我们声明
int (*pp)[4]
则我们对
pp++;
那么pp指针移动的可不只是一个int型的长度呢,那可是sizeof(int)*4的长度哦。
这里我们须区分下:
int arr[4]={0};
int (*p)[4]=&arr;
- int *pp=arr;
在上面的 &arr的值和arr的值是一样的,但是它们的意义可不太一样哦,&a代表的是整个数组的地址,数组指针,是int (*p)[4]类型。
但是arr确代表的是数组中第一个元素的代址是int*类型。
然后我们再来分析下指针的解引用和[]运算符。
看下面
T*p;
T a;
*p=&a;
首先我们定认了一个类型为T的指针,也就是指针 p指向的数据类型是T。大家都明白一个指针的大小是四个字节,也就是任何类型的指针大小是一样的(这个只和寻址的
地址空间有关)。那么为啥又要给指针声明为不同的类型呢?不是反正存的都是一个地址嘛。
其实这个就和那个“解引用”有关。例如你给一个
char *char_ptr;
int *int_ptr;
我们知道char 只占一个字节,而int占4个字节。
如果我们解引用
*char_ptr=a;
*char_int=b;
那么解引用char_ptr和char_int如何知道它所指向的类型有几个字节呢?这就和指针的声明类型有关了嘛。如果char_ptr那么解引用就只读它所指向的那一个字节,
而如果是char_int那么就要读它指向的及后面的三个字节,共四个字节嘛。
我们现在回到原来的问题:
int arr[4]={0};
int (*p)[4]=&arr;
int *pp=arr;
现在我们知道了,p指针在解引用时则会解它 p所指的后面连续sizeof(int)*4大小的空间。
其实*和[ ]运算符的意义是一样的啦 ,都是解析指针所指的空间内容。上面内容的共别自然就知道了。
同样&arr和arr的区别也出来了。
看一个问题:
int main()
{
int a[4]={1,2,3,4};
int *ptr1=(int *)(&a+1);
int *ptr2=(int *)((int)a+1);
printf("%x,%x",ptr1[-1],*ptr2);
return 0;
}
输出结果是多少?
有了上面的分析,我们知道&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:
//列大小固定的二维数组可以申请一维数据并将指针强转成二维数组
#include <iostream>
int main()
{
//列值固定
const int MAXCOL = 3;
int nRow;
std::cin<<nRow;
//申请一维数据并将其转成二维数组指针
int *pp_arr = new int[nRow * MAXCOL];
int (*p)[MAXCOL] = (int(*)[MAXCOL])pp_arr;
//为二维数组赋值
int i, j;
for (i = 0; i < nRow; i++)
for (j = 0; j < MAXCOL; j++)
p[i][j] = i + j;
//释放资源
delete[] pp_arr;
return 0;
}
我们来详细分析下这段代码:
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]的操作其实就是等价于
*((*int)p+sizeof(int)*MAXCOL*i+sizeof(int)*j)
由于方法1首先必须在编译前知道二维数组的列数(实际情况中可没这种好事给你知道,行数和列数一般在运行的时候才知道,动态变化的);
方法2:
int **dynamicMatrix(int rows,int cols){
int **arr=(int**)malloc(rows*sizeof(int*)+rows*cols*sizeof(int));
int *head=(int*)((char*)arr+sizeof(int*)*rows);
memset(arr,0,rows*sizeof(int*)+rows*cols*sizeof(int));
for(int i=0;i<rows; i++){
arr[i]=(int*)((char*)head+i*cols*sizeof(int));
}
return (int**)arr;
}
我们在数组的前面一段空间存储了每个行的地址。也就是说数组前面一段空间是指针类型啰?why?
不是说了吗,数组的前面一段空间用来记录每个行的首地址,只有指针才是存放地址的啊(这么说有点不太合适,不过知道就好)。
在上面的代码中标红的部分请留意,我就是在那两个地方坏了点小错误,导致结果不正确。其实也可以是:
int **dynamicMatrix(int rows,int cols){
int **arr=(int**)malloc(rows*sizeof(int*)+rows*cols*sizeof(int));
int *head=(int*)((int)arr+sizeof(int*)*rows);
memset(arr,0,rows*sizeof(int*)+rows*cols*sizeof(int));
for(int i=0;i<rows; i++){
arr[i]=(int*)((int)head+i*cols*sizeof(int));
}
return (int**)arr;
}
至于原因前面解释过,不再缀述。
于是当我们使用时:
int **p=dynamicMatrix(2,3);
p[0][2]=1;
std::cout<<p[1][1];
p[1][2]=1;
std::cout<<p[1][2];
p[1][1]=2;
std::cout<<p[1][1];
那么我们对于p进行加1时,那么和以前的方法1就不同了。
p++;
那么p移动的地址大小就是只是一个sizeof(int*) 也就是4个字节的空间大小。因为p是指针的指针,因为数组的开始一段的类型是指针类型嘛,
于是我们就是能是用指针的指针啰。
因为p所指向的类型是指针,于是加1向后移就是 sizeof(int*)大小啰。
由于
p[i][j]现在相当于
*(*((int*)p+sizeof(int*)*i)+sizeof(int)*j)