第3章结构类型初探3.1指针3.2引用3.3数组3.4向量容器3.5类型定义关键字3.6枚举3.7结构3.8联合实验习题•虽然C++提供了基本数据类型,但它们的能力有限,还需要利用基本类型构造一些复杂的数据类型,这些以基本类型为基础构造出来的类型统称为构造类型。•其实,构造类型的每一个分量都是一个对象,它可以是基本类型或者构造类型。这些分量可以与基本类型对象一样被赋值并在表达式中使用。合理地使用它们,不仅能准确、清晰地描述复杂的数据结构,而且还使得程序显得清晰、简洁。•本节将探讨几个典型的构造类型,并简单说明它们的使用方法。随着应用的深入,还会构造出新的类型,这些构造类型相互之间又有一定联系,从而为程序设计提供新的舞台。•假设将一个整型对象x存放在以0012FF7C开始的内存单元中,如果要访问对象x,既可通过对象的名字x,也可通过该对象的首地址0012FF7C。•可通过构造指针类型来实现这种操作。3.1指针•假设一个整型对象x的值为56,系统将为它在内存中分配一块连续的存储单元。如果这块存储区的首地址(即起始地址)为0012FF7C,则可以通过“&x”取得存放x的首地址(&称为取地址运算符,即&x=0012FF7C)。•如果要访问对象x,既可以通过对象的名字x,也可以通过存放对象的首地址0012FF7C。•如图3.1所示,假设使用一个运算符“*”来间接引用地址(&x)中存放对象的值,即“*&x”表示通过这个地址访问对象x。下面的程序演示了这种方法,实现了所做假设。3.1.1构造指针类型整型对象x存放的首地址0012FF7C地址运算:&x=0012FF7C赋值运算:x=56间接引用:*&x=5656图3.1通过存放对象x的首地址间接引用对象x的值#includeiostreamusingnamespacestd;voidmain(){intx=56;cout″x的值等于″x″,存储x的首地址是″&xendl;cout″通过名字x使用″x″,通过首地址″&x″使用″*&xendl;}程序输出结果如下:x的值等于56,存储x的首地址是0012FF7C通过名字x使用56,通过首地址0012FF7C使用56•现在想使用一个符号p代表&x,即p=&x。这时p代表x的地址,*p代表&x内的值x,即*p=x。•运算符“*”使p间接引用了x的值,称为“间接引用”运算符或“递引用”运算符。•这样,就可以将上面的第2条输出语句改写成如下形式:cout″通过名字x使用″x″,通过首地址″p″使用″*pendl;•它实现了同样的输出结果,并验证了p和x之间的关系为:p=&x和*p=x。•既然p是一个标识符,也就是新数据类型的一个对象,系统也要给p分配一个存储空间。•假设用内存单元0012FF78作为存放对象p的首地址,显然&p=0012FF78。不过,在这个地址0012FF78里不是存放p的值,而是存放变量x的首地址0012FF7C,即p=&x。•这样就可以根据p找到x,也就能对x进行存取。对象p和x之间的关系如图3.2所示。图3.2导出新的数据类型示意图•现在的问题是,如何表示这种新的数据类型?首先,它是针对整型对象的,所以可以借助整型类型int来声明。“p”这种形式已经被基本类型的对象使用,如果使用“intp;”的形式,则将p作为整型对象,显然是行不通的。如果使用“*”来标记这种数据类型,就可将p与整型变量区分开来,也就解决了问题。即int*p;//声明称为构造类型的指针类型对象p=&x;//地址运算*p=x;//值引用•因为这种数据类型声明的对象代表指向另一个数据类型对象的存储首地址,所以得名为“指针”类型。•这里是借助“*”号,而且与“*”号的位置没有关系,下面3种写法都是正确的:int*p;//“*”在中间,与谁都不连int*p;//“*”与p连写为*pint*p;//“*”与int连写为int*•读者有时会对使用如下方法在声明指针的同时初始化指针的方式感到困惑,即:int*p=&x;•实际上,选择“int*p;”,认为“int*”是一种指向整型的指针类型,用它声明指针对象p,p应该赋予x的地址,所以应是“p=&x”。声明指向整型的指针对象p并同时初始化,也就顺理成章为“int*p=&x”。显然,称p为指针对象(存放的是对象的首地址),而不是称*p为指针对象(*p代表指针指向的地址单元所存放的值)。•因为指针对象存放的是地址,所以必须有具体指向。•最常见的错误是声明了指针,没有为指针赋值。没有赋值的指针里含有随机地址,破坏性很大。•声明并同时初始化指针的方法避免了遗漏赋值。•由此可知,p的值是地址,虽然这个地址就是对象x在内存中的存储首地址,但我们并不直接说p的值是x的地址,而说成p指向x的存储首地址,简称p指向x的地址。【例3.1】演示使用指针及指针地址。#includeiostreamusingnamespacestd;voidmain(){intx=56;int*p=&x;cout″x的值等于″x″,x的首地址是″&x″,p指向的地址是″pendl;cout″通过名字使用″x″,通过p内的地址″p″使用″*pendl;cout″p指向的地址为″p″,存放p的地址是″&pendl;}程序输出如下:x的值等于56,x的首地址是0012FF7C,p指向的地址是0012FF7C通过名字使用56,通过p内的地址0012FF7C使用56p指向的地址为0012FF7C,存放p的地址是0012FF78•假设已经知道对象地址(NULL也算已知),现在将上面的构造语法总结如下:存储类型数据类型*指针名;指针名=对象地址;或者采取直接初始化的方法:存储类型数据类型*指针名=对象地址;•默认的存储类型为自动存储类型(auto),目前也仅以自动存储类型为例,以后将通过例子进一步介绍存储类型。•现在假设它们具有如图3.3所示的形式:3.1.2指针类型及指针运算图3.3指针操作关系图由关联关系可知,*p和x同步变化,即改变任何一个的值,它们的值保持一致。•如果改变p的内容,例如使用语句“p=&y;”,这使得*p=65,它与y同步,不再与x有任何关系。【例3.2】演示了这种情况。从例子中可见,指针本身的地址不会变化,它反映了系统需要为指针p分配地址这一概念。正如使用x不要再考虑&x一样,以后也不再考虑&p。【例3.2】演示指针及其运算概念的例子。#includeiostreamusingnamespacestd;voidmain(){intx=56,y=65,*p;p=&x;//指针指向xcoutx″,″p″,″&p″,″*pendl;p=&y;//指针改为指向ycouty″,″p″,″&p″,″*pendl;*p=85;//通过指针改变对象内容couty″,″p″,″&p″,″*pendl;++p;//对指针进行增1运算coutx″,″p″,″&p″,″*pendl;--p;//对指针进行减1运算coutx″,″p″,″&p″,″*pendl;p=p-2;//对指针进行减2运算coutx″,″p″,″&p″,″*pendl;coutx″,″p″,″&p″,″*(p+2)endl;coutx″,″p″,″&p″,″*pendl;*p=*(p+2);coutx″,″p″,″&p″,″*pendl;}•为了好理解,在程序输出的右方给出输出前的操作过程。程序输出结果如下:56,0012FF7C,0012FF74,56//p=&x;65,0012FF78,0012FF74,65//p=&y;85,0012FF78,0012FF74,85//*p=85;56,0012FF7C,0012FF74,56//++p;56,0012FF78,0012FF74,85//--p;56,0012FF70,0012FF74,-858993460//p=p-2;56,0012FF70,0012FF74,85//*(p+2)56,0012FF70,0012FF74,-858993460//*p56,0012FF70,0012FF74,85//*p=*(p+2);•指针可以和整数相加减。若p为指针,n为整数,则可以p+n或p-n。但这里必须弄清楚,编译程序在具体实现时并不是直接把n的值加到p上去,而是要把n乘上一个“比例因子”,然后再加上p。•不同类型的数据实际存储所占的单元数不同,例如char为1字节,int为2字节(VC为4字节),long和float为4字节,double为8字节等,这些数分别为它们的“比例因子”,具体采用哪个作为比例因子,取决于p指向的数据是什么类型。•对用户来说,不需要了解编译程序内部的实现,从用户观点看来,只要将p+n看成是将指针p移动n个数(不必涉及每个数所占的字节数)的位置。•需要注意如下问题:①对p进行++p和--p操作时,因为本程序的x和y顺次存放,所以可以使p的内容指向x或y,*p也随之变化并为x或y的值。当执行加2操作后,p所指向地址虽然惟一,但已经不是所需内容。如果这个内容很重要,又不慎将它修改,就会造成灾难性后果。这就是使用指针的危险之处。②对p进行运算,相应的*p为p指向地址的内容。也可以不改变p,将相对p的地址的内容取出,这就是使用*(p±n)。程序演示了使用*(p+2)输出85,但这并没有改变*p的内容,这可从下面输出*p的结果得到证实。最后一句使用语句“*p=*(p+2);”将p指向的内容改变为85。因此要正确区别(p±n)和*(p±n)操作。③对指针变量*p1和*p2来说,下面两种赋值的方法不是等价的:p1=p2;*p1=*p2;p1=p2表示指针p1放弃了原来指向的地址,与p2指向同一个对象,这也就理所当然地使*p1=*p2自然成立。*p1=*p2表示p1的目标值变为p2的目标值,但指针p1并没有放弃自己指向的地址,所以p1≠p2。④可使用下标“[]”描述连续的指针p,参见3.3.2的内容。•可以限制【例3.2】中“*p”和“p”的改变,以提高程序的可靠性。•可以用const限定符强制实现最低访问权原则。•用这个原则正确地设计软件可以大大减少调试时间和不良的副作用,使程序易于修改和调试。3.1.3对指针使用const限定符1.左值和右值•对象是一个指名的存储区域,左值是指某个对象的表达式。名字“左值”来源于赋值表达式“E1=E2”,其中左运算分量“E1”必须能被计算和修改。•左值表达式在赋值语句中既可以作为左操作数,也可以作为右操作数,例如“x=56”和“y=x”。而右值“56”只能作为右操作数,不能作为左操作数。•某些运算符可以产生左值。例如,如果“p”是一个指针类型的表达式,则“*p”就是一个左值表达式,它代表由p指向的对象,并且通过“*p=”改变这个对象的值。同理,“&p”也是左值表达式,可以通过“&p=”改变这个指针的指向。2.指向常量的指针•指向常量的指针是在非常量指针声明前面使用const,例如:constint*p;它告诉编译器,“*p”是常量,不能将“*p”作为左值进行操作,即限定了“*p=”的操作,所以称为指向常量的指针。•另外,如果需要将指针指向常量,则必须使用常量指针。下面是在定义时即初始化的例子:constinty=58;constint*p=&y;//指向常量的指针指向//常量y,y不能作为左值intx=45;constint*p1=&x;//数据只能通过左值x间接//改变*p1的值。“*p1”不能作为左值,但可以使用“x=”改变x的值,所以说,这个cons