1. 为自定义类型添加成员
是时候来关心对象自身的属性与功能了!在C++中,对象的属性,通常称为“成员数据”,对象的功能,通常称为“成员函数”。
前两节课中,我们还创造过“恐猪”和“恐蛇”物种啊,这两种类型,按说就应该很厉害才是。但可惜,它们只是在类型的名字上,听起来很厉害的样子,其实,我们也只是为它们定制了生和死的功能。
假设我们要写一个游戏程序,里面就有像“恐猪”和“恐蛇”这样的怪兽。游戏的玩家可能需要杀死它们。那么,怪兽是不是要有像血气值、攻击力、防护力这样的数据?同时,除了生和死,怪兽对外要有攻击和被攻击的能力。
攻击的意思就是,玩家进入某一只怪兽的某个范围时,怪兽可能会主动攻击玩家,对玩家造成一定的伤害。被攻击则相反,如果玩家给它一拳,也就是怪兽被攻击了,这时怪兽是直接挂掉?还是说只是血气值减少一丢丢说?或者说给它一拳以后,它反倒更加兴奋了?
血气值,攻击力,防护值 ,它们就是一个类型或者一个对象的属性,通常它们以一种特殊的数据形式出现,称为成员数据。而:怪兽会攻击人也会被攻击,这些就是一个类型或一个对象的能力,通常以特殊的函数形式的出现,称为成员函数,在更多对面向对象的语言,也称为“方法”。
2. 理解方法和属性之间的关系
结合怪兽这个案例,来看看,在定义一个组合类型时,我们需要表达什么?
1、这一类型的对象,需要拥有哪些属性数据?
2、这一类型的对象,它将拥有哪些功能?
3、这一类型的对象,它的各个属性和功能之间,有哪些关联关系?
其中理解第三点,非常重要:属性和功能之间,通常存在紧密的关系。一个功能的实施,可能会引发一些属性值的变化。比如,怪兽被攻击,通常是不是它的血气值 ,也就是游戏中常见的,顶在怪兽头上的“生命条”,会变短;当然,也可以“防护值”这个属性也考虑上,一只怪兽当前的“防护值”越大,比如说它正好穿着铠甲,那么,它被攻击时,血气值一下子减少的数量,通常就慢一些。
再来看“攻击力”这个属性,和“攻击”这个能力的关系——也很好理解 :这只怪兽当前攻击力越强,那么对玩家造成的伤害就越大。
再来看真实一些的例子:赛车。成员数据可以有 油量、速度、方向等等,其成员方法则可以有跑(行驶)、转向、踩油门(也就是加速),踩刹车(也就是减速等等)。大家肯定能够想象得出,汽车的这些成员数据和成员函数之间的合理关系。
如果把汽车的例子进一步简化到只有速度和油量两个数据,以及踩油门和踩刹车两个方法。那么这个赛车类型,用结构/struct的来表达的伪代码,大概就长这样子:
// 伪代码
struct 赛车
{
int 速度 = 0;
int 油量 = 100;
赛车 ()
{
cout << “赛车启动啦!油量100,速度为零,请踩油门!” << endl;
};
void 踩油门() { 加大速度, 消耗油量 };
void 踩刹车() { 降低速度 };
};
基本关系就是,调用一个赛车对象踩油门的方法,车就会加大速度,同时消耗更多的油;而调用踩刹车方法,则会降低速度。
实际定义一个完善的汽车的类型,还是有很多逻辑关系要想清楚的。比如踩油门并不一定会提速,因为汽车可能没油了。
3. 课堂视频
4. 完整示例代码
#include <iostream>
// 定义人类结构
struct Person
{
Person() { std::cout << "哇哇~" << std::endl; };
~Person() { std::cout << "呜呜~" << std::endl; }
// 自我介绍
void Introduce() // 成员函数,方法
{
std::cout << "大家好,我叫 " << name << std::endl;
}
std::string name; // 成员数据,属性
};
int main()
{
Person xiaoA;
xiaoA.name = "小A";
xiaoA.Introduce();
std::cout << "------------------\n";
auto* xiaoB = new Person;
// 写法一,取值后再访问成员
(*xiaoB).name = "小B";
(*xiaoB).Introduce();
delete xiaoB;
std::cout << "------------------\n";
// 写法二:简易写法“箭头访问符”
auto* xiaoC = new Person;
xiaoC->name = "小C";
xiaoC->Introduce();
delete xiaoC;
}
运行效果:
哇哇~
大家好,我叫 小A
------------------
哇哇~
大家好,我叫 小B
呜呜~
------------------
哇哇~
大家好,我叫 小C
呜呜~
呜呜~