第8章类与对象
8.1知识要点
1.掌握类与对象的概念和定义方法,掌握类成员的访问属性。
2.掌握构造函数的概念、定义方法和使用方法。
3.掌握析构函数的概念、定义方法和使用方法。
4.掌握拷贝构造函数的概念、定义方法和使用方法。
5.掌握包含对象成员的类构造函数的定义方法。
6.掌握静态成员的概念、定义方法、作用和特点。
7.掌握友元的概念、定义方法、作用和特点。
8.掌握类模板的定义格式与使用方法。
8.2典型例题分析与解答
例题1:下列有关类的说法不正确的是()。
A.对象是类的一个实例
B.任何一个对象只能属于一个具体的类
C.一个类只能有一个对象
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;
voidshow(){cout< }; } C. class P{ D. class P{ intf; public: }; int a; f=25; void Seta (int x) {a=x;} 答案:D 分析:在类体内不可对数据成员进行初始化;类定义结束时需用分号;只有类中的成员函数才能存取类中的私有数据。 例题5:拷贝构造函数具有的下列特点中,()是错误的。 A.如果一个类中没有定义拷贝构造函数时,系统将自动生成一个默认的 B.拷贝构造函数只有一个参数,并且是该类对象的引用 C.拷贝构造函数是一种成员函数 D.拷贝构造函数的名字不能用类名 答案:D 分析:如果一个类中没有定义拷贝构造函数时,系统将自动生成一个默认的;拷贝构造函数只有一个参数,并且是该类对象的引用;拷贝构造函数的名字与类同名,并且不被指定返回类型;拷贝构造函数是一种成员函数。 例题6:关于静态成员的描述中,()是错误的。 A.静态成员可分为静态数据成员和静态成员函数 B.静态数据成员定义后必须在类体内进行初始化 C.静态数据成员初始化不使用其构造函数 D.静态数据成员函数中不能直接引用非静态成员 答案:B 分析:静态成员可分为静态数据成员和静态成员函数;静态数据成员被定义后,必须对它进行初始化,初始化在类体外进行,一般放在该类的实现部分最合适,也可以放在其他位置,例如,放在主函数前面等;静态数据成员初始化与该类的构造函数和析构函数无关;在静态成员函数的实现中,可以直接引用静态成员,但不能直接引用非静态成员。 例题7:关于友元的描述中,()是错误的。 A.友元函数是成员函数,它被说明在类体内 B.友元函数可直接访问类中的私有成员 C.友元函数破坏封装性,使用时尽量少用 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 class one { int a1,a2; public: one(int x1=0, x2=0); }; void main() { one data(2,3); cout< cout< } 分析:出错原因:构造函数参数表语法错;构造函数没有函数体;类的对象不能直接访问类的私有成员变量。 改正后的程序如下: #include class one { int a1, a2; public: 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< cout< } 例题12:分析以下程序的错误原因,给出修改方案使之能正确运行。 #include class Amplifier{ 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<<"\n\nThe gain is =>"< } 分析:成员函数在类体外定义格式是:函数返回类型类名::成员函数名(参数表);成员函数调用格式是:对象名.成员函数名(参数表)。 改正后的程序如下: #include class Amplifier { 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<<"\n\nThe gain is =>"< } 例题13:下列程序的运行结果是。 #include class point {int x,y; public: point(int a,int b) {x=a;y=b; cout<<"calling the constructor function."< } point(point &p); friend point move(point q); ~point(){cout<<"calling the destructor function.\n";} int getx(){return x;} int gety() {return y;} }; point::point(point &p) {x=p.x;y=p.y; cout<<"calling the copy_initialization constructor function.\n"; } point move(point q) {cout<<"OK!\n"; 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<<"p="< } 分析:根据构造函数、拷贝构造函数和友元函数的特点,执行该程序后,输出结果是: 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 #include classstudent {int no; char name[10]; int age; 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” < } }; void main() { student d1(1001,”Tom”,18); d1.display(); student d2; d2.display(); } 例题15:计算两点之间的距离。 方法一:可以定义点类(Point),再定义一个类(Distance)描述两点之间的距离,其数据成员为两个点类对象,两点之间距离的计算可设计由构造函数来实现。#include #include class Point { public: 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 #include class Point {public: 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: ”< 8.3教材习题分析与解答 1.选择题 (1)对类的构造函数和析构函数描述正确的是()。 A. 构造函数可以重载,析构函数不能重载 B. 构造函数不能重载,析构函数可以重载 C. 构造函数可以重载,析构函数也可以重载 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 答案:C (6)为了使类中的某个成员不能被类的对象通过成员操作符访问,则不能把该成员的访问权限定义为()。 A.public B. protected C.private D. static 答案:A (7)下面对静态数据成员的描述中,正确的是()。 A.静态数据成员可以在类体内进行初始化 B.静态数据成员不可以在类体内进行初始化 C.静态数据成员不能受private控制符的作用 D.静态数据成员可以直接用类名调用 答案:C (8)下面对静态数据成员的描述中,正确的是()。 A.静态数据成员是类的所有对象共享的数据 B.类的每一个对象都有自己的静态数据成员 C.类的不同对象有不同的静态数据成员值 D.静态数据成员不能通过类的对象调用 答案:A 2.写出下列程序的运行结果。 (1)#include class Point{ int x,y; public: Point(){x=1;y=1;} ~Point(){cout<<"Point "< void main() {Pointa;} 运行结果为: Point 1,1 is deleted. (2) #include #include int count=0; class Point {int x,y; public: Point() { x=1;y=1; count++; } ~Point(){count--;} friend void display(); }; void display() { cout <<"There are "< { Point a; display(); {Point b[5]; display(); } display(); } 运行结果为: There are 1 points, There are 6 points, There are 1 points, (3)#include class Csample {int i; public: Csample( ); void Display( ); ~Csample(); }; Csample::Csample( ) { cout<<”Constructor”<<”,”; i=0; } void Csample::Display() {cout<<”i=”< { cout<<”Destructor”< void main( ) { Csample a; a.Display( ); } 运行结果为: Constructor,i=0,Destructor (4) #include #include class Csample { int i; public: Csample(){cout <<"constructor1" < Csample(int val){cout <<"Constructor2"< {cout<<"i="< ~Csample() {cout<<"Destructor"< }; 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( ); void Display( ); }; int Csample::k=0; Csample::Csample( ) { i=0;k++;} void Csample::Display( ){ cout<<”i=”< void main( ) { Csample a,b; 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;//显示剩余产品数量 }; 并用数据进行测试。 (2)计算两点之间的距离。 提示:可以定义点类(Point),再定义一个类(Distance)描述两点之间的距离,其数据成员为两个点类对象,两点之间距离的计算可设计由构造函数来实现。 参照本章例15 (3)定义盒子Box类,要求具有以下成员:可设置盒子形状;可计算盒子体积;可计算盒子的表面积。 #include class Box { 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<<"x= "< cout<<"长方形的周长和面积为:"< (6)编写一个程序,采用一个类求n!,并输出10!的值。 #include class fac { int p; public: fac() { p=1;} fac( int j) {p=1; if(j>=0) for(int i=1 ;i<=j;i++) p=p*i; else cout<<"数据错误\n"; } void show() { cout<<" 阶乘为:"< }; void main() { int n; cout<<"请输入一个整数:"; cin>>n; fac a(n); cout< } (7)编写一个程序,设计一个Cdate类,它应该满足下面的条件: 1)用这样的格式输出日期:日-月-年; 2)输出在当前日期上加两天后的日期; 3)设置日期。 #include class Cdate {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%400==0||year%4==0&&year%100!=0))) {m1=3;d1=1;};break; case 30:if(month==2 &&(year%400==0||year%4==0&&year%100!=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() {Cdated; int y,m,d1; cout<<"请输入年月日: "; cin>>y>>m>>d1; d.setdate(y,m,d1);//加一天 d.setdate(y,m,d1);//再加一天 d.datetwo(); d.show(); } 8.4 补充习题 1.选择题 (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(){ ... } (9) 以下关于静态成员变量的叙述不正确的是() A.静态成员变量为类的所有对象所公有 B.静态成员变量可以在类内任何位置上声明 C.静态成员变量的赋初值必须放在类外 D.定义静态成员变量时必须赋初值 (10) 定义静态成员函数的主要目的是() A.方便调用B.有利于数据隐藏 C.处理类的静态成员变量D.便于继承 (11) 以下叙述不正确的是() 使用静态数据成员: A.可以节省内存空间 B.是为了解决数据共享问题 C.可以直接用类名来引用D.可以提高序运算速度 2.填空题 (1) OOP技术由、、方法、消息和继承五个基本的概念所组成。 (2)类的成员函数可以在定义,也可以在定义。 (3)类test的析构函数名是。 (4)类是用户定义的类型,具有类类型的变量称作_______________。 (5)一个类的析构函数不允许有。 (6)建立对象时,为节省内存,系统只给_______分配内存。 (7)用于定义C++的类的关键字有____________、___________和union。 (8)类test的构造函数是和_______同名的函数,析构函数是__________。 (9)类中的数据和成员函数默认访问类型为。 (10)当建立一个新对象时,程序自动调用_______________。 3. 改错题 (1) 下面的程序定义了一个Point类,找出程序中的错误语句并改正。 #include class Point{ int x; public: void Point(int a) {x=a;} int Getx(){return x;} void Show()