二级指针和二维数组

二级指针和二维数组
首先注意一个事实:
[]是一个运算符, 称 为 下 标 运 算 符 亦 称 变 址 运 算 符  ,我觉得应该是变址取内容
*(p+n) 恒 等于   p[n]
 
我们不能简单把指针看成地址,不要忽略掉数据类型。也就是说一个指针包含两方面,一个是地址,一个是数据类型。指针是变量,这个变量的值是个地址。变量的类型是指向一个数据类型。可能有两个指针的值一样但是他们其实是不一样的,因为他们指向的数据类型不一样,此时使用取内容符号*取得的东西就会不一样了。比如下面的a[2][3],a是个指针,a[0]也是个指针,而且这两个指针的值是一样的。但是这两个指针指向的数据类型不一样,所以使用取内容符号*取得的东西也不一样。对a[0]取内容,得到的是a[0][0]是个整数,因为a[0]就是指向的类型就是整数,占4个字节。使用*a[0]以后,系统会从a[0]开始往后取4个字节。然后把这4个字节的内容转化为一个整数也就是我们的a[0][0]了。而对a取内容呢,由于a指向的类型是一个大小为3的整数数组,简单说就是a指向的也是一个数据类型(指针的定义并并不要求只能指向是C语言里的基本数据类型,甚至指向结构体都可以),这个数据类型的大小是3个整数的大小,也就是12个字节。所以取内容*a候,系统会从a的值(是个内存地址)往后取出12个字节。然后把这12个字节的内容转化为一个3个整数的一维数组。也就是说*a其实表示的是一个一维数组。而且由恒等式  *(p+n) 恒 等于     p[n]我们知道*a这个一维数组我们还可以表示为a[0]。到这里我们应该会明白为什么a和a[0]的值是一样的,而我们对他们都使用取内容*运算符并且打印出来以后 printf("%x %x %x ",*a[0],*a)显示的却不一样,这是由于他们指向的数据类型不一样(也就是说他们是两个不同类型的变量,虽然他们的值一样,但是由于这两个变量的类型不一样所以对他们进行同一个运算符操作后得到的结果不一样);[这和其他的变量也是类似,比如int a=128,unsigned char b=128,他们的值是一样的,但是a<<1和b<<1以后结果却不一样了,a变为256,b变为0了]。现在还有个问题,现在我们已经知道*a表示的是一个一维数组(其实计算机中数组都是一维的),我们用printf("%x",*a)会显示什么呢,很多人都知道显示的是*a这个一维数组的第一个元素(不是我们通常说的数,是个广义的数,比如一个结构体的实例,在这我么也称作一个数,严格说是一个数据类型的实例)的地址。
 
系统中其实没有二维数组这个概念,都是一维的,二维数组本质是一维数组(该数组的元素数据类型是一维数组)。
 
下面我们解释对于a[2][3]为啥,a和a[0]的值一样,首先a[0]是一个一维数组(数组的数据类型是整数),也是一个指针常量,1:该指针的值是数组第一个元素的地址,2:指向的数据类型是整数。所以a[0]=&a[0][0]。a呢,也是个数组(该数组数据类型是一维数组),a同时也是个指针常量,1:该指针的值等于该数组第一个元素的地址,该数组第一个元素是a[0](a[0]本身也是个一维数组),a[0]的地址是什么呢,a[0]是个数,该数的数据类型是一个大小为3的整数一维数组。所以a[0]这个数,内存大小是12个字节,数的首地址是xxx。该首地址也即是a[0]这个数组的第一个元素a[0][0]的首地址,(注意:a[0]和a[0][0]也都是数,而且它们的(首)地址相等,区别是他们的数据类型不一样,a[0]这个数的数据类型是一维数组,a[0][0]这个数的数据类型是整数,数据类型不相等往往大小也不想等,也有时候大小相等),所以a这个指针常量的值a=&a[0][0]。2:a指向的数据类型是一维数组,所以a+1内存移动12个字节(移动一个a所指向的数据类型这么大)。
 
问题:对于一个数组,我们只知道数组名就可以表示这个数组??数组名从另一个意义上只是一个指针常量。??其实对于数组int a[5],a和&a[0]的概念是不一样的,a是个指针,&a[0]是个内存地址。这两个不一样,比如虽然a=&a[0]但是a+1和&a[0]+1不一样。所以我认为,一个一维数组就是一个指针常量,他们完全等价,对于sizeof的问题还在研究。
 
这里还要说下一维数组 int a[2]。a我们可以看成是个变量,这个变量的数据类型是大小为2的一维数组。同时对一维数组这种数据类型的变量,变量名同时也是该数组的第一个元素的地址,但请记住并不只是第一个数据元素的地址这么简单。它的值没错的确是第一个元素的地址,但是它的数据类型我们别忘了,指向的是整数。
 
这里说下我们文中所指的数的概念,他不是我们数学里的数,它指的是数据类型的一个实例。数据类型他可以是C里的基本数据类型也可以是有这些基本数据类型构成的其他数据类型,比如结构体,数组等。
 
接下来的一个问题是对于 int a[5],和int * const p=a。那么a和p是一样的吗,有人说a是个指针常量,那么a完全就等于一个指针常量吗?如果a是且只是一个指针常量,那么p也是个指针常量。那么a和p效果应该完全一样,但是使用sizeof()运算符以后呢,得到的值却不一样,这是为什么呢。
 
总结:考虑指针a的时候一定要考虑两个方面,1:他的值,是个内存地址;2:他指向的数据类型。第二条会直接影响取内容*运算的结果,取内容时是以该指针的值为始址,往后取它指向的数据类型这么大小的内存。并把这段内存的数据内容(即二进制比特)读出转为它所指向的数据类型。第二条还会影响a++运算符和a+1的结果,a+1还是个指针,指向的数据类型也不变,不过他的值变成a+sizeof(a所指向的数据类型),也就是到了a这个指针的值往后跳一个它所指数据类型这么大小的那个内存地址。
 
数组就是个指针常量,指针常量就是代表一个数组。这是从*(p+n)恒等与p[n]得出这条的。在大多数情况是对的。只在sizeof()和&运算符下,此时不等价。在sizeof()和&(取地址)里,系统把int a[5] 的a不看成指针常量而是看成是一个变量(数),数据类型为一维数组的变量(数)。显然该变量的大小是20个字节,且该变量的地址,即&a是a的变量类型(数组)的第一个元素的地址即&a=&a[0]。所以&a也是合法的。现:int a[5],int *const p=a;那么a和p在大多数情况是等价的,只有在sizeof()和取地址&运算符下不一样。sizeof(a)=20,sizeof(p)=4。&a=&a[0],&p!=&p[0]。此时a不是一个指针常量而是一个数据类型为一维数组的变量。。。。。这也是由于程序中不保存数组的大小,真相在楼下的链接。
 
int a[2];那么&a这个指针的类型是int (*) [2],也就是说我们声明 int (*p) [2];此时我们可以把p=&a。int(*p)[2]那么p是一个指向两个整数的数组的指针
 
取地址运算符&返回的是,所取地址对象的指针,该指针的值是对象的内存地址,该指针所指数据类型就是 该对象的数据类型
 
http://topic.csdn.net/t/20060519/22/4765211.html的5楼
以防删帖粘贴过来
----------------------------------------------------------------------------------------------
资料如下
数组名的本质是代表数组对象的变量名,是一个左值,是一个不能被改变的左值。但是由于在程序中不保存数组的大小,所以通过数组名只能访问数组的左值,不能访问数组的右值。由于这个原因,数组名在作为右值使用的时候被赋予另外一个新的意义——指向数组第一个元素的指针,这就是   array-to-pointer   转换规则。
数组名在程序中作为左值使用的情况屈指可数——在大部分情况下数组名都是作为右值使用的,这时它是一个指针,一个指向数组第一个元素的指针,一个指向不能再被改变的指针——因此是一个指针常量。
那么数组名在什么情况下作为左值使用的呢?根据标准的规定,只有当数组名作为sizeof、&运算符的操作数的时候,它是一个左值,类型为数组类型;除此之外的所有情况,数组名都是一个右值,被编译器自动转换为指针类型,这种情况下我们就说数组名是一个指针,并且是一个指针常量。
单纯地说数组名是一个指针是片面的。我们通常说数组名是一个指针是建立在一个前提的基础之上的,那就是:数组名作为右值使用的时候。
对于数组名总结如下:
在作为左值使用的时候,数组名表示数组对象,是数组类型。在作为右值使用的时候,数组名是一个指针,是指针类型,不再是数组类型。数组名到底是数组还是指针取决于其上下文环境。
----------------------------------------------------------------------------------------------
 
printf("%x %x %x %x",&a[0][0],a,*a,**a)
打印出来是1cfda4  1cfda4  1cfda4  1。
printf("%x %x %x",&a[1][0],a+1,*(a+1));
打印出来是1ffdb0 1ffdb0 1ffdb0。
 
数据类型转换时:原则是值不变。但有的能表示的值的范围就不一样,此时没办法保持值不变,那就保内存不变,就如char a=-128,1000 0000把a转成unsigned char 后,值变为128但是内存中的二进制比特还是1000 0000。
可以解释为啥不能用int **p=a了,因为这两个指针的指向的数据类型不一样,int**p指向一个int型指针,而int a[2][3]的a指向一个一维数组。其实赋值操作(即等号)起作用就是赋值,把右值赋给左值,此时右值可能是个变量(数)该变量除了1:值,还有2:数据类型。但作为右值我们只需要他的值(即右值属性)。。。所以要用int **p=a还是可以的,我们目的不就是要把一个地址值赋给int **p这个指针变量的值吗。至于他们指向的数据类型不用管(虽然可能会带来麻烦,结果和你想要的不一样,但是是可以的)。好吧,我们只要强制转换就行,即int **p=(int **) a。转换后a的值不变,但是a指向的数据类型是一个指针(大小是4个字节),转换完后*p就相当于(a其实没变,a是指针常量变不了,此时只是产生了一个a的临时变量)从a的值(等于&a[0][0])往后取4个字节(指针变量的大小是4个字节)把这段4个字节的内存的数据内容读出来值转成指针的值的格式(即16进制的整数,内存地址可以看成整数)是1,再从系统内存1去取4个字节(整数)这可能是操作系统的保护区不让取了,所以程序中道这里会出错,其实即使不出错肯定也是不对的。。。讨论完这,我们会发现把二维数组看成指针的指针(二级指针)会多么可笑。二维数组所指向的数据类型是一维数组,二级指针所指向的数据类型是指针
 
#include <iostream>

 

void fun(char ** p)

{

char (*p1)[10] = (char(*)[10])p;

std::cout<<p1[0][0]<<std::endl;

}

 

int main(int argc, char* argv[])

{

char data[][10] = {"abc","def"};

fun((char **)data);

return 0;

}

 
3、通过建立一个指针数组来引用二维数组元素若有以下定义:int *p[3], a[3][2], i,j ;在这里,说明符*p[3]中,也遵照运算符的优先级,一对[]的优先级高于*号,因此p首先与[]结合,构成p[3],说明了p是一个数组名,系统将为它开辟3个连续的存储单元;在它前面的*号则说明了数组p是指针类型,它的每个元素都是基类型为int的指针。若满足条件:0≤i<3,则p[i]和a[i]的基类型相同,p[i]= a[i]是合法的赋值表达式。

 

若有以下循环:for(i=0; i<3; i++) p[i]= a[i];在这里,赋值号右边的a[i]是常量,表示a数组每行的首地址,赋值号左边的p[i]是指针变量,循环执行的结果使p[0]、p[1]、p[2]分别指向a数组每行的开头。这时,数组p和数组a之间的关系如图9.6所示。

 

当p数组的每个元素已如图9.6所示指向a数组每行的开头时,则a数组元素a[i][j]的引用形式*(a[i]+ j)和*(p[i]+j)是完全等价的。由此可见,这时可以通过指针数组p来引用a数组元素,它们的等价形式如下:(1)*(p[i]+j) (2)*(*(p+i)+j) (3)(*(p+i))[j] (4)p[i][j] 不同的是:p[i]中的值是可变的,而a[i]中的值是不可变的。

 

 图9.64、通过建立一个行指针来引用二维数组元素若有以下定义:int a[3][2], (*prt)[2];在这里,说明符(*prt)[2]中,由于一对圆括号的存在,所以*号首先与prt结合,说明prt是一个指针变量,然后再与说明符[2]结合,说明指针变量prt的基类型是一个包含有两个int元素的数组。在这里,prt的基类型与a的相同,因此prt=a;是合法的赋值语句。prt+1等价于a+1、等价于a[1]。当prt指向a数组的开头时,可以通过以下形式来引用a[i][j]:(1) *(prt[i]+j) (2) *(*(prt+i)+j) (3)(*(prt+i))[j] (4) prt[i][j] 在这里,prt是个指针变量,它的值可变,而a是一个常量。

 

 

 

 

 

 

 

 

 

 

级指针和二维数组之间关系的理解.
我们知道char array[]=”abcdef”;   array是数组的首地址,
那么在二维数组中array当然也是数组的首地址,
看看这个定义char Array[][3] ={“ab“,“cd“,“ef“};
怎么知道的呢?定义这样一个数组,在vc调试窗口中
我们看到:
Array ---------0x64324234
  |------Array[0]---0x64324234 “ab“
  |------Array[1]---0x64324337 “cd“
  |------Array[2]---0x6432433A “ef”
已经很明白了,实际编译器是这样实现二维数组的,实际上Array是“一维指针数组“的首地址,其中每一个元素指针都
对应一个字符串,那么好我们来看看是否可以这样来使用Array二维数组.
char **pArray = Array;编译器提示出错,怎么办呢?加个(char **)试试,仍然出错,设断看一下pArray的值和Array
的值是相等的,但我们是否可以象使用Array[i]那样来同样输出字符串呢?很明显是不行的,编译器不会把
pArray+i处理成pArray+i*3寻找到第i个指针的地址,而只是简单的加了一个i.这说明编译器只做了很简单的将地址值赋给
了pArray,而它实际没有任何意义.我们不能用它来访问任何数据.很奇怪吗?
再来看看这样定义char *p[] = {“ab“, “cd“, “ef“};定义了一个指针数组.char **sp = p;这样的用法经常看到,为什么这样
就可以使用sp[i]来访问字符串了呢,的确编译器在编译的时候识别出了sp是一个指向一维数组的指针的
指针,那么我们就可以把它做为数组名来操纵整个数组了

 

 

 

先看下面一个简单例子:

int main()

{

char *onedp = NULL;  //one dimension array

char **twodp = NULL;  //two dimension array

        char a[3][16] = {"China_and_me", "c++", "It is a desk"};

        int i = 0;

 

        twodp = (char **)a;

        for(i = 0; i < 3; i++)

{

                printf("%s/n",a[i]);      

                printf("%s/n", *(twodp + i));          

        }

        return 0;      

}

在以上程序中,twodp 是一个二级指针,a[3][16]是一个二维数组。

for本要循环输出a[3][16]中的每一个字符串,但是遗憾的是,在执行printf("%s/n", *(twodp + i))时程序就down了。

下面看一下其中用到了那些知识:

     1) 以一维数组角度看待二维数组。

         对于a[3][16],这样来认识:首先把a作为一维数组看待,不过这个数组的元素仍是数组,即有3个数组为a的元素:a[0],a[1],a[2]。因此,a+1就是跨越一个行,所以a也称为行地址。关于行指针后面叙述。

         而a[i](i=0,1,2)又是一个一维数组,他的元素为a[i][j](j=0,1,2,...15),所以a[i]+1跨越a[3][16]的一个元素大小。

2) 应该要知道一点,指针变量中存放的是某个地址的值。

所以一维指针和二维指针变量的值,即onedp和twodp值都是某个地址,一维指针和二维指针变量不同的是,对二维指针变量操作:*twodp仍然是一个地址,而*onedp就是代表one所指的变量了。

这样看来,对上面的程序,twodp = (char **)a;这样twodp就指向二维数组的首地址,而这个首地址指向了字符C,当i为0时,twodp + i存放的是字符C的地址,那么*(twodp + i)的值就是字符C了,而字符C的ASCII码的16进制值是0x63,所以*(twodp + i)是0x63。注意,不要忘了twodp是一个二维指针,所以*(twodp + i)仍是一个地址,不幸,该值0x63却是内存中我们所无法访问的空间。这样我们就不难理解上面程序down的原因了。知道了原因,就离解决方法不远了,不过在说解决方法之前,我们先看另一个问题

3)对指针的运算,均是以指针所代表的类型为单位进行。

如果i=2,twodp = 0x0012fe90那么twodp + i是多少?

如果你答0x0012fe92,那就错了。

因为对指针的运算,均是以指针所代表的类型为单位进行。对于二级指针twodp,它的一级指针*twodp仍是指针类型,无论是char指针,还是其他指针,只要是指针类型,就占有4个字节(32位机)。所以twodp + i就是0x0012fe90 + i*4,当i=1时为0x0012fe94!

4)“行指针”概念

看一个例子:

int (*p)[16];

它定义了这样一个指针:这个指针指向一个一维数组,这个一维数组共有16个元素。

如果这样使用:

int a1[16];

p = a1;

那么p+1将发生越界,因为对p而言,它以行为单位进行。它每次走一行,一行是它的最小单位。对于a1[16],一行就是16个元素。每个元素4个字节,所以每一次p+1将跨越64个字节。行指针用在二维数组表现的很充分。因为二维数组可以看成一维数组的数组。对于二维数组a[3][16],共有3行。每一行又是含有16个元素的一维数组。从刚才上面对二位数组分析,二位数组名就是该数组第0行首地址,就是行地址。但是一定要注意a不是行指针,只是一个常量。

注意定义行指针时保持和二维数组的第二维一致!因为行指针移动按照这个“维”进行的。

 

有了以上知识,我们就可以方便操作二维数组了。

对于开始的例子,为了输出二维数组a[3][16]的每个字符串,以下几种方法:

1)设一个行指针。

char (*linep)[16];  // line type pointer

 

linep = a[0];

        for(i = 0; i < 3; i++)

        {

                printf("%s/n",linep + i);      

                               

    }

 

2)直接使用二级指针,不过一定注意二级指针的计算

twodp = (char **)a;

 

       

 

        for(i = 0; i < 3; i++)

        {

                printf("%s/n", twodp + i*4);           

    }      

这里一定要知道为什么是i与4相乘!

如果定义二维数组b为b[3][17],内容和二维数组a相同。那么将很难找到直接使用二级指针来访问b的每一个字符串的方法了。

 

转载于:https://www.cnblogs.com/zhixin/articles/3396789.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值