C++ OOP

来源:互联网 发布:个人网盘网站源码 编辑:程序博客网 时间:2024/06/04 09:37

OOPinheritance

The programming philosophy underlying classes is OOP(面向对象)

C语言编程的时候,是面向过程的(procedural)的。 C语言中我们structure我们的程序是按照如下方式:

(1)       将程序split 一组taskssubtasks.

(2)       对每一个task编写函数实现子任务。

(3)       组合起来, instruct 计算机按照顺序执行

 

然而面向过程有一个缺点,就是当数据量很大,或者子任务的数量很大的时候,我们的变量可能太多,散布在在各个地方,也很难将编写的各个函数理清楚。而且我们编写的程序不容易维护(unmaintainable)。所以编写大型的程序的时候,具有OOP的的编程理念是很重要的。

OOP观念的优点主要如下:

(1)       适合处理大型程序的复杂度,更容易将我们的代码打包,模块化。

(2)       更符合人的实际的思考方式。因为我们习惯于将世界看做是相互作用的对象(people think of the world in terms of interacting objects)。举个例子,对于一辆car,我们会将其想象成方向盘(steering wheel),脚踏板(the pedals,车轮子(the wheels)等等objectsinteracting(相互作用),从而定义了car。也正是因为如此, OOP是的程序员能够编写出干净的的, self-contained boxes(objects),然后从更加抽象的角度去看待objects,这样就可以集中精力关注这些objects之间的相互作用部分。

OOP主要有三个最基本的特征。

(1)       封装(Encapsulation)。将相关的data functions group在一起,组成object(物件),然后定义好这些objects之间的接口(interface)。

(2)       继承(Inheritance):允许我们将相关的代码重用,具体分析接下来会讲到。

(3)       多态(Polymorphism: allowing a value to be one of several types。在runtime(程序运行的时候),根据相关的types决定调用哪一个函数。

 

封装(encapsulation):

封装的直白的意思就是将相关的stuff打包起来。我们已经看到了C++是使用类(class)将数据和operations打包起来的。

封装的好处就是掩盖实现细节,真正让程序员只关注与如何去使用。例如如果某个人给你一个class,我们并不需要知道具体的methods的实现细节就可以正确使用它。我们仅仅需要知道哪些存取方式为publicmehods/data,也就是类的接口(interface)。这些接口就想对象上的button一样,按一下(push),对象就给我们相应的信息或者作出相应的反应。下面仍以如何操作一个car为例:你在开一辆车的时候,你仅仅需要知道(interface)方向盘能够实现你转向的goal,而不需要知道方向盘是如何控制车轮子的复杂的机械原理。

这也就是为什么C++ 要求你specify 存取修饰符是public还是private by dafault C++假设你定义的具体的接口的实现细节不是你的users所需要worry的。这种对client(客户)掩盖具体实现细节的做法被称为data hiding

想一想,我们定义了objects,然后这些objects通过调用自己类的methods去告诉其他objects关于自己的内部相关信息以及自己做了哪些操作,这就实现了information exchange

 

继承(Inheritance

C++ 中继承机制是的我们能够定义出hierarchies of related classes(相关类的层次关系)。

设想一下,我们需要编写一个carsclass,和一个trucksclass,我们可以定义两个完全不相关的(unrelated)的classes,一个是cars,另一个是trucks。可是考虑到无论是car,还是truck,都是属于一个更高层次的class vehicle,二者具有很多相同的特征。所以我们通过列出所有的vehicle的共同特征,然后通过继承的方式得到旧的共同属性,然后添加各个类具有的新的属性,得到类cartrucks

例如,我们定义vehicles父类如下:

 

class Vehicle {   protected:      string license;      int year;         public:      Vehicle(const string &myLicense, const int myYear)         : license(myLicense), year(myYear) {}      const string getDesc() const { // description methods         return license + " from " + stringify(year);      }      const string &getLicense() const {         return license;      }      const int getYear() const {         return year;      }};


对于上面的程序, 给出如下相关的解释:

(1)对于字符串, 可以使用‘+’号将所给的strings concanate 在一起。

(2)大部分情况下, protected 和private的存取修饰符是等价的, 至于区别, 我们会在今后提及。

(3) 建构子初始化的时候, 采用的格式是member initializer syntax。 有的时候, 我们在定义建构子对成员变量初始化的时候,特别是对于const 的members, 我们希望在construtor body 之前完成初始化。此时我们只需要在函数名字后加一个colon, followed by a  comma separated list items of the form datamember(initialvalue)。

(4)程序中使用了string 库中的函数 stringify, 作用是将数字转换为strings。

 

下面我们利用上面的vehicle code 定义我们的car class:

class Car: public Vehicle {      string style;      public:      Car(const string &myLicense, const int myYear, const string           &myStyle)          :Vehicle(myLicense, myYear), style(myStyle) {}                const string &getStyle() {         return style;      }          };


关于该程序, 我们有如下说明:

(1)类Car 是以公开的方式继承了(inherits from)Vehicle。我们常称Car为derived class(派生类), Vehicle 称为base class(基类)。

(2)类Car除了具有Vehicle的所有的属性(成员)之外, 还有自己特有的新的成员变量和成员函数。

(3)我们采用member initializer Syntax的方式调用基类的建构子对旧的成员变量值(继承来的)进行初始化, 然后在对自己本上的新的属性初始化, 定义自己的建构子。

 

类似的, 我们可以定义类Truck,该类也是继承自Vehicle。

当定义完成之后, 得到如下的class hierarchy:

其中箭头的方向是从派生类指向基类, 即意思‘为继承自’。  

 

两大常见的关系: Is-a vs Has-a 的关系

class A 和class B如果有关系, 则有如下两种可能:

(1) 每一个class A 的object 都有一个(has a)class B的object。 例如上例, 每一个Vehicle类型的object 都有一个(has  a) string 类的object。

(2)每一个A的instance(或者称为object)都是一个(is a) B object。例如上例, 每一个Car的object 同时也是一个Vehicle的object。

 

继承帮我们实现的是两个相关类的的“is-a”的关系。 ‘Has-a’的关系是通过在一个类中用另一个类声明的一个对象作为其成员变量实现的, 而不是通过继承实现的。 这点需要注意。

 

Overriding Methods(覆盖方法)

有的时候, 我们希望在派生类中定义一个和基础类中某个method的signiture 完全一样的成员函数, 用于覆盖掉旧的成员函数。 这个函数就称为override methods。

注意覆盖和重载的不同之处, 覆盖是用于两个函数的signiture 完全一样的情况, 尽管这两个函数做的事情是不同的, 举个例子如下:

class Car: public Vehicle {      string style;      public:      Car(const string &myLicense, const int myYear, const string           &myStyle)          :Vehicle(myLicense, myYear), style(myStyle) {}      const string getDesc() { // overriding this member function          return stringify(year) + ' ' + style + ":" +license;      }      const string &getStyle() {         return style;      }          };


上例子中, 我们在派生类Car中用新定义的getDesc 覆盖函数覆盖掉了继承而来的旧的基类函数getDesc。

 

Access modifier and Inheritance:

在类中:

如果我们在Vehicle中声明year 和 license 中声明存取方式为private, 那么我们在派生类Car中就无法看到(存取到) 这两个成员了(year 和 license)。为了允许可以在派生类中access 到基础类的数据成员和成员函数, 在其他地方不能存取到这些成员, 我们会将他们声明为protected的存取方式。

 

现在说说在基类之前表示继承方式的public的意思。

即 class Car: public Vehicle {

// code here

};

public 是对派生类使用基础类(继承来的)成员函数使用的限制。通常, 我们使用的是公开的继承方式, 这样在基础类中为public的成员函数在派生类中依然是public的。

如果我们声明的继承方式是protected, 那么对于继承来的methods, 即使为public的, 在派生类中也就有了protected 的存取属性。

 

多态(Polymorphism)

Polymorphism 的英文含义是“many shape" , 也就是多种形态的意思。 也即是说, it refers to the ability of one object to have many types(它代表着一个对象具有多种类型的能力)。 如果我们有一个函数需要一个Vehicle类的对象(object), 我们可以传进去一个Car类型的object, 因为一个car类型的object具有Vehicle类型的object的所有东西。 或者说每一个Car 也是一个Vehicle(当然不能反过来说)。类似的, 对于Reference 和pointers: 任何可以使用Vehicle* 的地方, 我们都可以使用 Car*。 这就是Polymorphism。 也就是说派生类的对象(object)具有任何父类的任何类型(types)。 这就是多种形态。 C++中的多态具有两种多态的实现形式。 一种多态是compile-time Polymorphism, 另一个是run-time Polymorphism。 编译时的多态通过采用静态绑定(static binding)的方式, 优点是速度比较快, 一般通过函数重载(function overloading)和运算符重载(operator oveloading)。 运行时多态(run-time Polymorphism)运用动态绑定(dynamic binding), 优点是具有较高的灵活性。 运行时的多态主要通过 继承(Inheritance) + virtual functions(虚函数) 实现。 下面介绍一下用于实现运行时多态的的一个强大的tools: 虚函数(Virtual functions)和纯虚函数(pure Virtual functions)

Virtual Functions(虚函数)

Motivation, 如下例子:

Car c("VANITY", 2003);Vehicle *vPtr = &c;cout << vPtr -> getDesc(); // ptr->member 等价于 (*ptr).member

整个程序如下:

#include <iostream>#include <string>using namespace std;class Vehicle {   protected:      string license;      int year;   public:      Vehicle(const string &myLicense, const int myYear)         : license(myLicense), year(myYear) {}      const string getDesc() const {         return license + " from " + to_string(year);       }      const string &getLicense() const {         return license;      }      const int getYear() const {         return year;      }};class Car: public Vehicle {      string style;   public:      Car(const string &myLicense, const int myYear, const string          &myStyle)          :Vehicle(myLicense, myYear), style(myStyle) {}      const string getDesc() { // overriding this member function          return to_string(year) + " " + style + " : " +license;       }      const string &getStyle() {         return style;      }};int main() {   Car c("VANITY", 2003,"A");   Vehicle *vPtr = &c;   cout << vPtr -> getDesc();   return 0;}

上述程序应该是对的, 但是由于我使用的编译器code::blocks不识别to_string 函数, 所以编译没有通过。用google搜索了一下, 给出的答案是:


没办法, 只能自己写一个to_string 函数具体如下:

#include <iostream>#include <sstream>#include <string>using namespace std;template <typename T>string to_string(T value){ostringstream os ;os << value ;return os.str() ;}class Vehicle {   protected:      string license;      int year;   public:      Vehicle(const string &myLicense, const int myYear)         : license(myLicense), year(myYear) {}      const string getDesc() const {         return license + " from " + to_string(year);      }      const string &getLicense() const {         return license;      }      const int getYear() const {         return year;      }};class Car: public Vehicle {      string style;   public:      Car(const string &myLicense, const int myYear, const string          &myStyle)          :Vehicle(myLicense, myYear), style(myStyle) {}      const string getDesc() const { // overriding this member function          return   "year " + style + " : " +license;      }      const string &getStyle() {         return style;      }};int main() {   Car c("VANITY", 2003,"A");   Vehicle *vPtr = &c;   cout << vPtr -> getDesc();   return 0;}


运行结果如下:

 

 

上例子中, 我们声明一个Vehicle* 的指针vPtr, 但是当我们将vPtr 指针指向Car类型的一个对象的时候, 虽然Car和Vehicle的类中均声明了自己的成员函数getDesc(), 但是我们希望指针vPtr能够在运行的时候自动根据其所指的对象在运行的时候决定选择所指向的成员。 但是事实上却不是这样的。 vPtr 会根据自己的型态(即Vehicle)调用Vehicle的成员(在这里是getDesc())。

为了实现上面我们希望的效果, 我们在基础类中将需要实现运行时多态的函数用Virtual去修饰:

class Vehicle {

   ...

   virtual const string getDesc() {

  ....

  }

};

具体的整个程序如下:

#include <iostream>#include <sstream>#include <string>using namespace std;template <typename T>string to_string(T value){ostringstream os ;os << value ;return os.str() ;}class Vehicle {   protected:      string license;      int year;   public:      Vehicle(const string &myLicense, const int myYear)         : license(myLicense), year(myYear) {}     virtual const string getDesc() const {         return license + " from " + to_string(year);      }      const string &getLicense() const {         return license;      }      const int getYear() const {         return year;      }};class Car: public Vehicle {      string style;   public:      Car(const string &myLicense, const int myYear, const string          &myStyle)          :Vehicle(myLicense, myYear), style(myStyle) {}      const string getDesc() const { // overriding this member function          return   to_string(year) + style + " : " +license;      }      const string &getStyle() {         return style;      }};<pre class="cpp" name="code">int main() {   Car c("VANITY", 2003,"A");   Vehicle *vPtr = &c;   cout << vPtr -> getDesc();   return 0;}

运行结果如下:

 

另外, 由于Reference(别名)也是隐藏的指针, 所以虚函数实现的多态也适用。主函数如下:

int main() {   Car c("VANITY", 2003,"A");   Vehicle &y = c;   cout << y.getDesc();   return 0;}

运行结果与上卖弄相同:

 

一旦一个类的某个method被宣告为virtual的, 那么有该类延伸出来的所有派生类中, 每一个这样的method均默认为Virtual的。 可以在这些派生类中不加上virtual的修饰。 然而如果明确修饰的话, 是建议的for clarity。

 

纯虚函数(pure virtual function)

有的时候, 基类中某些method(例如在Vehicle中getDESc)是没有定义的概念(或者没有的概念), 但是在其所有的派生类中却是有定义的(例如Car, truck的有getDesc的概念), 此时我们可以在基类中定义这个概念, 这就是纯虚函数(虽然基类中无这一概念), 具体参见如下:

class Vehicle {

   Virtual const string getDesc const = 0; // Pure virtual

};

其中=0 的意思就是表示没有定义。 所以我们也就无法使用这个基础类去声明任何的对象, 该基类只是供其他的类继承的。 这样的Vehicle(具有纯虚函数的)被称之为抽象基础类(abstract class)。

只有实现了(implement)这个纯虚函数的派生类才能够用于产生对象。 这也要求继承自抽象类的派生类必须定义这个纯虚函数。 只要不定义, 具有纯虚函数, 就会被称之为抽象类。

 

多重继承(Multiple inherittance)

有的时候, 派生类可能有很多个父类。这意味着该派生类具有其所有父类的属性, 当然还有自己新的属性。

例如:

class Car: public Vehicle, public InsuredItem {

 

};

这里, Car具有两个基类: Vehicle 和InsurdItem。

然而多重继承是我们编程应该要避免的, 我们常常不希望多重继承的发生, 因为处理多重继承通常比较tricky 和 dangerous。 主要有如下两个问题:

(1)如果Vehicle和InsuredItem都定义了一个成员变量x, 为了避免歧义, 我们需要使用scope resolution的方式分开。 例如Vehicle::x 和InsuredItem::x, 有的时候很容易忘了加scope resolution, 降低我们编程的效率.

 

(2)  如果Vehicle 类和 InsuredItem 均继承自同一个基础类, 虽然也可以解决, 但是这样问题就变得messy了。

 

 


 

 

 

 

 

 

0 0
原创粉丝点击