实验名称:类和对象的定义及使用
一、实验目的和要求
(1)掌握类与对象的定义与使用方法,理解面向对象方法中通过对象间传递消息的工作机制。
(2)正确掌握类的不同属性成员的使用方法。
(3)掌握构造函数与析构函数的概念,理解构造函数与析构函数的执行过程。
(4)掌握友元函数和友元类的定义和使用。
(5)基本掌握指针和引用作为函数参数的应用。
二、实验环境(实验设备)
硬件: 微型计算机
软件: Windows 操作系统、Microsoft Visual Studio 2010
三、实验原理及内容
实验题目1 定义一个借书证类BookCard,在该类定义中包括如下内容。
(1)私有数据成员:
string id; //借书证学生的学号
string stuName; //借书证学生的姓名
int number; //所借书的数量
(2)公有成员函数:
构造函数 //用来初始化3 个数据成员,是否带默认参数值参考结果来分析
void display() //显示借书证的3 个数据成员的信息
bool borrow() //已借书数量不足10 则将数量加1,数量达到10 则直接返回false
主函数及f()函数代码如下。请结合输出结果完成程序。
void f(BookCard &bk)
{
if (!bk.borrow())
{
bk.display();
cout<<"you have borrowed 10 books,can not borrow any more!"<<endl;
}
else
bk.display();
}
int main()
{
BookCard bk1("B20190620","东平",10),bk2;
f(bk1);
f(bk2);
return 0;
}
程序的运行结果为:
B20190620 东平 10
you have borrowed 10 books,can not borrow any more!
B19010250 雪峰 4
参考实验教材中相应的实验指导,完成源程序代码如下:
//Test1—_h.h 定义BookCard类
#include<iostream>
#include<string.h>
using namespace std;
class BookCard
{
private:
string id; //借书证的学生的学号
string stuName; //借书证学生的姓名
int number; //所借书的数量
public:
BookCard(string a="B19010250", string b="雪峰", int c=4);
//用来初始化3个数据成员,是否带默认参数值参考结果来分析
void display(); //显示借书证的3 个数据成员的信息
bool borrow(); //已借书数量不足10 则将数量加1,数量达到10 则直接返回false
};
//BookCard类成员函数
#include"1-1.h"
#include<iostream>
using namespace std;
BookCard::BookCard(string a, string b, int c) :id(a), stuName(b), number(c)
//初始化列表初始化
{ }
void BookCard::display()
{
cout << id << " " << stuName << " " << number << endl;
}
bool BookCard::borrow()
{
if (number < 10)
{
number++;
return true;
}
else
return false;
}
//主程序
#include"Test1_h.h"
#include<iostream>
using namespace std;
void f(BookCard& bk)
{
if (!bk.borrow())
{
bk.display();
cout << "you have borrowed 10 books,can not borrow any more!" << endl;
}
else
bk.display();
}
int main()
{
BookCard bk1("B20190620", "东平", 10), bk2;
f(bk1);
f(bk2);
return 0;
}
实验题目2 定义一个时间类Time,有三个私有成员变量Hour、Minute、Second,定义构造函数、析构函数以及用于改变、获取、输出时间信息的公有函数,主函数中定义时间对象,并通过调用各种成员函数完成时间的设定、改变、获取、输出等功能。
① 按要求完成类的定义与实现。
② 修改数据成员的访问方式,观察编译结果。
③ 在Time类中定义一个成员函数,用于实现时间增加一秒的功能,主函数中通过对象调用该函数,并输出增加一秒后的时间信息。
④ 定义一个普通函数。
void f(Time t)
{
t. PrintTime( );
}
在Time类中增加拷贝构造函数的定义,主函数中调用该函数,运用调试工具跟踪,分析整个程序调用构造函数(包括拷贝构造函数)和析构函数的次数;再将f函数的形式参数分别修改为引用参数和指针参数(此时函数代码修改为{t-> PrintTime( );},主函数中调用,再分析此时调用构造函数和析构函数的次数。
参考实验教材中相应的实验指导完成程序,并回答相关问题。完成后的源程序代码如下:
#include<iostream>
using namespace std;
class Time
{
private:
int Hour, Minute, Second;
public:
Time(int h = 0, int m = 0, int s = 0);
void Change(int h, int m, int s);
int GetHour();
int GetMinute();
int GetSecond();
void PrintTime();
//void IncreaseOneSecond();
~Time();
};
Time::Time(int h, int m, int s)
{
Hour = h;
Minute = m;
Second = s;
cout << "Construct" << endl;
}
void Time::Change(int h, int m, int s)
{
Hour = h;
Minute = m;
Second = s;
}
int Time::GetHour()//获取Hour
{
return Hour;
}
int Time::GetMinute()//获取Minute
{
return Minute;
}
int Time::GetSecond()//获取Second
{
return Second;
}
void Time::PrintTime()
{
cout << Hour << ":" << Minute << ":" << Second<< endl;
}
Time::~Time()
{
cout << "Destructor:" << Hour << ":" << Minute << ":" << Second << endl;
}
int main()
{
Time t1;
Time t2(20);
Time t3(20, 30);
Time t4(20, 30, 56);
t1.PrintTime();
t2.PrintTime();
t3.PrintTime();
t4.PrintTime();
t4.Change(23, 59, 59);
t1.Change(14, 28, 12);
cout << "ChangeTime" << t1.GetHour() << ":" << t1.GetMinute() << ":" << t1.GetSecond() << endl;
cout << "ChangeTime" << t4.GetHour() << ":" << t4.GetMinute() << ":" << t4.GetSecond() << endl;
}
程序的运行结果是:
构造函数与析构函数的调用方式及执行顺序是:
调用方式:自动调用
执行顺序:先执行构造函数,程序结束时执行析构函数。
析构函数的调用顺序与构造函数相反。
③取消类中成员函数IncreaceOneSecond( )的注释标志,将该函数补充完整,注意时间在增加一秒情况下的进位关系。
该函数的代码如下:
void Time::IncreaseOneSecond()
{
if (Second < 59)
Second++;
else
{
Second = 0;
if (Minute < 59)
Minute++;
else
{
Minute = 0;
if (Hour < 23)
Hour++;
else
Hour = 0;
}
}
}
④主函数中定义一个Time类对象并调用一次f函数,观察结果填写下表:
f函数的原型 | 主函数中调用f的语句 | 构造函数 调用次数 | 拷贝构造函数 调用次数 | 析构函数调用次数 |
void f(Time t); | f(t1); | 1 | 1 | 2 |
void f(Time &t); | f(t1); | 1 | 0 | 1 |
void f(Time *t); | f(&t1); | 1 | 0 | 1 |
通过以上结果,关于对象作形式参数、对象引用作形式参数、对象指针作形式参数时构造函数、析构函数的调用次数及顺序,你得到什么结论?
对象作为形式参数:
调用时,实参的值传给形参,要调用复制构造函数,且形参占内存空间,析构函数调用两次
对象引用作为形式参数:
相当于是实参的别名,就是对实参对象进行操作,形参不占内存空间,也不需要调用拷贝构造函数。
对象指针作形式参数:
不调用拷贝构造函数,通过指针可以访问实参对象的值,且未再次调用构造函数。
实验题目3 定义一个Girl类和一个Boy类,这两个类中都有表示姓名、年龄的私有成员变量,都要定义构造函数、析构函数、输出成员变量信息的公有成员函数。
①根据要求定义相应的类。
②将Girl类作为Boy类的友元类,在Girl类的成员函数VisitBoy(Boy & )中访问Boy类的私有成员,观察程序运行结果。
③在Boy类的某成员函数VisitGirl(Girl & )中试图访问Girl类的私有成员,观察编译器给出的错误信息,理解原因。
④主函数中正确定义两个类的对象,调用各自的成员函数实现相应功能。
⑤再将Boy类作为Girl类的友元类,在Boy类的某成员函数VisitGirl(Girl & )中访问Girl类的私有成员,观察编译器给出的信息。
⑥删除两个类中的函数VisitGirl(Girl & ) ,VisitBoy(Boy & ),定义一个顶层函数VisitBoyGirl(Boy &, Girl &),作为以上两个类的友元,通过调用该函数输出男孩和女孩的信息。
实验解答:
①定义相应的类,主函数中定义相应的类成员,调用各类的输出函数显示信息。
源程序代码如下:
#include<iostream>
#include<string>
using namespace std;
class Boy;
class Girl
{
private:
string Name;
int Age;
public:
Girl(string N="ABC",int A=18 );
void Output();
//void VisitBoy(Boy&);
~Girl()
{
cout << "Girl destructing" << endl;
}
};
class Boy
{
private:
string Name;
int Age;
//friend Girl;
public:
Boy(string N = "ABC", int A = 18);
void Output();
//void VisitGirl(Girl&);
~Boy()
{
cout << "Boy destructing" << endl;
}
};
Girl::Girl(string N , int A )
{
Name = N;
Age = A;
cout << "Girl constructing" << endl;
}
void Girl::Output()
{
cout << "Girl's name:" << Name << endl;
cout << "Girl's age:" << Age << endl;
}
Boy::Boy(string N , int A )
{
Name = N;
Age = A;
cout << "Boy constructing" << endl;
}
void Boy::Output()
{
cout << "Boy's name:" << Name << endl;
cout << "Boy's age:" << Age << endl;
}
int main()
{
Girl g("Lm", 19);
Boy b("Zs", 20);
g.Output();
b.Output();
}
程序的运行结果是:
②将Girl类作为Boy类的友元类, 写出Girl类的成员函数VisitBoy(Boy & )的实现代码。
void Girl::VisitBoy(Boy& boy)
{
cout << "Boy's name:" << boy.Name << endl;
cout << "Boy's age:" << boy.Age << endl;
}
程序的运行结果是:
③在Boy类的某成员函数VisitGirl(Girl & )中试图访问Girl类的私有成员,记录编译器给出的错误信息,与②对比,你能得出友元的什么特性?
友元关系是单向的,不具有交换性
④在上面代码的基础上,在Girl类的定义中,增加一行代码:friend Boy; 在主函数中通过Boy类对象. VisitGirl(Girl类对象) 的形式输出Girl类对象的信息。编译的结果是什么?写出这一步你的主函数代码,要求分别用友元函数Girl类对象. VisitBoy(Boy类对象);和Boy类对象. VisitGirl(Girl类对象) ;和输出两个类对象的信息。
int main()
{
Girl g("Lm", 19);
Boy b("Zs", 20);
g.VisitBoy(b);
b.VisitGirl(g);
b.Output();
g.Output();
}
⑤定义一个顶层函数void VisitBoyGirl(Boy &, Girl &),作为以上两个类的友元函数,主函数中通过调用该函数输出男孩和女孩的信息。写出该友元函数的完整代码,以及主函数的代码。
void VisitBoyGirl(Boy& boy, Girl& girl)
{
cout << "Boy's name:" << boy.Name << endl;
cout << "Boy's age:" << boy.Age << endl;
cout << "Girl's name:" << girl.Name << endl;
cout << "Girl's age:" << girl.Age << endl;
}
int main()
{
Girl g("Lm", 19);
Boy b("Zs", 20);
VisitBoyGirl(b, g);
}
四、实验小结(包括问题和解决方法、心得体会、意见与建议等)
(一)实验中遇到的主要问题及解决方法
1.在题目2中不改变main()函数中的对象的定义方式,若取消构造函数中参数的默认值,编译程序错误提示信息及出错原因是:
错误提示信息:
不能接受0个参数,没有重载函数接受两个参数
原因:
实际参数个数不能少于无默认值的形式参数个数
2.在题目2中如果删除类中自定义的构造函数,仅使用系统默认构造函数,再编译,程序错误提示信息及出错原因是:
错误提示信息:
没有与参数列表匹配的构造函数
原因:
系统默认的构造函数无形式参数
3.在题目2中如果将main()函数中的输出语句改为:cout<<对象名.Hour<<":"<<对象名.Minute<<":"<<对象名.Second<<endl; 重新编译,会出现什么错误提示?在这种情况下,如果将成员变量的访问属性修改为public再编译,结果如何?
错误提示信息:
Time::Hour无法访问private成员、Time::Minute无法访问private成员Time::Second无法访问private成员
结果:
成功运行
4.其它问题及解决办法
问题:函数声明时指定默认参数后,函数首部再次指定
方法:删除函数首部的默认值,只保留形参
(二)实验心得
通过本次实验,进一步熟悉了类的定义以及成员函数的使用,同时通过题目2和题目3对于public和private属性有了更深的体会。同时,掌握了关于构造函数和析构函数的调用方式和调用顺序的异同,以及不同形式对象做形式参数时,两者调用的情况和背后的原因。