一、实验目的
1. 理解类的继承和派生机制
2. 掌握派生类的定义和使用
3. 理解和掌握派生类构造函数和析构函数的定义及调用次序
4. 理解和掌握派生类成员的标识和访问中同名覆盖原则、二元作用域分辨符、虚基类的用法
5. 理解和体会类的组合(has-a)和类的继承(is-a)之间的差别,能根据问题场景合理设计多个类之间的 关系
6. 灵活应用继承机制编程解决现实世界的问题
7. 了解C++标准库map容器,掌握其基础用法
二、实验内容
task2:验证性试验
task2.cpp:
#include <iostream> #include <typeinfo> // definitation of Graph class Graph { public: void draw() { std::cout << "Graph::draw() : just as an interface\n"; } }; // definition of Rectangle, derived from Graph class Rectangle : public Graph { public: void draw() { std::cout << "Rectangle::draw(): programs of draw a rectangle\n"; } }; // definition of Circle, derived from Graph class Circle : public Graph { public: void draw() { std::cout << "Circle::draw(): programs of draw a circle\n"; } }; // definitaion of fun(): as a call interface void fun(Graph *ptr) { std::cout << "pointer type: " << typeid(ptr).name() << "\n"; std::cout << "RTTI type: " << typeid(*ptr).name() << "\n"; ptr -> draw();//调用的都是 Graph.draw() ,因为将派生类当作基类对象使用时,只能使用作为基类的那一部分接口 //但若基类的draw()变成virtual void draw()时,就是各调用各的 } // test int main() { Graph g1; Rectangle r1; Circle c1; // call by object name g1.draw(); r1.draw(); c1.draw(); std::cout << "\n"; // call by object name, and using the scope resolution operator:: r1.Graph::draw(); c1.Graph::draw(); std::cout << "\n"; // call by pointer to Base class fun(&g1); fun(&r1); fun(&c1); }
运行结果如下:
若将基类的draw()函数改为virtual void draw(),运行结果如下:
task3:使用类的组合和继承模拟简单的车辆信息管理。
问题场景描述如下: 为了对车量基本信息进行管理,对现实世界车量基本信息抽象后,抽象出Car类、ElectricCar类、 Battery类,它们之间的关系描述如下:ElectricCar类公有继承自Car类,ElectricCar中新增数据成员为 Battery类对象。
类图简化模型如图所示。
基于以下描述设计并实现Battery类
每个Battery类对象有如下信息:
车载动力电池容量(capacity)
要求Battery类提供成员函数实现以下要求:
带有默认形参值的构造函数。实现Battery类对象的构造,在构造对象时对电池容量 capacity进行初始化,默认参数值为70(单位: kWh)。 返回当前电池容量的函数 get_capacity()。
基于以下描述设计并实现Car类
每辆汽车有如下信息:
制造商(maker), 型号(model), 生产年份(year), 行车里程数 (odometers)。
要求Car类提供成员函数实现以下要求:
带参数的构造函数。实现Car类对象的构造,并在构造对象时实现对制造商、型号、生产 年份的初始化。初始构造时,行车里程数总是0。 显示车辆信息的函数info()。实现车辆 信息显示输出,输出信息包括:制造商、型号、生 产年份、当前行车里程数。 更新行车里程数的函数update_odometers()。更新后的行车里程数通过参数传递。
要求 能对参数进行合理性检查,如果更新后的行车里程数值小于当前行车里程数,打印警告 信息,提示更新数值有误。
基于以下描述设计并实现ElectricCar类
ElectricCar类继承自Car类,新增数据成员电池(battery)。
其中,battery是Battery类对象。 带参数的构造函数。实现ElectricCar类对象的构造,并在构造时实现对制造商、型号、生产年 份以及新增数据成员电池的初始化。初始构造时,行车里程数总是0。 显示车辆信息info()。实现车辆信息显示输出,包括:制造商、型号、生产年份、当前行车里 程数、当前电池容量。 在主程序中测试设计的类。 要求采用多文件组织代码,文件列表如下: battery.hpp 电池类Battery的定义和实现 car.hpp 汽车类Car的定义和实现 electricCar.hpp 电动汽车类ElectricCar类的定义和实现 electricCar.cpp 电动汽车类ElectricCar类的实现 task3.cpp 主程序。测试Car类对象和Electric类对象。
Car.hpp:
#include<string> #include<iostream> using namespace std; class Car{ private : string maker;//制造商 string model;//型号 int year;//生产年份 double odometers;//行车里程数 public: Car(string maker0, string model0, int year0, double odometers0 = 0); void info() const;//展示信息 void update_odometers(double odo);//更新行车里程数 }; Car::Car(string maker0, string model0, int year0, double odometers0): maker{maker0}, model{model0}, year{year0}, odometers{odometers0} { } void Car::info() const { cout << "maker:\t\t\t" << maker << endl << "model:\t\t\t" << model << endl << "year:\t\t\t" << year << endl << "odometers:\t\t" << odometers << endl; } void Car::update_odometers(double odo) { if(odo < odometers){ cout << "更新后的行车里程数值小于当前行车里程数,更新数值有误!" << endl; } else{ odometers = odo; } }
Battery.hpp:
#include<string> using namespace std; class Battery{ private: double capacity;//车载动力电池容量 public: Battery(double capacity0 = 70); double get_capacity() const; }; Battery::Battery(double capacity0): capacity{capacity0} { } double Battery::get_capacity() const { return capacity; }
ElectricCar.hpp:
#include<string> #include<iostream> #include "Battery.hpp" #include "Car.hpp" using namespace std; class ElectricCar:public Car{ private: Battery battery; public: ElectricCar(string maker0, string model0, int year0, double odometers0 = 0, double capacity0 = 70); void info() const; }; ElectricCar::ElectricCar(string maker0, string model0, int year0, double odometers0,double capacity0):Car{maker0, model0, year0, odometers0}, battery{capacity0}{} void ElectricCar::info() const{ Car::info(); cout << "capacity:\t\t" << battery.get_capacity() << "-kWh" << endl; }
task3.cpp:
#include <iostream> #include "electricCar.hpp" int main() { using namespace std; // test class of Car Car oldcar("Audi", "a4", 2016); cout << "--------oldcar's info--------" << endl; oldcar.update_odometers(25000); oldcar.info(); cout << endl; // test class of ElectricCar ElectricCar newcar("Tesla", "model s", 2016); newcar.update_odometers(2500); cout << "\n--------newcar's info--------\n"; newcar.info(); }
运行结果如下:
task4:使用类的继承,模拟简单的机器宠物。
问题场景描述如下:
对机器宠物进行抽象后,抽象出三个简单的类:机器宠物类MachinePets、宠物猫类PetCats、宠物狗 类PetDogs。
设计并实现一个机器宠物类MachinePets。
每个机器宠物有如下信息:昵称(nickname) 每个机器宠物有如下成员函数: 带参数的构造函数MachinePets(const string s),为机器宠物初始化昵称。 虚函数string talk()为机器宠物派生类提供宠物叫声的统一接口。(关于虚函数,参考实 验任务2)
设计并实现电子宠物猫类PetCats,该类公有继承自MachinePets。
每个电子宠物猫类有如下成员 函数: 带参数的构造函数PetCats(const string s),为机器宠物猫初始化昵称。 string talk(),返回电子宠物猫叫声。
设计并实现电子宠物狗类PetDogs, 该类公有继承自MachinePets。
每个电子宠物狗类有如下成 员函数: 带参数的构造函数PetCats(const string s),为机器宠物狗初始化昵称。 string talk(),返回电子宠物狗叫声。
pets.hpp:
#include <iostream> #include<string> using namespace std; class MachinePets{ private: string nickname; public: MachinePets(const string s):nickname(s){ } virtual string talk() { return ""; } string get_nickname() const { return nickname; } }; class PetCats:public MachinePets{ public: PetCats(const string s):MachinePets(s){ } string talk() { return "miao wu~"; } }; class PetDogs:public MachinePets{ public: PetDogs(const string s):MachinePets(s){ } string talk() { return "wang wang~"; } };
task4.cpp:
#include <iostream> #include "pets.hpp" void play(MachinePets *ptr) { std::cout << ptr->get_nickname() << " says " << ptr->talk() << std::endl; } int main() { PetCats cat("miku"); PetDogs dog("da huang"); play(&cat); play(&dog); }
运行结果如下:
三、实验结论
①当派生类与基类中有相同成员/函数时,若未强行指名,则通过派生类对象访问的是派生类的同名成员/函数。
②如果要通过派生类的对象访问基类被覆盖的同名成员/函数,需要使用 对象名.基类名::同名成员/函数 来限定(二元作用域分辨符)。
③当将派生类当作基类对象使用时,只能使用作为基类的那一部分接口,但若基类的draw()变成virtual void draw()时,就是各调用各的。
④typeid可以用于获取运行时类型信息。
⑤rbegin和rend是反向迭代器,将内容倒序输出,与迭代器begin和end正好相反。
⑥继承的一些小小的需要注意的写法有如下几种:
例如:
①
ElectricCar::ElectricCar(string maker0, string model0, int year0, double odometers0, double capacity0): Car{maker0, model0, year0, odometers0}, battery{capacity0} { }
②
void ElectricCar::info() const { Car::info();//调用基类函数 cout << "capacity:\t\t" << battery.get_capacity() << "-kWh" << endl; }
③
newElectricCar.Car::info();//调用基类的同名函数
⑦在构造函数中,变量的初始化要在声明时就给出,并且按照从右到左的顺序初始化。