📄 面向对象基础_动物运动会.cpp
字号:
int getShoutNum()
{
return shoutNum;
}
};
//狗类
class Dog: public Animal
{
public:
Dog(): Animal()
{ }
Dog(string name): Animal(name)
{ }
void Shout()
{
string result = "";
for(int i=0; i<shoutNum; i++)
{
result += " 汪";
}
cout << "我的名字叫" << name << result << endl;
}
};
9. 多态
需求:
举办一个动物运动会,来参加的有各种各样的动物,其中有一项是"叫声比赛"。
需要:有一个"动物报名"来确定动物的种类和报名的顺序。
另一个是"叫声比赛",就是报名的动物依次叫出声音来比赛。
注意:报名的动物的种类,我们并不知道,当然它们需要都会叫才行。
分析:
"动物报名",其实就是建立一个动物对象(指针)数组。让不同的动物加入其中。
"叫声比赛",其实就是遍历整个数组,来让动物们都"Shout()"就可以了。
// 客户端代码
int main()
{
Animal *animal[5] = {NULL};
animal[0] = new Cat("小花");
animal[1] = new Dog("大黄");
animal[2] = new Dog("旺财");
animal[3] = new Cat("咪咪");
animal[4] = new Dog("小黑");
for(int i=0; i<5; i++)
{
// 不同的对象可以执行相同的动作,但要通过它们自己的实现代码来执行。
animal[i]->Shout();
}
for(i=0; i<5; i++)
{
delete animal[i];
}
return 0;
}
1. 多态表示不同的对象可以执行相同的动作,但要通过他们自己的实现代码来执行。
例如:
我们国粹'京剧',以前都是子承父业,代代向传的艺术。假设有这样一对父子,父亲是非常有名的京剧艺术家,儿子长大成人后,模仿父亲的戏也惟妙惟肖。有一天,父亲突然发高烧,上不了台演出,而票已经早早的卖出了,退票显然会大大影响声誉。怎么办呢?由于京剧都是需要化妆才可以上台的,于是决定儿子带父亲上台表演。
这里注意几点:
1). 子类以父类的身份出现。
儿子带老子表演,化妆后就是以父亲的身份出现了。
2). 子类在工作时以自己的方式来实现。
儿子模仿的再好,那也是模仿,儿子只能用自己理解的表现方式模仿父亲的作品。
3). 子类以父亲的身份出现时,子类特有的属性和方法不可以使用。
儿子经过多年的学习,其实已经有了自己的创作,自己的绝活。但是现在是代表父亲表演,因此绝活是不能表现出来的。
当然,如果父亲还有别的儿子会表演,也可以在此时代表父亲上场,道理是一样的,这就是多态。
2. 为了使子类的实例完全接替来自父类的类成员,父类必须将该成员声明为虚拟的;而子类需要将父类的实现替换为它自己的实现,这就是 "方法重写"(Override)。
3. 多态的原理是:
当方法被调用时,无论对象是否被转换为其父类,都只有位于对象继承链最末端的方法实现会被调用,也就是说,虚方法是按照其运行类型而非编译时类型进行多态绑定调用的。
class Animal
{
//...
virtual void Shout()
{ }
};
10. 重构:
需求:
增加小牛和小羊来参加比赛。如何做?
// 直接增加小牛类
class Cattle: public Animal
{
public:
Cattle(): Animal()
{}
Cattle(string name): Animal(name)
{}
void Shout()
{
string result = "";
for(int i=0; i<shoutNum; i++)
{
result += " 哞";
}
cout << "我的名字叫" << name << result << endl;
}
};
// 直接增加小羊类
class Sheep: public Animal
{
public:
Sheep(): Animal()
{}
Sheep(string name): Animal(name)
{}
void Shout()
{
string result = "";
for(int i=0; i<shoutNum; i++)
{
result += " 咩";
}
cout << "我的名字叫" << name << result << endl;
}
};
问题:
1. 四种动物除了声音不同外,没有任何的差异.
2. 如果要把"我的名字叫" 改为 "我叫" ,需要修改四个类的代码.
修改:
将声音的部分,修改为 getShoutSound 即可.
class Animal
{
protected:
// ...
// 只需要给继承的子类使用
virtual string getShoutSound()
{
// 动物类本身并不知道具体的叫声
return "";
}
public:
// ...
// 去掉 virtual 成为普通的公共方法
void Shout()
{
string result = "";
for(int i=0; i<shoutNum; i++)
{
result += " ";
result += getShoutSound();
}
cout << "我的名字叫" << name << result << endl;
}
};
class Cat: public Animal
{
public:
Cat(): Animal()
{}
Cat(string name): Animal(name)
{}
protected:
string getShoutSound()
{
return "喵";
}
};
11. 抽象类
问题:
1. 发现 Animal 类其实根本就不可能实例化的. 例如: 说一只猫长什么样, 你可以想象; 说一个动物长什么样, 你想象的出来吗?!
动物是一个抽象的名词, 没有具体对象与之对应.
2. 可以考虑把实例化没有任何意义的父类, 改成抽象类; 同样,对于 Animal 类中的 getShoutSound 方法, 其实方法体没有任何意义, 可以将它声明为纯虚函数, 成为抽象方法.
// 抽象动物类
class Animal
{
protected:
virtual string getShoutSound()=0;
// ...
};
抽象类的说明:
1). 抽象类不能实例化.
动物实例化没有意义.
2). 抽象方法是必须被子类重写的方法.
抽象方法可以被看成是没有实现体的虚函数.
3). 如果类中包含抽象方法, 那么类就必须定义为抽象类, 不论是否还包含其它的一般方法.
4). 应该考虑让抽象类拥有尽可能多的共同代码, 拥有尽可能少的数据.
5). 到底什么时候应该使用抽象类呢?
抽象类通常代表一个抽象概念, 它提供一个继承的出发点, 当设计一个新的抽象类时, 一定是用来继承的, 所以, 在一个以继承关系形成的等级结果里, 树叶节点应当是具体类, 而树枝节点均应当是抽象类.
具体类不是用来继承的.
例如:
若猫, 狗, 牛, 羊是最后一级, 则它们应该是具体类.
若下面还有一层, 例如'波斯猫'继承于猫, 则需要考虑把猫改为抽象类, 但是这也要看实际情况具体分析而定.
12. 接口: interface
需求:
动物运动会, 还有一项特殊的比赛为有特异功能的动物展示其特殊的才能.
例如:
机器猫叮当(猫),石猴孙悟空(猴),八戒(猪)
叮当:会从口袋里变出东西
孙悟空:可以拔毫毛变出东西,而且有72般变化
八戒:有32般变化
'变出东西':是叮当,孙悟空,八戒的行为方法。如果想使用多态,不能将这个方法放在 Animal 类中,否则就变成所有动物都可以'变出东西'。
接口:
1. 是把隐式公共方法和属性组合起来,以封装特定功能的一个集合。
2. 一旦类实现了接口,类就可以支持接口所指定的所有属性和成员。
3. 声明接口在语法上和声明抽象类完全相同,但不允许提供接口中任何成员的执行方式。
4. 接口不能实例化,不能用构造函数和属性。
5. 实现接口的类必须要实现接口中的所有方法和属性。
6. 一个类可以支持多个接口,多个类也可以支持相同的接口。
7. 接口的命名,前面要加一个大写字母'I'
// 变东西的接口类
class IChange
{
public:
virtual void ChangeThing(string thing)=0;
virtual ~IChange(){}
};
// 机器猫类
class MachineCat: public Cat, public IChange
{
public:
MachineCat(): Cat()
{}
MachineCat(string name): Cat(name)
{}
void ChangeThing(string thing)
{
Shout();
cout << " 我有万能的口袋, 可以变出: " << thing << endl;
}
};
// 猴子类
class Monkey: public Animal
{
public:
Monkey(): Animal()
{}
Monkey(string name): Animal(name)
{}
protected:
string getShoutSound()
{
return "吱";
}
};
// 孙悟空类
class StoneMonkey: public Monkey, public IChange
{
public:
StoneMonkey(): Monkey()
{}
StoneMonkey(string name): Monkey(name)
{}
void ChangeThing(string thing)
{
Shout();
cout << " 我会七十二变, 可以变出: " << thing << endl;
}
};
int main()
{
MachineCat *mcat = new MachineCat("叮当");
StoneMonkey *wukong = new StoneMonkey("孙悟空");
IChange *array[2];
array[0] = mcat;
array[1] = wukong;
array[0]->ChangeThing("各种各样的东西! ");
array[1]->ChangeThing("各种各样的东西! ");
return 0;
}
总结:
1. 让两个完全不相干的对象,'叮当'和'孙悟空'来做同样的事情'变出东西',所以可以让它们去实现做这件'变出东西'的接口。这样,程序就可以根据实现接口的对象的不同作出反映。
例如:
同样是'飞',鸟是用翅膀去'飞',飞机是用引擎加机翼去'飞',而超人?举起双手,握紧拳头去'飞',它们是不同的对象,可以使用'飞行'行为的接口。
抽象类与接口:
1. 抽象类可以给出一些成员的实现,接口却不包含成员的实现, 抽象类的抽象成员可被子类部分实现,接口的成员需要实现类完全实现,一个类只能继承一个抽象类,但可以实现多个接口.
2. 类是对对象的抽象; 抽象类是对类的抽象; 接口是对行为的抽象.
接口是对类的局部(行为)进行的抽象,而抽象类是对类整体(属性,方法)的抽象.
如果只关注行为抽象,那么也可以认为接口就是抽象类.
3. 如果行为跨越不同类的对象, 可使用接口; 对于一些相似的类对象, 用继承抽象类.
4. 实现接口和继承抽象类并不冲突.
例如:
可以让'超人'继承'人'类, 在实现'飞行'接口.
'超人': 除了内裤外穿, 基本就是一个正常人的样子.
可以让'超人'实现'飞行'接口,与飞机比飞行.
可以让'超人'实现'力大无穷'接口,与大象比力气大.
5. 从设计角度讲, 抽象类是从子类中发现了公共的东西, 泛化出父类, 然后子类继承父类, 而接口是根本不知子类的存在, 方法如何实现还不确定, 预先定义.
例如:
动物运动会的主办方, 可以设置 '飞的最远', '叫的最响', '力气最大'项目(声明为行为接口).
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -