C++教程4之面向对象编程概念
第IV章
Object-Oriented Concepts 面向对象概念
前面章节我们介绍了C++对基于对象编程和用模板泛型编程的支持。我们例子中(rational,vector等等)我们结合存在的类型创建新的类型。本章我们会对一直谈的概念给出更正式定义,展示众多类和对象之间的关系以及介绍继承概念。
IV-1 Classes and objects 类和对象
一个类是抽象数据类型的实现(ADT)。它定义了数据成员(属性)和成员函数(也称之为方法),这些实现了数据结构以及ADT的一组操作。这些操作称之为ADT的接口
类的实例称之为对象,反之亦然。类的一个对象由其名字唯一标识。对象属性的值在某些特定时刻称之为对象的状态。用于对象的操作集定义为对象的行为(behavior)。
对象之间透过类接口提供的方法调用相互作用。自然地,类定义了对象的属性和行为集。
IV-2 Relationships among classes and objects 众多类和对象间的关系
IV-2.1 Physical
再看看rational类的实现:
- class rational
- {
- private:
- int num, den;
- public:
- // ...
- };
这类定义阐述了两种关系。第一个是源自从外部看类rational。我们可以这么说,类rational有两个整型数据成员num和den。换句话说,我们可以这么说整型数据成员num和den是类rational的一部分。如果一个类含有其它类的对象,我们说这是一种拥有(has-a)的关系。反过过来称之为属于(part-of)关系。
IV-2.2 Conceptual
再看看我们在I-5小节用于实现图形系统的类定义,用于画点、园等等。一个点的实现如下:
- class point
- {
- public:
- void set_x(int);
- void set_y(int);
- int get_x() const;
- int get_y() const;
- // ...
- private:
- int x, y;
- };
圆的实现如下:
- class circle
- {
- public:
- void set_x(int); // set the x-coord of the circle center
- void set_y(int); // set the y-coord of the circle center
- int get_x() const; // get the x-coord of the circle center
- int get_y() const; // get the y-coord of the circle center
- void set_r(int); // set the radius of the circle
- int get_r() const; // set the radius of the circle
- // ...
- private:
- int x, y, r;
- };
如果比较这两个类定义,可以很容易看出两个类有共同数据成员(x和y)。在类point里,这两个数据描述点的坐标,在类circle这两个数据描述circle的圆心坐标。因此在这两类用一个点指明他们的起点/位置(origin/position)。这起点可以透过共同的函数set_x和set_y。最后,类circle增加了一个数据成员r。
从概念上观点看,因为circle可以用如point方式使用,所以我们可以说circle是一种(a-kind-of)point。类型circle的一个对象是一个(is-a)point,有点特殊化点,因为其有半径(radius)。
IV-3 Inheritance 继承
前面章节为了阐述a-kind-of和is-a关系,我们给出类point和circle的一个实现。本节看看如何用C++表达这关系。
IV-3.1 Single Inheritance 单一继承
再看看point和circle的实现。从程序员的观点来看,它引入了代码重复的问题:尽管设定和获取坐标的接口实现是相同的,我们实现了两次。幸运地,这问题可以使用C++继承很优雅解决掉。在我们新实现里,point保持不变。circle类做了改变,继承于point:
- class circle : public point
- {
- public:
- void set_r(int); // set the radius of the circle
- int get_r() const; // set the radius of the circle
- // ...
- private:
- int r;
- };
从一个类继承的句法相当简单。继承类和一般定义一样。只是后面增加一点小尾巴--冒号:,表明我们将从什么类型继承。然后添加关键字public以及被继承的类名字(本例中是point类)。这样写表明我们想继承point的public接口。因为我们只从一个类继承,这种C++继承称之为单一继承。
有了上面的声明,下面代码就合法了:
Circle c;
c.set_x(10); // inherited from point
c.set_y(10); // inherited from point
c.set_r(5); // added by circle
这意味着我们可以像使用point那样使用circle,这正是我们希望的,因为circle是一种(a-kind-of)point。我们也希望任何circle对象可以在能用point的地方能被使用,事实确实如此:
- void move_x(point & p, int offset)
- {
- point.set_x (p.get_x() + offset);
- }
- circle c;
- move_x(c, 10); // ok: a circle is-a point
Inheritance and templates 继承和模板
继承也能用于模板类。考虑模板类vector,基于一些类型T实现了动态vector:
- // declare a vector of elements of type T
- template class vector
- {
- public:
- int size() const;
- T & operator[] (int i);
- const T & operator[] (int i) const;
- // ...
- };
可以这么使用vector实现一个类polynomial(多项式):
- // declare a vector of elements of type T
- template class polynomial : public vector
- {
- public:
- int degree() const { return size() - 1; }
- // ...
- // additional operators +, - , *, etc
- };
这省去了重新实现对多项式系数的处理:设定和获取大小、设定和获取第i个元素等等。现在这由vector类去做。我们只需要实现额外一些vector没有,而多项式类有的方法和操作。
Single inheritance support C++对单一继承的支持
简单说,编译器在我们的例子中做以下事情:
1. 把point数据成员增加到类circle里
2. 把point公有成员函数增加到circle类
3. 把point的公有operator增加到circle类
在内部,circle在编译后,看起来像这样:
- class circle
- {
- public:
- void set_x(int); // added by the compiler
- void set_y(int); // added by the compiler
- int get_x() const; // added by the compiler
- int get_y() const; // added by the compiler
- void set_r(int); // set the radius of the circle
- int get_r() const; // set the radius of the circle
- // ...
- private:
- point { int x, y; } // added by the compiler
- int r;
- };
Inheritance terminology 继承术语
继承是这样一种机制--让一个类A继承另一个类B的属性。我们说A从B继承。类A的对象因此可以访问类B的属性和方法,而不需要重新定义它们。A称之为源自B。另一种说法称A是B的子类(child class or subclass),B是A的父类或超类(parent class or superclass)。
一般用来表示继承关系的图形使用箭头:
circle---->point
箭头的方向指向超类
Conversions from base and superclasses 从基类和超类转换
在前面circle和point例子中,在两个类的接口里我们没有碰到名字冲突。但是如果一个后代类提供一个完全一样定义的成员函数(和父类相比)会发生什么状况呢?例如,考虑下面的类层级,描述了一个简单的模型--驱动一个机器去煮咖啡或茶:
- class liquid_machine
- {
- public:
- void cook() const
- { cout << "liquid_machine::cook()" << endl; }
- };
- class coffee_machine : public liquid_machine
- {
- public:
- void cook() const
- { cout << "coffee_machine::cook()" << endl; }
- };
- class tea_machine : public liquid_machine
- {
- public:
- void cook() const
- { cout << "tea_machine::cook()" << endl; }
- };
- void cook_liquid(const liquid_machine & l)
- { l.cook(); }
再考虑下面简单的程序:
- coffee_machine c;
- tea_machine t;
- c.cook();
- t.cook();
- cook_liquid(c);
- cook_liquid(t);
你能预测它的输出吗?
coffee_machine::cook()
tea_machine::cook()
liquid_machine::cook()
liquid_machine::cook()
子类(coffee_machine, tea_machine)的成员函数cook()覆写了超类的同名函数(liquid_machine)。但是调用函数cook_liquid(c)会出现什么状况?
答案是:编译器所做的恰和继承情况相反--它在转换coffee_machine到liquid_machine时,编译器把继承时增加的额外数据成员和成员函数移除(通常称之为slicing机制),复现超类的cook()函数实现。换句话说,在参数传递后,再没啥东东属于coffee_machine。如果你觉得这不太直观,问问你自己--一个比较大的子类对象怎么能在一个较小的父类对象里保存呢?这是不可能的,因为在执行前,编译器已经计算和固定了类型的大小,这称之为静态绑定(static binding)。
virtual functions 虚拟函数
然而可以使用关键字virtual对成员函数(不是数据成员)实现动态绑定(dynamic binding)。例如,把liquid_machine实现修改如下:
- class liquid_machine
- {
- public:
- virtual void cook() const
- { cout << "liquid_machine::cook()" << endl; }
- };
而cook_liquid()保持不变:
- void cook_liquid(const liquid_machine & l)
- { l.cook(); }
上面程序输出变为:
coffee_machine::cook()
tea_machine::cook()
coffee_machine::cook()
tea_machine::cook()
现在编译器在运行时决定该调用哪个cook(),所以这输出成为了可能。使用一个函数指针表,指向cook()的可用的实现,这样就可以使用运行时类型信息决定调用哪个版本函数。这样一个表称之为虚拟表(virtual table),保存在每一个对象里(虚表增大了对象的size-一个固定的常数乘以虚拟函数的数目)。
IV-3.2 Multiple Inheritance 多重继承
在我们前面的例子中,circle从point类继承。然而还有些情况,我们需要从多个(不止一个)超类继承。对于这种情形称之为多重继承(multiple inheritance)。
例如,考虑a ring over a domain T。 ring是个数学结构,有两个操作+和*。+操作让ring组成abelian group,而*操作简单组成一个semigroup。这相对复杂的关系可以用C++用下面的实现表述出来:
- template class abelian_group { /* ... */ };
- template class semi_group { /* ... */ };
- template
- class ring : public abelian_group,
- public semi_group
- {
- // ...
- };
有了这个使用了多重继承的定义,不管需要abelian_group或者semi_group都可以使用ring。
上面的例子用图形表示如下:
+----> abelian_group
|
ring ----+----> semi_group
Inheritance terminology (continued) 继承术语(续)
如果类A从多个类继承,例如,B1,B2,...,Bn,我们称之为多重继承(multiple inheritance)。
虚拟继承 virtual inheritance
谨慎使用多重继承。考虑下面情形:
class A { /* ... */ };
class B : public A { /* ... */ };
class C : public A { /* ... */ };
class D : public B, public C { /* ... */ };
图形表示如下:
+----> B ----+
| |
D --+----> C ----+----> A
[A是所有类的基类;B,C继承于A,D又继承于B,C;B和C是单一继承;D是多重继承]
类型D的对象从A继承了两次,这样在D里有A的数据成员和成员函数的两份拷贝。如果用一些实际的名字替代这里的A、B、C,可以很容易看出显然不是我们期望的:
class base_vector { /* ... */ };
class sort_vector : public base_vector { /* ... */ };
class math_vector : public base_vector { /* ... */ };
class vector : public sort_vector,
public math_vector { /* ... */ };
vector类型的对象没有理由拥有base_vector数据两次。在C++里,我们透过使用virtual继承克服这个问题。上面的例子可重写如下:
class A { /* ... */ };
class B : virtual public A { /* ... */ };
class C : virtual public A { /* ... */ };
class D : virtual public B, public C { /* ... */ };
增加了virtual关键字,确保编译器在D里只产生A的一个拷贝。
IV-4 Exercises
完成polynomial类实现。提供加,减,乘,伪除法操作。实现一个漂亮的打印输出函数。
- 评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)
-
