面向对象习题
更新时间:2024-04-04 14:19:01 阅读量: 综合文库 文档下载
类与对象
例题1:下列有关类的说法不正确的是( )。
A. A. 对象是类的一个实例
B. B. 任何一个对象只能属于一个具体的类 C. C. 一个类只能有一个对象
D. D. 类与对象的关系和数据类型与变量的关系相似 答案:C
分析:对象是类的一个实例,类与对象的关系和数据与变量的关系相似,所以一个类可以有多个对象。
例题2:下面( )项是对构造函数和析构函数的正确定义。
A.void X::X(), void X::~X() B.X::X(参数), X::~X() C.X::X(参数), X::~X(参数)
D.void X::X(参数), void X::~X(参数) 答案:B
分析构造函数无返回类型、可带参数、可重载;析构函数无返回类型、不可带参数、不可重载。
例题3:( )的功能是对象进行初始化。
A.析构函数 B. 数据成员 C.构造函数 D.静态成员函数 答案:C
分析:当一个对象定义时,C++编译系统自动调用构造函数建立该对象并进行初始化;当一个对象的生命周期结束时,C++编译系统自动调用析构函数注销该对象并进行善后工作;
例题4:下列表达方式正确的是( )。
A.class P{ B. class P{
public: public: int x=15; int x;
void show(){cout< f=25; void Seta (int x) {a=x;} 答案:D 分析:在类体内不可对数据成员进行初始化(从类对象区别于其数据成员解释);类定义结束时需用分号;只有类中的成员函数才能存取类中的私有数据。 例题5: 拷贝构造函数具有的下列特点中,( )是错误的。 A. A. 如果一个类中没有定义拷贝构造函数时,系统将自动生成一个默认的 B. B. 拷贝构造函数只有一个参数,并且是该类对象的引用 C. C. 拷贝构造函数是一种成员函数 D. D. 拷贝构造函数的名字不能用类名 答案:D 分析:如果一个类中没有定义拷贝构造函数时,系统将自动生成一个默认的;拷贝构造函数只有一个参数,并且是该类对象的引用;拷贝构造函数的名字与类同名,并且不被指定返回类型;拷贝构造函数是一种成员函数。 例题6:关于静态成员的描述中,( )是错误的。 1 A. A. 静态成员可分为静态数据成员和静态成员函数 B. B. 静态数据成员定义后必须在类体内进行初始化 C. C. 静态数据成员初始化不使用其构造函数 D. D. 静态数据成员函数中不能直接引用非静态成员 答案:B 分析:静态成员可分为静态数据成员和静态成员函数;静态数据成员被定义后,必须对它进行初始化,初始化在类体外进行,一般放在该类的实现部分最合适,也可以放在其他位置,例如,放在主函数前面等;静态数据成员初始化与该类的构造函数和析构函数无关;在静态成员函数的实现中,可以直接引用静态成员,但不能直接引用非静态成员。 例题7:关于友元的描述中,( )是错误的。 A. A. 友元函数是成员函数,它被说明在类体内 B. B. 友元函数可直接访问类中的私有成员 C. C. 友元函数破坏封装性,使用时尽量少用 D. D. 友元类中的所有成员函数都是友元函数 答案:A 分析:友元函数是非成员函数,在类体内说明了,在类体外定义,定义和调用等同于一般的普通函数;由于它可以直接访问类的私有成员,因此破坏了类的封装性和隐藏性,尽量少用。 例题8:设有如下程序结构: class Box { … }; void main() {Box A,B,C; } 该程序运行时调用 (1) 次构造函数;调用 (2) 次析构函数。 答案:(1)3 (2)3 分析:每创建一个对象自动调用一次构造函数,在这里创建了A、B、C三个对象,所以共调用了三次构造函数;每释放一个对象,系统自动调用一次析构函数,A、B、C对象释放时,分别调用析构函数,所以析构函数共调用了三次。 例题9:设A为test类的对象且赋有初值,则语句test B(A); 表示 。 答案:将对象A复制给对象B。 分析:执行test B(A);语句相当于调用了默认复制构造函数,将A对象的属性复制给B对象。 例题10:利用“对象名.成员变量”形式访问的对象成员仅限于被声明为 (1) 的成员;若要访问其他成员变量,需要通过 (2) 函数或 (3) 函数。 答案:(1) public (2) 成员函数 (3)友元函数 分析:类体内的数据成员可声明为公有的、私有的和保护的,公有的数据成员可利用“对象名.成员变量”形式来进行访问;私有的数据成员能被类中的其他成员函数或友元函数所调用;保护的数据成员可以在类体中使用,也可以在派生类中使用,但不能在其他类外通过对象使用。 例题11:分析找出以下程序中的错误,说明错误原因,给出修改方案使之能正确运行。 #include private: int a1,a2; public: one(int x1=0, x2=0); }; void main() {one data(2,3); cout< 2 } 分析:出错原因:构造函数参数表语法错;构造函数没有函数体;类的对象不能直接访问类的私有成员变量。 改正后的程序如下: #include one(int x1=0, int x2=0) {a1=x1;a2=x2;} int geta1() { return a1; } int geta2() { return a2; } }; void main() {one data(2,3); cout< 例题12:分析以下程序的错误原因,给出修改方案使之能正确运行。 #include float invol,outvol; public: Amplifier(float vin,float vout) {invol=vin;outvol=vout;} float gain(); }; Amplifier::float gain() { return outvol/invol; } void main() { Amplifier amp(5.0,10.0); cout<<\} 分析:成员函数在类体外定义格式是:函数返回类型 类名::成员函数名(参数表);成员函数调用格式是:对象名.成员函数名(参数表)。 改正后的程序如下: #include { float invol,outvol; public: Amplifier(float vin,float vout) {invol=vin;outvol=vout;} float gain(); }; float Amplifier::gain() { return outvol/invol; } void main() { Amplifier amp(5.0,10.0); cout<<\} 例题13:下列程序的运行结果是 。 #include point(int a,int b) {x=a;y=b; cout<<\ } point(point &p); 3 friend point move(point q); ~point(){cout<<\ int getx() {return x;} int gety() {return y;} }; point::point(point &p) {x=p.x; y=p.y; cout<<\} point move(point q) {cout<<\ int i,j; i=q.x+10; j=q.y+20; point r(i,j); return r; } void main() { point m(15,40),p(0,0); point n(m); p=move(n); cout<<\} 分析:根据构造函数、拷贝构造函数和友元函数的特点,执行该程序后,输出结果是: calling the constructor function. calling the constructor function. calling the copy_initialization constructor function. calling the copy_initialization constructor function. OK! calling the constructor function. calling the copy_initialization constructor function. calling the destructor function. calling the destructor function. calling the destructor function. P=25,60 calling the destructor function. calling the destructor function. calling the destructor function. 说明: (1)构造函数执行三次,分别初始化主函数中的对象m,p和move函数中的对象r。 (2)拷贝构造函数共执行了三次。第一次,初始化对象n;第二次在调用函数move()时,实参n给形参q进行初始化;第三次是执行函数move的return r;语句时,系统用r初始化一个匿名对象时使用了拷贝构造函数。 (3)析构函数执行了六次。在退出函数move时释放对象r和q共调用二次;返回主函数后,匿名对象赋值给对象p后,释放匿名对象又调用一次析构函数;最后退出整个程序时释放对象m,n和p调用三次。 例题14:定义一个学生类,其中有3个数据成员:学号、姓名、年龄,以及若干成员函数。同时编写main函数使用这个类,实现对学生数据的赋值和输出。 程序代码如下: #include char name[10]; int age; 4 public: student(int i, char *str, int g) {no=i; strcpy(name,str); age=g; } student( ) {no=0; strcpy(name,”none”); age=-1; } void display() {if (no>0) cout<<”no” < { student d1(1001,”Tom”,18); d1.display(); student d2; d2.display(); } 例题15:计算两点之间的距离。 方法一:可以定义点类(Point),再定义一个类(Distance)描述两点之间的距离,其数据成员为两个点类对象,两点之间距离的计算可设计由构造函数来实现。 #include Point(int a=0, int b=0) {x=a; y=b; } int xcord() { return x;} int ycord (){ return y;} private: int x,y; }; class Distance { public: Distance(Point q1,Point q2); double getdist() {return dist; } private: Point p1,p2; double dist; }; Distance::Distance(Point q1,Point q2):p1(q1),p2(q2) { double x=double(p1.xcord()-p2.xcord()); double y=double(p1.ycord()-p2.ycord()); dist=sqrt(x*x+y*y); } void main() { Point p(0,0),q(1,1); Distance dis(p,q); cout<<”The distance is: ”< 方法2:将两点之间距离函数声明为Point类的友元函数。 #include 5 #include Point(int a=0, int b=0) {x=a; y=b; } int xcord() { return x;} int ycord (){ return y;} private: int x,y; friend double Distance(Point p1,Point p2); }; double Distance(Point p1,Point p2) { double dx=double(p1.x-p2.x); double dy=double(p1.y-p2.y); return sqrt(dx*dx+dy*dy); } void main() { Point q1(0,0),q2(1,1); cout<<”The distance is: ”< 1. 选择题 (1)对类的构造函数和析构函数描述正确的是()。 A. A. 构造函数可以重载,析构函数不能重载 B. B. 构造函数不能重载,析构函数可以重载 C. C. 构造函数可以重载,析构函数也可以重载 D. D. 构造函数不能重载,析构函数也不能重载 答案:A (2)类的析构函数的作用是( D )。 A.一般成员函数 B.类的初始化 C.对象初始化 D.删除对象 答案:D (3)假设OneClass为一个类,则该类的拷贝初始化构造函数的声明语句为( )。 A.OneClass(OneClass p); B. OneClass& (OneClass p); C. OneClass(OneClass & p); D. OneClass (OneClass *p); 答案:C (4)下面对于友元函数描述正确的是( )。 A.友元函数的实现必须在类的内部定义 B.友元函数是类的成员 C.友元函数破坏了类的封装性和隐藏性 D.友元函数不能访问类的私有成员 答案:C (5)对于结构中定义的成员,其默认的访问权限为( )。 A.public B. protected C.private D. static 答案:A (6)为了使类中的某个成员不能被类的对象通过成员操作符访问,则不能把该成员的访问权限定义为( A.public B. protected C.private D. static 答案:A (7)下面对静态数据成员的描述中,正确的是( )。 A.静态数据成员可以在类体内进行初始化 B.静态数据成员不可以在类体内进行初始化 。 6 )C.静态数据成员不能受private控制符的作用 D.静态数据成员可以直接用类名调用 答案:C (8)下面对静态数据成员的描述中,正确的是( )。 A.静态数据成员是类的所有对象共享的数据 B.类的每一个对象都有自己的静态数据成员 C.类的不同对象有不同的静态数据成员值 D.静态数据成员不能通过类的对象调用 答案:A 2. 2. 写出下列程序的运行结果。 (1)#include class Point{ int x,y; public: Point(){x=1;y=1;} ~Point(){cout<<\}; void main() {Point a;} 运行结果为: Point 1,1 is deleted. (2) #include #include public: Point() { x=1;y=1; count++; } ~Point() {count--;} friend void display(); }; void display() { cout <<\void main() { Point a; display(); { Point b[5]; display(); } display(); } 运行结果为: There are 1 points, There are 6 points, There are 1 points, (3)#include 7 { int i; public: Csample( ); void Display( ); ~Csample(); }; Csample::Csample( ) { cout<<”Constructor”<<”,”; i=0; } void Csample::Display() {cout<<”i=”< Csample::~Csample( ) { cout<<”Destructor”< 运行结果为: Constructor,i=0,Destructor (4) #include #include Csample() {cout <<\ Csample(int val) {cout <<\ void Display() {cout<<\ ~Csample() {cout<<\}; void main() {Csample a,b(10); a.Display(); b.Display(); } 运行结果为: Constructor1 Constructor2 i=-858993460 i=10 Destructor Destructor (5)#include class Csample {private: int i; static int k; public: Csample( ); 8 void Display( ); }; int Csample::k=0; Csample::Csample( ) { i=0; k++; } void Csample::Display( ) { cout<<”i=”< a.Display( ); b.Display( ); } 运行结果为: i=0,k=2 i=0,k=2 3.按要求编写程序。 (1)编写一个程序,设计一个产品类Product,其定义如下: class Product { char *name; //产品名称 int price; //产品单价 int quantity; //剩余产品数量 public: product(char *n,int p int q); //构造函数 ~product( ); //析构函数 void buy(int money); //购买产品 void get() const; //显示剩余产品数量 }; 并用数据进行测试。 (3)定义盒子Box类,要求具有以下成员:可设置盒子形状;可计算盒子体积;可计算盒子的表面积。#include { int x,y,z; int v,s; public: void init(int x1=0,int y1=0,int z1=0) {x=x1;y=y1;z=z1;} void volue() {v=x*y*z;} void area() {s=2*(x*y+x*z+y*z);} void show() {cout<<\ cout<<\ } }; void main() { Box a; a.init(2,3,4); a.volue(); a.area(); a.show(); } (4)定义计数器类Counter。要求具有以下成员:计数器值;可进行增值和减值记数;可提供记数值。 #include 9 { int n; public: Counter(int i=0) {n=i;} void init_Counter(int m) {n=m;} void in_Counter() {n++;} void de_Counter() {n--;} int get_Counter() {return n;} void show() {cout< void main() { Counter a; a.in_Counter(); a.show(); a.init_Counter(10); a.show(); a.de_Counter(); cout< (5)编写一个程序计算两个给定的长方形的周长和面积。 #include rectangle(int i=0,int j=0) { len=i;wid=j;} friend int area(rectangle temp) { int s=temp.len*temp.wid; return s; } friend fun(rectangle temp) {int p=2*(temp.len+temp.wid); return p; } }; void main() { rectangle a(10,20); cout<<\长方形的周长和面积为:\ rectangle b(100,200); cout<<\长方形的周长和面积为:\} (6)编写一个程序,采用一个类求n!,并输出10!的值。 #include fac() { p=1;} fac( int j) {p=1; if(j>=0) for(int i=1 ;i<=j;i++) p=p*i; else cout<<\数据错误\\n\ 10 } void show() { cout<<\阶乘为:\}; void main() { int n; cout<<\请输入一个整数:\ cin>>n; fac a(n); cout< (7)编写一个程序,设计一个Cdate类,它应该满足下面的条件: 1)用这样的格式输出日期:日-月-年; 2)输出在当前日期上加两天后的日期; 3)设置日期。 #include { int year,month,day; int y1,m1,d1; public: void setdate(int y,int m,int d) {year=y;month=m;day=d;} void show() {cout<<\当前日期: \ cout<<\两天后日期:\ } void datetwo() //加一天后的年月日 { d1=day;y1=year;m1=month; for(int i=0;i<2;i++) { d1++; switch(d1) {case 29:if(!(month==2 &&(year@0==0||year%4==0&&year0!=0))) {m1=3;d1=1;};break; case 30:if(month==2 &&(year@0==0||year%4==0&&year0!=0)) {m1=3;d1=1;};break; case 31:if(month==4||month==6||month==9||month==11) {m1=m1+1;d1=1;};break; case 32: m1=m1+1;d1=1;if(month==12){y1=y1+1;m1=1;};break; } } } }; void main() { Cdate d; int y,m,d1; cout<<\请输入年月日: \cin>>y>>m>>d1; d.setdate(y,m,d1);//加一天 d.setdate(y,m,d1);//再加一天 d.datetwo(); d.show(); } 1.选择题 11 (1) 下列有关类的说法不正确的是 。 A.类是一种用户自定义的数据类型 B.只有类中的成员函数或类的友元函数才能存取类中的私有数据 C.在类中(用class定义),如果不作特别说明,所有的数据均为私有数据 D.在类中(用class定义),如果不作特别说明,所有的成员函数均为公有数据 (2) 以下有关析构函数的叙述不正确的是( ) A. 在一个类只能定义一个析构函数 B. 析构函数和构造函数一样可以有形参 C. 析构函数不允许用返回值 D. 析构函数名前必须冠有符号“~” (3) 以下有关类与结构体的叙述不正确的是( ) A. 结构体中只包含数据;类中封装了数据和操作 B. 结构体的成员对外界通常是开放的;类的成员可以被隐藏 C. 用struct不能声明一个类型名;而class可以声明一个类名 D. 结构体成员默认为public;类成员默认为private (4) 以下叙述中不正确的是( ) A. 一个类的所有对象都有各自的数据成员,它们共享函数成员 B. 一个类中可以有多个同名的成员函数 C. 一个类中可以有多个构造函数、多个析构函数 D. 在一个类中可以声明另一个类的对象作为它的数据成员 (5) 以下不属于构造函数特征的是( ) A. 构造函数名与类名相同 B. 构造函数可以重载 C. 构造函数可以设置默认参数 D. 构造函数必须指定函数类型 (6) 以下有关类和对象的叙述不正确的是( ) A. 任何一个对象都归属于一个具体的类 B. 类与对象的关系和数据类型与变量的关系相似 C. 类的数据成员不允许是另一个类的对象 D. 一个类可以被实例化成多个对象 (7) 设有定义: class person { int num; char name[10]; public: void init(int n, char *m); ... }; person std[30]; 则以下叙述不正确的是( ) A. std是一个含有30个元素的对象数组 B. std数组中的每一个元素都是person类的对象 C. std数组中的每一个元素都有自己的私有变量num和name D. std数组中的每一个元素都有各自的成员函数init (8) 设有以下类的定义: class Ex { int x; public: void setx(int t=0); }; 若在类外定义成员函数setx(),以下定义形式中正确的是( ) A. void setx(int t) { ... } B. void Ex::setx(int t) { ... } C. Ex::void setx(int t) { ... } D. void Ex::setx(){ ... } 12 (9) 以下关于静态成员变量的叙述不正确的是( ) A.静态成员变量为类的所有对象所公有 B.静态成员变量可以在类内任何位置上声明 C.静态成员变量的赋初值必须放在类外 D.定义静态成员变量时必须赋初值 (10) 定义静态成员函数的主要目的是( ) A.方便调用 B.有利于数据隐藏 C.处理类的静态成员变量 D.便于继承 (11) 以下叙述不正确的是( ) 使用静态数据成员: A.可以节省内存空间 B.是为了解决数据共享问题 C.可以直接用类名来引用 D.可以提高序运算速度 2.填空题 (1) OOP技术由 、 、方法、消息和继承五个基本的概念所组成。 (2) (2) 类的成员函数可以在 定义,也可以在 定义。(3) (3) 类test的析构函数名是 。 (4) (4) 类是用户定义的类型,具有类类型的变量称作_______________。 (5) (5) 一个类的析构函数不允许有 。 (6) (6) 建立对象时,为节省内存,系统只给_______分配内存。 (7) (7) 用于定义C++的类的关键字有____________、___________和union。 (8) (8) 类test的构造函数是和_______同名的函数,析构函数是__________。 (9) (9) 类中的数据和成员函数默认访问类型为 。 (10) (10)当建立一个新对象时,程序自动调用_______________。 3. 改错题 (1) 下面的程序定义了一个Point类,找出程序中的错误语句并改正。 #include public: void Point(int a) {x=a;} int Getx(){return x;} void Show() {cout< void main() {Point A(76); cout< (2) 根据静态成员的特点,指出下列程序的错误。 #include Part(char *pname=”no name”) { strncpy(name,pname); noofpart++; no=noofpart; cout<<”create the no: ”< { noofpart--; 13 cout<<”destroy the no: ”< static int number() { return no; } protected: static int noofpart=0; int no; char name[40]; }; void main() { Part p1; Part p2; } (3) 以下程序的功能是:利用友员函数为类的成员变量进行初始化,然后利用成员函数输出。请改正程序中的错误,使之能正确运行。 #include friend void setval(int i,int j); void showA() {cout< void setval(int i,int j) {a=i; b=j;} void main() {A obj1; setval(2,3); obj1.showA(); } 4. 看程序写结果 (1) #include class test { public: test(); test(int); ~test(); void display(); protected: int n; }; test::test() {cout<<”Constructing normally\\n”;} test::test(int num) {n=num; cout<<”Constructing with a number: ”< void test::display() {cout<<”Display a number: ”< (2) #include 14 Count() { count++;} static int getn() {return count;} ~Count() { count--; } private: static int count; }; int Count::count=100; void main() { Count c1,c2,c3,c4; cout< (3) #include class myclass { int a,b; public: void init(int i, int j) {a=i; b=j;} friend int sum(myclass x); }; int sum( myclass x) {return x.a+x.b; } void main() {myclass y; y.init(15,20); cout< 继承与派生 下列对派生类的描述中,( )是错误的。 A. A. 一个派生类可以作为另一个派生类的基类 B. B. 派生类至少有一个基类 C. C. 派生类的成员除了它自己的成员外,还包含了它的基类成员 D. D. 派生类中继承的基类成员的访问权限到派生类保持不变 答案:D 分析:一个派生类可以作为另一个派生类的基类。无论是单继承还是多继承,派生类至少有一个基类。派生类的成员除了它自己的成员外,还包含了它的基类成员。派生类中继承的基类成员的访问权限到派生类受继承方式影响的,对于私有继承,基类的public,protected成员在派生类中作为private成员;对于公有继承,基类的public,protected成员在派生类中访问属性不变;对于保护继承,基类的public、protected成员在派生类中作为protected成员。 例题2:派生类的对象对它的哪一类基类成员是可以访问的?( ) A.公有继承的基类的公有成员 B. 公有继承的基类的保护成员 C. 公有继承的基类的私有成员 D. 保护继承的基类的公有成员 答案:A 分析:公有继承的基类的公有成员在派生类中保持公有访问权限,所以派生类对象可以访问它;公有继承的基类的保护成员在派生类中保持保护访问权限,所以派生类对象不可以访问它;基类的私有成员不能被派生到派生类中,所以派生类对象不可以访问它;保护继承的基类的公有成员在派生类中变成保护的访问权限,所以派生类对象不可以访问它。 例题3:关于多继承二义性的描述,( )是错误的。 15 A. A. 派生类的多个基类中存在同名成员时,派生类对这个成员访问可能出现二义性 B. B. 一个派生类是从具有共同的间接基类的两个基类派生来的,派生类对该公共基类的访问可能出现二义性 C. C. 解决二义性最常用的方法是作用域运算符对成员进行限定 D. D. 派生类和它的基类中出现同名函数时,将可能出现二义性 答案:D 分析:出现二义性有两种情况:调用不同基类的相同成员时可能出现二义性;访问共同基类的成员时可能出现二义性。消除二义性的方法是采用作用域运算符。派生类和它的基类中出现同名函数时,不可能出现二义性。 例题4:多继承派生类构造函数构造对象时,( )被最先调用。 A.派生类自己的构造函数 B.虚基类的构造函数 C.非虚基类的构造函数 D.派生类中子对象类的构造函数 答案:B 分析:多继承派生类构造函数构造对象时,构造函数的调顺序是:虚基类的构造函数,派生类中子对象类的构造函数, 派生类自己的构造函数。 例题5: C++类体系中,能被派生类继承的是( )。 A.构造函数 B.虚函数 C.析构函数 D.友元函数 答案:B 分析:C++类体系中,构造函数、析构函数和友元函数是不能被派生类继承的. 例题6:设有基类定义: class Cbase { private: int a; protected: int b; public: int c; }; 派生类采用何种继承方式可以使成员变量b成为自己的私有成员( ) A. 私有继承 B.保护继承 C. 公有继承 D.私有、保护、公有均可 答案:A 分析:私有继承时,基类的protected成员在派生类中作为private成员。 例题7:C++将类继承分为 (1) 和 (2) 两种。 答案:(1)单继承 (2)多继承 分析:派生类可以只从一个基类中派生,也可以从多个基类中派生。从一个基类中派生的继承方式称为单继承。从多个基类中派生的继承方式称为多继承。 例题8:派生类可以定义其_______________中不具备的数据和操作。 答案:基类 分析:派生类是从基类派生的,派生类包含了它所有基类的除构造函数、析构函数之外的所有成员,同时还拥有它自己的新成员。 例题9:派生类构造函数的初始化列表中包含____________________________。 答案:初始化基类数据成员、新增内嵌对象数据及新增一般成员数据所需要的全部参数。 分析:在创建派生类对象时,不仅要给派生类中说明的数据成员初始化,而且还要给基类中说明的数据成员初始化。由于构造函数不能被继承,因此,派生类的构造函数必须通过调用基类的构造函数来初始化基类中的数据成员。所以,在定义派生类的构造函数中,不仅要包含对自己数据成员进行初始化,还要包含调用基类的构造函数对基类中数据成员进行初始化。如果派生类中还有子对象时,还应包含调用对子对象初始化的构造函数。 例题10:在继承机制下,当对象消亡时,编译系统先执行 (1) 的析构函数,然后才执行 (2) 的析构函数,最后执行 (3) 的析构函数。 答案:(1)派生类 (2)派生类中子对象类 (3)基类 16 分析:派生类的析构函数的执行次序与构造函数正好相反,先调用派生类的析构函数,再调用派生类中子对象类的析构函数,最后调用基类的析构函数。 例题11:设有以下类的定义: class A class B: protected A class C: private B { int A1; { int b1; { int c1; protected: int A2; protected: int b2; protected: int c2; public: int A3; public: int b3; public: int c3; }; }; }; 请按访问权限写出派生类C中具有的成员。 私有成员: (1) 保护成员: (2) 公有成员: (3) 。 答案:(1)c1、b2、b3、A2、A3 (2)c2 (3)c3 分析:B类有它自己的私有成员b1、保护成员b2和公有成员有b3,另外B类是以保护方式从A类中派生出来的,所以A类保护成员A2和公有成员A3在B类中都变成保护类型的;C类有自己的私有成员c1、保护成员c2和公有成员有c3,C类是以私有方式从B类中派生出来的,所以B类中的b2、b3、A2和A3在C类中都变成私有的访问方式。 例题12:指出并改正下面程序中的错误。 #include Point(int a=0,int b=0) {x=a; y=b;} void move(int xoffset,int yoffset) {x+=xoffset; y+=yoffset;} int getx() {return x;} int gety() {return y;} }; class Rectangle:protected Point { int length,width; public: Rectangle(int x,int y,int l,int w):Point(x,y) { length=l;width=w;} int getlength(){return length;} int getwidth(){return width;} }; void main() { Rectangle r(0,0,8,4); r.move(23,56); cout< < } 分析:保护继承方式使基类的public成员在派生类中的访问属性变为protected,所以派生类Rectangle的对象r不能直接访问基类的成员函数move()、getx()和gety()。其改正方法有两种:1)将Rectangle的继承方式改为公有继承public;2)在Rectangle类中重定义move(),getx()和gety()函数,覆盖基类的同名函数。 void Rectangle::move(int xoffset,int yoffset){Point::move(xoffset,yoffset);} void Rectangle::getx(){return Point::getx();} void Rectangle::gety(){return Point::gety();} 例题13:指出并改正下面程序中的错误。 #include 17 { public: int x; A(int a=0) {x=a;} void display() { cout<<\}; class B { public: int x; B(int a=0) {x=a;} void display() {cout<<\}; class C:public A,public B { int y; public: C(int a,int b,int c) :A(a),B(b) { y=c; } int gety() { return y; } }; void main() { C myc(1,2,3); myc.x=10; myc.display(); } 分析:类A、B中有同名公有数据成员x和同名成员函数display(),在主函数中访问对象myc的数据成员x是无法确定是访问从A中继承的还是从B中继承的x;调用成员函数也是如此,无法确认是调用类A中的还是类B中的,产生二义性。改正方法,可以用作用域区分符加以限定,如改成myc.A::x=10; myc.A::display();或myc.B::x=10; myc.B::display(); 例题14:看程序写结果 #include Base(int n){cout <<\ ~Base(){cout <<\ void showi(){cout << i<< \ int Geti(){return i;} }; class Derived:public Base { int j; Base aa; public: Derived(int n,int m,int p):Base(m),aa(p){ cout << \ j=n; } ~Derived(){cout <<\ void show(){Base::showi(); cout << j<<\ }; void main() { Derived obj(8,13,24); 18 obj.show(); } 说明:派生类的构造函数的执行次序,先调用基类的构造函数,再调用派生类中子对象类的构造函数,最后调用派生类的构造函数。析构函数的执行次序与构造函数正好相反,先调用派生类的析构函数,再调用派生类中子对象类的析构函数,最后调用基类的析构函数。 运行结果: Constucting base class Constucting base class Constructing derived class 13,8,24 Destructing derived class Destructing base class Destructing base class 例题15:看程序写结果 #include A(char *s) { cout< class B:virtual public A { public: B(char *s1, char *s2):A(s1) { cout< class C: virtual public A { public: C(char*s1,char *s2):A(s1) { cout< class D:public B,public C { public: D(char *s1, char *s2,char *s3, char *s4):B(s1,s2),C(s1,s3),A(s1) { cout< void main() { D *p=new D(\ delete p; } 说明:创建D对象时,只有在D的构造函数的初始化列表中列出的虚基类构造函数被调用,D的两个基类B、C的构造函数中的虚基类构造函数被忽略,不执行,从而保证在D对象中只有一个虚基类子对象。 运行结果: class A 19 class B class C class D 例题16:建立一个基类Building ,用来存储一座楼房的层数、房间数以及它的总平方英尺数。建立派生类Housing,继承Building,并存储下面的内容:卧室和浴室的数量,另外,建立派生类Office,继承Building,并存储灭火器和电话的数目。然后,编制应用程序,建立住宅楼对象和办公楼对象,并输出它们的有关数据。 程序代码: #include Building(int f,int r,double ft) {floors=f; rooms=r; footage=ft; } void show() { cout<<\ cout<<\ cout<<\ } protected: int floors; int rooms; double footage; }; class Housing:public Building {public: Housing(int f,int r,double ft,int bd,int bth):Building(f,r,ft) { bedrooms=bd; bathrooms=bth; } void show() {cout<<\ Building::show(); cout<<\ cout<<\ } private: int bedrooms; int bathrooms; }; class Office:public Building { public: Office(int f,int r,double ft,int ph,int ex):Building(f,r,ft) { phones=ph; extinguishers=ex; } void show() {cout<<\ Building::show(); cout<<\ cout<<\ } private: int phones; 20 int extinguishers; }; void main() { Housing hob(5,7,140,2,2); Office oob(8,12,500,12,2); hob.show(); oob.show(); } 1.选择题 (1)C++中的类有两种用法:一种是类的实例化,即生成类对象,并参与系统的运行;另一种是通过()派生了新的类。 A.复用 B.继承 C.封装 D.引用 答案:B (2)继承具有( ),即当基类本身也是某一个类派生类时,底层的派生类也会自动继承间接基类的成员。 A)规律性 B.传递性 C.重复性 D.多样性 答案:B (3)下列对基类和派生类关系的描述中,错误的是( )。 A.派生类是基类的具体化 B.派生类是基类的子集 C.派生类是基类定义的延续 D.派生类是基类的组合 答案:B (4)下列对派生类的描述中,错误的是( )。 A. A. 一个派生类可以作为另一个派生类的基类 B. B. 派生类至少有一个基类 C. C. 派生类的缺省继承方式是private D. D. 派生类只含有基类的公有成员和保护成员 答案:D (5)下列对继承的描述中,错误的是( )。 A. A. 析构函数不能被继承 B. B. 派生类也是基类的组合 C. C. 派生类的成员除了它自己的成员外,还包含了它的基类的成员 D. D. 派生类中继承的基类成员的访问权限到派生类保持不变 答案:D (6)派生类的对象对它的基类成员中( )是可以访问的。 A.公有继承的公有成员 B.公有继承的私有成员 C.公有继承的保护成员 D.私有继承的公有成员 答案:A (7)下列说法错误的是( )。 A. A. 公有继承的基类中的public成员在派生类中仍是public的 B. B. 公有继承的基类中的private成员在派生类中仍是private的 C. C. 私有继承的基类中的public成员在派生类中变成private的 D. D. 保护继承的基类中的public成员在派生类中变成protected的 答案:B (8)下面叙述错误的是( A )。 A. A. 基类的protected成员在派生类中仍然是protected B. B. 基类的protected成员在public派生类中仍然是protected的 21 C. C. 基类的protected成员在private派生类中是private的 D. D. D. 基类的protected成员不能被派生类的对象访问 答案:A (9)C++类体系中,不能被派生类继承的有( )。 A.构造函数 B.虚函数 C.静态成员函数 D.赋值操作函数 答案:A (10)设置虚函数的声明中,正确的是( )。 A.简化程序 B.消除二义性 C.提高运行效率 D.减少目标代码 答案:B (11)下列虚基类的声明中,正确的是( D )。 A.class virtual B:public A B.virtual class B:public A C.class B:public A virtual D.class B: virtual public A 答案:D (12)在下面的表达式中,不表示虚继承的是( )。 A.virtual public B.public virtual C.public D. virtual 答案:C 2. 比较类的三种继承方式public(公有继承)、protected(保护继承)、private(私有继承)之间的差别。 答:继承方式决定了基类中的成员在派生类中的属性。三种继承方式的共同点:基类的private成员在派生类中不可见。区别:对于私有继承,基类的public,protected成员在派生类中作为private成员;对于公有继承,基类的public,protected成员在派生类中访问属性不变;对于保护继承,基类的public,protected成员在派生类中作为protected成员。 3. 派生类构造函数执行的次序是怎样的? 答:派生类构造函数的执行顺序是先执行所有基类的构造函数(顺序按照定义派生类是指定的各基类顺序),再执行对象成员所在类的构造函数(顺序按照他们在类中的声明顺序),最后执行派生类构造函数体中的内容。 4. 如果在派生类B已经重载了基类A的一个成员函数fn1(),没有重载成员函数fn2(),如何调用基类的成员函数fn1(),fn2()? 答:因为在派生类B已经重载了基类A的一个成员函数fn1(),所以要用作用域运算符对fn1()函数加以限定,调用基类的成员函数fn1()是A::fn1();因为在派生类B没有重载成员函数fn2(),所以直接可调用fn2()。 5. 什么叫做虚基类?它有何作用? 答:在多重继承中,如果多条继承路径上有一个公共的基类,则在这些路径的汇合点上的派生类会产生来自不同路径的公共基类的多个拷贝,如果用virtual把公共基类定义成虚基类,则只会保留公共基类的一个拷贝。引进虚基类的目的是为了解决二义性问题,使得公共基类在它的派生类对象中只产生一个基类子对象。 6. 声明一个Shape基类,在此基础上派生出Rectangle和Circle类,二者都有GetArea()函数计算对象的面积。使用Rectangle类创建一个派生类Square。 #include void show() {cout<<\ } }; class Rectangle:public shape 22 { double x,y; public: Rectangle(double x1=0,double y1=0) void GetArea() {s=x*y;} }; class Circle:public shape { double r; public: Circle(double r1=0) {r=r1;} void GetArea() {s=r*r*PI; } }; class Square :public Rectangle { double a; public: Square(double a1=0 ) {a=a1;} void GetArea() {s=a*a; } }; {x=x1;y=y1;} 7. 声明一个哺乳动物Mammal类,再由此派生出狗Dog类,声明一个Dog类的对象,观察基类与派生类的构造函数与析构函数的调用顺序。 #include Mammal() { cout<<\ } ~Mammal() {cout<<\ } }; class Dog :public Mammal { public: Dog() {cout<<\ ~Dog() {cout<<\}; void main() { Dog b; } 8.定义一个基类有姓名、性别、年龄,再由基类派生出教师类和学生类,教师类增加工号、职称和工资,学生类增加学号、班级、专业和入学成绩。 #include class base //定义一个基类 {protected: char name[20]; //姓名 char sex[3]; //性别 int age; //年龄 ?? ?? }; class teacher:public base //基类派生出教师类 { int sno; //工号 char zc[20]; //职称 double wages; //工资 ?? ?? 23 }; class student :public base //基类派生出学生类 { int sno; //学号 char bj[10]; //班级 char zy[10]; //专业 double score; //入学成绩 ?? ?? }; 1.选择题 (1) 以下对派生类的描述中不正确的是( ) A.一个派生类可以作另一个派生类的基类 B.一个派生类可以有多个基类 C.具有继承关系时,基类成员在派生类中的访问权限不变 D.派生类的构造函数与基类的构造函数有成员初始化参数传递关系 (2) 设有如下程序: #include { public: int i; display() { cout<<”class A\\n”; } }; class B {public: int i; display() { cout<<”class B\\n”; } }; class C: public: A,public B { int j; public: int i; show() {j=i*i; display();} }; void main() {C demo; demo.show(); } 主程序运行时将( ) A. 因变量i的重复定义而报错 A. B. 因对象demo间接调用display函数时产生歧义性而报错 B. C. 因类定义的语法错而不能运行 C. D. 没有语法错,正常输出结果 (3) 若要用派生类的对象访问基类的保护成员,以下观点正确的是( ) A.不可能实现 B.可采用保护继承 C.可采用私有继承 D.可采用公有继承 (4) 设有基类定义: class base { private: int a; protected: int b; public: int c; }; 派生类采用何种继承方式可以使成员变量c能被派生类的对象访问( ) A. 私有继承 B.保护继承 C. 公有继承 D.私有、保护、公有均可 24 (5) 以下关于私有和保护成员的叙述中,不正确的是( ) A. A. 私有成员不能被外界引用,保护成员可以 B. B. 私有成员不能被派生类引用,保护成员在公有继承下可以 C. C. 私有成员不能被派生类引用,保护成员在保护继承下可以 D. D. 私有成员不能被派生类引用,保护成员在私有继承下可以 2.填空题 (1) 生成一个派生类对象时,先调用 (1) 的构造函数,然后调用 (2) 的构造函数。 (2) 继承发生在利用现有类派生新类时,其中 (1) 称为基类,或 (2) 类; (3) 称为派生类,或 (4) 类。 (3) 在继承关系中, (1) 称为多重继承, (2) 称为多层继承。 (4) 在公有继承关系下,派生类的对象可以访问基类中的 (1) 成员,派生类的成员函数可以访问基类中的 (2) 成员。 (5) 在保护继承关系下,基类的公有成员和保护成员将成为派生类中的 (1) 成员,它们只能由派生类的 (2) 来访问;基类的私有成员将成为派生类中的 (3) 成员。 3.看程序写结果 (1)#include { int i; protected: int x; public: parent( ) {x=0; i=0;} void change( ) {x++; i++; } void display(); }; class son: public parent { public: void modify(); }; void parent:: display() {cout<<\void son::modify() {x++; } void main() { son A; parent B; cout<<\ A.display(); A.change(); A.display(); A.modify(); A.display(); cout<<\ B.change(); B.display(); } (2)#include class base { public: void show() {cout<<\}; class derived: public base { public: void show() {cout<<\}; void main() {base demo1; derived demo2; demo1.show(); demo2.show(); demo2.base::show(); 25 } 4.编程题 (1)定义一个国家基类Country,包含国名、首都、人口等属性,派生出省类Province,增加省会城市、人口数量属性。 (2)定义一个车基类Vehicle,含私有成员speed,weight。派生出自行车类Bicycle,增加high成员;汽车类Car,增加seatnum成员。从bicycle和car中派生出摩托车类Motocycle。 多态性 知识要点 1.多态性:多态是指同样的消息被不同类型的对象接收后导致完全不同的行为。 2.面向对象的多态性可以分为4类:重载多态、强制多态、包含多态和参数多态。 3.多态从实现的角度来讲可以划分为两类:编译时的多态和运行时的多态。 4.运算符重载是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据导致不同类型的行为。 5.运算符重载的规则如下: 1)C++语言中的运算符除了少数几个之外,全部可以重载,而且只能重载C++语言中已有的运算符。 2)重载之后运算符的优先级和结合性都不会改变。 3)运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造。一般来讲,重载的功能应当与原有功能相类似,不能改变原运算符的操作对象个数,同时至少要有一个操作对象是自定义类型。 不能重载的运算符只有5个,它们是类属关系运算符“.”、成员指针运算符“*”、作用域分辨符“::”、sizeof运算符和三目运算符“?:”。前面两个运算符保证了C++语言中访问成员功能的含义不被改变。作用域分辨符和sizeof运算符的操作数是类型,而不是普通的表达式,也不具备重载的特征。 6.运算符的重载形式有两种,重载为类的成员函数和重载为类的友元函数。运算符重载为类的成员函数的一般语法形式为: 函数类型 operater 运算符(形参表) { 函数体;} 运算符重载为类的友元函数的一般语法形式为: friend 函数类型 operater 运算符(形参表) { 函数体;} 7.虚函数 说明虚函数的一般格式如下: virtual <函数返回类型说明符> <函数名>(<参数表>) 在定义虚函数时要注意: (1) 虚函数是非静态的、非内联的成员函数,而不能是友元函数,但虚函数可以在另一个类中被声明为友元函数。 (2) 虚函数声明只能出现在类定义的函数原型声明中,而不能在成员函数的函数体实现的时候声明。 (3) 一个虚函数无论被公有继承多少次,它仍然保持其虚函数的特性。 (4) 若类中一个成员函数被说明为虚函数,则该成员函数在派生类中可能有不同的实现。当使用该成员函数操作指针或引用所标识的对象时,对该成员函数调用可采用动态联编。 (5) 定义了虚函数后,程序中声明的指向基类的指针就可以指向其派生类。在执行过程中,该函数可以不断改变它所指向的对象,调用不同版本的成员函数,而且这些动作都是在运行时动态实现的。虚函数充分体现了面向对象程序设计的动态多态性。 8.虚析构函数 在析构函数前加上关键字virtual进行说明,则该析构函数就称为虚析构函数。虚析构函数的说明格式如下: 26 virtual ~<类名>() 在使用虚析构函数时要注意以下几点: (1) 只要基类的析构函数被声明为虚函数,则派生类的析构函数,无论是否使用virtual关键字进行声明,都自动成为虚函数。 (2) 如果基类的析构函数为虚函数,则当派生类未定义析构函数时,编译器所生成的析构函数也为虚函数; (3) 当使用delete运算符删除一个对象时,隐含着对析构函数的一次调用,如果析构函数为虚函数,则这个调用采用动态联编。动态联编可以保证析构函数被正确执行。 (4) 子类型化要求析构函数应被声明为虚函数,特别是在析构函数完成一些有意义的工作时。因此,当不能决定是否应将析构函数声明为虚函数时,就将析构函数声明为虚函数。 9.纯虚函数 当在基类中不能为虚函数给出一个有意义的实现时,可以将其声明为纯虚函数,其实现留待派生类完成。 纯虚函数的作用是为派生类提供一个一致的接口。 纯虚函数的声明格式如下: virtual <函数返回类型说明符> <函数名>(<参数表>)=0; 10.抽象类 带有纯虚函数的类称为抽象类。抽象类具有下述一些特点: (1) 抽象类只能作为基类使用,其纯虚函数的实现由派生类给出;但派生类仍可不给出纯虚函数的定义,继续作为抽象类存在。 (2) 抽象类不能定义对象,一般将该类的构造函数说明为保护的访问控制权限。 (3) 可以声明一个抽象类的指针和引用。通过指针和引用,可以指向并访问派生类对象,进而访问派生类的成员,这种访问是具有多态特征的。 10.2 典型例题分析与解答 例题1: 指出下列对定义重载函数的要求中,哪些是错误的提法。 A. 要求参数的个数不同。 B.要求参数中至少有一个类型不同。 C. 求函数的返回值不同。 D. 要求参数的个数相同时,参数类型不同。 答案:A C 分析:将函数体与函数调用相联系称为捆绑或约束,与多态性称之为静态联编。因为静态联编的函数调用是在编译阶段就确定了的,所以对重载的要求就是在编译阶段能够产生唯一的内部标识符号,以使编译器能够根据这个标识符号来确定到底要调用哪个函数。不必要求参数个数相同,但是如果参数个数相同时,参数类型一定不能全部相同。这也是选项B表达的意思,为什么可以通过参数来重载,而不能通过返回值来重载?这是因为在调用一个函数时会忽略它的返回值,所以不能通过返回值来重载。参数个数或类型不同P83, 与P264判断一个派生类的函数成员是否为虚函数,由此判断该函数是否可以覆盖基类的同名虚函数 例题2:下列运算符中,( )运算符在C++中不能重载。 A.?: B.[] C.new D.&& 答案:A 例题3: 下面关于友元的描述中,错误的是( )。 A. 友元函数可以访问该类的私有数据成员 B. 一个类的友元类中的成员函数都是这个类的友元函数 C. 友元可以提高程序的运行效率 D. 类与类之间的友元关系可以继承 答案:D 分析:友元关系不能被继承,并且是单向的,不可交换的。不清楚友元函数可以直接访问类的私有成员,误选答案A。 不清楚友元类的含义,误选答案B。当一个类作为另一个类的友元时,这个类的所有成员函数都是另一个类的友元函数, 27 即友元类中的所有成员函数都可以访问另一个类中的私有成员。 不清楚引入友元函数的目的,误选答案C。 例题4: 下述静态成员的特性中,( )是错误的。 A. 静态成员函数不能利用this指针 B. 静态数据成员要在类体外进行初始化 C. 引用静态数据成员时,要在静态数据成员名前加<类名>和作用域运算符 D. 静态数据成员不是所有对象所共有的 答案:D 分析:静态数据成员是类的所有对象共享的成员,同一个类的不同对象拥有一个共同的静态数据成员。不清楚静态成员函数的特点,误选答案A。静态成员函数属于整个类,是类的所有对象共享的成员函数,它与一般成员函数不同,没有指向调用该成员函数对象的this指针。不理解静态数据成员如何初始化,误选答案B。静态数据成员的初始化应在类体外进行。不清楚静态数据成员的使用方法,误选答案C。静态数据成员是属于整个类的,因此可以不通过对象名,而直接使用类名和作用域运算符表明其所属的类即可。 例题5: 关于虚函数的描述中,( )是正确的。 A. 虚函数是一个静态成员函数 B. 虚函数是一个非成员函数 C. 虚函数既可以在函数说明时定义,也可以在函数实现时定义 D. 派生类的虚函数与基类中对应的虚函数具有相同的参数个数和类型 参考答案:D 分析:派生类的虚函数与基类中对应的虚函数具有相同的函数名、相同的参数个数和类型。返回值类型或者相同,或者都返回指针或引用,并且派生类虚函数所返回的指针或引用的基类型是基类中的虚函数所返回的指针或引用的基类型的子类型。不清楚虚函数必须是一个非静态的成员函数,误选答案A。不清楚虚函数必须是一个普通的成员函数,误选答案B。虚函数是非内联的,也不能是友元函数。不清楚虚函数的说明方法,误选答案C。虚函数声明只能出现在类定义的函数原型声明中,而不能在成员函数的函数体实现的时候。 分析:类属关系运算符“.”、成员指针运算符“.*”、作用域运算符“::”、sizeof运算符和三目运算符“?:”在C++中不能重载。 例题6:利用成员函数对二元运算符重载,其左操作数为 ,右操作数为 。 答案:(1) this指针 (2) 成员函数参数 分析:将双目运算符重载为类的成员函数时,由于this指针在每次非静态成员函数操作对象时都作为第一个隐式参数传递给对象,因此它充当了二元运算符的左操作数,而该成员函数的形参则表示二元运算符的右操作数。 容易错误分析:(1)不了解this指针的作用,所以不知道二元运算符的左操作数如何表示;(2) 误将成员函数的参数作为二元运算符的左操作数,而不知道右操作数如何表示;(3) 混淆了成员函数和友元函数两种方式,误认为二元运算符的所有的操作数都必须通过函数的形参进行传递,函数的参数与操作数自左至右一一对应; 例题7:可以用pow( )表示幂,也能创造符号**来表示幂运算符。描述是否正确? 答:错误。 分析:重载运算符应限制在C++语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符。 (自学)例题8:分析下列程序的输出结果。 #include 28 #include void Init(char n[]) { strcpy(name,n); } int& operator[](int sub); char* GetName() { return name; } private: char name[25]; int divisionTotals[5]; }; int& Sales::operator [](int sub) { if(sub<0||sub>4) { cerr<<\ abort(); } return divisionTotals[sub]; } void main() { int totalSales=0,avgSales; Sales company; company.Init(\ company[0]=123; company[1]=456; company[2]=789; company[3]=234; company[4]=567; cout<<\ for(int i=0;i<5;i++) cout< 运行结果: Here are the sales for Swiss Cheese's divisions: 123 456 789 234 567 The total sales are 2169 The average sales are 433 分析:(1) 在上述程序中,并没有创建company的对象数组。程序中的下标被重载,以便在使用company时用一种特殊的方式工作。下标总是返回和下标对应的那个部门的销售额divisionTotals[];(2) 重载下标运算符\时,返回一个int型引用,可使重载的\用在赋值语句的左边,因而在main()中,可对每个部门的销售额divisionTotals[]赋值。这样,虽然divisionTotals[]是私有的,main()还是能够直接对其赋值,而不需要使用函数Init();(3) 在上述程序中,设有对下 29 标的检验,以确保被赋值的数组元素存在。当程序中一旦向超出所定义的数组下标范围的数组元素进行赋值时,便会自动终止程序,以免造成不必要的破坏;(4)与函数调用运算符\一样,下标运算符\不能用友元函数重载,只能采用成员函数重载。 例题9:定义Point类,有数据成员X和Y,重载++和--运算符,要求同时重载前缀方式和后缀方式。 答案: #include Point() { X=Y=0; } int GetX() { return X; } int GetY() { return Y; } Point& operator ++(); Point operator ++(int); Point& operator --(); Point operator --(int); void Print() { cout<<\ private: int X,Y; }; Point& Point::operator ++() { X++; Y++; return *this; } Point Point::operator ++(int) { Point temp=*this; ++*this; return temp; } Point& Point::operator --() { X--; Y--; return *this; } Point Point::operator --(int) { Point temp=*this; --*this; return temp; } void main() { Point obj; obj.Print(); obj++; 30 obj.Print(); ++obj; obj.Print(); obj--; obj.Print(); --obj; obj.Print(); } 分析:注意重载前缀单目运算符和后缀单目运算符的区别。前缀单目运算符重载为类的成员函数时,不需要显式说明参数,即函数没有形参;而后缀单目运算符重载为类的成员函数时,函数要带有一个整型形参。 例题10:指出下列程序中的错误,并说明错误原因。 class X //1 {public: //2 int readme() const {return m;} //3 void writeme(int i) {m=i;} //4 private: //5 int m; //6 }; //7 void f(X& x1,const X& x2) //8对象的引用作形参 {x1.readme(); //9 x1.writeme(1); //10 x2.readme(); //11 x2.writeme(2); //12 } //13 答案: 行12出错,删除。 分析:常对象不能调用一般成员函数,因此 (1)类X中定义了两个成员函数:常成员函数readme和一般成员函数writeme;(2) 函数f中定义了两个参数:常参数x2和一般参数x1。两个对象分别调用了两个成员函数;(3)成员函数与对象之间的操作关系是:常对象和一般对象都可以调用常成员函数,而一般成员函数只能由一般对象调用,常对象调用它时将产生错误;(4) 根据以上分析,一般对象x1对常成员函数readme和一般成员函数writeme的调用正确,常对象x2对常成员函数readme的调用正确,但对一般成员函数writeme的调用错误。 不清楚函数的调用者和被调用者,颠倒了成员函数和对象二者之间的关系,导致判断错误,认为行11出错。 课本P141: 常引用所引用的对象不能被更新; 常对象必须进行初始化,且不能被更新。改变对象数据成员的途径有两个:1、在类外通过对象名访问其公有数据成员,由语法限制不能再赋值。 常成员函数不能更新对象的数据成员。 常对象只能调用它的常成员函数。 2、类的成员函数中改变数据成员的值,因此语法规定,不能通过常对象调用普通的成员函数。 例题11:分析下列程序的输出结果。 #include 31 A() { cout<<\virtual ~A() { cout<<\ virtual void f() { cout<<\void g() { f(); } }; class B : public A{ public: B() { f(); cout<<\~B() { cout<<\}; class C : public B{ public: C() { cout<<\~C() { cout<<\void f() { cout<<\}; void main() { A *a=new C; a->g(); delete a; } 运行结果: A's cons. A's f(). B's cons. C's cons. C's f(). C's des. B's des. A's des. 分析:(1) 类B从类A公有继承,类C从类B公有继承,B是A的子类型,C是B的子类型,也是A的子类型。因此可以使用类C的对象去初始化基类A的指针;(2) 类A中定义了两个虚函数:虚成员函数f和虚析构函数。由于类C中的f函数与基类A中的f函数的参数和返回类型相同,因此类C中的成员函数f也是虚函数;由于基类A中定义了虚析构函数,因此派生类B和C的析构函数也都是虚析构函数;(3) 主程序中A *a=new C;隐含了两步操作,即:首先建立一个派生类C的对象,然后使用该派生类对象去初始化基类A的指针a;(4) 用new运算符创建C对象时,要调用C的构造函数。C是一个派生类,它必须负责对其直接基类的构造函数的调用,因此执行顺序是:先执行直接基类B的构造函数,再执行类C的构造函数体;(5) 类B也是一个派生类,它也必须负责调用它的直接基类A的构造函数,其执行顺序是:先执行直接基类A的构造函数,再执行类B的构造函数体;(6)执行类A的构造函数,输出:A's cons.;(7)类B的构造函数体中,首先调用了虚函数f,但由于是在构造函数中调用虚函数,所以对它的调用采用静态联编。因为类B中没有对f函数进行定义,因此调用基类A中的f函数,首先输出A's f().;继续执行类B的构造函数体,输出B's cons.;(8) 执行类C的构造函数体,输出:C's cons.;(9) 成员函数g中调用了虚函数f,此时满足动态联编的条件:C是A的子类型;存在虚函数f;在成员函数中调用了虚函数,因此,通过指针a访问g函数时,采用动态联编,即a->g()调用的是派生类C中的成员函数f,输出:C's f().;(10)delete a将调用析构函数。由于整个类族中都定义了虚析构函数,因此此处将进行动态联编,即调用的是基类指针a当前正在指向的派生类C的析构函数。由于C是一个派生类,所以 32 它必须负责调用它的基类的析构函数,其执行顺序是:先调用派生类C的析构函数,再调用直接基类B的析构函数。而直接基类B也是一个派生类,所以它也必须负责调用它的直接基类A的析构函数。因此,析构的顺序是:C、B、A,输出:C's des.;B's des.;A's des.。析构的顺序与构造的顺序完全相反。 容易发生误解是,不清楚用new运算符建立对象时将自动调用构造函数; 不清楚派生类构造函数的执行顺序,误认为先调用派生类构造函数,再调用基类构造函数,导致先输出:C's cons.;不清楚派生类只负责对其直接基类构造函数的调用,在建立C类的对象时,错误地既调用了直接基类B的构造函数,又调用了间接基类A的构造函数,导致错误输出:B's cons.;A's cons.;不清楚在构造函数和析构函数中调用虚函数时只能进行静态联编,误认为此处也是动态联编,导致错误输出:C's f().;不清楚动态联编的实现条件,不知道在成员函数中调用虚函数是进行动态联编,导致对g函数的调用产生错误输出:A's f().;不清楚用delete运算符释放对象时将自动调用析构函数;不清楚对虚析构函数的调用采用的是动态联编,误认为释放的是基类A的对象,导致错误输出:A's des.;不清楚派生类的析构函数的执行顺序,误认为与构造时的顺序相同,导致错误输出:A's des.;B's des.;C's des.。 1.选择题 (1)下列关于动态联编的描述中,错误的是()。 A.动态联编是以虚函数为基础 B.动态联编是运行时确定所调用的函数代码的 C.动态联编调用函数操作是指向对象的指针或对象引用 D.动态联编是在编译时确定操作函数的 答案:D (2)关于虚函数的描述中,正确的是()。 A.虚函数是一个静态成员函数 B.虚函数是一个非成员函数 C.虚函数即可以在函数说明定义,也可以在函数实现时定义 D.派生类的虚函数与基类中对应的虚函数具有相同的参数个数和类型 答案:D (3)下面4个选项中,( )是用来声明虚函数的。 A.virtual B.public C.using D.false 答案:A (4)编译时的多态性可以通过使用( )获得。 A.虚函数和指针 B.重载函数和析构函数 C.虚函数和对象 D.虚函数和引用 答案:A (5)关于纯虚函数和抽象类的描述中,错误的是( )。 A.纯虚函数是一种特殊的虚函数,它没有具体的实现 B.抽象类是指具体纯虚函数的类 C.一个基类中说明有纯虚函数,该基类派生类一定不再是抽象类 D.抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出 答案:B (6)下列描述中,( )是抽象类的特征。 A.可以说明虚函数 B.可以进行构造函数重载 C.可以定义友元函数 D.不能说明其对象 答案:D (7)以下( )成员函数表示虚函数。 A.virtual int vf(int); B.void vf(int)=0; C.virtual void vf()=0; D.virtual void vf(int) { }; 答案:D 33 (8)如果一个类至少有一个纯虚函数,那么就称该类为( A )。 A.抽象类 B.虚函数 C.派生类 D.以上都不对 答案:A (9)要实现动态联编,必须通过( )调用虚函数。 A.对象指针 B.成员名限定 C.对象名 D.派生类名 答案:A (10)下面描述中,正确的是(A )。 A.virtual可以用来声明虚函数 B.含有纯虚函数的类是不可以用来创建对象的,因为它是虚基类 C.即使基类的构造函数没有参数,派生类也必须建立构造函数 D.静态数据成员可以通过成员初始化列表来初始化 答案:A 2.什么叫做多态性?在C++语言中是如何实现多态的? 答:多态是指同样的消息被不同类型的对象接收时导致完全不同的行为,是对类的特定成员函数的再抽象.c十+支持的多态有多种类型.重载(包括函数重载和运算符重载)和虚函数是其中主要的方式. 3.什么叫做抽象类?抽象类有何作用?抽象类的派生类是否一定要给出纯虚函数? 答:带有纯虚函数的类是抽象类.抽象类的主要作用是通过它为一个类族建立一个公共的接口.使它们能够更有效地发挥多态特性。抽象类声明了一组派生类共同操作接口的通用语义.面接口的完整实现,即纯虚函数的函数体,要由派生类自己给出.但抽象类的派生类并非一定要给出纯虚函数的实现.如果派生类没有给出纯虚函数的实现,这个派生类仍然是一个抽象类. 4.声明一个参数为整型、无返回值、名为fun1的虚函数。 答: virtual void fnl(int); 5.在C++语言中,能否声明虚构造函数?为什么?能否声明虚析构函数?有何用途? 答:在C++-中不能声明虚构造函数.多态是不同的对象对同一消息有不同的行为特性.虚函数作为运行过程中多态的基础,主要是针对对象的,面构造函数是在对象产生之前运行的,因此虚构造函数是没有童义的. 在C++中可以声明虚析构函数.析构函数的功能是在该类对象消亡之前进行一些必要的清理工作,如果一个类的析构函数是虚函数,那么,由它派生而来的所有子类的析构函数也是虚函数.析构函数设置为虚函数之后,在使用指针引用时可以动态联编,实理运行时的多态,保证使用基类的指针就能够谓用适当的析构函数指针对不同的对象进行清理工作. 6.实现重载函数Double(x),返回值为输人参数的两倍;参数分别为整型、浮点型、双精度型,返回值类型与参数一样。 程序: #include base (T n) {m=n+n;} void show() {cout< 34 }; void main() {base double y=12.354356788; base #include Rectangle(); Rectangle(int width,int length); ~Rectangle(){ } int GetWidth() const{ return itsWidth;} int GetLength() const{ return itsLength;} private: int itsWidth; int itsLength; }; Rectangle::Rectangle() { itsWidht=5; itsLength=10; } Rectangle::Rectangle(int width,int length) { itsWidth=width; itsLength=length; } void main() { Rectangle Rect1; cout<<”Rect1 width:”< cout<<”\\nEnter a length”; cin>>aLength; Rectangle Rect2(aWidth,aLength); 35 cout<<”\\nRect2 width:”< 8.声明计数器Counter类,对其重载运算符“+”。课本8_7 程序: #include Counter() Counter(int initialValue); ~Counter(){} int GetItsVal()const {return itsVal;} void SetItsVal(int x) { itsVal=x;} Counter operator+(const Counter &); private: int itsVal; }; Counter::Counter(int initialValue):itsVal(initialValue){ } Counter::Counter():itsVal(0) { } Counter Counter::operator+(const Counter &rhs) { return Counter(itsVal+rhs.GetItsVal());} void main() { Counter varOne(2),varTwo(4),varThree; varThree=varOne+varTwo; cout<<\ cout<<\ cout<<\ } 9.声明一个哺乳动物Mammal类,再由此派生出狗Dog类,二者都定义Speak( )成员函数,基类中定义为虚函数。声明一个Dog类的对象,调用Speak()函数,观察运行结果。 程序: #include Mammal() {cout<<\ virtual void speak() {cout<<\ } }; class Dog :public Mammal { public: Dog() {cout<<\ } void speak() {cout<<\} }; 36 void main() { Mammal a; a.speak(); Dog b; b.speak(); } 10.声明一个Shape抽象类,在此基础上派生出Rectangle和Circle类,二者都有GetArea( )函数计算对象的面积,GetPerim( )函数计算对象的周长。 解答: #include virtual void getarea()=0; virtual void getperim()=0; }; class rectangle:public shape { int a,b,c; double s,p; public: rectangle(int a1,int b1,int c1) {a=a1;b=b1;c=c1;} void getperim() { p=a+b+c; cout<<\周长 \ void getarea() { p=(a+b+c)/2.0; s=sqrt(p*(p-a)*(p-b)*(p-c)); cout<<\面积 \} }; class circle :public shape { float r,s,p; public: circle(float r1) {r=r1;} void getperim() { p=2*r*3.1415926;cout<<\周长 \ void getarea() { s=r*r*3.1415926; cout<<\面积 \}; void show(shape *p) { p->getarea(); p->getperim(); } void main() { shape *p; rectangle a(3,4,5); circle b(10); 37 p=&a; show(p); p=&b; show(p); } 11.应用抽象类,求圆、圆内接正方形和圆外切正方形的面积和周长。 程序: #include class circle:public base //公有派生 { protected: double r,s,p; public: circle(double x=0) { r=x;} void display( ) { cout<<\圆的面积 \ cout<<\圆的周长 \ //虚成员函数 }}; class incircle:public circle //公有派生 { double a; public: incircle(double x=0): circle(x) {} void display( ) { a=sqrt(r); cout<<\内接正方形面积\ cout<<\内接正方形周长\ //虚成员函数 }}; class outcircle:public circle //公有派生 { public: outcircle(double x=0): circle(x) {} void display( ) { cout<<\外切正方形面积\ cout<<\外切正方形周长\ //虚成员函数 }}; void fun(base *ptr) //普通函数 { ptr ->display( ); } void main( ) //主函数 { base *p; //声明抽象基类指针circle b1(10); //声明派生类对象 incircle d1(9); //声明派生类对象 outcircle e1(10); 38 p=&b1; fun(p); p=&d1; fun(p); p=&e1; fun(p); } 12.分别用成员函数和友元函数重载运算符,使对实型的运算符=、+、-、*、/ 适用于复数运算。 程序: #include complex operator +(complex c2); //运算符\重载成员函数 complex operator - (complex c2); //运算符\重载成员函数 complex operator *(complex ); complex operator /(complex); complex operator =(complex c2) {real=c2.real;imag=c2.imag; return complex(real,imag); } void display(); //输出复数 private: //私有数据成员 double real; //复数实部 double imag; //复数虚部 }; complex complex::operator + (complex c2) //重载运算符函数实现 { return complex(real + c2.real,imag + c2.imag); } //创建-个临时无名对象作为返回值 complex complex::operator - (complex c2) //重载运算符函数实现 { return complex(real - c2.real,imag - c2.imag); } complex complex::operator *(complex c2) { double x,y; x=real*c2.real-imag*c2.imag; y=real*c2.imag+imag*c2.real; return complex(x,y); } complex complex::operator /(complex c2) { double x,y,z; x=real*c2.real+imag*c2.imag; y=real*c2.imag-imag*c2.real; z=real*c2.real+imag*c2.imag; return complex(x/z,y/z); } void complex::display() { cout<<\ 39 } 1.单选题 (1)在重载运算符函数时,下面( )运算符必须重载为类成员函数形式。 A. + B. - C. ++ D. -> (2)友元运算符obj1>obj2被C++编译器解释为( )。 A. operator >(obj1,obj2) B. >(obj1,obj2) C. obj2.operator >(obj1) D. obj1.operator >(obj2) (3)重载赋值操作符时,应声明为( )函数。 A. 友元 B. 虚 C. 成员 D. 多态 (4)一个类的友元函数或友元类能够通过成员操作符访问该类的( )。 A. 私有成员 B. 保护成员 C.公用成员 D.所有成员 (5)下面对静态数据成员的描述中,正确的是( )。 A. 静态数据成员是类的所有对象共享的数据 B. 类的每个对象都有自己的静态数据成员 C. 类的不同对象有不同的静态数据成员值 D.静态数据成员不能通过类的对象调用 (6)有如下类的定义。空格处的语句是( )。 class MyClass {public: MyClass(int a = 0, int b = 0){ X = a;Y = b; } void Change() const {X -= 10; Y += 10; } private: ( ) int X, Y; }; A. static B. const C. mutable D. 不需要填入内容 (7)编译时的多态性可以通过使用( )获得。 A.虚函数和指针B.重载函数和析构函数 C.虚函数和对象 D.虚函数和引用 2.填空 (1).单目运算符作为类成员函数重载时 形参;双目运算符作为 重载时需声明其右操作数,作为 重载时需声明全部操作数。 (2)下列程序定义了一实部为real,虚部为imag的复数类complex,并在类中重载了复数的+、-操作。请将下列程序补充完整。 class Complex {public: Complex(double r=0.0,double i=0.0) {(1)} (2)operator +(Complex); (3)operator -(Complex,Complex); private: double real,imag; }; (4)operator +(Complex c) { return Complex((5));} (6)operator -(Complex c1,Complex c2) { return Complex((7));} 40 3.写出下列程序的运行结果。 #include void main() { char *s=\ Words word(s); word.Print(); int n=strlen(s); while(n>=0) { word[n-1]=word[n-1]-32; n--; } word.Print(); } 3.写出下列程序的运行结果。 (1) #include Rect(int l,int w) { length=l; width=w; } void Print() { cout<<\void* operator new(size_t size) { return malloc(size); } void operator delete(void *p) { free(p); } private: int length,width; }; void main() { Rect *p; p=new Rect(5,4); p->Print(); delete p; } (2)#include 41 class Test {public: Test (double m,double n,double d):p(d) { x=m; y=n; } void Show(); void Show() const; private: double x, y; const double p; }; void Test::Show() { cout< void Test::Show() const { cout< void main() { Test a(8.9,2.5,3.1416); a.Show(); const Test b(2.5,8.9,3.14); b.Show(); } (3) #include Mammal() { cout<<\ virtual ~Mammal() { cout<<\virtual void Speak() const { cout<<\}; class Dog : public Mammal {public: Dog() { cout<<\~Dog() { cout<<\void Speak() const { cout<<\}; void main() { Mammal *p=new Dog; p->Speak(); delete p; } 4.通过重载函数调用运算符实现下列数学函数的抽象: f(x,y)=a*x2+b*y+c 5.C++在运行期间不会自动检查数组是否越界。设计一个类用来检查数组是否越界。 42 6.开发多项式类Polynomial,多项式的每一项用数组表示,每项包含一个系数和一个指数。例如:2x4的指数为4,系数为2。试开发一个完整的Polynomial类,包括构造函数、析构函数、“get”函数和“set”函数,以及下述重载的运算符: (1) 重载加法运算符+,将两个多项式相加。 (2) 重载减法运算符-,将两个多项式相减。 (3) 重载乘法运算符*,将两个多项式相乘。 (4) 重载加法赋值运算符+=,减法赋值运算符-=以及乘法赋值运算符*=。 7.定义一个Cat类,拥有静态数据成员HowManyCats,记录Cat的个体数目;静态成员函数GetHowMany(),存取HowManyCats。 8.定义Boat和Car两个类,二者都有weight属性,定义二者的一个友元函数totalWeight(),计算二者的重量和。 9.应用抽象类,求圆、圆内接正方形和圆外切正方形的面积和周长。 10.根据给定的程序执行结果,将下列程序补充完整。 #include Base(int i) {b=i;} (1) protected: int b; }; class Derive1:public Base {public: (2) void Print(){cout<<\}; class Derive2:public Base { (3) }; void fun( (4) ){ obj->Print();} void main() { (5) fun(d1); fun(d2); } 程序的执行结果如下: Derive1's Print() called. Derive2's Print() called. 43
正在阅读:
面向对象习题04-04
125个简单的选股指标公式03-21
地质学期末复习总结汇总11-28
北京中医药大学东方医院召开2012年度上半年工作总结会11-02
“洋洋得意”教学案例与反思02-22
工程制图习题集答案(何文平主编)习题答案-第三章_立体的表面交线08-06
北京工商大学03-08
度米文库汇编之学校教学工作计划范文05-29
几种提高微波频率测量精度的方法11-10
- 多层物业服务方案
- (审判实务)习惯法与少数民族地区民间纠纷解决问题(孙 潋)
- 人教版新课标六年级下册语文全册教案
- 词语打卡
- photoshop实习报告
- 钢结构设计原理综合测试2
- 2014年期末练习题
- 高中数学中的逆向思维解题方法探讨
- 名师原创 全国通用2014-2015学年高二寒假作业 政治(一)Word版
- 北航《建筑结构检测鉴定与加固》在线作业三
- XX县卫生监督所工程建设项目可行性研究报告
- 小学四年级观察作文经典评语
- 浅谈110KV变电站电气一次设计-程泉焱(1)
- 安全员考试题库
- 国家电网公司变电运维管理规定(试行)
- 义务教育课程标准稿征求意见提纲
- 教学秘书面试技巧
- 钢结构工程施工组织设计
- 水利工程概论论文
- 09届九年级数学第四次模拟试卷
- 习题
- 面向
- 对象
- 数学竞赛中的数论问题(学生版)第三讲
- 新314省道交通信号灯工程顶管施工方案新(1)
- 极品指标主图源码通达信指标公式源码
- 人力资源管理参考文献目录
- 公路工程安全知识竞赛试题题库讲义
- 机械工程测试技术试卷及答案
- 目前我国财政支农存在的问题及对策研究
- 大学物理作业及答案详解1-22
- 治污减霾方案
- 推焦车点检巡检内容
- 保安员资格考试模拟试题一
- 20120421-2 智慧杭州规划评审打印稿
- 药房新入职员工岗前培训试题
- 全球金融危机下的资本主义
- 中山大学2013年博士研究生招生学科专业目录 - 图文
- 美工考核标准
- 海洋 - 21世纪的希望教学设计
- 六年级数学下册 期末复习 解决问题的策略习题北师大版
- 公司理财 计算题
- 实验小学教师十项基本功训练方案