继承

访问控制

基类的成员函数可以有public、protected、private三种访问属性。

类的继承方式有public、protected、private三种。

公有继承

当类的继承方式为public时,基类的public成员和protected成员的访问属性在派生类中不变,而基类的private成员无法直接访问。

私有继承

当类的继承方式为private时,基类的public成员和protected成员都以私有成员的身份出现在派生类中,而基类的private成员无法直接访问。

保护继承

当类的继承方式为protected时,基类的public成员和protected成员都以保护成员的身份出现在派生类中,而基类的private成员无法直接访问。

私有继承和保护继承的比较

在直接派生类中,所有成员的访问属性是完全相同的。当派生类继续派生时,会有以下区别。

  • 如果是私有继承,那么间接子类无法访问基类的所有成员。
  • 如果是保护继承,那么基类的成员在间接子类中可能是保护的或者是私有的,由第二次继承的方式决定。

类型兼容规则

类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类来替代。

规则如下:

  • 派生类的对象可以隐式转换为基类对象。a = b;
  • 派生类的对象可以初始化为基类的引用。A& a = b;
  • 派生类的指针可以隐式转换为基类的指针。A* p = &b;

代替之后,派生类的对象就可以作为基类对象来使用了,但只能使用从基类继承过来的成员。一般通过oop的多态来提现他的作用。

派生类的构造函数和析构函数

构造函数

构造派生类的对象时,就要对基类的成员对象和新增对象进行才初始化。

派生类的构造函数一般形式:

派生类名::派生类名(参数列表) :基类名1(基类名1初始化参数表),...,成员变量1(成员变量1初始化参数表)
{
派生类构造函数的其他初始化操作;
}

派生类构造函数执行的顺序:

  1. 调用基类构造函数,调用顺序是按照他们被继承的顺序(从左向右)
  2. 对派生类新增的成员对象初始化,调用顺序按照他们在类中申明的顺序
  3. 执行派生类的构造函数体。

示例代码和运行结果:

#include<iostream>
using namespace std;
class Base1{
public:
Base1(int i){cout<<"Constructing Base1 "<<i<<endl;}
~Base1(){cout<<"Destructing Base1 "<<endl;}
};
class Base2{
public:
Base2(int j){cout<<"Constructing Base2 "<<j<<endl;}
~Base2(){cout<<"Destructing Base2 "<<endl;}
};
class Base3{
public:
Base3(){cout<<"Constructing Base3* "<<endl;}
~Base3(){cout<<"Destructing Base3 "<<endl;}
};
class Derived:public Base2,public Base1,public Base3
{
public:
Derived(int a,int b,int c,int d):Base1(a),member2(d),member1(c),Base2(b)
{ }
private:
Base1 member1;
Base2 member2;
Base3 member3;
};
int main()
{
Derived obj(1,2,3,4);
return 0;
}
运行结果:
Constructing Base2 2
Constructing Base1 1
Constructing Base3*
Constructing Base1 3
Constructing Base2 4
Constructing Base3*
Destructing Base3
Destructing Base2
Destructing Base1
Destructing Base3
Destructing Base1
Destructing Base2

Derived(int a,int b,int c,int d):Base1(a),member2(d),member1(c),Base2(b){ }

Base3只有默认的构造函数,不需要传递参数,所以基类Base3和Base3的对象在派生类中的初始化列表中不必列出。注意运行的顺序!

注:有关于构造函数初始化列表的相关知识可以参见http://www.cnblogs.com/graphics/archive/2010/07/04/1770900.html

拷贝构造函数

派生类如果没有编写拷贝构造函数,系统会生成一个隐含的拷贝构造函数,这个函数会自动调用基类的拷贝构造函数,然后对派生类新增的成员对象一一复制。

如果要编写,需要为基类的拷贝构造函数传递参数。

Derived::Derived(const Derived& v):Base(V){...}

析构函数

派生类的析构函数只要负责派生类中新增的非对象成员进行清理。系统会自动调用基类及成员对象的析构函数。但执行顺序与构造函数的顺序相反。

  1. 执行析构函数函数体。
  2. 对派生类新增的类类型成员对象进行清理。
  3. 对基类继承过来的成员进行清理。

代码见构造函数执行结果。

派生类成员的标识与访问

作用域分辨符

作用域分辨符,就是“::”,他可以限定要访问的成员所在的类的名称。

类名::成员名
类名::成员名(参数表)

规则:

  1. 如果派生类中申明了与基类成员函数同名的新函数,即使函数的参数列表不同,从基类继承的同名函数的所有重载形式也都会被隐藏。
  2. 如果某派生类的多个基类拥有同名的成员,同时,派生类又新增这样的同名的成员,在这种情况下,派生类成员将隐藏所有基类的成员。

多继承同名隐藏实例1

class Base1{
public:
int var;
void fun(){cout<<"Member of Base1"<<endl;}
};
class Base2{
public:
int var;
void fun(){cout<<"Member of Base2"<<endl;}
};
class Derived:public Base1,public Base2{
public:
int var;
void fun(){cout<<"Member of Derived"<<endl;}
};
int main()
{
Derived d;
Derived* p = &d;
d.var = 1;//访问Derived类的成员
d.fun();//访问Derived类的成员 d.Base1::var = 2;//作用域分辨符标识
d.Base1::fun();//访问Base1基类的成员 p->Base2::var = 3;
p->Base2::fun();
return 0;
}

如果派生类中没有申明同名的成员,那么“对象名.成员名”就无法访问到任何成员。

如果某个派生类的部分或全部直接基类是从另一个共同的基类派生而来,在这些直接基类中,从上一级基类继承来的成员就拥有相同的名称,因此派生类中会产生同名的现象,对这种类型的同名成员也要使用作用域分辨符来唯一标识,而且必须用直接基类来限定。

#include<iostream>
using namespace std;
class Base0{
public:
int var0;
void fun0(){cout<<"Member of Base1"<<endl;}
}; class Base1:public Base0{
public:
int var1;
}; class Base2:public Base0{
public:
int var2;
}; class Derived:public Base1,public Base2{
public:
int var;
void fun(){cout<<"Member of Derived"<<endl;}
}; int main()
{
Derived d;
d.Base1::var0 = 2;
d.Base1::fun0(); d.Base2::var0 = 3;
d.Base2::fun0();
return 0;
}

在这种情况下,派生类对象在内存中就同时拥有成员var0的两份同名副本。然而其实我们只需要一份就够了。此时可以引入虚基类。

虚基类

将共同基类设为虚基类,这时从不同路径继承过来的同名数据成员在内存中就只有一个副本,同一个函数名也只有一个映射。

class Base0{
public:
Base0(int var):var0(var){}
int var0;
void fun0(){cout<<"Member of Base1"<<endl;}
}; class Base1:virtual public Base0{
public:
Base1(int var):Base0(var){}
int var1;
}; class Base2:virtual public Base0{
public:
Base2(int var):Base0(var){}
int var2;
}; class Derived:public Base1,public Base2{
public:
Derived(int var):Base0(var),Base1(var),Base2(var){}
int var;
void fun(){cout<<"Member of Derived"<<endl;}
};

多态

操作符 重载

牵扯的内容较多,单独成篇。

虚函数

虚函数必须是非静态的成员函数,它经过派生后,就可以实现运行过程的多态。

一般虚函数成员

虚函数的申明语法:

virtual 函数类型 函数名(形参表)

虚函数只能在类的定义中的函数原型声明中,而不能在成员函数实现的时候。

运行时多态需要满足的三个条件:

  1. 满足赋值兼容规则。
  2. 需要声明虚函数。
  3. 由成员函数来调用或者通过指针、引用来访问虚函数。

虚函数需要动态绑定,一般不声明会inline函数

在派生类中的函数满足:

  1. 与基类虚函数同名。
  2. 与基类虚函数参数个数和参数类型相同。
  3. 返回值相同。

这时,派生类的虚函数会覆盖基类的虚函数,同时,派生类的虚函数还会隐藏基类中同名函数的所有其他重载形式。

用基类指针如果要调用被覆盖的成员函数,可以用作用域来限定。

基类的构造函数和析构函数调用虚函数时,不会调用派生类的虚函数。这是由于基类在构造时或者在析构事,对象已经不再是派生类的对象了。

虚函数的默认形参值是静态绑定的,也就是说默认形参值只能来自基类。

虚析构函数

析构函数设置成虚函数后,在使用指针引用时可以动态绑定,保证基类指针可以调用适当的析构函数对不同的对象进行清理。

class Base{
public:
~Base();
};
Base::~Base(){
cout<<"base destructor"<<endl;
} class Derived:public Base
{
public:
Derived();
~Derived();
private:
int *p;
};
Derived::Derived(){
p = new int(0);
} Derived::~Derived(){
cout<<"Derived Destructor"<<endl;
delete p;
} void fun(Base* b){
delete b;
} int main()
{
Base* b = new Derived();
fun(b);
return 0;
}

输出为:

base destructor

说明通过基类指针删除派生类的对象时是调用基类的析构函数。派生类的析构函数没有被执行,所以派生类中的动态分配的内存没有被释放,造成内存泄露。

此时应该使用虚析构函数:

class Base{
public:
virtual ~Base();
};

输出为:

Derived Destructor`
base destructor

纯虚函数和抽象类

纯虚函数

纯虚函数是一个在基类声明的虚函数。

virtual 函数类型 函数名(形参表) = 0

声明纯虚函数之后,基类中就可以不再给出函数的实现部分,具体的函数体由派生类给出。

细节:

基类中仍然允许对纯虚函数给出实现,即使给出,也必需由派生类覆盖,否则无法实例化。如果把析构函数声明为纯虚函数,必须要给出函体,因为派生类的析构函数在执行完后要调用基类的纯虚函数。

抽象类

带有纯虚函数的类是抽象类。

抽象类不能实例化。

C++继承和多态的更多相关文章

  1. Objective-C中的继承和多态

    面向对象编程之所以成为主流的编程思想和他的继承和多态是分不开的,只要是面向对象语言都支持继承和多态,当然不同的OOP语言之间都有其特点.OC中和Java类似,不支持多重继承,但OOP语言C++就支持多 ...

  2. java中抽象、分装、继承和多态的理解

    1.抽象.封装装.继承和多态是java面向对象编程的几大特点. 抽象:所谓抽象就是对某件事务,我们忽略我们不关心不需要的部分,提取我们想要的属性和行为,并且以代码的形式提现出来:例如我们需要对一个学生 ...

  3. [转] JS中简单的继承与多态

    这里讲了一个最最最简单的JS中基于原型链的继承和多态. 先看一下以下这段代码的实现(A是“父类”,B是“子类”): var A = function(){ this.value = 'a'; this ...

  4. 网络电视精灵~分析~~~~~~简单工厂模式,继承和多态,解析XML文档,视频项目

    小总结: 所用技术: 01.C/S架构,数据存储在XML文件中 02.简单工厂模式 03.继承和多态 04.解析XML文档技术 05.深入剖析内存中数据的走向 06.TreeView控件的使用 核心: ...

  5. OC的封装、继承与多态

    面向对象有三大特征:封装.继承和多态. 一.封装 封装是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问.简而言之,信息隐藏,隐 ...

  6. 2、C#面向对象:封装、继承、多态、String、集合、文件(上)

    面向对象封装 一.面向对象概念 面向过程:面向的是完成一件事情的过程,强调的是完成这件事情的动作. 面向对象:找个对象帮你完成这件事情. 二.面向对象封装 把方法进行封装,隐藏实现细节,外部直接调用. ...

  7. Java学习笔记 07 接口、继承与多态

    一.类的继承 继承的好处 >>使整个程序架构具有一定的弹性,在程序中复用一些已经定义完善的类不仅可以减少软件开发周期,也可以提高软件的可维护性和可扩展性 继承的基本思想 >>基 ...

  8. JavaScript 面向对象程序设计(下)——继承与多态 【转】

    JavaScript 面向对象程序设计(下)--继承与多态 前面我们讨论了如何在 JavaScript 语言中实现对私有实例成员.公有实例成员.私有静态成员.公有静态成员和静态类的封装.这次我们来讨论 ...

  9. Java继承和多态实例

    我们知道面向对象的三大特性是封装.继承和多态.然而我们有时候总是搞不清楚这些概念.下面对这些概念进行整理, 为以后面向抽象的编程打下坚实的基础. 封装的概念还是很容易理解的.如果你会定义类,那么相信你 ...

  10. python基础——继承和多态

    python基础——继承和多态 在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类.父类或超类 ...

随机推荐

  1. 谈 CSS 模块化

    以前看过模块化的相关资料以及解释,对模块化有了一个表皮的了解,自己也做了一些相关的实践,由于接触到的项目交小,所以也没能更好的去体现和理解模块化,但总体还是有那么一些感悟,但是如果要说怎么才能算是好的 ...

  2. [LintCode] Merge Two Sorted Lists 混合插入有序链表

    Merge two sorted (ascending) linked lists and return it as a new sorted list. The new sorted list sh ...

  3. 关于 iframe 在ie11 height:100% 无效的巨坑

    好的,今天公司分配了个解决ie中的bug的任务,其中,有一个就是iframe 的高度 100% 没有生效的问题: 一开始,由于我真的没有怎么去了解过iframe这个货,所以,网上各种搜索一大堆关于这货 ...

  4. viso

  5. 《寒江独钓_Windows内核安全编程》中修改类驱动分发函数

    最近在阅读<寒江独钓_Windows内核安全编程>一书的过程中,发现修改类驱动分发函数这一技术点,书中只给出了具体思路和部分代码,没有完整的例子. 按照作者的思路和代码,将例子补充完整,发 ...

  6. Mvc form提交

    在项目开发中,我们离不开表单提交,本篇主要记录mvc的Ajax.BeginForm提交方式. 需要用到的js     @Url.Script("~/Content/Scripts/jquer ...

  7. int (*p)[10] 与*p[10]的区别

    定义指向具有10个整型元素的一维数组的指针格式为:int (*p)[10] ,而起初我一直以为int (*p)[10] 是定义二维数组的方法 ][],(*p)[]; p=a; /*有了这个定义后,指针 ...

  8. AWS 免费套餐

    AWS 免费套餐 转载自:https://aws.amazon.com/cn/free/?sc_channel=PS&sc_campaign=acquisition_CN&sc_pub ...

  9. Outlook HTML渲染引擎

    OutLook始终不离不弃 是不是很讨厌为Email代码兼容Outlook? 太遗憾了!虽然光都有尽头,但Outlook始终存在. 为了应付Email的怪癖,我们花了很多时间测试,确保我们搞定了所有O ...

  10. WCF 的 Service Instance模式和并发处理

    WCF 的 Service Instance(实例)有三种模式 PerCall:每一次调用都创建一个实例,每一次调用结束后回收实例.此模式完全无状态. PerSession:调用者打开Channel时 ...