3.8. Hello object 成员版
前例中,我们定义了一个“不知道是什么东西的”Object类型。一开始,它是一个空空的类型:
struct Object
{
};
而后,我们为它加入了自定义的构造与析构函数。
struct Object
{
Object()
{
std::cout << "Hello world!" << endl;
}
~Object()
{
std::cout << "Bye-bye world!" << endl;
}
};
于是,这个类型的对象拥有自定义的“生”与“死”的过程,然而,光光讨论对象的“生”与“死”,未免太形而上,太哲学,我们要考虑如何从“抽象”过渡到“具体”。
读者有没有玩过电脑游戏?游戏里面有“魔兽”,“魔兽”通常有以下特点:
第一、 内部通常有“血气值”。
第二、 对外通常有“攻击”能力。
第三、 内部的“血气值”和对外“攻击”能力有一点关联。
有关游戏软件的设计,我们就谈这一些。因为这一些已经够了。这三点说出C++所有“对象”共有的特点。不仅仅是C++程序,现实生活中的“对象”也如此。
〖重要〗:“对象”在表达什么?
第一、对象拥有属性
第二、对象拥有能力
第三、对象的属性改变,往往影响其能力。
想一想“汽车”吧。汽车内部有“油”。汽车的外在能力是:“会跑”。但是,汽车如果油用光了,它就不会跑了。
我们把对象拥有的特征,称为“成员数据”、把它拥有的能力,称为“成员函数”。
人 是最复杂的“对象”之一。因为人有很多“成员数据”,比如:“饥饿度”、“情绪值”、“健康度”;人的能力就更多了,比如“吃喝拉撒”——似乎都算不上什 么本事,再如:“诗书琴画”什么的——似乎现代人有些困难,再往下说,似乎又要开始哲学了,因为人肯定会“死”,可惜析构函数在上节课就说过了。
在 程序中,当我们真的要用一种“类型”来表达“人类”的话,肯定不会费心地把一个人的所有生物的、社会的属性与能力都在代码中做一个映射。我们总中仅仅针对 当前程序所要处理的需求,截取“人”在某一特定方面的数据与能力,,然后用C++类型设计中的“成员数据”与“成员函数”来表达。
本小节中,我们就准备有用“人”来写例子代码。我将根据课程的需要来选择使用“人类”哪些属性与能力。我想没有哪个人类学家会对我有意见。
请在Code::Blocks中创建一个控制台应用项目,取名为:“HelloOOMember”。如有需要,请打开向导自动创建的main.cpp文件,并将其文件编码修改为“系统默认”。
先来一个空的“人”类
struct Person
{
};
然后添加人类的“生死”函数:
struct Person
{
Person()
{
cout << "Wa~Wa~" << endl;
}
~Person()
{
cout << "Wu~Wu~" << endl;
}
};
3.8.1. 成员数据
进入本节重点,我们为Person添加第一个成员数据:姓名。为了不浪费篇幅,我在下面示例代码中,删除一些前面已经出现的内容,但又为了让大家看起来比较有“感觉”,我将被删除的内容,换成一些文字说明。
struct Person
{
//此处略去:构造与析构函数
string name;
};
新添加的成员数据,类型为“string”,变量名为“name”。为了能正确编译string类型,请自行在main.cpp第002行处添加:
#include <string>
来看看如何使用这个成员数据。
021 int main()
{
023 Person xiaoA;
024 xiaoA.name = "Xiao A";
026 Person *xiaoB = new Person;
027 (*xiaoB).name = "Xiao B";
028 delete xiaoB;
029 return 0;
}
程序中,我们定义了两个Person的对象。其中xiaoA是“栈对象”,xiaoB是“堆对象”。
024 行演示了如何访问对象的“成员数据”。
024 xiaoA.name = "Xiao A";
这一行将“=”右边的值,赋给“=”左边的对象。
通过对象访问其某一成员数据,语法很简单:
对象.成员数据
不过第027行看上去有些不同:
027 (*xiaoB).name = "Xiao B";
那 是因为xiaoB是堆对象。而堆对象的“真身”其实是“ *xiaoB ”。因此对于栈对象,写的是 xiaoA.name;对于堆对象,写的就本该是 *xiaoB.name了。那又为什么需要一对圆括号呢?这里的括号的作用类似: (1+2)*3 中括号,用来改变运行符的优先级别。在C++中,“星”操作符的优先级比“点”低,因此*xiaoB.name 相当于 *(xiaoB.name),这不是我们想要的,我们要的是:(*xiaoB).name。
这样写实在有些绕,所以C++提供了“->”操作给堆对象使用,所以,027行更常见的写法是:
xiaoB->name = "Xiao B";
下面,我们除了把027行改成通俗写法以外,还在后面加上用于显示两个对象的name成员的代码。
int main()
{
Person xiaoA;
xiaoA.name = "Xiao A";
Person *xiaoB = new Person;
027 xiaoB->name = "Xiao B";
029 cout << xiaoA.name << endl;
030 cout << xiaoB->name << endl;
032 delete xiaoB;
return 0;
}
请编译并运行以上代码,以下运行结果:
(图 25 成员数据访问)
〖课堂作业〗:复习关键字delete
请大家考虑,上述代码中,030行与032行代码位置,可以对调吗?必须实际动手,将这两行代码对换位置,然后编译并运行。看看结果是什么。
3.8.2. 成员函数
我们为Person增加的第一个成员函数,或者说是它第一个能力是:自我介绍。
struct Person
{
//此处省略:构造函数和析构函数
018 void Introduction()
019 {
020 cout << "Hi, my name is " << name << "." << endl;
021 }
std::string name;
};
〖小提示〗:成员数据与函数的位置
把构造与析构函数放在类型定义中最前面,其它成员函数放在中间,成员数据放最后面,这是当今C++界比较流行的风格噢。
018到021行定义了一个成员函数。和构造或析构函数不同,成员函数必须指定其返回值类型,本例中,“自我介绍”这一行为不需要返回,所以定为“void”。
有了Introduction函数,它可以替换main函数中原有输出。
int main()
{
Person xiaoA;
xiaoA.name = "Xiao A";
Person *xiaoB = new Person;
xiaoB->name = "Xiao B";
034 xiaoA.Introduction();
035 xiaoB->Introduction();
delete xiaoB;
return 0;
}
034和035行被替换了,并且我们也看到了,访问和成员函数访问数据在语法并无差别,同样区分“栈对象”和“堆对象”。
请编译、并运行本例程序。