《C++及Windows可视化程序设计第3章7204191565.ppt》由会员分享,可在线阅读,更多相关《C++及Windows可视化程序设计第3章7204191565.ppt(144页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、C+及及Windows可视可视化程序设计第化程序设计第3章章72041915653.7 结构3.8 联合实验习题虽然C+提供了基本数据类型,但它们的能力有限,还需要利用基本类型构造一些复杂的数据类型,这些以基本类型为基础构造出来的类型统称为构造类型。其实,构造类型的每一个分量都是一个对象,它可以是基本类型或者构造类型。这些分量可以与基本类型对象一样被赋值并在表达式中使用。合理地使用它们,不仅能准确、清晰地描述复杂的数据结构,而且还使得程序显得清晰、简洁。本节将探讨几个典型的构造类型,并简单说明它们的使用方法。随着应用的深入,还会构造出新的类型,这些构造类型相互之间又有一定联系,从而为程序设计提
2、供新的舞台。假设将一个整型对象x存放在以0012FF7C开始的内存单元中,如果要访问对象x,既可通过对象的名字x,也可通过该对象的首地址0012FF7C。可通过构造指针类型来实现这种操作。3.1 指针假设一个整型对象x的值为56,系统将为它在内存中分配一块连续的存储单元。如果这块存储区的首地址(即起始地址)为0012FF7C,则可以通过“&x”取得存放x的首地址(&称为取地址运算符,即&x=0012FF7C)。如果要访问对象x,既可以通过对象的名字x,也可以通过存放对象的首地址0012FF7C。如图3.1所示,假设使用一个运算符“*”来间接引用地址(&x)中存放对象的值,即“*&x”表示通过这
3、个地址访问对象x。下面的程序演示了这种方法,实现了所做假设。3.1.1 构造指针类型整型对象x存放的首地址0012FF7C地址运算:&x=0012FF7C赋值运算:x=56间接引用:*&x=5656图3.1 通过存放对象x的首地址间接引用对象x的值 #includeusing namespace std;void main() int x=56; coutx的值等于x ,存储x的首地址是&xendl; cout通过名字x使用x,通过首地址 &x使用*&xendl;程序输出结果如下:x的值等于56,存储x的首地址是0012FF7C通过名字x使用56,通过首地址0012FF7C使用56现在想使用一
4、个符号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,即
5、p=&x。这样就可以根据p找到 x,也就能对x进行存取。对象p和x之间的关系如图3.2所示。图3.2 导出新的数据类型示意图现在的问题是,如何表示这种新的数据类型?首先,它是针对整型对象的,所以可以借助整型类型int来声明。“p”这种形式已经被基本类型的对象使用,如果使用“int p;”的形式,则将p作为整型对象,显然是行不通的。如果使用“*”来标记这种数据类型,就可将p与整型变量区分开来,也就解决了问题。即 int *p;/声明称为构造类型的指针类型对象 p=&x; /地址运算 *p=x; /值引用因为这种数据类型声明的对象代表指向另一个数据类型对象的存储首地址,所以得名为“指针”类型。这里
6、是借助“*”号,而且与“*”号的位置没有关系,下面3种写法都是正确的: int * p;/“*”在中间,与谁都不连 int *p;/“*”与p连写为*p int* p;/“*”与int连写为int*读者有时会对使用如下方法在声明指针的同时初始化指针的方式感到困惑,即: int *p=&x;实际上,选择“int* p;”,认为“int*”是一种指向整型的指针类型,用它声明指针对象p,p应该赋予x的地址,所以应是“p=&x”。声明指向整型的指针对象p并同时初始化,也就顺理成章为“int* p=&x”。显然,称p为指针对象(存放的是对象的首地址),而不是称*p为指针对象(*p代表指针指向的地址单元所
7、存放的值)。因为指针对象存放的是地址,所以必须有具体指向。最常见的错误是声明了指针,没有为指针赋值。没有赋值的指针里含有随机地址,破坏性很大。声明并同时初始化指针的方法避免了遗漏赋值。由此可知,p的值是地址,虽然这个地址就是对象x在内存中的存储首地址,但我们并不直接说p的值是x的地址,而说成p指向x的存储首地址,简称p指向x的地址。【例3.1】 演示使用指针及指针地址。#includeusing namespace std;void main()int x=56;int* p=&x;coutx的值等于x,x的首地址是 &x,p指向的地址是pendl;cout通过名字使用x,通过p内的地址 p使
8、用*pendl;coutp指向的地址为p,存放p的地址是 &pendl;程序输出如下:x的值等于56,x的首地址是0012FF7C,p指向的地址是0012FF7C通过名字使用56,通过p内的地址0012FF7C使用56p指向的地址为0012FF7C,存放p的地址是0012FF78假设已经知道对象地址(NULL 也算已知),现在将上面的构造语法总结如下: 存储类型数据类型*指针名; 指针名=对象地址;或者采取直接初始化的方法: 存储类型数据类型*指针名=对象地址;默认的存储类型为自动存储类型(auto),目前也仅以自动存储类型为例,以后将通过例子进一步介绍存储类型。现在假设它们具有如图3.3所示
9、的形式: 3.1.2 指针类型及指针运算图3.3 指针操作关系图由关联关系可知,*p和x同步变化,即改变任何一个的值,它们的值保持一致。如果改变p的内容,例如使用语句“p=&y;”,这使得*p=65,它与y 同步,不再与x有任何关系。【例3.2】演示了这种情况。从例子中可见,指针本身的地址不会变化,它反映了系统需要为指针p分配地址这一概念。正如使用x不要再考虑&x一样,以后也不再考虑&p。【例3.2】 演示指针及其运算概念的例子。#include using namespace std; void main() int x=56,y=65,*p;p=&x;/指针指向xcoutx,p,&p, *
10、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, *p”(由键盘上的-和两键组成)运算符访问对象的成员,即: 对象指针名-对象成员名3.1.4
11、 进一步讨论指针【例3.3】 演示对象指针及访问对象成员的例子。#include#includeusing namespace std;void main()string str(How are you?);string *p=&str;/初始化对象指针p /str使用“.”访问str的成员函数size()coutstr size=str.size()”访问str的成员函数size() cout*p size=size()endl;coutsubstr(4,8) size= size()-4endl;使用对象和对象指针的效果一样,这从下面程序的输出中可以得到证实: How are you? s
12、ize=12 How are you? size=12 are you? size=82. void指针一般情况下,指针的值只能赋给相同类型的指针。void类型不能声明变量对象,但可以声明void类型的指针,而且void指针可以指向任何类型的C+对象。下面给出一个使用void指针的例子。【例3.4】 演示void指针的例子。#includeusing namespace std;void main()int x=56,y=65,*p=&x;void *vp=&x;/void指针指向xcoutvp,p,x endl; /*vp不能间接引用对象x的值vp=&y;/void指针改为指向yp=(int
13、*)vp; /强制将void指针赋值给整型指针coutvp,p,*p endl; /*p可以间接引用对象y的值虽然void指针指向整型变量对象x,但不能使用*vp引用整型对象的值。要引用这个值,必须强制将void指针赋值给与值相对应的整型指针类型。程序输出如下: 0012FF7C,0012FF7C, 56 0012FF78, 0012FF78, 653. 指针的指针可以将定义指针的方法推广下去,定义指向指针的指针,不过并不建议使用多级指针。假如p1是指向整数对象的一级指针,语句“int *p2=&p1”定义了二级指针。可以通过下面的语句演示多级指针的定义及输出对象x的方法。 int x=88;
14、 /定义多级指针 int *p1=&x,*p2=&p1,*p3=&p2; cout*p1,*p2, *p3endl;/输出: 88,88,884. 自己给指针分配地址如果不使用对象地址初始化指针,可以自己给它分配地址。4. 自己给指针分配地址如果不使用对象地址初始化指针,可以自己给它分配地址。对于只存储一个基本类型数据的指针,如果为它申请可以存储size个该数据类型的对象,则申请的方式为: new 类型名size例如申请可以存储3个double类型的对象: double *p;/声明double型指针 p=new double3;/分配3个double型长度的地址不再使用时,简单地使用如下方式
15、释放存储空间: delete 指针名【例3.5】 演示使用new和delete的例子。#includeusing namespace std; void main()double *p;/声明double型指针p=new double3;/分配3个double型长度的地址for(int i=0;i*(p+i);/将输入数据存入指定地址for(i=0;i3;i+) cout*(p+i) ;/将地址里的内容输出delete p; 在同一行输入“12.1 12.2 12.3”,则输出: 12.1 12.2 12.3因为引用是指针通过“间接引用地址”实现存取对象的,所以语法比较难懂,也很容易出错。仍以
16、对象x=56为例,如果给对象x再起一个名字a,a与x的地址一样,则x和a同步变化。这就像一个作家,本来的名字叫“张三”,起个笔名叫“雨季”,则“雨季”即“张三”,“张三”即“雨季”。也就是说,给整数对象x起个“别名”,问题是如何起这个别名。一般的标识符已经被变量对象占用,“&”和“*”号也用掉了。既然这个别名与原来对象的地址有关,那就选定命名时使用地址符号“&”,再选用数据类型与之配合,声明方式为:3.2 引用 数据类型& 别名=对象名;图3.4是引用示意图。使用引用语句“int& a=x;”之前,对象x必须事先初始化。图图3.4 引用示意图引用示意图【例3.6】 演示引用的例子。#inclu
17、de using namespace std;void main()int x=56;int& a=x;/定义a是x的引用,a和x的地址一样int&r=a; /定义r是a的引用,r和a的地址一样 /即r和x的地址一样coutx=x,&x=&x,a= a,&a=&a ,r=r,&r=&rendl;r=25;/改变r,则a和x也同步变化coutx=x,&x=&x,a= a,&a=&a ,r=r,&r=&rendl;由输出结果可见,引用对象和被引用对象的地址一样,所以同步变化。程序输出如下: x=56,&x=0012FF7C,a=56,&a=0012FF7C,r=56,&r=0012FF7Cx=25
18、,&x=0012FF7C,a=25,&a=0012FF7C,r=25,&r=0012FF7C由此可见,所谓“引用”就是将一个新标识符和一块已经存在的存储区域相关联。因此,使用引用时没有分配新的存储区域,它本身并不是新的数据类型。引用在某些地方很像一个可以被逆向访问的常量指针。这里的逆向访问指的是可以通过引用来修改被引用的对象,同样可以通过被引用的对象来修改引用。可以对比一下常量指针和引用的使用规则: 创建一个引用时,该引用必须被初始化;常量指针亦是如此。 一旦一个引用被初始化指向一个对象,它不能被改变为对另外一个对象的引用;常量指针一旦指向了一个对象,也不能更改为指向另外的对象。 不能有空引用
19、。在程序中必须要确保引用是和一块正确的存储区域关联,但是可以有指向空的常量指针,例如这样的声明是正确的: int* const x=NULL; 但是这种指向空的常量指针在实际应用中并没有什么价值。引用通常用于函数的参数表中和函数的返回值。对引用实质的理解应抓住如下两点: 引用实际上就是变量的别名,使用引用就如同直接使用变量一样。引用与变量名在使用的形式上是完全一样的,但引用本身并不是对象,只是作为一种标识对象的手段。因此,引用有其与对象不同的特点,即不能声明引用的引用。语句“int& & r=x;”是对x引用的引用,是错误的。可以声明对指针的引用(如p2是指针,则int*& p1=p2;是可以
20、的),但不能声明指针对x的引用,即“int* & p=&x;”是错误的,但可以声明指向引用的指针“int* p=&a;”。 引用的作用与指针有相似之处,它会对内存地址上存在的变量进行修改,但它不占用新的地址,从而节省开销。但要注意,引用与指针是有区别的,除了使用形式上的不同以外,在本质上它们也有区别: 指针是低级的直接操作内存地址的机制,指针功能强大但使用不慎极易产生错误。在C+语言中,指针可由整型数强制类型转换得到,这就为不良程序的产生提供了可能,处理不当就可能对系统造成极大的破坏。另一方面,引用则是较高级地封装了指针的特性,它不直接操作内存地址,不可以由强制类型转换而得,因而具有较高的安全
21、性,也不容易产生由于使用指针而常常产生的那些不易察觉的错误。在处理对象时,使用引用不失为一种很好的选择。【例3.7】 演示指针引用的例子。#include using namespace std;void main()int x=56,*p1=&x;int& a=x; /a引用x,符号&的位置无关int*& p2=p1;/指针p2引用指针p1,*&的顺序不能错coutx=x,a=a,*p1= *p1,*p2=*p2endl;*p2=65; /通过引用的指针p2使它们同步变化coutx=x,a=a,*p1= *p1,*p2=*p2endl;int* p3=&a;/定义指针p3,使它指向x的别名a
22、 *p3=32; /通过指针p3使它们同步变化coutx=x,*p1=*p1,&x= &x,p3=p3endl;输出结果如下:x=56,a=56, *p1=56, *p2=56x=65,a=65, *p1=65, *p2=65x=32,*p1=32,&x=0012FF7C, p3=0012FF7C【例3.8】 演示string对象引用的例子。#include #include using namespace std;void main() string s1=We are here!;string&str=s1; /str引用s1string* sp1=&s1; /string指针sp1指向对
23、象s1string*& sp2=sp1;/指针sp2引用指针sp1coutstr=str,*sp1=*sp1, *sp2=*sp2endl;*sp2=Go home!;/通过引用指针使它们同步变化coutstr=str,*sp1= *sp1,s1=s1endl;输出结果如下:str=We are here!,*sp1=We are here!,*sp2=We are here!str=Go home!, *sp1=Go home!, s1=Go home!数组是另一种构造型数据类型。本节重点讨论一维数组,简要介绍多维数组。3.3 数组假如要表示5个连续整数对象15,则需要5个整数对象名称,例如
24、用X1X5表示。这批对象的特点是它们的基本数据类型一样。3.3.1 一维数组图3.5 数组构成示意图现现在构造一个新的数据类型,假设它的名称为在构造一个新的数据类型,假设它的名称为A,在符号,在符号“ ”内用序号表示为内用序号表示为A 0A 4,它们的对应关系如图它们的对应关系如图3.5所示:所示: 后一种表示有很大的进步。X1X5之间没有内在关系,而A0A4之间是通过“”内的序号04(也就是下标)构成了惟一的连续对应关系。如果把A0A4看做连续的房间,则可以改变房间里所放整数的值。暂且将A称做整数房间。由此可见,房间里必须存放同一种对象,这样构造出来的数据类型称为整型数组。假定使用语法定义如
25、下: int A5=1,2,3,4,5;它的含义是整型数组A的下标从0开始,A4的值为5,A5本身不是数组的元素。这个数字5代表数组A共有5个元素A0A4。由此可见,把若干个同类对象线性地组合起来,就构成一维数组。在使用一维数组之前首先必须声明它,这包括数组的类型、数组名和数组元素的个数。声明一维数组的一般方式如下: type array_namen;其中,type是数据类型,可以是已经介绍过的基本数据类型,也可以是即将介绍的构造类型。array_name为数组名(标识符),n为数组中所包含的数组元素的个数。数组与前面介绍的各种基本数据类型对象的不同之处是: 数组须由type、数组标志 及长度
26、三者综合描述,它是在基本数据类型基础上导出的一种数据类型。例如:/定义整型数组具有10个元素/每个元素是一个整数对象 int a10;/定义浮点数组具有5个元素/每个元素是一个实数对象float b5;/定义字符数组具有20个元素/每个元素是一个字符对象char c20;数组中各元素总是从0开始连续编号,直到n-1为止。所以,上述定义的数组a、b、c的数组元素分别为: a0、a1、a2、a9b0、b1、b2、b3、b4c0、c1、c2、c19对数组中的任何元素都可单独表示并对它进行访问。例如语句“couta3;”输出a3的值。C+语言中规定只能对数组的元素操作,而不能对整个数组操作,即语句“c
27、outa;”是不允许的,必须使用for语句依次遍历整个数组元素。例如遍历数组a: for(int i=0; i10; i+) couta iendl;数组下标可以是变量,这个变量称为下标变量。下标变量的值原则上是从0开始到n-1的范围之内,但是在C+语言中对数组的下标变量的值是不进行合法检查的,所以允许数组的下标越界,对此程序员必须引起注意,避免产生错误。有丰富程序设计经验的程序员,有时常巧妙地利用数组的下标越界来进行程序设计。【例3.9】 给出求斐波那契数列中前20个元素值的问题。所谓斐波那契数列,就是可以把它表示成F0、F1、F2、,其中除F0和F1以外,其他元素的值都是它们前两个元素值之
28、和,即Fn=Fn-2+Fn-1,而F0、F1分别为0和1。为此,声明整数数组Fibonacci20来依次存放斐波那契数列的前20个元素值。#include #include using namespace std;void main()int fibonacci 20=0,1;/初始化 /输出右端对齐coutsetw(6)fibonacci 0setw(6)fibonacci 1; for(int n=2; n20; n+)/计算后18个元素值 fibonacci n=fibonacci n-2+fibonacci n-1; /分4行打印,按每行5个数打印输出,右对齐 if(n%5=0) co
29、utendl; coutsetw(6)fibonacci nsetw(6); 输出结果如下: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377610 987 1597 2584 4181实际上,斐波那契数列在数学和计算机算法研究领域有许多用途。斐波那契数列起源于“兔子问题”。开始有一对兔子,假设每对兔子每个月生下一对新的兔子,而每一对新生下来的兔子在出生后的第2个月底开始繁殖后代,而且这些兔子永远不死,那么在1年之后一共会有多少对兔子?这一问题的答案建立在这样一个事实上,即在第n个月结束时,有总数为Fn+2的兔子。所以,根据程序输出结果,在第12个月结束时将一共
30、有377对兔子。数组的元素可像一般对象那样进行赋值、比较和运算。如同简单对象一样,在说明数组之后就能对它的元素赋值,方法是从数组的第1个元素开始依次给出初始值表,表中各值之间用逗号分开,并用一对花括号将它们括起来。例如: int A =1,2,3,4,5;A数组元素的个数由编译程序根据初始值的个数来确定。如果数组很大,又不需要对整个数组进行初始化,数组初始值表可以只对前几个元素置初值,但这时必须指出数组的长度。例如,【例3.9】中的存放斐波那契数列的数组中前两个元素是已知的,而后面18个元素是要计算产生的,所以对此可初始化为: intfibonacci 20=0,1;这样数组中的前2个元素分别
31、为0、1,而后18个元素皆为0。字符数组初始化格式比较特殊,可以用字符串来代替,如: charpattern =the;也可以同整型数组一样初始化: charpattern = t,h,e, 0 ;上述2种方式置初值是等价的。在对字符数组初始化时,为使程序能觉察到此数组的末尾,在内部表示中,编译程序要用0来结束这个数组,从而存储的长度比双引号之间的字符的个数多一个。在上面的具体例子中,the字符串中字符数是3,而pattern的长度却是4。如果没有明确进行初始化,则编译程序将外部型和静态型的数组初始化为0,而自动型数组的值不定。如不给自动数组置初值,程序中又没有使用它,编译系统会给出警告信息。
32、C+语言允许自动型数组有初始值,它与外部型和静态型数组初始值都是在定义语句的花括号里,每个初始值用逗号分隔。例如: int d5=0,1,2,3,4;如果定义时只对数组元素的前几个初始化,则其他元素均被初始化为0;如没有进行初始化,则C+编译程序使用如下规则对其初始化: 外部型和静态型对象的初始值为0。 自动型和寄存器型对象的初始值为随机数。C+的数组名字就是这个数组的起始地址,所以指针和数组有密切关系。任何能由数组下标完成的操作,也能用指针来实现。使用指向数组的指针,有助于产生占用存储空间小、运行速度快的高质量的目标代码。指向数组的指针实际上指的是能够指向数组中任一个元素的指针。这种指针应当
33、说明为数组元素类型。例如,程序中有整型数组a int a5;于是,只要说明一个整型指针变量: int *pa;3.3.2 数组与指针的关系就可使用pa指向整型数组a中的任何一个元素。使pa指向a的第1个元素最简单的方法是: pa=a;因为“pa=&ai”代表下标为i的元素的地址,所以也可使用如下赋值语句指向第1个元素: pa=a0;如果需要表示指针所指向的存储单元的内容,可使用“*”运算符,例如: *pa=*a;于是,如果pa正指向数组a中的最后一个元素,那么赋值语句: a4=123;也可以表示成 *pa=123;在数组名和指针之间有一个区别,必须记住指针是变量,故pa=a或pa+都是有意义的
34、操作。但数组名是指针常量,而不是变量,因此表达式a=pa,a+,pa=a都是非法操作。假设指针现在指向 a0,则数组的第i个(下标为i)元素可表示为ai或*(pa+i)。还可使用带下标的指针pa,即pai和*(pa+i)的含义一样。若要将a的最后一个元素值设为123,下面语句是等效的: a4=123; *(a+4)=123; *(pa+4)=123; pa 4=123;所以在程序设计中,凡是用数组表示的均可使用指针来实现,一个用数组和下标实现的表达式可以等价地用指针和偏移量来实现。表3.1给出了一个数组元素的4种关系。(表3.1见书89页)表表3.1 指针与数组的关系指针与数组的关系下 标数组
35、名指 针指 针 下标四 者 逻 辑 关 系A0 a papa0 a0= = *a = = *pa = = pa0A1 a+1 pa+1pa1 a1= = *(a+1) = = *(pa+1) = = pa1A2 a+2 pa+2pa2 a2= = *(a+2) = = *(pa+2) = = pa2A3 a+3 pa+3pa3 a3= = *(a+3) = = *(pa+3) = = pa3A4 a+4 pa+4pa4 a4= = *(a+4) = = *(pa+4) = = pa4【例3.10】 演示没有另外定义指针,直接使用数组名作为指针的例子。#includeusing namespac
36、e std;void main()int a =1,2,3,4,5;*(a+4)=34;/使用数组名a作为数组第1个元素的指针for(int i=0; i5; +i) couta i *(a+i) ;/交替使用两种方式输出程序输出为: 1 1 2 2 3 3 4 4 34 34【例3.11】 演示数组与指针关系的例子。#include using namespace std;void main() int a=1,2,3,4,5,*p=a;/相当于int *p=&a0; for(int i=0; i5; +i) /演示3种输出方式 coutai *(a+i) *(p+i) ; coutendl
37、a, pendl; /演示a即数组地址 for(i=0; i5; i+) /演示指针使用下标 coutpi ; for(;pa+5;+p) /演示从a0开始输出至a4 cout=a;-p) /演示从a4开始输出至a0 cout*p ;开始执行最后一个for语句时,p=&a5,这时已经造成数组越界,所以要使用减一操作。程序输出如下: 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 0012FF6C,0012FF6C 1 2 3 4 5 1 2 3 4 5 5 4 3 2 1可以定义任何维的数组,多维数组用来表示由多个下标才能决定的量。例如: intx32;表示数组x是二维数组,它有3
38、行2列,x数组中的每个元素都具有整数类型。在C+语言中,二维数组的元素按行存储,即当按照存储顺序存取元素时,最右边的下标变化最快,数组x的元素依次为: x00 x01 x10 x11 x20 x213.3.3 多维数组其实二维数组是一种抽象,因为计算机存储器是一维的。编译程序必须实现从所使用的抽象数组到组成计算机存储的实际的一维数组的映射。下面来研究日期转换问题,把某月的第几天转换为这年的第几天或反之。例如,3月1日是非闰年的第60天,把某月某日转换为这年的第几天。由于2月份的天数因闰年和非闰年而异,故用二维数组的第2行第3列存放闰年的2月份天数。详细实现见下面的【例3.12】。【例3.12】
39、 将年月日转换成一年中第几天。#includeusing namespace std;int day_of_year(int,int,int);void main() int year,month,day; cout输入格式示范: 1997 1 5nyearmonthday; coutday_of_year(year,month,day)endl;int day_of_year(int year,int month,int day) int leap=0; static intday_tab 2 13= 0,31,28,31,30,31,30,31,31,30,31,30,31 , 0,31,
40、29,31,30,31,30,31,31,30,31,30,31 ; leap=year % 4 = 0 & year % 100 !=0 year % 400 =0; for(int i=0; imonth; +i) day+=day_tab leap i; return(day);运行实例: 输入格式示范: 1997 1 5 输入: 2002 3 5 64本例演示了day_tab二维数组的初始化。一个二维数组的每一行由对应的子表赋初值,数组的第0列为0。月份号是1 12,而不是0 11。这里空间并不那么珍贵,故此处共有13列,这比起调整下标来要容易得多。对二维数组进行初始化可用两种方法,一
41、种如本例中对day_tab213进行的初始化,按行排列比较清晰。另一种是按顺序列出,例如: static intday_tab213= 0,31,28,31, 30,31,30,31,31,30,31,30,31,0,31, 29,31,30,31,30,31,31,30,31,30,31 ;但是,这种方法不直观。像一维数组一样,对二维数组也可以进行局部初始化,如: int y43= 1,3,5 , 2,4,6 , 3,5,7 ;它只对数组中前3行的元素赋初值,其余元素 y30、y31、y32的值为0。数组变化时,是先变最右边的下标。而 int y43= 1,2,3,4 ;只对y的第0列置初值
42、,即y00=1, y10=2,y20=3,y30=4,其余均为0。推而广之,可以用同样方式定义多维数组和使用其元素,例如: intw456;定义了w为一个三维数组,其各维长度分别是、,下标变化规律与二维的相同。字符串数组以“0”结束,它的长度应是存储字符长度加1。下面是两种初始化方法: char str3= w,e, 0; /必须手工以“0”结束 char str3=we;/编译程序将自动加上 /结束标记“0”两种形式均可不给出长度,而使用“ ”号。与数值数组一样,字符串数组的名字就是字符串的首地址。不同的是,字符串是顺序存放并以“0”作为结束标志,所以字符串既可以按顺序输出,也可以用名字整体
43、输出。在读入字符串时,单个字符串中不能有空格。3.3.4 字符串数组【例3.13】 演示字符串数组和指针的例子。#includeusing namespace std;void main() char a=DATA,*p=data;/定义字符串和指针 couta,p,; /整体输出各自的内容 while(*p) /顺序输出,直到为空 cout*p+; /输出当前*p p=a; /重新为指针p赋值 coutendl p,aendl; /整体输出字符串顺序存放,字符串名是其首地址,p输出的不是地址,而是地址里的内容。即: DATA,data,data DATA,DATA一般很少使用二维以上的字符串
44、数组。多维字符串数组中的每一个元素都是一个字符串。例如如下定义 char str412;说明str有4个字符串,每个字符串的最大长度是11个字符。二维字符串数组一般可以表示为: char 数组名字符串数量字符串最大长度;二维字符串数组初始化可按行进行。例如: char str 412= Turbo C, Microsoft C,FORTRAN , PASCAL;也可以使用赋值语句,如给第1个字符串赋值: str00=T; str01=u; str02=r; str03=b; str04=o; str05= ; str06=C; str07=0;使用方法与二维数组一样,但专业化的调用方式是只指出
45、左下标。例如: str0=Turbo C; str3=PASCAL;指针本身也是变量对象,所以将指向相同对象的指针变量集合在一起就构成一个指针数组。指针数组的每一个元素均为指针变量,例如: int*p5;说明p是一个数组,它由5个元素组成,每个元素均是指向整型对象的指针。3.3.5 指针数组【例3.14】 演示字符串指针数组的例子。#includeusing namespace std;void main()char *p =DOG,CAT,COMPUTER, MICROPROCESSOR,DATA; for(int i=0; i5; +i) cout*(p+i) ;p 为具有 5 个字符指针
46、的数组,而地址里所含有的字符数量不一,这就大大减少了内存开销。程序输出为: DOG CAT COMPUTER MICROPROCESSOR DATA在程序开始执行时,可将命令行传送给程序。当调用主程序main时,让它带有2个参数。第1个参数习惯上叫做argc,是表示被调用程序所带命令行参数的数目;第2个参数argv是指针数组,其中每个元素是指向包含命令行参数的字符串的指针,即每个指针对应一个字符串,而第1个指针指向的通常是命令名字符串。在实际应用中,命令行参数是很有用的。3.3.6 命令行参数【例3.15】 实现echo命令,该命令实现参数回响,将它的命令行参数回响在一个单行上,并用空格将它们
47、分隔。#includeusing namespace std;void main(int argc,char *argv ) for(int i=1; iargc; +i) cout(argv i,(iecho hello world输出: hello world在执行上述命令时,这两个参数值将是: argc=3argv0 是“echo”argv1 是“hello”argv2 是“world”也就是说,main函数可以获得命令行参数的个数及参数的内容。在数组生存期内,数组的大小是不会改的,向量容器则可在运行中动态地增长或缩小。向量是类模板,具有成员函数,例如可以使用size()方法动态地获得v
48、ector的当前大小。面向对象的向量vector是使用最广泛的一个容器类,本节重在介绍它的使用方法。3.4 向量容器vector类模板定义在头文件vector中,它提供4种构造函数,用来定义由各元素组成的列表。用length表示长度,数据类型用type表示,列表名为name,则: /定义type的向量空表 vectorname; /定义具有length个type的向量,元素初始化为0 vectorname(length); /定义具有length个type的向量,元素初始化为a vectorname(length,a);3.4.1 定义向量列表 /使用已定义的向量name构造向量name1 v
49、ectorname1(name); 空表没有元素,它使用成员函数获取元素。下面是典型的使用方法: vectorA; /空的char向量 /具有20个int的向量,元素初始化值均为0 vectorB(20); /具有20个int的向量,其元素均被置为1 vectorC(20,1); vectorD(C);/用C初始化D,即D与C一样 /具有20个char的向量,其元素均被置为t vectorE(20,t); /具有4个int的向量,其元素均被置为5 vectorF(2*2,5); /与F相同,有4个int向量且均被置为5 vectorG(F); D=F;/整体赋值,使D与F相同 G=C;/整体赋
50、值,使G与C相同向量定义的赋值运算符“=”,允许同类型的向量列表相互赋值,而不管它们的长度。它可以改变赋值目标的大小,使它的元素数目与赋值源的元素数目相同。虽然E和C的元素数目相同,因为它们的类型不同,因此也不能相互赋值。向量第1个元素也是从0开始,使用下面语句 coutB.size(),C.size(), D.size(),E.size(), F.size(),G.size()endl;可得到它们的大小为: 20,20,4,20,4,20。使用for语句循环输出时,可以利用size()函数。例如输出向量B: for(int i=0; iB.size(); i+) coutB iendl;不能