更多感受-Hello "OO"
OO:Object Oriented。Object:
对象,Oriented:以……为导向的。所以,OO就是“以对象为导向的……”,也就是我们常听常说的“面向对象”。严格来讲,它应该是一个形容词:“面向对象的”什么什么,比如OOP:面向对象的编程方法,OOD:面向对象的设计思想,等等。
问题是,什么叫“面向对象”?
“对象”,就是“东西”——这说法太土;不过“对象”又太抽象,最合适的,是“物体”。“面向”呢?前面说了,就是“以……为导向”。
合起来,“面向对象”就是你在实现程序时,或者你在做设计时,你的头脑里基本考虑的对象应该是一些完整的“物体”,而不是……
而不是什么呢?让我们回忆前面课程《第一感受——main 函数》所提的一段话:
“日常生活中,要完成一件复杂的功能,我们总是习惯把“大功能”分解为多个“小功能”以实现。在C++程序的世界里,“功能”可称呼为“函数”,因此“函数”其实就是一段实现了某种功能的代码,并且可以供其它代码调用。”
这段话表达另一种编程思想,叫做“面向过程”,也就是,思考问题时,以如何将解决问题的某一个大过程,拆分成小过程。再看一下这个图就更明白了:

(“面向过程”)
如图所示,做菜的这个大过程,先分解为“买菜”、“切菜”、“炒菜”等小过程,不断细化下去……

(“面向对象”)
也就是说,现在您想做菜,您的头脑里得“换一种思路”,由于来的过程分解式,改为首先考虑需要什么东西(也就是对象,比如图中的菜、锅、炉、等等,这里仅示意,少了很多),然后再了解各个对象的属性,比如,菜应该如何处理?那个炉子该如何点火?等等。这还不够,各个对象之间有什么关系?如何互相配合?比如,想爆炒,则炉火该调大,同时锅里的菜要加速翻动(否则会烧焦)……总之,同样是做一道菜,如果你精通的话,你是名厨;否则做出的菜能吃就算不错了……
哪种方法直观又简单?当然是面向过程;但是,当碰上复杂问题时,面对过程的组织方法,就会有点力不从心了。“哲学”一点地讲:“简单的事情简单处理、复杂的事情复杂处理,如果您非要把简单的事情复杂处理,或者复杂的事情简单处理,估计都比较悬”。
两种方法冲突吗?不冲突;相反,它们互补。当我们通过“面向对象”的方法把各个事物都理清楚了,复杂的事情就可以回归简单,最终总是要以大大小小的“过程”来处理。
今天这节课,仍然只是一节“感受”课,所以,它应该是简单处理的。下面例子,更是简单,但希望有助您理解程序世界里的万千“对象”。
2.5 更多感受-Hello "OO"
2.5.1 要造世界,先造“物种”
对象的第一属性,就是它的类型。譬如前面的做菜例子。首先你得认出那是一颗小白菜,如果你一直以为它是胡萝卜来处理——那能指望您做出什么好菜来呢?
编程语言内置的,仅仅是一些最最基础,且高度抽象的数据类型,比如整数、字符串等(C++语言甚至连完整的内置字符串类型都不支持,还好有相应的标准库提供的字符串类型的实现)。在编程时,现实的事物总是要被高度的抽象,达到可以通过这些基本数据类型来表达出它的属性。比如“学生”,往往被抽象成:姓名、学号、所在年段班级、等等有限的向样数据。
回看一下我们在《更多感受-Hello STL》曾经有下面的代码:
//定义一个包括姓名和成绩的学生结构:
struct Student
{
string name;
int score;
};
|
(有关学生的定义)
根据需要,我们只为“学生”这个类型,提供了“姓名”和“成绩”两个属性——这有点像是“按需分配”——假如我们关心的学生的身体发育情况,那么为学生的视力、体重、身高等属性,就少不了。
让我们“感受”一下定义新“类型”的方法之一,那就是使用关键字struct:
struct 新类型的名称
{
//该类型应具备的属性
};
|
(定义新类型的方法之一)
再次强调一下,样例代码中,我们所定义的是一个“类型”,而不是一个“对象”。即Student现在表示的“学生”这个类型,而不是哪位具体的学生。就好像“人类”和“张三”的关系。相信上帝在创造这个世界之前,也一定在脑海里先构思了这世间万物的“类型”。非常庆幸:上帝
在构思“人类”时,他没有加上“尾巴”这个属性。
定义一个“类型”,是不是只需要列出它的“属性”呢?不,“属性”相当于只这个“类型”应该拥有的“数据”,那么如何体现这个“类型”所拥有的“动作”呢?我们认为:“程序就是‘动作’处理‘数据’”;此时,在“定义类型”这里,“动作”和“数据”二者得到统一,请看:
//定义一个包括姓名和成绩的学生结构:
struct Student
{
string name;
int score;
//声明一个动作:学习
void Study();
};
|
(有关学生的定义:动作+数据)
现在,“Student”所描述的类型中,既包含了两个属性,又包含了一个“动作”。你可能发现了,所谓的“动作”,就是一个函数的声明,只不过它是声明在Student的结构定义之内。这个Study()函数如何实现,不是我们今天的课程内容了。
上帝首先定义叫“人类”这个类型,然后他老人家就开始按照“类型”来创造实际对象,也就是个体的“人”。今天我们也要当一回上帝。
但是,我们不是要造人(奇怪,为什么我一说到“造人”,底下一对小夫妻学员的脸就红了?)。我们的任务是:首先定义“东西”这种类型,然后凭据“东西”这个类型,来实际造出一个“东西”。
2.5.2“物种起源”
“东西”?就是“Object”——记得吧,课程一开始,我们就说了,“Object”?就是“东西”啦。
请看:
//定义Object
struct Object
{
};
|
(Object的定义)
不要诧异,更不要怀疑,没错,这就Object的定义,它空空的,一无所有。有道是:“无极生太极,太极万物”。
你尽可以把这个空无一物的Object看待成“无极”。
现在,启动Turbo C++ Explorer,然后创建一个控制台应用工程,我们来感受一下什么叫“无极”。
在Unit1.cpp中输入以下代码(部分代码由IDE自动生成)。
//---------------------------------------------------------------------------
#pragma hdrstop
#include <cstdlib>
//---------------------------------------------------------------------------
struct Object
{
};
#pragma argsused
int main(int argc, char* argv[])
{
Object o;
std::system("pause");
return 0;
}
//---------------------------------------------------------------------------
|
(Object 运用)
编译,运行,退出程序……这其间一切都很平静,似乎什么也没有发生……是这样吗?没错,运行时,控制台的世界产生了,然后我们按发任意键,这个世界又悄然消失,只有程序员才知道,在这期间,曾经有一个叫“o”的对象,它诞生、生存、死亡……一切都在悄无声息之间,像蚍蜉、像朝露;像秋天的草,像夏天的花……(此处略去1024个字节,因为有同学举手问我为什么程序员都这么喜欢YY)。
我们在代码中通过:
Object o;
这一行,定义了一个对象,这个对象属于前面定义的“Object”类型。在运行期,o存活过——对于程序的世界,存活的意思就是:这对象占过一段内存空间——没错,对象在内存空间中占着,就表明它还“活着”(当然,某些情况可能是“僵尸”或“幽灵”)。
“面向对象”的特性有很多,我们无法在这里一一感受。当我们定义一个“空空如是”的Object类型时,请记住了,提到“面向对象”,您的第一感受应该是“抽象”。因为在进入OO的范畴之后,我们可以对编程的本质有另一种表达:“用程序语言所支持的有限的类型系统,来表达近乎无限的真实的类型世界”,这就要求每个程序员必须学会:“抽象、抽象、再抽象”。
我们要感受的第二个“面对对象”的特性,就是理解“对象”的“生”和“死”。没错,毛主席说过“人固有一死,或重于泰山,或……”噢,我又开始YY了。下一小节!
2.5.3 “生”和“死”
请看代码:
//---------------------------------------------------------------------------
#pragma hdrstop
#include <cstdlib>
#include <iostream>
//---------------------------------------------------------------------------
struct Object
{
Object()
{
std::cout << "我生……" << std::endl;
}
~Object()
{
std::cout << "我死……" << std::endl;
}
};
#pragma argsused
int main(int argc, char* argv[])
{
Object o;
std::system("pause");
return 0;
}
//---------------------------------------------------------------------------
|
(Object 的生和死)
运行时首先出现:

(我生……)
然后按任意键之后,在窗口即将消失的一瞬间,屏幕出现“我死……”——你可能没看见,因为这取决您的系统运行速度和您的眼力。
我们为Object类型定义了两个函数,其一为和Object类型同名的函数,称为“构造函数”,某种意义上,它表达类型Object“生”这个“动作”;其二为~Object,即在类型名前加一~符号。一般它被称为“析构函数”。对应的,您可以认为它表达了Ojbect类型“死”这个动作。
| 函数名 |
意义 |
特定命名规则 |
| Object() |
构造,“生” |
和类型同名 |
| ~Object() |
析构,“死” |
在类型名前加~ |
在哲学上,任何事物都有其生存和消亡的过程,C++之父或许不并关心马克思哲学思想,但不管如何,C++要求,并且保证了我们所定义的类型,必须有“构造”和“析构”函数,就算我们不自己定义——一开始的Object空空如是——编译器也会强制为它暗中产生这两个函数。
记住,C++中的对象,和世间万物一样,有“生”和“死”的动作,我们可以通过在它所属的类型定义中,去规定它们:当它们“生”或“死”时,它们应该做些什么。这很好理解,不是吗?英雄人物的生,应该是风雨交加,雷电霹雳……而一个坏蛋的死,应该是两眼发直,口吐白沫,胸口流出像墨一样黑的血,最后一头栽倒在收费茅坑里。不同类型的对象,就会有不同的生死。
OO的世界,真的很好玩。为什么有些人学习起编程会在那里死搞硬记语法呢?那是因为他们不喜欢像为师我这样,常常将“程序世界”和“现实世界”做关联!古龙同志云:“只有心中有程序,万事万物皆可为程序……”喂!谁丫的又拿西红柿砸我?凭什么古龙讲得,我就讲不得:(
? 每当俺的课程稍微有点“哲学”色彩,就有人这么对我!不管如何,这回我在讲台上挣扎着,也要把下面这段话说完 :) 。
| C++语言的设计“哲学”:
Bjarne Stroustrup(C++ 之父):“……我从不认为这个世界上只存在一种正确的编程方式。从一开始,对于各种编程范型的需要就一直存在,我常列出的范型有C形式的程序设计、数据抽象、面向对象以及泛型式程序设计,它们都得到了C++的直接支持
,而且从一开始,将这些范型组合使用的例子也一直存在……”
Bjarne Stroustrup:
“……我们的历史从来都不是一张白纸,仅仅提供一种东西是不够的……”
没错,C++是一门多范型(multi-paradigm)编程语言。它支持面向对象以及另外数种有用的编程风格。比如基于过程(procedure-based)、基于对象(object-based)、面向对象(object-oriented)、泛型编程(generic
programming)等。
所以,我必须严肃地告诉大家,我们现在所学的语言,是流行的编程语言中,公认最难的一门。其中原因之一在于它的历史包袱(必须尽量兼容C语言),其二就是C++的哲学是它从不认为仅用一种编程思想就可以解决所有编程问题,包括“面向对象”;所以,C++程序员的最高境界是:拿刀、拿剑、拿一根树枝,都能舞出威风,如果您向往的提一把剑就能走天涯,那么您应该选择Java,因为它是一门纯OO的语言。其三就是C++本身也在不断发展,譬如在“泛型编程”上,很多用了C++多年人,竟然对我说“看不太懂”《Hello
STL》的里的类似以下的代码。
//录入成绩:
copy(istream_iterator<int>(cin), istream_iterator<int>(), back_inserter(ss));
这当然不是说他们的C++基础不好,只能说他们一直在吃C++的老本。
扩展到我们学习计算机语言的“哲学”:从来没有只擅长一门语言的高手。深入也好,浅尝也好,多静下心去学习一门经典语言,然后在不同语言之间对比,细细品味不同语言在设计上微妙差别……我深知自己当不了高手,但作为一位平凡的程序员,能偶有机会感受某种“醍醐灌顶”的滋味,已经足以吸引我前行……
有关C++的设计哲学,请参考经典C++书籍:《The Design and Evolution of
C++》。 |
下面我们分析时对象o的生存期间,我们用一张图表示。

(o的生存期)
如图所示,对象o的生命期,从它被定义那一行开始(此时构造函数被调用),一直持续到它所在的函数结束。由于main函数被退出(此是它的析构函数被调用),控制台窗口很快就被关掉,所以那一行“我死……”的出现很难看到。
下面,我继续给出一张图,图中的代码发生了点变化:一对{}将o对象的定义包围起来,结果是什么呢?

(改变了o的生存期)
一对花括号,改变了对象o的生存期,现在,它更加要感叹生如夏花了……
作业:
- 完成课程中改变o生存期的例子。
- 仿照课程例子代码,定义多个Object的对象,比如o1、o2、o3等等。
|