《C++语言中数组指针和指针数组彻底分析.doc》由会员分享,可在线阅读,更多相关《C++语言中数组指针和指针数组彻底分析.doc(18页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、C+语言中数组指针和指针数组彻底分析近来在论坛中机场经常看到有关数组指针和指针数组的讨论。这个是学习c+等语言中不可少的步骤,不过向来指针的东西就是很有用但是也是很难用的东西,所以学习起来也不是很容易了。近来本人也没有什么项目可以做的,所以就随便写的自己关于这些方面的理解,供同行参考,同时也可以把自己的错误理解暴露在阳光下,接受大家评判的洗礼。file:/Powered By ZosaTapo file:/dertyang# # 基本知识 # #当然我们一切都是从最简单的内建类型开始,最后我会做一些推广。先看一下基本的形式,我们从这里起步!-指针-int a=10;int *p=&a;-指针的
2、指针-int b=20;int *p=&b;int *p2p=&p;-简单数组-int c10;/整数数组,含有10个整数元素 file:/也就是说每一个元素都是整数 -指针数组-int *p10;/指针数组,含有10个指针元素 file:/也就是说每一个元素都是指针 -数组指针-int (*p)10;/数组指针,这个指针可以用来指向 file:/含有10个元素的整数数组上面这些简单的形式是我们必须要首先理解,这个是基本的知识。同时我们从上面也要得出一个很重要的知识提示:c+语言层面上关于变量声明的部分,后缀结合变量的优先级比前缀要高的。看我们上面的例子的最后两个就明白了,我们为了实现数组指针
3、的声明我们不得不变通一下。我们采用()来实现优先级的改变,实现了数组指针的声明。# # 进一步提高知识 # #数组,数组的指针,指针的数组,概念太多了。我接受概念一多的时候,我就想把这些复杂的东西简单一下。因为我太懒了,概念简化一下,记住更容易一点。所以我们这里要认识一下上面这些概念本质。这样可以简化概念,减少记忆的难度。先看一段程序。#include #include using namespace std;int main()int vInt=10;int arr2=10,20;int *p=&vInt;int *p2p=&p;int *parr2=&vInt,&vInt;int (*p2
4、arr)2=&arr;coutDeclaration int vInt=10 type=typeid(vInt).name()endl;coutDeclaration arr2=10,20 type=typeid(arr).name()endl;coutDeclaration int *p=&vInt type=typeid(p).name()endl;coutDeclaration int *p2p=&p type=typeid(p2p).name()endl;coutDeclaration int *parr2=&vInt,&vInt type=typeid(parr).name()end
5、l;coutDeclaration int (*p2arr)2=&arr type=typeid(p2arr).name()endl;return 0;运行的结果如下:(我在前面加了行号#XX)#01 Declaration int vInt=10 type=int#02 Declaration arr2=10,20 type=int *#03 Declaration int *p=&vInt type=int *#04 Declaration int *p2p=&p type=int * *#05 Declaration int *parr2=&vInt,&vInt type=int *#0
6、6 Declaration int (*p2arr)2=&arr type=int (*)2现在我们来分析一下结果。因为我们已经具有了第一部分的基本知识,我们现在可以很明确区别出来我们声明的类型。这里主要有两个很重要的部分,我们不过是就事讲事情,编译器是如何实现的原理不在这里讨论之列。-#02:数组-现在看#02,想到了什么没有呀?在编译器看来数组只是相对应类型的指针类型。当我们把数组传递给函数作为参数的时候,传递的是指针,所以我们可以利用参数来修改数组元素。这个转化是编译器自动完成的。void f(int);int a2=10,20;f(a);/这行等价于编译器完成的函数转化f(int *p
7、)也就是说这里编译器自动完成了int类型到int *的转化,注意是编译器完成的,也可以说是语言本身实现的,我们对此只有接受的份了。-#05:指针数组-指针数组的编译器内部表示也是对应类型的指针。-#06:数组指针-数组指针的编译器内部表示就是有一点特别了。编译器(或者说是语言本身)有数组指针这个内部表示。由于c+语言的类型严格检查的语言(当然还有一些是存在隐式类型转化的)所以我们下面的写法是不能编译通过的。file:/-编译不能通过-int arr3=10,20;/注意是3个元素数组int (*p2arr)2=&arr;/注意是指向2个元素数组的指针file:/-编译不能通过-# # 初步小结
8、 # #通过上面两个小节的内容,大家应该基本明白了,数组,指针,指针数组,数组指针到底是怎么一回事情了吧。-补充开始-关于数组和指针的转化,以及我们使用指针(+,-)等来操作数组,是基于数组在内存中是连续分布的。但是我们使用“迭代器”的时候,情况是不一样的。这个问题本文不讨论。-补充结束-不过c+语言本身有很多诡异的地方(因为c+要考虑到跟c语言以及旧的c+版本兼容)。内建类型的这些性质特征到了函数部分会有一点小的变化,不过如果你了解了编译器做了什么以后的话,你也就不会太奇怪了。不过关于函数部分的内容我下次再说了。现在回到上面的内容。我们这里还是讲一下内建类型。显然一样类型的变量是可以互相赋值
9、。不过当然还有一些其他情况也是可以的,比如类型的宽化,关于类的继承体系问题等等。当然了,不一样的类型一般来说是不能互相赋值,当然这里的例外就是强制转化,类的继承体系等情况了。看到这里就会明白下面的程序为什么会运行的了。我这里也把下面的程序作为今天内容的总结:#include using namespace std;int main()int a2=10,20;int *p=a;/根据上面说明,由于编译器的参与,两者类型转化后一致int vInt=10;int *parr2=&vInt,&vInt;int *p2p=parr;/上面分析,类型一致return 0;-函数指针部分下回再写了-上一次
10、我们主要说明数组,指针,指针数组,数组指针这几个很基本的概念,不过有一点上一次我没有提及,那就是我们使用typedef定义指向数组指针的数组,这个叫法是不是很拗口呀,不过我们看具体的程序就清楚了。file:/-数组-int a2=10,20;int b2=30,40;file:/简单的指向两个元素数组的指针int (*p2arr)2=&a;file:/-注意这里的变化-file:/这里我们声明简单的类型P2ARR.可以这样描述file:/P2ARR是一种用户自定义的类型。她的功能描述file:/是用来描述整数数组的,这个数组只能含有两个元素 typedef int (*P2ARR)2;P2AR
11、R pp=&b;pp=p2arr;/类型一致file:/这里我们定义指向指向数组指针的数组P2ARR ap2a2=&a,&b;file:/如果我们采用下面的写法则是错误的,原因看下面分析file:/P2ARR ap2a2=a,b;/-错误写法-还有一点注意-有人可以能会说,定义数阻指针的数组,下面这样的形式好像也可以做到的。int (*p2arr)22;咋一看,好像是的,但是仔细想想。原来上面是定义一个指针,这个指针是指向二维数组的指针,还是数组指针,而不是数组指针数组。这一点,应该很容易明白的。上一次我讲到int 数组声明与int *类型在函数参数等方面转换的例子。这里有一点补充的,我们实际
12、上现在还是不清楚数组来C+编译器层面的表示,也就是我们不知道编译器使用什么样的结构形式来表示数组的。所以上面这些转换只能转换而已,我们不能有更多的假设。-我的想象,我的猜想-我是这样相像的,C+采用类似JAVA中Array类的形式来管理数组。当然其中有许多c+语言的特征,比如operator *,operator -等重载,但是这些类型转换运算符的使用,只有编译器认为需要的时候才会调用的,因为数组表示本身就是编译器内部结构的嘛。目前到这里,我们也只能认为在函数参数使用等情况下才发生int 到int *的转换。看下面的程序。#include using namespace std;void ch
13、ange(int pa,int index)paindex=200;int main()int a3=1,2,3;change(a,0);/这里编译器已经把 int a3,转换成int *指针传给了函数 file:/这个是语言实现细节,请参考the c+ programming languagecouta0;return 0;但是我们上面的标注行/P2ARRap2a2=a,b;/-错误写法-则是应该这样理解:数组就是数组,指针就是指针,虽然编译器需要的时候会进行一定的转换,但是我们不能认为那些转换是“想当然“的。上面这一行,编译器就没有为我们进行类型转换的,所以我们要自己动手了。/P2ARR
14、ap2a2=&a,&b;/-正确写法-上面这些文字,主要一点就是说明了,有些行为是编译器完成的,而且还有一些诡秘。但是我们要知道编译器做了那些,那些是要完成自己做的。重要的一点,就是我们学会如何去简单使用typedef定义类型.一、 数组指针(1)数组指针的声明如:int (* arrPtr)10 = NULL; /arrPtr为一指针,指向一个具有10个元素的数组,这些元素的类型为int型注意:上面的括号不能省略,如果没有括号,则变成一个指针数组:int * arrPtr10; 表示arrPtr是一数组(而非指针),且该数组元素的类型为int型指针。(2)数组指针的操作*arrPtr表示ar
15、rPtr所指向的数组,因此(* arrPtr)i会返回数组的第i+1个元素值。更进一步,(* arrPtr)i是等价于*(*arrPtr) + i)的,因此当i=0时,*arrPtr就表示数组第一个元素的值了。(3)给数组指针赋值注意以下例子:int a10;int (* arrPtr)10 = NULL;arrPtr = a; /错误:指针类型符原因是,数组的名字(比如上面例子中的a)会被隐式地转换为指针,但它指向的是数组的第一个元素,而不是指向整个数组(试想想,可以通过*(a+i)来获取某个元素的值,道理一样)。因此上面的a会隐式地转换成 int *类型(且指向a0),也就是说,若想把ar
16、rPtr指向数组a,需要作以下的显示类型转换:arrPtr = (int (*)10)a; /10可省略再看看以下列子:int (* arrPtr)10 = NULL;int matrix310;arrPtr = matrix; /正确,因为matrix会隐式地转换为指向matrix010的指针,与arrPtr类型一致。再往前走一步:既然arrPtr = matrix,那么*arrPtr=*matrix,而*arrPtr表示的是matrix010第一个元素的值,因此*matrix也是表示matrix010第一个元素的值,即matrix00,如此类推,如果matrix是一个多维数组(大于两维),
17、那可通过*.*matrix这种形式来获取matrix00.00的值,即*.*matrix=matrix00.00。二、指针数组这比较常用,在此就不多赘述。指针的指针,数组指针,指针数组(2008-11-11 11:26:23)标签:杂谈 分类:编程学习指针的指针void FindCredit(int *);main() int vals=7,6,5,-4,3,2,1,0; int *fp=vals; FindCredit(&fp); printf(%dn,*fp); void FindCredit(int * fpp) while(*fpp!=0) if(*fpp0) break; else
18、(*fpp)+; 首先用一个数组的地址初始化指针fp,然后把该指针的地址作为实参传递给函数FindCredit()。FindCredit()函数通过表达式*fpp间接地得到数组中的数据。为遍历数组以找到一个负值,FindCredit()函数进行自增运算的对象是调用者的指向数组的指针,而不是它自己的指向调用者指针的指针。语句(*fpp)+就是对形参指针指向的指针进行自增运算的。但是因为*运算符高于+运算符,所以圆括号在这里是必须的,如果没有圆括号,那么+运算符将作用于二重指针fpp上。指向指针数组的指针 指针的指针另一用法旧处理指针数组。有些程序员喜欢用指针数组来代替多维数组,一个常见的用法就是
19、处理字符串。char *Names= Bill, Sam, Jim, Paul, Charles, 0 ;main() char *nm=Names; while(*nm!=0) printf(%sn,*nm+); 先用字符型指针数组Names的地址来初始化指针nm。每次printf()的调用都首先传递指针nm指向的字符型指针,然后对nm进行自增运算使其指向数组的下一个元素(还是指针)。注意完成上述认为的语法为*nm+,它首先取得指针指向的内容,然后使指针自增。 注意数组中的最后一个元素被初始化为0,while循环以次来判断是否到了数组末尾。具有零值的指针常常被用做循环数组的终止符。程序员称零
20、值指针为空指针(NULL)。采用空指针作为终止符,在树种增删元素时,就不必改动遍历数组的代码,因为此时数组仍然以空指针作为结束指向指针的指针,很早以前在说指针的时候说过,但后来发现很多人还是比较难以理解,这一次我们再次仔细说一说指向指针的指针。先看下面的代码,注意看代码中的注解:#include #include using namespace std;void print_char(char* array,int len);/函数原形声明 void main(void) /-段1-char *a=abc,cde,fgh;/字符指针数组char* *b=a;/定义一个指向指针的指针,并赋予指针
21、数组首地址所指向的第一个字符串的地址也就是abc0字符串的首地址cout*b|*(b+1)|*(b+2)endl;/-段2-char* test=abc,cde,fgh;/注意这里是引号,表示是字符串,以后的地址每加1就是加4位(在32位系统上)int num=sizeof(test)/sizeof(char*);/计算字符串个数print_char(test,num);cin.get(); void print_char(char* array,int len)/当调用的时候传递进来的不是数组, 而是字符指针他每加1也就是加上sizeof(char*)的长度for(int i=0;ilen;
22、i+)cout*array+endl; 下面我们来仔细说明一下字符指针数组和指向指针的指针,段1中的程序是下面的样子:char *a=abc,cde,fgh;char* *b=a;cout*b|*(b+1)|*(b+2)endl;char *a定义了一个指针数组,注意不是char,char是不能同时初始化为三个字符的,定义以后的a其实内部有三个内存位置,分别存储了abc0,cde0,fgh0,三个字符串的起始地址,而这三个位置的内存地址却不是这三个字符串的起始地址,在这个例子中a是存储在栈空间内的,而三个字符串却是存储在静态内存空间内的const区域中的,接下去我们看到了char* *b=a;
23、这里定义了一个指向指针的指针, 如果你写成char *b=a;那么是错误的,因为编译器会返回一个无法将char* *3转换给char *的错误,b=a的赋值,实际上是把a的首地址赋给了b,由于b是一个指向指针的指针, 程序的输出cout*b|*(b+1)|*(b+2)endl;结果是abc cde fgh可以看出每一次内存地址的+1操作事实上是一次加sizeof(char*)的操作,我们在32位的系统中sizeof(char*)的长度是4,所以每加1也就是+4,实际上是*a内部三个位置的+1,所以*(b+1)的结果自然就是cde了,我们这时候可能会问,为什么输出是cde而不是c一个呢?答案是这
24、样的,在c+中,输出字符指针就是输出字符串,程序会自动在遇到0后停止。我们最后分析一下段2中的代码,段2中我们调用了print_array()这个函数,这个函数中形式参数是char *array和代码中的char *test一样,同为字符指针,当你把参数传递过来的时候,事实上不是把数组内容传递过来,test的首地址传递了进来,由于array是指针,所以在内存中它在栈区,具有变量一样的性质,可以为左值,所以我们输出写成了:cout*array+endl;当然我们也可以改写为:coutarrayiendl这里在循环中的每次加1操作和段1代码总的道理是一样的,注意看下面的图!到这里这两个非常重要的知
25、识点我们都说完了,说归说,要想透彻理解希望读者多动手,多观察,熟能生巧。下面是内存结构示意图:最近好像是跟指针卯上了,发现以前真的学得不好,太多的东西是模模糊糊的。可能是因为S现在做的项目有太多地方使用到指针,而且有的时候用到复杂的指针,所以才觉得有必要好好的研究下,这样可以减轻S的负担,也为我以后做准备吧。要搞清一个指针首先必须搞清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区(这里只对int型进行说明,其他的类推)。指针的类型:从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型,这是指针本身所具
26、有的类型。例如:(1)int *ptr; /指针的类型是 int * 整型指针(2)int *ptr; /指针的类型是 int * 指针的指针(3)int (*ptr)3; /指针的类型是 int(*)3 数组指针(4)int *ptr3; /指针的类型是 int*3 指针数组指针所指向的类型:(1)int *ptr; /指针所指向的类型是 int 指向整型(3)int *ptr; /指针所指向的的类型是 int * 指向整型指针(4)int (*ptr)3; /指针所指向的的类型是 int()3 指向整型数组(5)int *ptr3;/指针所指向的类型是 int 指向指针数组的第一个单元指针
27、的值(又称指针所指向的内存区或地址):指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。指针本身所占据的内存区:指针类型本身占据的内存空间,在32位平台中,指针本身占据4个字节的长度,可以使用sizeof(
28、指针类型)来测试下指针占据的存储空间。以上是预备知识,接下来言归正传。指针的指针:这个比较简单,如下代码1:指针pni指向整型变量ni,pni的值为ni所在内存中的地址,指针的指针ppni指向一个整型指针,ppni的值为指针pni所在的存储单元的地址。int ni=10;int* pni=∋int* ppni=&pni;数组指针:顾名思义,指向数组的指针,这里一定要区分指向数组首单元的指针和数组指针的区别。如下代码2:int narr3;int *p=narr;在以上代码中,narr只是一个指代数组首地址的指针,而不是一个指向数组的指针。所以p只是一个指向数组首地址的指针。这个例子在指针
29、和数组名的区别文章有详细介绍。数组指针的使用见下代码3:void main() int array23 = 1,2,3,4,5,6;/二维数组看作是两个一维数组int (*pa)3; /申明一个数组指针,若(*pa)3中不为3则出错/array0指代二维数组中1,2,3的首地址,那么&array0是数组指针型pa = &array0;/即(*pa)相当于array0。coutpa0|pa1|pa2endl;coutpaendl;cout*paendl;cout(*pa)0|(*pa)1|(*pa)2endl;cout*paendl;cout*(*pa+2)endl;pa+;coutpa0|pa
30、1|pa2endl;coutpaendl;cout*paendl;cout(*pa)0|(*pa)1|(*pa)2endl;cout*paendl;cout*(*pa+2)endl; 输出为:0012FF68|0012FF74|0012FF800012FF680012FF681|2|3130012FF74|0012FF80|0012FF8C0012FF740012FF744|5|646以上代码中第三行定义的pa是指向一个3维数组的数组指针。*pa就是一维数组的指针,自然可以使用(*pa)i的方式访问数组,而*pa+1则是(*pa)1这个元素的地址,所以自然得到第5,6,7行所注释的输出。经过p
31、a+语句,由于pa是数组指针,后来pa中存放的地址应该是*pa+sizeof(array0),所以现在*pa就是一维数组4,5,6对应的首地址,同理得到第9,10,11行所注释的输出结果。指针数组:也就是指针的数组,存放指针的数组,数组中的元素是指针。例如:int n1=10;int n2=20;int* np2=&n1,&n2;cout*(np0)endl;cout*(np1);/输出20上述代码的第三行定义了一个指针数组,数组中含有两个元素,分别是n1、n2的地址,np0,np1就是对应的这两个地址,所以*np0,*np1分别是10,20对于指针的指针、数组指针、指针数组,我们还可以给出更
32、为直观的定义方法:指针的指针:typedef int* intP;intP* p;数组指针:typedef int intArray2;intArray* p;指针数组:typedef int* intPtr;intPtr p2;由于一些运算符优先级不同(*的优先级比低),所以在适当的时候使用typedef可以有效的避免迷惑性。这几天的研究结束了我和S对指针的困惑,所以作此总结,希望对大家都有帮助。数组指针与指针数组的区别2008-11-04 23:29int (*p)10;定义了一个数组指针,这个指针与一般的指针没有什么区别,仅仅是这个指针指向一个数组。这里我们把数组作为了基本的元素处理。也
33、就是说,将整个数组作为一种类型,而数组名就是这个类型的一个具体变量。例如:int a10;一个数组类型,形状就是这样:int 10;a就是新定义的一个变量。int b3;一个数组类型,形状就是这样:int 3;b就是新定义的一个变量。因为这两个类型形状不一样,因此是两个不同的类型,因此a,b就是不同类型的变量。这就好比int a和double b :a和b不一样。不知道大家是否已经对数组类型有了基本的印象?那么把数组名作为该数组类型的一个具体变量,我们就可以定义指向这个变量的指针,即数组指针。对于数组类型:int 10,我们可以定义一个指针,int (*p) 10.注意这里一定要加上小括弧。否
34、则就会变成了指针数组。定义了指针之后,我们可以对该指针赋值,如p=&a;如果定义了一个二维数组,int c310。我们可以认为定义了一个一维的数组,这个数组有三个int10的元素。因此和一般的数组一样,我们可以将该数组名赋给指针,其实也就是第一个元素的地址付给指针。即: p=c;或者p=&c0。其余情况以此类推。-数组指针是指针类型,它指向一个数组,代表数组的首地址。指针数组首先是一个数组,只不过这个数组的元素是指针而己。数组指针是定义的一个指针,而指针所指的对象是一个数组,指针指向该数组的首单元的地址,它对数组的内部元素的属性不了解,只是规定了首单元的地址,通过它可以找到该数组。 指针数组指
35、的是一个数组,它其中的所有元素都是指针类型,这里所有指针都指向不同的地址,而所指地址的数据也不一定相同,但是必须属于同一数据类型。 二者相差很多。-下面的程序有错误嘛?为什么?#includevoid main() int *p=new int 10; int arr10; int (*ptr)10; ptr=p;/cannot convert from int * to int (*)10 ptr=arr;/cannot convert from int 10 to int (*)10 ptr=&arr;/正确 typedef int (*type)10;/正确 type pa=&(new
36、int10); int* pb; type pc=&(pb=new int10); type pd=(type)(new int10);数组指针是指向数组的,如: int a33,(*p)3; p=a;这里(*p)3用于告诉编译系统,它是一个指针,指向一个长度为3的整型数组。这样在用指针访问其所指向的内存单元的时候就可以用*(*(p+i)+j)来表示aij;若:int a33,*p; p=a;就需用:*(p+3*i+j)来表示aij.指针数组是这样一种特殊的数组:它的每一个数组元素都是一个指针。如:int *p3;*p0,*p1,*p2都是一个指针。我看了你所说的有关指针问题的看法,我非常同意
37、你所说的.其实我也有一点看法:所谓指针数组吗,其实就可以把他看成一个结构提指针,只是他里面的元素都是相同的类型.例如:int (*p)22;p就是指向一个由10个整型单元构成的数组的指针.p必须是指向由10个整型单元构成的数组,就如一个结构体指针必须指向与它相同类型的一个结构体一样前言:其实数组也就是一个逻辑上的拥有首地址的连续的内存空间。1。我们常常用改变下标的方式来访问数组,本质上和通过加减某种特定类型的指针来实现在逻辑内存上的跳跃是相同的,其实数组的越界和程序员通过指针强行访问逻辑内存是一样的原理,只是指针更加灵活,同时也带来的大量的安全性问题。2。而对于诸如 int*(*p)i 这样的问题,无非就是一个指针的指针,而后一指针表象上就是那个数组,所以如果我们要想访问数组元素,就必须通过多加一个指针的方式来访问,而且还需要注意的是,后一指针也就是表象上的数组的寻址或者是跳跃方式是按照每一个元素以sizeof(int)的空间来跳跃的,我们也可以让这样的int成为其他的基本类型或者是扩展类型,只不过改变的跳跃的空间的大小或者是方式(这里的方式可以认为是某种具有嵌套关系的扩展类型)。