C++第四章习题解答
第四章类与对象习题
一.基本概念与基础知识自测题
填空题
引入类定义的关键字是(1)。类的成员函数通常指定为(2),类的数据成员通常指定为(3)。指定为(4)的类成员可以在类对象所在域中的任何位置访问它们。通常用类的(5)成员表示类的属性,用类的(6)成员表示类的操作。
答案:
(1)class
(2)公有的public
(3)私有的private
(4)公有的public
(5)数据
(6)$
(7)函数
类的访问限定符包括(1)、(2)和(3)。私有数据通常由(4)函数来访问(读和写)。这些函数统称为(5)。
答案:
(1)public(公有的)
(2)private(私有的)
(3)protected(保护的)
(4)公有的成员函数
(5)类的接口
通常在逻辑上,同一类的每个对象都有(1)代码区,用以存储成员函数。而在物理上通常只有(2)代码区。只有在(3)定义,并(4)的函数和加了关键字(5)的函数例外。
答案:
(1)|
(2)独立的
(3)共用的
(4)在类说明中
(5)不包括循环等复杂结构
(6)inline
C++中支持三种域:(1)、(2)、(3)。函数域被包括在(4)中,全局域被包括在(5)中。using指示符以关键字using开头,后面是关键字(6),最后是(7)。这样表示以后在该名字空间中所有成员都(8)。
如不使用using指示符则在使用时要加::,称为(9)运算符。
答案:
(1)局部域(local scope)
(2)名字空间域(namespace scope)
(3)类域(class scope)
(4)/
(5)局部域
(6)名字空间域
(7)namespace
(8)名字空间名
(9)可以直接被使用
(10)域
引用通常用作函数的(1)和(2)。对数组只能引用(3)不能引用(4)。
答案:
(1)参数
(2)返回值
(3)~
(4)数组元素
(5)数组名本身
构造函数的任务是(1)。构造函数无(2)。类中可以有(3)个构造函数,它们由(4)区分。如果类说明中没有给出构造函数,则C++编译器会(5)。拷贝构造函数的参数是(6),当程序没有给出复制构造函数时,系统会自动提供(7)支持,这样的复制构造函数中每个类成员(8)。
答案:
(1)初始化数据成员
(2)函数返回类型说明
(3)多
(4)不同的参数表
(5)自动给出一个默认的构造函数
(6)同一类对象的引用
(7):
(8)默认的的复制构造函,称为默认的按成员语义支持。
(9)被依次复制
一个类有(1)个析构函数。(2)时,系统会自动调用析构函数。
答案:
(1)一
(2)对象注销时
运算符重载时,其函数名由(1)构成。成员函数重载双目运算符时,左操作数是(2),右操作数是(3)。
答案:
(1)关键字operator和该运算符
(2)对象
(3)…
(4)该函数的参数
面向过程的程序设计中程序模型描述为(1),面向对象程序设计的程序模型可描述为(2)。
答案:
(1)“程序=算法+数据结构”。其数据与数据处理是分离的。
(2)程序=(对象+对象+……+对象)+消息;对象=(算法+数据结构)。面向对象设计将数据和对数据的操作方法放在一起,形成一个相对独立的整体——对象(Object),并通过
简单的接口与外部联系。对象之间通过消息(Message)进行通讯。
简答题
简单解释什么是面向对象程序设计的封装性。
答:对象是一个封装体,在其中封装了该对象所具有的属性和操作。对象作为独立的基本单元,实现了将数据和数据处理相结合的思想。此外,封装特性还体现在可以限制对象中数据和操作的访问权限,从而将属性“隐藏”在对象内部,对外只呈现一定的外部特性和功能。
封装性增加了对象的独立性,C++通过建立数据类型——类,来支持封装和数据隐藏。一个定义完好的类一旦建立,就可看成完全的封装体,作为一个整体单元使用,用户不需要知道这个类是如何工作的,而只需要知道如何使用就行。另一方面,封装增加了数据的可靠性,保护类中的数据不被类以外的程序随意使用。这两个优点十分有利于程序的调试和维护。(
C++编译器怎样对标识符进行解析
答:编译器对标识符的解析分两步,第一步查找在声明中用到的标识符,特别是函数成员声明中用到的参数类型,第二步是函数成员体内的标识符。
为什么说类与对象的概念是客观世界的反映
答:客观世界的事物都具有某些属性和行为(或操作),具有相同属性和行为的事物可以归属于一类,用分类的方法可以提高认识事物的效率。C++中定义的类则是通过抽象的方法将某一类事物共有的静态特征(属性)和动态特征(行为)概括出来并加以描述,而对象是类的具体实现,所以说类与对象的概念是客观世界的反映。
什么叫类域为什么说类域是抽象的
答:类域是类体所包括的范围。每个类定义都引入了一个独立的类域,在类域中说明的标识符仅在该类的类域中有效。由于类只是一个说明,看上去有数据,有函数,有类型定义,但是它并非实体,不分配内存,当然也不能运行。所以说类域是抽象的。
{
引用作为函数参数时为什么能实现两个实参之间的数据交换为什么对应实参不能为引用为什么返回值为引用的函数可以作为左值
答:引用变量是其关联变量的别名,二者在内存中占据同一个存储单元。在一个以引用作为参数的函数中,交换两个参数的值,实际上就是交换两个实参的值。如果函数的参数是引用,调用时需要取得实参的地址,而实参如果已经是一个地址,再进行引用将产生错误,故对应实参不能为引用。
函数返回引用实际是指明(返回)了相应的关联变量,所以声明返回值为引用的函数实际上是将关联变量作为左值参与运算。
什么是缺省的构造函数缺省的构造函数最多可以有多少个
答:如果在类定义中不显式地定义构造函数,C++编译器会自动产生一个缺省的构造函数,不过该函数不做具体的初始化工作。只要构造函数是无参的或者只要各参数均有缺省值的,C++编译器都认为是缺省的构造函数。缺省的构造函数只能有一个。
拷贝构造函数用于哪三个方面
答:
(1)用类的一个对象去初始化该类的另一个对象时使用。
(2);
(3)当函数的形参是类的对象,调用函数时,进行形参与实参结合时使用。
(4)当函数的返回值是类对象,函数执行结束返回调用者时使用。
写出含有对象成员的类的构造函数的格式,并做简单说明。
答:C++中对含对象成员的类对象的构造函数有固定的格式:
类名::构造函数名(参数总表):对象成员1(参数名表1),对象成员2(参数名表2),……对象成员n(参数名表n){……}
冒号后用逗号隔开的是要初始化的对象成员,附在后面的参数名表1,…,参数名表n依次为调用相应对象成员所属的构造函数时的实参表。这些表中的参数通常来自冒号前的参数总表,但没有类型名。
所有类对象未重载的赋值运算符“=”是怎样工作的为什么它可以进行连续赋值
答:对所有的类对象,未重载的赋值运算符“=”称作缺省的按成员拷贝赋值操作符,同类对象之间可以用“=”直接拷贝。因为缺省的赋值操作返回一个对象的引用,所以它可以进行连续赋值。
…
为什么在友元函数的函数体内访问对象成员时,必须用对象名加运算符“.”再加对象成员名
答:友元函数不是类的成员函数,在函数体中访问对象的成员,必须用对象名加运算符“.”加对象成员名。这一点和一般函数一样。
重载复数运算符+时,采用下面友元函数声明:
friend Complex operator+(Complex &c1,Complex &c2);
为什么不能用于“实数+复数”怎样改进才能适用为什么
答:使用引用类型变量作为运算符重载函数的参数,身为左值的实数类型实参不能被转换为复数,编译时无法通过。添加const说明,使实数到复数的转换隐式地在一份拷贝上进行,则可以实现“实数+复数”运算。修改后的说明为:
friend Complex operator+(const Complex &c1, const Complex &c2);
}
类的静态数据成员与函数中的静态成员有何异同
答:类的静态成员为其所有对象共享,不管有多少对象,静态成员只有一份存于公用内存中,为该类所有对象公用。函数中的静态变量也位于公用内存中,不随函数调用而重新分配,所以总是保留上次进入并执行该函数后留下的信息。
C++中结构、联合与类三者间有何异同
答:在C++中结构(structure)与类几乎是完全一样的类型,差别仅仅在于缺省情况下结构的成员为公有的。联合(union)是C++的导出数据类型,在语法与功能上类似于结构,二者的区别是:结构变量的各成员同时被分配了各自独立的内存区,而联合变量的各个成员的存储开始地址都相同,所以在任一时刻联合变量只能存储一个成员。
对象的第一特征是封装,那么由对象组成的面向对象的程序怎样建立各对象之间的有效联系面向对象程序的组织与面向过程有什么不同
答:因为对象的操作主要用来响应外来消息并为其他对象提供服务,所以面向对象的程序利用消息传递机制来建立各对象之间的有效联系,协调各对象的运行。一个对象可以向其他对象发送消息以请求服务,也可以响应其他对象传来的消息,完成自身固有的某些操作,从而服务于其他对象。
面向过程的程序是模块化的,模块的组织具有分层结构特点,层与层之间是调用关系。面向对象程序是由一个个封装的对象组成,而对象是由紧密结合在一起的算法和数据结构组成。对象之间是相互请求和相互协作的关系。
》
简叙Windows下应用程序的运行方式。
答:Windows系统支持多个应用程序同时执行,在界面形式上,它支持多个窗口同时活动。它的运行机制是“消息传递和事件驱动(message based and event driven)”。
Windows系统使用事件驱动的编程模式。所谓事件的含义非常广泛。输入设备的动作,如敲打键盘、按鼠标等会产生一系列的事件(注意不是一个事件)。操作系统所作的一举一动也被当作某种类型的事件,应用程序也会产生各种事件。事件用来标识发生的某件事情。
Windows系统对于应用程序环境中发生的每一个事件都会以对应的某种消息的形式标识,并放入相应的Windows建立的消息队列中,然后由对应的应用程序或窗口函数去处理。窗口函数处理是分层的,前面处理不了的送到后面,最后处理不了剩下的全由缺省的窗口函数处理。
类的成员函数在什么情况下应该定义为私有的这样做的目的是什么
答:除接口函数和创建本类对象的构造函数和撤消该对象的析构函数外。其余成员函数应该定义为私有的,这是开发类时故意对外隐蔽起来的操作,而这些往往是最复杂最关键的部分。类中故意的隐藏也为以后的升级扩展留下了余地,只要接口不变,内部再变,也不必修改原来的程序,就象MFC(微软基础类)升级后,由MFC底层类所编的程序完全不必修改,自动升级。
二.编程与综合练习题
构造一个日期时间类(Timedate),数据成员包括年、月、日和时、分、秒,函数成员包括设置日期时间和输出时间,其中年、月请用枚举类型,并完成测试。(包括用成员函数和用普通函数)
)
解:本题要求仅是定义类的练习,并非实用的提供日期时间的程序。实用的日期时间程序见附录二的日期时间函数。
#include
#include
using namespace std;
enum YR{Y2000,Y2001,Y2002,Y2003,Y2004,Y2005};
Circumference=2*r*3.;
}
Circle::Circle(Circle & cl){
r=;
Area=;
…
Circumference=;
}
void Circle::SetR(double R){
r=R;
Area=r*r*3.;
Circumference=2*r*3.;
}
int main(){
$
Circle cl1(2),cl2,cl3=cl1;
cout<<"圆半径:"<<()<<'\t'<<"圆周长:"<<()
<<'\t'<<"圆面积:"<<()< (4); cout<<"圆半径:"<<()<<'\t'<<"圆周长:"<<() <<'\t'<<"圆面积:"<<()< return 0; } 设计一个学校在册人员类(Person)。数据成员包括:身份证号(IdPerson),姓名(Name),性别(Sex),生日(Birthday)和家庭住址(HomeAddress)。成员函数包括人员信息的录入和显示。还包括构造函数与拷贝构造函数。设计一个合适的初始值。 * 解:本题为指出构造函数等的调用,加了一些提示语句。 #include #include using namespace std; enum Tsex{mid,man,woman}; class Person{ char IdPerson[19]; //身份证号,18位数字 char Name[20]; //姓名 Tsex Sex; //性别 int Birthday; //生日,格式1986年8月18日写作 < char HomeAddress[50]; //家庭地址 public: Person(char *,char *,Tsex,int,char *); Person(Person &); Person(); ~Person(); void PrintPersonInfo(); void inputPerson(); //其他接口函数 }; " Person::Person(char *id,char *name,Tsex sex,int birthday,char *homeadd){ cout<<"构造Person"< strcpy(IdPerson,id); strcpy(Name,name); Sex=sex; Birthday=birthday; strcpy(HomeAddress,homeadd); } Person::Person(){ cout<<"缺省构造Person"< ! IdPerson[0]='\0';Name[0]='\0';Sex=mid; Birthday=0;HomeAddress[0]='\0'; } Person::Person(Person & Ps){ cout<<"拷贝构造Person"< strcpy(IdPerson,; strcpy(Name,; Sex=; Birthday=; strcpy(HomeAddress,; ' } Person::~Person(){ cout<<"析构Person"< } void Person::inputPerson(){ char ch; cout<<"请输入身份证号,18位数字:"< (IdPerson,19); cout<<"请输入姓名:"< (Name,20); ! cout<<"请输入性别m或w:"< cin>>ch; if(ch=='m') Sex=man; else Sex=woman; cout<<"请输入生日,格式1986年8月18日写作:"< cin>>Birthday; (); //吸收回车符,否则地址输不进去 cout<<"请输入地址:"< (HomeAddress,50); } ^ void Person::PrintPersonInfo(){ int i; cout<<"身份证号:"< if(Sex==man)cout<<"男"<<'\n'; else if(Sex==woman)cout<<"女"<<'\n'; else cout<<" "<<'\n'; cout<<"出生年月日:"; i=Birthday; cout< i=i%10000; ^ cout< } int main(){ Person Ps1("320","朱海鹏",man,,"南京市黄浦路1号"), Ps2(Ps1),Ps3; (); (); (); (); ; return 0; } 对上面4道题中的类对象的数据,由友元函数来进行访问。并对比友元函数、成员函数和普通函数使用上的不同。 解:友元函数可直接访问私有成员。友元函数在类中声明时不问访问权限。仅以题为例。#include #include using namespace std; class Rectangle { double left, top ; ? double right, bottom; public: Rectangle(double l=0, double t=0, double r=0, double b=0); ~ Rectangle(){}; //析构函数,在此函数体为空 void Assign(double l,double t,double r,double b); double getLeft(){ return left;} // 以下四个函数皆为内联成员函数 double getRight(){ return right;} double getTop(){return top;} double getBottom(){return bottom;} void Show(); \ friend void showprint(Rectangle); double Area(); double Perimeter(); }; // 构造函数,带缺省参数,缺省值为全0,在声明中指定 Rectangle::Rectangle(double l , double t, double r, double b) { left = l; top = t; right = r; bottom = b; } ` void Rectangle::Assign(double l, double t, double r, double b){//赋值left = l; top = t; right = r; bottom = b; } double Rectangle::Area(){ return fabs((right-left)*(top-bottom)); } double Rectangle::Perimeter(){ return 2*(fabs(right-left)+fabs(top-bottom)); } : void Rectangle::Show(){//成员函数 cout<<"left-top point is ("< cout<<"right-bottom point is ("< } void showprint(Rectangle rt){//友元函数,要加类名,但直接调用私有数据成员cout<<"left-top point is ("<<<<","<<<<")"<<'\n'; cout<<"right-bottom point is ("<<<<","<<<<")"<<'\n'; } void print(Rectangle rt){//普通函数,要用接口函数间接调用私有数据成员cout<<"left-top point is ("<<()<<","<<()<<")"<<'\n'; ; cout<<"right-bottom point is ("<<()<<","<<()<<")"<<'\n'; } int main(){ Rectangle rect; (); (100,400,300,200); (); Rectangle rect1(0,200,200,0); (); ^ Rectangle rect2(rect1); (); print(rect); showprint(rect); cout<<"面积"<<()<<'\t'<<"周长"<<()< return 0; } 为复数类(Complex)增加重载的运算符-、-=、*=和/=。设++为实部和虚部各自增一,亦请重载前置与后置++运算符。分别使用成员函数和友元函数各做一遍。并测试。 解:注意后++,返回的是原值,但实部和虚部已各自增一,所以要先保留原值,再++。这里是使用成员函数。 [ #include using namespace std; class complex{ private: double real; //实部 double imag; //虚部 public: complex(double r = ,double i = ); //构造函数 void print(); //显示复数 complex operator+(complex c); //重载复数"+" & complex operator-(complex c); //重载复数"-" complex operator*(complex c); //重载复数"*" complex operator/(complex c); //重载复数"/" complex operator +=(complex c); //重载复数"+=" complex operator -=(complex c); //重载复数"-=" complex operator *=(complex c); //重载复数"*=" complex operator /=(complex c); //重载复数"/=" complex operator ++(); //重载复数前缀"++" complex operator ++(int); //重载复数后缀"++" }; . complex::complex(double r,double i){ real = r; imag = i; } complex complex::operator +(complex c){ //重载复数"+" complex temp; =real+; =imag+; return temp; //返回局部变量,函数声明不可为引用,这时返回了局部变量的地址} ) complex complex::operator -(complex c){ //重载复数"-" complex temp; =; =; return temp; } complex complex::operator *(complex c){ //重载复数"*" complex temp; =real**; =real*+imag*; | return temp; } complex complex::operator /(complex c){ //重载复数"/" complex temp; double d; d=*+*; =(real*+imag*/d; =*imag-real*/d; return temp; } ) complex complex::operator +=(complex c){ //重载复数"+=" complex temp; =real+; =imag+; real=; imag=; return temp; } complex complex::operator -=(complex c){ //重载复数"-=" complex temp; 、 =; =; real=; return temp; } complex complex::operator *=(complex c){ //重载复数"*=" complex temp; =real**; //如果没有用temp过渡,real赋了新值,下一行的计算就错了& =real*+imag*; real=; imag=; return temp; } complex complex::operator /=(complex c){ //重载复数"/=" complex temp; double d; d=*+*; =(real*+imag*/d; ( //如果没有用temp过渡,real赋了新值,下一行的计算就错了=*imag-real*/d; real=; imag=; return temp; } complex complex::operator ++(){ //重载复数前缀"++" complex temp; =++real; =++imag; ¥ return temp; } complex complex::operator ++(int){ //重载复数后缀"++" complex temp(real,imag); real++; imag++; return temp; } void complex::print(){ //显示复数cout << real; \ if(imag>=0) cout<<'+'; cout< } complex A(30,40),B(15,30),C,D; //定义4个复数对象cout<<"A为:"; (); C= ++(1);//即C=A++完整的表达式 cout<<"C=A++后,C为:"; (); cout<<"A为:"; (); C=++();//即++A ; cout<<"C=++A后,C为:"; (); cout<<"A为:"; (); C=A*B; cout<<"C=A*B后,C为:"; (); A*=B; cout<<"A*=B后,A为:"; (); C=A/B; cout<<"C=A/B后,C为:"; (); A/=B; cout<<"A/=B后,A为: "; (); — cout<<"B为:"; (); D=A+B+C; cout<<"D=A+B+C后,D为:"; (); A+=B+=C; cout<<"A+=B+=C后,A为:"; (); cout<<"B为:"; (); D=A-B-C; cout<<"D=A-B-C后,D为:"; (); A-=B+=C; cout<<"A-=B+=C后,A为:"; (); } cout<<"B为:"; (); A=B; cout<<"A=B后,A为:"; (); D=A*B*C; cout<<"D=A*B*C后,D为:"; (); D=D/B/C; cout<<"D=D/B/C后,D为:"; (); A*=B*=C; cout<<"A*=B*=C后,A为:"; (); A/=B/=C/=C; # cout<<"A/=B/=C/=C后,A为:"; (); return 0; } 解2: #include<> class complex{ private: double real; //实部 double imag; //虚部 public: ( complex(double r = ,double i = ); //构造函数 void print(); //显示复数 friend complex operator+(const complex &,const complex &); //重载复数"+" friend complex operator-(const complex &,const complex &); //重载复数"-" friend complex operator*(const complex &,const complex &); //重载复数"*" friend complex operator/(const complex &,const complex &); //重载复数"/" friend complex &operator+=(complex &,const complex &); //重载复数"+=" friend complex &operator-=(complex &,const complex &); //重载复数"-=" friend complex &operator*=(complex &,const complex &); //重载复数"*=" friend complex &operator/=(complex &,const complex &); //重载复数"/=" # friend complex operator++(complex &); //重载复数前缀"++" friend complex operator++(complex &,int); //重载复数后缀"++" }; complex::complex(double r,double i){ real=r; imag=i; } complex operator +(const complex &c1,const complex &c2){ //重载复数"+" complex temp; =+; : =+; return temp; //返回局部变量,函数声明不可为引用,这时返回了局部变量的地址} complex operator -(const complex &c1,const complex &c2){ //重载复数"-" complex temp; = = return temp; } complex operator *(const complex &c1,const complex &c2){ //重载复数"*" complex temp; =* =*+*; , return temp; } complex operator /(const complex &c1,const complex &c2){ //重载复数"/" complex temp; double d; d=*+*; =*+*/d; =* return temp; } complex &operator +=(complex &c1,const complex &c2){ //重载复数"+=" & =+; =+; return c1; //返回由引用参数传递过来的变量,函数声明可为引用 } complex &operator -=(complex &c1,const complex &c2){ //重载复数"-=" = = return c1; } complex &operator *=(complex &c1,const complex &c2){ //重载复数"*=" complex temp; =* =*+*; ` c1=temp; return c1; } complex &operator /=(complex &c1,const complex &c2){ //重载复数"/=" complex temp; double d; d=*+*; =*+*/d; =* c1=temp; return c1; ^ } complex operator ++(complex & c){ //重载复数前缀"++" ++; ++; return c; } complex operator ++(complex & c,int){ //重载复数后缀"++" complex temp,; ++; ++; … return temp; } void complex::print(){ //显示复数 cout< if(imag>=0) cout<<'+'; cout< } int main(){ complex A(30,40),B(15,30),C,D; //定义4个复数对象— cout<<"A为:"; (); C= operator++( A,1);//即C=A++完整的表达式 cout<<"C=A++后,C为:"; (); cout<<"A为:"; (); C= operator++( A);//即++A cout<<"C=++A后,C为:"; (); cout<<"A为:"; (); C=A*B; cout<<"C=A*B后,C为:"; (); A*=B; ~ cout<<"A*=B后,A为:"; (); C=A/B; cout<<"C=A/B后,C为:"; (); A/=B; cout<<"A/=B后,A为: "; (); cout<<"B为:"; (); D=A+B+C; cout<<"D=A+B+C后,D为:"; (); A+=B+=C; cout<<"A+=B+=C后,A为:"; (); { cout<<"B为:"; (); D=A-B-C; cout<<"D=A-B-C后,D为:"; (); A-=B+=C; cout<<"A-=B+=C后,A为:"; (); cout<<"B为:"; (); A=B; cout<<"A=B后,A为:"; (); D=A*B*C; cout<<"D=A*B*C后,D为:"; (); D=D/B/C; cout<<"D=D/B/C后,D为:"; (); A*=B*=C; cout<<"A*=B*=C后,A为:"; (); A/=B/=C/=C; cout<<"A/=B/=C/=C后,A为:"; (); return 0; } 内置数据类型可以进行类型强制转换,类也可以进行同样的转换,这是通过定义类型转换函数实现的。它只能是类的成员函数,不能是友元函数。格式为: 类名::operator 转换后的数据类型( ) {…} 如:operator float()是转换为浮点数的成员函数。使用时的格式为: float(对象名); 或(float) 对象名; 定义人民币类,数据成员包括:圆、角、分,均为整型。类型转换函数将人民币类强制转换为浮点数,以圆为单位。并编程进行检验。 解:本题的解答未考虑角、分最多为9。类型强制转换的两种格式编程时毋需考虑,自动实现。 #include using namespace std; class IntRMB{ //人民币类 private: int IYuan; int Jiao; int Fen; public: IntRMB(int y=0,int j=0,int f=0); //构造函数 void print(); //数据输出函数 operator float(); //浮点数类型转换函数 }; IntRMB::IntRMB(int y,int j,int f){//构造函数 IYuan=y; Jiao=j; Fen=f; } IntRMB::operator float(){ float temp; temp=float(IYuan + (Jiao/ + (Fen/); return temp; } void IntRMB::print(){