您好,欢迎访问三七文档
当前位置:首页 > IT计算机/网络 > 数据库 > 孙鑫VC++教程之第03节指针与数组
第三节、数组与指针目标:1.一维数组与指针2.数组越界3.指针和堆的内存分配4.丢失指针一、一维数组与指针1、什么是数组?数组是一组数据存储单元,每个单元保存相同数据类型,每个存储单元成为数组的一个元素。2、什么是指针?指针是一个变量,它保存了一个变量的地址。首先我们看一下下面的例子:chara[10],*p;p=a;p是一个char类型的指针,它里面放的是数组a的首地址,即a[0];a这里有双重含义:地址,也可以看作是常量指针;里面包含了10个char元素。在这种情况下,指针可以完成数组的操作,例如,对于数组的访问,可以用指针进行,也可以像数组一样对数组单元赋值和取址。但是数组不能看做普通指针,应该理解成一个固定的地址,她是在编译期间确定的,也可以理解成常量指针。但是,由于数组名的双重含义,决定了他自己的特性,不信请看下面的程序:#includeiostream#includestdlib.husingnamespacestd;intmain(intargc,char*argv[]){inta[10]={1,2,3,4,5,6,7,8,9,10},*p;p=a;coutaendl;cout&aendl;cout&a[0]endl;coutpendl;cout&pendl;cout&p[0]endl;getchar();return0;}我们看到a,&a,&a[0]输出的结果都是一致的,他们都是数组的首地址,这里a体现的是指针的特性,&a则体现了结构的特性。但是对于p来讲就不一样了,因为指针p在开始的时候开辟了一块自己的内存空间,经过p=a后,p里面放的是数组a的首地址,所以p和&p[0]结果都是数组a的首地址,但是&p则是指针p本身的内存地址。那么一维数组究竟是什么含义呢?实际上,一维数组应该理解成一个线性表,他在内存中开辟了一块连续的内存空间。在对数组访问的时候,实际上,编译器做了一定的转换工作:数组名(数组的起始地址)加上相对于起始地址的相对量(由下标变量给出),得到要访问的数组元素的单元地址,然后再对计算出的单元地址的内容进行访问。例如a[3]被转换成*(a+3)。由于数组是在编译期间确定,代表的是一块固定的内存空间,所以数组是不能够改变的,即a++,++a,a--,--a,a+=3,a-=3等都是不允许的,但是指针p做这些动作是毫无问题的。在win32系统下,任何类型的指针都占4个字节的内存空间。想想为什么?答:因为指针是是保存变量地址的变量,在win32下,32位地址,正好占用4字节宽。二、数组越界例如:1:inta[10];2:inti;3:i=2;4:for(intj=0;j3;j++)i--;5:a[i]++;6:printf(i=%d\n,i);对于上述代码,在line4结束时,i值为-1,而当line5结束时,i为0;原因是a[-1]所在单元恰好是i所在单元。而c++不对数组越界作检查,而是按偏移去寻址。数组越界有时候还是一种攻击手段,例如:我们如果要修改一个对象的虚函数表指针,由于虚函数表指针保存在对象中,是一个const类型的指针。常规的办法无法修改,但是我们可以通过数组越界的方法来修改它。呵呵。正所谓技术是一把双刃剑,就看你怎么用了。三、指针和堆的内存分配前面谈到无论什么类型的指针,win32下都是占4个字节的内存空间。那么对于指针的类型,有什么作用呢?答:指针的类型代表着指针内存空间中存储的变量的类型,同时也是指针运算时的步长。例如:chara[]=“hello”;char*p=a;p++;//p++时,对于char型,p向前迈进1个字节。Inta[]={1,2,3,4,5,6};Int*p=a;P++;//p++时,对于int型,p向前迈进4个字节。在前面,我们曾经提到一个进程的用户区分为4个部分,全局/静态区、代码区、堆区、栈区。对于这四个区的数据,我们都可以通过指针进行间接访问。在C++中,申请和释放堆中分配的存贮空间,分别使用new和delete的两个运算符来完成:格式:指针类型指针变量名=new指针类型(初始化);delete指针名;例如:1、int*p=newint(0);它与下列代码序列大体等价:2、inttmp=0,*p=&tmp;区别:p所指向的变量是由库操作符new()分配的,位于内存的堆区中,并且该对象未命名。下面是关于new操作的说明:1、new运算符返回的是一个指向所分配类型变量(对象)的指针。对所创建的变量或对象,都是通过该指针来间接操作的,而动态创建的对象本身没有名字。2、一般定义变量和对象时要用标识符命名,称命名对象,而动态的称无名对象(请注意与栈区中的临时对象的区别,两者完全不同:生命期不同,操作方法不同,临时变量对程序员是透明的)。3、堆区是不会在分配时做自动初始化的(包括清零),所以必须用初始化式(initializer)来显式初始化。new表达式的操作序列如下:从堆区分配对象,然后用括号中的值初始化该对象。下面是从堆中申请数组1、申请数组空间:指针变量名=new类型名[下标表达式];注意:“下标表达式”不是常量表达式,即它的值不必在编译时确定,可以在运行时确定。这就是堆的一个非常显著的特点,有的时候程序员本身都不知道要申请能够多少内存的时候,堆就变的格外有用。2、释放数组空间:delete[]指向该数组的指针变量名;注意:方括号非常重要的,如果delete语句中少了方括号,因编译器认为该指针是指向数组第一个元素的,会产生回收不彻底的问题(只回收了第一个元素所占空间),我们通常叫它“内存泄露”,加了方括号后就转化为指向数组的指针,回收整个数组。delete[]的方括号中不需要填数组元素数,系统自知。即使写了,编译器也忽略。Thinkinc++上说过以前的delete[]方括号中是必须添加个数的,后来由于很容易出错,所以后来的版本就改进了这个缺陷。下面是个例子,VC上编译通过#includeiostreamusingnamespacestd;#includestring.hvoidmain(){intn;char*p;cout请输入动态数组的元素个数endl;cinn;//n在运行时确定,可输入17p=newchar[n];//申请17个字符(可装8个汉字和一个结束符//)的内存空间strcpy(pc,“堆内存的动态分配”);//coutpendl;delete[]p;//释放pc所指向的n个字符的内存空间return;}通过指针使堆空间,编程中的几个可能问题1.动态分配失败。返回一个空指针(NULL),表示发生了异常,堆资源不足,分配失败。data=newdouble[m];//申请空间if(NULL==data)……2指针删除与堆空间释放。删除一个指针p(deletep;)实际意思是删除了p所指的目标(变量或对象等),释放了它所占的堆空间,而不是删除p本身,释放堆空间后,p成了空悬指针,不能再通过p使用该空间,在重新给p赋值前,也不能再直接使用p。因此,为了防止空悬指针被误用,在指针delete以后,将指针赋值为NULL.例如:if(p!=NULL){deletep;p=NULL;}3.内存泄漏(memoryleak)和重复释放。new与delete是配对使用的,delete只能释放堆空间。如果new返回的指针值丢失,则所分配的堆空间无法回收,称内存泄漏,同一空间重复释放也是危险的,因为该空间可能已另分配,而这个时候又去释放的话,会导致一个很难查出来的运行时错误。所以必须妥善保存new返回的指针,以保证不发生内存泄漏,也必须保证不会重复释放堆内存空间。4.动态分配的变量或对象的生命期。无名变量的生命期并不依赖于建立它的作用域,比如在函数中建立的动态对象在函数返回后仍可使用。我们也称堆空间为自由空间(freestore)就是这个原因。但必须记住释放该对象所占堆空间,并只能释放一次,在函数内建立,而在函数外释放是一件很容易失控的事,往往会出错,所以永远不要在函数体内申请空间,让调用者释放,这是一个很差的做法。你再怎么小心翼翼也可能会带来错误。例如:上节课后题。相信大家还有印象。下面我们再来看一下指针数组和数组指针如果你想了解指针最好理解以下的语句:(1)int*ptr;//指针所指向的类型是int(2)char*ptr;//指针所指向的的类型是char(3)int**ptr;//指针所指向的的类型是int*(也就是一个int*型指针)(4)int(*ptr)[3];//指针所指向的的类型是int()[3]//二维指针的声明(1)指针数组:一个数组里存放的都是同一个类型的指针,通常我们把他叫做指针数组。比如int*a[10];它里边放了10个int*型变量,由于它是一个数组,已经在栈区分配了10个(int*)的空间,也就是32位机上是40个byte,每个空间都可以存放一个int型变量的地址,这个时候你可以为这个数组的每一个元素初始化,或者单独做个循环去初始化它。例子:int*a[2]={newint(3),newint(4)};//在栈区里声明一个int*数组,它的每一个元素都在堆区里申请了一个无名变量,并初始化他们为3和4,注意此种声明方式具有缺陷,VC下会报错。例如:int*a[2]={newint(3),newint(4)};deletea[0];deletea[1];应该写成如下:int*a[2];a[0]=newint(3);a[1]=newint(4);deletea[0];deletea[1];(2)数组指针:一个指向一维或者多维数组的指针;int*b=newint[10];指向一维数组的指针b;注意,这个时候释放空间一定要delete[],否则会造成内存泄露,b就成为了空悬指针.int(*b2)[10]=newint[10][10];注意,这里的b2指向了一个二维int型数组的首地址.注意:在这里,b2等效于二维数组名,但没有指出其边界,即最高维的元素数量,但是它的最低维数的元素数量必须要指定!就像指向字符的指针,即等效一个字符串,不要把指向字符的指针说成指向字符串的指针。这与数组的嵌套定义相一致。int(*b3)[30][20];//三级指针――指向三维数组的指针;int(*b2)[20];//二级指针;b3=newint[1][20][30];b2=newint[30][20];两个数组都是由600个整数组成,前者是只有一个元素的三维数组,每个元素为30行20列的二维数组,而另一个是有30个元素的二维数组,每个元素为20个元素的一维数组。删除这两个动态数组可用下式:delete[]b3;//删除(释放)三维数组;delete[]b2;//删除(释放)二维数组;再次重申:这里的b2的类型是int(*),这样表示一个指向二维数组的指针。b3表示一个指向(指向二维数组的指针)的指针,也就是三级指针.(3)二级指针的指针看下例:参见example1。int(**p)[2]=(int(**)[2])newint*[3][2];p[0]=newint[2][2];p[1]=newint[2][2];p[2]=newint[2][2];delete[]p[0];delete[]p[1];delete[]p[2];delete[]p;注意:此地方的指针类型为int(*),碰到这种问题就把外边的[2]先去掉,然后回头先把int**p=newint*[n]申请出来,然后再把外边的[2]附加上去;p代表了一个指向二级指针的指针,在它申请空间的时候要注意指针的类型,那就是int(*)代表二级指针,而int(**)顾名思义就是代表指向二级指针的指针了。既然是指针要在堆里申请空间,那首先要定义它的范围:(int*[n])[2],n个这样的二级指针,其中的每一个二级指针的最低维是2个元素.(因为要确定一个二级指针的话,它的最低维数是必须指定的,上边已经提到)。然
本文标题:孙鑫VC++教程之第03节指针与数组
链接地址:https://www.777doc.com/doc-2560399 .html