本帖最后由 45gfg9 于 2020-8-31 19:07 编辑
Prev: C++Ⅵ:再谈类
Next: None
索引贴
答案帖
Prev: C++Ⅵ:再谈类
Next: None
1 继承
1.1 继承(一)
继承 是面向对象编程中最重要的概念之一。
继承允许我们在其他类之上定义类。这显著降低了开发并维护应用程序的难度。
其属性被另一个类继承的类叫做基 类。继承其属性的类叫做派生 类。举个栗子,Daughter(派生)类可以是从Mother(基)类继承来的。
派生类继承了基类的所有特性,并可以有它自己额外的特性。

Part题:
对于Mother类和Daughter类,哪种解释是正确的?
A. 两者都是基类
B. 基类Mother和派生类Daughter
C. 派生类Mother和基类Daughter
1.2 继承(二)
要演示继承,我们来创建Mother类和Daughter类。
```cpp
class Mother
{
public:
Mother() {};
void sayHi() {
cout << "Hi";
}
};
class Daughter
{
public:
Daughter() {};
};
```
Mother类有一个公有方法sayHi()。
Part题:
用选项填空,为Mother类定义函数sayName(),其输出"I'm a mother"到屏幕。
```cpp
void ______ ::sayName() {
______ << "I'm a mother";
}
```
`<< cin class cout Mother`
1.3 继承(三)
用这样的语法让Daughter类派生于Mother类。
```cpp
class Daughter : public Mother
{
public:
Daughter() {};
};
```
基类以冒号和访问修饰符指定:public意味着,基类的所有public成员在派生类中也是public。
Part题:
填上缺失的关键字,让Daughter继承Mother。
```cpp
class Daughter : ______ Mother
{
};
```
1.4 继承(四)
由于Mother类所有public成员成为Daughter类的所有成员了,我们可以创建一个Daughter类的对象并调用它从Mother类继承的sayHi()函数:
```cpp
#include <iostream>
using namespace std;
class Mother
{
public:
Mother() {};
void sayHi() {
cout << "Hi";
}
};
class Daughter: public Mother
{
public:
Daughter() {};
};
int main() {
Daughter d;
d.sayHi();
}
//输出 "Hi"
```
派生类会继承基类的所有public成员,除了:
Part题:
填上缺失的符号,让派生类继承基类。
```cpp
class Derived _ public Base
{
};
```
2 保护成员
2.1 访问修饰符
到现在,我们已经和public与private访问修饰符共事过了。
公有成员在类以外任何地方都可访问;对私有成员的访问仅限于它所属的类和其友元函数。
Part题:
一个类的public成员...
A. ...只对派生类的成员函数可见
B. ...对所有人可见
C. ...仅对类成员函数可见
2.2 保护
还有一个访问修饰符——protected 。
一个protected成员变量或成员函数和private成员很相似,除了一点——它们能被派生类访问到。
```cpp
class Mother {
public:
void sayHi() {
cout << var;
}
private:
int var=0;
protected:
int someVar;
};
```
Part题:
protected意味着什么?
A. 成员是私有的
B. 成员对派生类成员可见
C. 成员对所有人可见
2.3 继承类型
访问修饰符也用于指定继承类型。
还记得我们之前在派生Daughter类时用到的public么?
```cpp
class Daughter: public Mother
```
private和protected访问修饰符在这也能用。
公有继承:基类的public成员成为派生类的public成员,基类的protected成员成为派生类的protected成员。基类的private成员在派生类中不可见,但能通过调用基类的public或protected成员函数访问。
受保护继承:基类的public和protected成员成为派生类的protected成员。
私有继承:基类的public和protected成员成为派生类的private成员。
Part题:
用选项填空,在Base类中声明一个protected成员,通过Derived类的foo函数访问。
```cpp
______ Base {
______ :
int baseVar;
};
class Derived : ______ Base {
public:
void foo() {
baseVar = 12;
}
};
```
`class Base foo Derived public protected`
3 派生类的构造函数和析构函数
3.1 继承(一)
当继承类时,基类的构造函数和析构函数不被继承。
然而,当派生类创建或销毁时,它们会被调用。
为了解释这一现象,我们来创建一个样例类,包含一个构造函数和析构函数:
```cpp
class Mother {
public:
Mother()
{
cout <<"Mother ctor"<<endl;
}
~Mother()
{
cout <<"Mother dtor"<<endl;
}
};
```
在main中创建对象会有下面输出:
```cpp
int main() {
Mother m;
}
/* 输出
Mother ctor
Mother dtor
*/
```
Part题:
用选项填空,为Mother类定义一个构造函数和析构函数。
```cpp
______ ::Mother() {
cout << "constructor" << endl;
}
Mother:: ______ () {
cout << "destructor" << endl;
}
```
`protected ~Mother public Mother define`
3.2 继承(二)
接着来创建Daughter类和它自己的构造函数和析构函数,并让它派生于Mother:
```cpp
class Daughter: public Mother {
public:
Daughter()
{
cout <<"Daughter ctor"<<endl;
}
~Daughter()
{
cout <<"Daughter dtor"<<endl;
}
};
```
Part题:
用选项填空,声明B类和其构造函数与D类和其构造函数,且D继承B。
```cpp
______ B {
public:
______ () {
cout << "B's constructor"; }
};
class D : ______ B {
______ () {
cout << "D's constructor"; }
};
```
`public D this B protected class`
3.3 继承(三)
现在创建Daughter对象会发生什么呢?
```cpp
int main() {
Daughter m;
}
/*输出
Mother ctor
Daughter ctor
Daughter dtor
Mother dtor
*/
```
注意到基类的构造函数先被调用,之后调用派生类构造函数。
当对象被销毁时,派生类的析构函数先被调用,然后基类的析构函数被调用。
Part题:
如果类D继承类B,当创建类D的对象时...
A. ...D的构造函数在B的构造函数之前调用
B. ...只调用D的构造函数
C. ...B的构造函数在D的构造函数之前调用
3.4 总结
构造函数
基类的构造函数先被调用。
析构函数
派生类的析构函数先被调用,之后再调用基类的析构函数。
Part题:
如果类D继承类B,当一个D对象被销毁时...
A. ...D的析构函数在其基类析构函数之前调用
B. ...D基类的析构函数在其析构函数之前调用
4 多态
4.1 多态(一)
词语“多态 ”的意思是“有多种形态”。
一般地,多态在类的派生和继承中发生。
C++的多态意味着对成员函数的调用会根据调用对象的类型导致执行不同的实现。
Part题:
一言以蔽之,多态是...
A. ...一个函数,多种实现
B. ...不同函数的每种实现
C. ...一种实现,多个函数
4.2 多态(二)
多态能用下面的例子更清楚地表示:
假设你要写一个简单的游戏,包含不同种类的敌人:怪物、忍者等。所有的敌人都有一个共同的函数:attack 函数。然而,它们的攻击方式都不一样。在这种情况下,多态就可以让不同类型的对象有同一个attack函数,但行为不同。
第一步是创建Enemy 类。
```cpp
class Enemy {
protected:
int attackPower;
public:
void setAttackPower(int a){
attackPower = a;
}
};
```
Part题:
填空声明Enemy类,有一个protected变量"attackPower"和public函数"setAttackPower"。
```cpp
_____ Enemy {
_________ :
int attackPower;
______ :
void setAttackPower( ___ a) {
attackPower = a;
}
};
```
4.3 多态(三)
我们的第二步是为两种不同类型的敌人创建类,忍者 和怪物 。这两个类都继承Enemy类,所以每种都有其攻击力。同时,每种都有其attack函数。
```cpp
class Ninja: public Enemy {
public:
void attack() {
cout << "Ninja! - "<<attackPower<<endl;
}
};
class Monster: public Enemy {
public:
void attack() {
cout << "Monster! - "<<attackPower<<endl;
}
};
```
如你所见,它们自己的attack函数都不同。
接着我们就可以在main中创建Ninja和Monster对象了。
```cpp
int main() {
Ninja n;
Monster m;
}
```
Ninja和Monster从Enemy派生,所以所有Ninja和Monster对象都是Enemy对象。因此我们可以这样:
```cpp
Enemy *e1 = &n;
Enemy *e2 = &m;
```
Part题:
用选项填空,声明Ninja和Monster对象,和两个Enemy指针,分别指向Ninja对象和Monster对象。
```cpp
______ ninjaObj;
______ monsterObj;
Enemy* e1 = ______ ;
______ e2 = &monsterObj;
```
`&ninjaObj class Monster pointer Enemy* Ninja`
4.4 多态(四)
现在,我们就能调用对应的函数了。
```cpp
int main() {
Ninja n;
Monster m;
Enemy *e1 = &n;
Enemy *e2 = &m;
e1->setAttackPower(20);
e2->setAttackPower(80);
n.attack();
m.attack();
}
/* 输出:
Ninja! - 20
Monster! - 80
*/
```
我们通过直接调用对象上的方法也能达到相同的效果。然而,使用指针速度更快,效率更高。
而且,指针就能演示,你可以使用Enemy指针而不需要知道它实际指向的对象类型。
Part题:
填空声明Enemy指针指向Ninja和Monster对象。通过Enemy指针设置对象的攻击力,之后调用Ninja和Monster类的attack()函数。
```cpp
Enemy _ e1 = &ninjaObj;
Enemy* e2 = _ monsterObj;
e1 __ setAttackPower(29);
e2 __ setAttackPower(00);
ninjaObj.attack();
monsterObj. ______ ();
```
5 虚函数
5.1 虚函数(一)
之前的例子演示了基类指针指向派生类。这有什么用呢?还是拿我们的游戏例子,我们想要每个Enemy都有attack()函数。
要想能够通过Enemy指针调用每个派生类对象的attack()函数,我们要将基类的函数声明为虚 函数。
在基类中定义一个虚函数,并且在派生类中有对应的版本,就能让多态通过基类指针调用子类函数。
每个派生类都会覆盖attack()函数并有自己的实现:
```cpp
class Enemy {
public:
virtual void attack() {
}
};
class Ninja: public Enemy {
public:
void attack() {
cout << "Ninja!"<<endl;
}
};
class Monster: public Enemy {
public:
void attack() {
cout << "Monster!"<<endl;
}
};
```
Part题:
填空为Enemy类声明attack()虚函数。
```cpp
_____ Enemy {
public:
_______ void attack() {
cout << "enemy attacks"; }
};
```
5.2 虚函数(二)
现在,我们可以用Enemy指针调用attack()函数了。
```cpp
int main() {
Ninja n;
Monster m;
Enemy *e1 = &n;
Enemy *e2 = &m;
e1->attack();
e2->attack();
}
/* 输出:
Ninja!
Monster!
*/
```
Part题:
填空声明Enemy类,有attack虚函数,之后声明Ninja类继承Enemy类并覆盖attack虚函数。
```cpp
class Enemy {
public:
_______ void attack() {
cout << "Enemy attacks"; }
};
_____ Ninja : ______ Enemy {
public:
void ______ () {
cout << "Ninja attacks"; }
};
```
5.3 虚函数(三)
我们的游戏例子是用来演示多态的概念的;我们用Enemy指针调用相同的attack()函数,得到不同的结果。
```cpp
e1->attack();
e2->attack();
```
如果基类中的一个函数是虚函数,函数在派生类中的实现会根据实际引用的对象调用,而与指针的声明类型无关。
Part题:
多态类是...
A. ...通过关键字polymorphic声明的类
B. ...有虚函数的类
C. ...有友元函数的类
6 抽象类
6.1 虚函数(四)
虚函数在基类中也能有实现:
```cpp
class Enemy {
public:
virtual void attack() {
cout << "Enemy!"<<endl;
}
};
class Ninja: public Enemy {
public:
void attack() {
cout << "Ninja!"<<endl;
}
};
class Monster: public Enemy {
public:
void attack() {
cout << "Monster!"<<endl;
}
};
```
现在,当你创建Enemy指针,调用attack()函数时,编译器会根据指针指向实际对象的类型,调用函数:
```cpp
int main() {
Ninja n;
Monster m;
Enemy e;
Enemy *e1 = &n;
Enemy *e2 = &m;
Enemy *e3 = &e;
e1->attack();
// 输出 "Ninja!"
e2->attack();
// 输出 "Monster!"
e3->attack();
// 输出 "Enemy!"
}
```
Part题:
当通过基类指针调用虚函数时...
A. ...什么也不会发生
B. ...根据基类指针指向的类调用函数
C. ...调用基类的函数
6.2 纯虚函数(一)
在某些情况下,你可能想要在基类中包含一个虚函数,以供派生类根据自己的对象需求重定义,但在基类中无法给出一个有意义的定义。
没有定义的虚成员函数正是纯虚函数 。它们基本上指明,派生类要自己定义那个函数。
语法是将定义替换成 =0(一个等号和一个零):
```cpp
class Enemy {
public:
virtual void attack() = 0;
};
```
Part题:
填空声明一个纯虚函数foo()。
```cpp
_______ void foo() _ 0;
```
6.3 纯虚函数(二)
一个纯虚函数基本上表明,派生类对那个函数会有自己的定义。
所有从有纯虚函数的类派生的类都必须覆盖那个函数。
Part题:
纯虚函数...
A. ...必须有实现
B. ...没有函数体且必须在派生类中实现
C. ...必须返回void
6.4 纯虚函数(三)
Enemy类中的纯虚函数必须在派生类中覆盖。
```cpp
class Enemy {
public:
virtual void attack() = 0;
};
class Ninja: public Enemy {
public:
void attack() {
cout << "Ninja!"<<endl;
}
};
class Monster: public Enemy {
public:
void attack() {
cout << "Monster!"<<endl;
}
};
```
Part题:
填空声明Person类,有一个sayHello()纯虚函数,并在Student类中覆盖。
```cpp
class Person {
public:
_______ void sayHello() _ 0;
};
class Student : ______ Person {
public:
____ sayHello() {
cout << "Student says hello"; }
};
```
6.5 抽象类
你不能创建有虚函数的类的对象。
下面的代码会导致编译错误:
```cpp
Enemy e; // 错误
```
这样的类被叫做抽象 类。它们是只能被用作基类的类,因此可以有虚函数。
你可能会想抽象基类是没用的,但并不是那样。它可以用来创建指针,并利用它的一切多态性质。
例如,你可以:
```cpp
Ninja n;
Monster m;
Enemy *e1 = &n;
Enemy *e2 = &m;
e1->attack();
e2->attack();
```
Part题:
抽象类是...
A. ...用abstract关键字声明的类
B. ...有最少两个方法的类
C. ...有纯虚函数的类
本章测试
1. 填上缺失的关键字,让D类公有继承Base类。
```cpp
class D : ______ Base
{
};
```
2. 哪个关键字使类成员只对派生类可见?
3. 下面哪条关于继承中的析构函数的陈述是正确的?
A. 派生类析构函数在基类析构函数前调用
B. 基类析构函数在派生类析构函数前调用
4. 填空声明Dog和Cat对象,和两个Pet指针分别指向之。
```cpp
Dog dogObj; Cat catObj;
Pet _ pet1 = _ dogObj;
Pet* pet2 = _ catObj;
```
5. 填空声明Person类,有一个hello()虚函数,之后声明Student类继承Person类,并覆盖其hello()虚函数。
```cpp
class Person {
public:
_______ void hello() {
cout << "Person says hello"; }
};
_____ Student : ______ Person {
public:
void hello() {
cout << "Student says hello"; }
};
```
6. 填空使foo成为纯虚函数。
```cpp
_______ void foo _ 0;
```
Prev: C++Ⅵ:再谈类
Next: None
2021.12 数据,可能有更多内容
索引贴
答案帖
Prev: C++Ⅵ:再谈类
Next: None
1 继承
1.1 继承(一)
继承允许我们在其他类之上定义类。这显著降低了开发并维护应用程序的难度。
其属性被另一个类继承的类叫做
派生类继承了基类的所有特性,并可以有它自己额外的特性。
继承的思想实现了层次 关系。举例,哺乳动物是 动物,狗是 哺乳动物,因此狗也是 动物。
Part题:
对于Mother类和Daughter类,哪种解释是正确的?
A. 两者都是基类
B. 基类Mother和派生类Daughter
C. 派生类Mother和基类Daughter
1.2 继承(二)
要演示继承,我们来创建Mother类和Daughter类。
```cpp
class Mother
{
public:
Mother() {};
void sayHi() {
cout << "Hi";
}
};
class Daughter
{
public:
Daughter() {};
};
```
Mother类有一个公有方法sayHi()。
下一步就是让Daughter继承(派生)于Mother。
Part题:
用选项填空,为Mother类定义函数sayName(),其输出"I'm a mother"到屏幕。
```cpp
void ______ ::sayName() {
______ << "I'm a mother";
}
```
`<< cin class cout Mother`
1.3 继承(三)
用这样的语法让Daughter类派生于Mother类。
```cpp
class Daughter : public Mother
{
public:
Daughter() {};
};
```
基类以冒号和访问修饰符指定:public意味着,基类的所有public成员在派生类中也是public。
换言之,所有Mother类的public成员成为Daughter类的public成员。
Part题:
填上缺失的关键字,让Daughter继承Mother。
```cpp
class Daughter : ______ Mother
{
};
```
1.4 继承(四)
由于Mother类所有public成员成为Daughter类的所有成员了,我们可以创建一个Daughter类的对象并调用它从Mother类继承的sayHi()函数:
```cpp
#include <iostream>
using namespace std;
class Mother
{
public:
Mother() {};
void sayHi() {
cout << "Hi";
}
};
class Daughter: public Mother
{
public:
Daughter() {};
};
int main() {
Daughter d;
d.sayHi();
}
//输出 "Hi"
```
派生类会继承基类的所有public成员,除了:
- 构造函数和析构函数
- 重载的操作符
- 友元函数
一个类可以从多个类派生,方法是将基类声明在逗号分隔列表里。例子:class Daughter : public Mother, public Father
Part题:
填上缺失的符号,让派生类继承基类。
```cpp
class Derived _ public Base
{
};
```
2 保护成员
2.1 访问修饰符
到现在,我们已经和public与private访问修饰符共事过了。
公有成员在类以外任何地方都可访问;对私有成员的访问仅限于它所属的类和其友元函数。
如我们之前所见,通过public方法访问private成员事好文明。
Part题:
一个类的public成员...
A. ...只对派生类的成员函数可见
B. ...对所有人可见
C. ...仅对类成员函数可见
2.2 保护
还有一个访问修饰符——
一个protected成员变量或成员函数和private成员很相似,除了一点——它们能被派生类访问到。
```cpp
class Mother {
public:
void sayHi() {
cout << var;
}
private:
int var=0;
protected:
int someVar;
};
```
现在someVar可以被任何派生于Mother类的类访问。
Part题:
protected意味着什么?
A. 成员是私有的
B. 成员对派生类成员可见
C. 成员对所有人可见
2.3 继承类型
访问修饰符也用于指定继承类型。
还记得我们之前在派生Daughter类时用到的public么?
```cpp
class Daughter: public Mother
```
private和protected访问修饰符在这也能用。
公有继承:基类的public成员成为派生类的public成员,基类的protected成员成为派生类的protected成员。基类的private成员在派生类中不可见,但能通过调用基类的public或protected成员函数访问。
受保护继承:基类的public和protected成员成为派生类的protected成员。
私有继承:基类的public和protected成员成为派生类的private成员。
公有继承是最常用的继承类型。
如果继承时没有指定访问修饰符,则使用默认的private。
如果继承时没有指定访问修饰符,则使用默认的private。
Part题:
用选项填空,在Base类中声明一个protected成员,通过Derived类的foo函数访问。
```cpp
______ Base {
______ :
int baseVar;
};
class Derived : ______ Base {
public:
void foo() {
baseVar = 12;
}
};
```
`class Base foo Derived public protected`
3 派生类的构造函数和析构函数
3.1 继承(一)
当继承类时,基类的构造函数和析构函数不被继承。
然而,当派生类创建或销毁时,它们会被调用。
为了解释这一现象,我们来创建一个样例类,包含一个构造函数和析构函数:
```cpp
class Mother {
public:
Mother()
{
cout <<"Mother ctor"<<endl;
}
~Mother()
{
cout <<"Mother dtor"<<endl;
}
};
```
在main中创建对象会有下面输出:
```cpp
int main() {
Mother m;
}
/* 输出
Mother ctor
Mother dtor
*/
```
对象先被创建,程序运行结束后被销毁。
Part题:
用选项填空,为Mother类定义一个构造函数和析构函数。
```cpp
______ ::Mother() {
cout << "constructor" << endl;
}
Mother:: ______ () {
cout << "destructor" << endl;
}
```
`protected ~Mother public Mother define`
3.2 继承(二)
接着来创建Daughter类和它自己的构造函数和析构函数,并让它派生于Mother:
```cpp
class Daughter: public Mother {
public:
Daughter()
{
cout <<"Daughter ctor"<<endl;
}
~Daughter()
{
cout <<"Daughter dtor"<<endl;
}
};
```
创建了有自己构造函数和析构函数的Daughter类。
Part题:
用选项填空,声明B类和其构造函数与D类和其构造函数,且D继承B。
```cpp
______ B {
public:
______ () {
cout << "B's constructor"; }
};
class D : ______ B {
______ () {
cout << "D's constructor"; }
};
```
`public D this B protected class`
3.3 继承(三)
现在创建Daughter对象会发生什么呢?
```cpp
int main() {
Daughter m;
}
/*输出
Mother ctor
Daughter ctor
Daughter dtor
Mother dtor
*/
```
注意到基类的构造函数先被调用,之后调用派生类构造函数。
当对象被销毁时,派生类的析构函数先被调用,然后基类的析构函数被调用。
你可以这么想:派生类需要基类来工作——这就是为什么先初始化基类。
Part题:
如果类D继承类B,当创建类D的对象时...
A. ...D的构造函数在B的构造函数之前调用
B. ...只调用D的构造函数
C. ...B的构造函数在D的构造函数之前调用
3.4 总结
构造函数
基类的构造函数先被调用。
析构函数
派生类的析构函数先被调用,之后再调用基类的析构函数。
这个顺序使你可以为派生类指定构造和析构方案。
Part题:
如果类D继承类B,当一个D对象被销毁时...
A. ...D的析构函数在其基类析构函数之前调用
B. ...D基类的析构函数在其析构函数之前调用
4 多态
4.1 多态(一)
词语“
一般地,多态在类的派生和继承中发生。
C++的多态意味着对成员函数的调用会根据调用对象的类型导致执行不同的实现。
简单来说,多态意味着一个函数能有多种不同的实现。
Part题:
一言以蔽之,多态是...
A. ...一个函数,多种实现
B. ...不同函数的每种实现
C. ...一种实现,多个函数
4.2 多态(二)
多态能用下面的例子更清楚地表示:
假设你要写一个简单的游戏,包含不同种类的敌人:怪物、忍者等。所有的敌人都有一个共同的函数:
第一步是创建
```cpp
class Enemy {
protected:
int attackPower;
public:
void setAttackPower(int a){
attackPower = a;
}
};
```
我们的Enemy类有一个public方法叫做setAttackPower来设置protected成员变量attackPower 。
Part题:
填空声明Enemy类,有一个protected变量"attackPower"和public函数"setAttackPower"。
```cpp
_____ Enemy {
_________ :
int attackPower;
______ :
void setAttackPower( ___ a) {
attackPower = a;
}
};
```
4.3 多态(三)
我们的第二步是为两种不同类型的敌人创建类,
```cpp
class Ninja: public Enemy {
public:
void attack() {
cout << "Ninja! - "<<attackPower<<endl;
}
};
class Monster: public Enemy {
public:
void attack() {
cout << "Monster! - "<<attackPower<<endl;
}
};
```
如你所见,它们自己的attack函数都不同。
接着我们就可以在main中创建Ninja和Monster对象了。
```cpp
int main() {
Ninja n;
Monster m;
}
```
Ninja和Monster从Enemy派生,所以所有Ninja和Monster对象都是Enemy对象。因此我们可以这样:
```cpp
Enemy *e1 = &n;
Enemy *e2 = &m;
```
现在我们创建了两个Enemy类型的指针,让它们分别指向Ninja和Monster对象。
Part题:
用选项填空,声明Ninja和Monster对象,和两个Enemy指针,分别指向Ninja对象和Monster对象。
```cpp
______ ninjaObj;
______ monsterObj;
Enemy* e1 = ______ ;
______ e2 = &monsterObj;
```
`&ninjaObj class Monster pointer Enemy* Ninja`
4.4 多态(四)
现在,我们就能调用对应的函数了。
```cpp
int main() {
Ninja n;
Monster m;
Enemy *e1 = &n;
Enemy *e2 = &m;
e1->setAttackPower(20);
e2->setAttackPower(80);
n.attack();
m.attack();
}
/* 输出:
Ninja! - 20
Monster! - 80
*/
```
我们通过直接调用对象上的方法也能达到相同的效果。然而,使用指针速度更快,效率更高。
而且,指针就能演示,你可以使用Enemy指针而不需要知道它实际指向的对象类型。
Part题:
填空声明Enemy指针指向Ninja和Monster对象。通过Enemy指针设置对象的攻击力,之后调用Ninja和Monster类的attack()函数。
```cpp
Enemy _ e1 = &ninjaObj;
Enemy* e2 = _ monsterObj;
e1 __ setAttackPower(29);
e2 __ setAttackPower(00);
ninjaObj.attack();
monsterObj. ______ ();
```
5 虚函数
5.1 虚函数(一)
之前的例子演示了基类指针指向派生类。这有什么用呢?还是拿我们的游戏例子,我们想要每个Enemy都有attack()函数。
要想能够通过Enemy指针调用每个派生类对象的attack()函数,我们要将基类的函数声明为
在基类中定义一个虚函数,并且在派生类中有对应的版本,就能让多态通过基类指针调用子类函数。
每个派生类都会覆盖attack()函数并有自己的实现:
```cpp
class Enemy {
public:
virtual void attack() {
}
};
class Ninja: public Enemy {
public:
void attack() {
cout << "Ninja!"<<endl;
}
};
class Monster: public Enemy {
public:
void attack() {
cout << "Monster!"<<endl;
}
};
```
虚函数是基类中用关键字virtual声明的函数。
Part题:
填空为Enemy类声明attack()虚函数。
```cpp
_____ Enemy {
public:
_______ void attack() {
cout << "enemy attacks"; }
};
```
5.2 虚函数(二)
现在,我们可以用Enemy指针调用attack()函数了。
```cpp
int main() {
Ninja n;
Monster m;
Enemy *e1 = &n;
Enemy *e2 = &m;
e1->attack();
e2->attack();
}
/* 输出:
Ninja!
Monster!
*/
```
因为attack()函数被声明virtual,它就像一个模板,提醒着派生类可能有自己的attack()函数。
Part题:
填空声明Enemy类,有attack虚函数,之后声明Ninja类继承Enemy类并覆盖attack虚函数。
```cpp
class Enemy {
public:
_______ void attack() {
cout << "Enemy attacks"; }
};
_____ Ninja : ______ Enemy {
public:
void ______ () {
cout << "Ninja attacks"; }
};
```
5.3 虚函数(三)
我们的游戏例子是用来演示多态的概念的;我们用Enemy指针调用相同的attack()函数,得到不同的结果。
```cpp
e1->attack();
e2->attack();
```
如果基类中的一个函数是虚函数,函数在派生类中的实现会根据实际引用的对象调用,而与指针的声明类型无关。
声明或继承了虚函数的累叫做多态 类。
Part题:
多态类是...
A. ...通过关键字polymorphic声明的类
B. ...有虚函数的类
C. ...有友元函数的类
6 抽象类
6.1 虚函数(四)
虚函数在基类中也能有实现:
```cpp
class Enemy {
public:
virtual void attack() {
cout << "Enemy!"<<endl;
}
};
class Ninja: public Enemy {
public:
void attack() {
cout << "Ninja!"<<endl;
}
};
class Monster: public Enemy {
public:
void attack() {
cout << "Monster!"<<endl;
}
};
```
现在,当你创建Enemy指针,调用attack()函数时,编译器会根据指针指向实际对象的类型,调用函数:
```cpp
int main() {
Ninja n;
Monster m;
Enemy e;
Enemy *e1 = &n;
Enemy *e2 = &m;
Enemy *e3 = &e;
e1->attack();
// 输出 "Ninja!"
e2->attack();
// 输出 "Monster!"
e3->attack();
// 输出 "Enemy!"
}
```
这就是多态的一般用法。你有多个类,其中都有一个同名,甚至形参都相同的函数,但有着不同的实现。
Part题:
当通过基类指针调用虚函数时...
A. ...什么也不会发生
B. ...根据基类指针指向的类调用函数
C. ...调用基类的函数
6.2 纯虚函数(一)
在某些情况下,你可能想要在基类中包含一个虚函数,以供派生类根据自己的对象需求重定义,但在基类中无法给出一个有意义的定义。
没有定义的虚成员函数正是
语法是将定义替换成 =0(一个等号和一个零):
```cpp
class Enemy {
public:
virtual void attack() = 0;
};
```
=0告诉编译器,这个函数没有函数体。
Part题:
填空声明一个纯虚函数foo()。
```cpp
_______ void foo() _ 0;
```
6.3 纯虚函数(二)
一个纯虚函数基本上表明,派生类对那个函数会有自己的定义。
所有从有纯虚函数的类派生的类都必须覆盖那个函数。
如果派生类中不覆盖纯虚函数,那么在当尝试实例化一个派生类对象时会编译出错。
Part题:
纯虚函数...
A. ...必须有实现
B. ...没有函数体且必须在派生类中实现
C. ...必须返回void
6.4 纯虚函数(三)
Enemy类中的纯虚函数必须在派生类中覆盖。
```cpp
class Enemy {
public:
virtual void attack() = 0;
};
class Ninja: public Enemy {
public:
void attack() {
cout << "Ninja!"<<endl;
}
};
class Monster: public Enemy {
public:
void attack() {
cout << "Monster!"<<endl;
}
};
```
Part题:
填空声明Person类,有一个sayHello()纯虚函数,并在Student类中覆盖。
```cpp
class Person {
public:
_______ void sayHello() _ 0;
};
class Student : ______ Person {
public:
____ sayHello() {
cout << "Student says hello"; }
};
```
6.5 抽象类
你不能创建有虚函数的类的对象。
下面的代码会导致编译错误:
```cpp
Enemy e; // 错误
```
这样的类被叫做
你可能会想抽象基类是没用的,但并不是那样。它可以用来创建指针,并利用它的一切多态性质。
例如,你可以:
```cpp
Ninja n;
Monster m;
Enemy *e1 = &n;
Enemy *e2 = &m;
e1->attack();
e2->attack();
```
在这个例子中,对象是不同的,但相关的类型是通过唯一类型的指针(Enemy*)引用的,正因为它们是virtual,所以每次都会调用正确的成员函数。
Part题:
抽象类是...
A. ...用abstract关键字声明的类
B. ...有最少两个方法的类
C. ...有纯虚函数的类
本章测试
1. 填上缺失的关键字,让D类公有继承Base类。
```cpp
class D : ______ Base
{
};
```
2. 哪个关键字使类成员只对派生类可见?
3. 下面哪条关于继承中的析构函数的陈述是正确的?
A. 派生类析构函数在基类析构函数前调用
B. 基类析构函数在派生类析构函数前调用
4. 填空声明Dog和Cat对象,和两个Pet指针分别指向之。
```cpp
Dog dogObj; Cat catObj;
Pet _ pet1 = _ dogObj;
Pet* pet2 = _ catObj;
```
5. 填空声明Person类,有一个hello()虚函数,之后声明Student类继承Person类,并覆盖其hello()虚函数。
```cpp
class Person {
public:
_______ void hello() {
cout << "Person says hello"; }
};
_____ Student : ______ Person {
public:
void hello() {
cout << "Student says hello"; }
};
```
6. 填空使foo成为纯虚函数。
```cpp
_______ void foo _ 0;
```