面向对象编程与面向过程的一个根本区别,就是面向对象编程在虚拟的计算机世界构造出了形形×××的对象,很显然这些对象之间必然要发生关系,如何区别描述这些关系也是面向对象这门课所要研究的问题。我们都知道其中有非常多的概念,有些概念还非常抽象,根本难以理解,特别是如果逻辑思维不好的话,会根本被这些概念绕进去。那么这里我们怎么去理解这些东西呢?以我的经验,我想我们还是从生活来理解,我们相像一下计算机描述的世界同现实世界是一模一样的。我们讲两个人,比喻说你和你爸是父子关系,那你的朋友张三与张三的爸是不是也是父子关系呢,当你描述你的某样特征继承你爸时候,实际上这种继承关系也发生在张三与张三的爸之上。那么你可能会说,我们两者之间继承的肯定不一样啊,是的,继承的结果是不一样的,但继承的逻辑是一样的。由此我们推出在现实世界中描述继承虽然理解上好像是两个对象之间的事情,但实际上它的逻辑映射是类结构上。所以讨论计算机中C++语言中的继承也一样,它也是通过描述两个不同的类之间关系,来映射出两个对象之间继承结果。因此,两个类之间具有继承逻辑定义,就说明由这两个类创建的对象具有继承的结果,这个结果就是作为子类生成子对象可以访问作为父类的父对象一些特定函数和属性。这样理解,也可能会让你产生一种误会,比喻,说你跟你爸,你能访问你爸什么呢?显然这里的访问不能作交互理解,更多的理解是你具有了父亲的一些特征。所以说子类创建的对象自然具有了父类的函数与属性。定义一个派生类的格式:
class 派生类名:继承方式(public/protect/private):父类名
{
派生成员函数;
派生成员数据;
}
从上面的格式来看,继承方式同前面的控制属性使用相同的关键字。那么它们两者之间有什么关联呢?我们前面说控制属性是站在两个对象能否相互访问的立场来看,比喻说一个属性或者一个函数,是public,那么另外的对象它是可以直接访问到这个对象的。那么前面所说的继承方式是不是也一样呢?显然不一样,因为前面的控制属性是两个对象之间,而现在继承方式是两个类之间,明显类是一种抽象概念,是不具有访问控制这一说法。类只是通过函数表现某种继承特征。这一点,我们可以这样相像,比喻说爸爸有双眼皮,那么儿子可能有双眼皮,也可能没有。从双眼皮这个特征上说,它在子类中表现为可以具备也可能不具备。因此,继承方式实际上表明了父类中的某个函数在子类中因为继承方式的不同,子类可能不具备父类的某个函数特征。所以C++设计了三个不同的继承方式,通过这三个不同方式,子类有选择的继承了父类的不同特征。具体概念要求就是子类降一级。具体如下:
【1】public(公有继承):继承时保持基类中各成员属性不变,外部类对象可以通过继承类对象访问基类的公有和保护成员,但不能访问基类中private成员。派生类的其它成员函数也可以访问基类中public和protect成员,但不能访问private成员。
【2】protect(保护继承):继承时基类中的public和protect成员在派生类中变成protect,外部类对象不能通过派生类对象访问基类所有成员,但派生类其它成员函数可以访问基类中的public和protect成员,同样也不能访问private成员
【3】private(私有继承):继承时基类中的public和protect成员在派生类中变成private,外部类对象不能通过派生类对象访问基类所有成员,但派生类其它成员函数可以访问基类中的public和protect成员,同样也不能访问private成员。那可能有人会问私有继承与保护继承有什么区别,这里区别在于C++是有多种继承,实际上进行了私有继承,就不可以再往下继承了。
前面我们讲述了派生类与子类之间关于继承方式不同而影响到不同的控制属性。那么接下来我们思考三个问题?而这三个问题显然非常明显,都具有共同性,就是同名属性与现名函数问题。我们分成三个点来学习:
【1】构造函数 父类与子类都具有构造函数,并且构造函数还一样,入参不一样。那么很显然,子类对象初始化时如何构造子类,而子类构造函数怎么构造父类构造函数。这就说出另一个问题,就是基类的构造函数能否被继承呢?按照前面的理解,似乎构造构造函数只要不是private都可以继承,但实际上构造函数是不能继承的,为什么构造函数不能被继承,这一点需要从构造函数的出发点来说明,构造函数它不是普通的成员函数,它是在创建对象时调用的,也就是说它是编译器特定的一种函数,它是由编译器来控制的函数,只有当类实例化成对象时,构造函数才会被调用。所以如果子类能继承基类构造函数,当基类创建对象时就会同时创建出一个父类对象出来,这是不可能的 。对象只有一个。那么学过C++的人都知道,实际上子类构造函数是可以调用父类构造函数进行初始化的,那这种调用其出发点是干什么呢?因为子类的构造函数在构造子类对象时,因为子类继承了父类的一些特定成员,这些成员需要通过父类的构造函数才能初始化。所以子类需要通过父类的构造函数构造出父类的成员来。
【2】同名成员属性或者多继承时来自共同祖先但不同中间祖先,如下图所示中x属性,在A对象和B对象都有,显然,C对象中x是继承自哪个父类呢?从下图中显然说不清楚?
上图是继承的两个父类具有相同成员属性。那么三重继承中最上层的属性肯定被中间层继承,最底层的类如果使用祖先类成员又会分不清楚哪个父亲了。
我们必须避免这种情况,避免这种情况有一种方法可以使用A::x来调用指定来自哪个继承父类。对后面一种情况,多重继承我们还有另外一种说法,就是虽然有两个父亲,但我只想一个祖先成员。这时候可以使用虚继承。
【3】同名函数 显然父类与子类是一定会出现同名函数,并且这些同名函数还具有相同的形参。那么如果子类继承了父类的同名函数,对子类对象来说,使用这个同名函数究竟是父类的函数还是子类的呢?这里面有什么规律呢?
同名函数在覆盖的前提下,对由不同层次的类生成的对象调用的都是自身的函数,而不是继承来的函数。但是我们还有一种类型自动转型概念,这个是C++这种动态语言的一个特点,比喻说 A p*; B b; p=&b;这时候虽然指针p的类型还是A,但是已经指向b 对象了,那么这种情况下同名函数是哪一个呢?令人惊讶的是这时候它还是A的output函数,这时候它就不具有多态性。这一点,如果想不通的话,可能需要借助汇编或者编译器去了解底层实现细节。
要想实现在指针或者引用的条件,同名函数使用真实对象自身的同名函数,需要将其基类的函数声明为虚函数 virtual。虚函数是指当某个函数在不同层次的类中完全相同时,在程序执行时根据不同对象,应该调用哪个层次的函数时,我们采用了一种虚函数的机制。如果这个函数不加vitual,则通过指针或者引用来调用这个函数时,这个函数只具备一态性。而如果这个函数加了virtual,则通过指针或者引用来调用这个函数时,这个函数具备多态性。从虚函数可以实现多态性的情况,如果基类内部函数全部是虚函数,并且这些虚函数都没有实现,也就是纯虚函数时,这个类一般称之为抽像类。抽象类通常是不能产生对象的,但是可以声明指针,并通过指针指向抽象类的子类,实现多态性。
转载于:https://blog.51cto.com/acreep/711371