继承

访问控制

基类的成员函数可以有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. 【Beta】Daily Scrum Meeting第二次

    1.任务进度 学号 已完成 接下去要做 502 系负责人及所负责专业的表 写出PHP测试的demo:将okHttp的请求放在非UI线程中执行 509 PHP更该用户信息:更新系负责人所负责系:删除任务 ...

  2. [LintCode] Product of Array Except Self 除本身之外的数组之积

    Given an integers array A. Define B[i] = A[0] * ... * A[i-1] * A[i+1] * ... * A[n-1], calculate B WI ...

  3. SQL存储过程基础(从基础开始学,加油!)

    Transact-SQL中的存储过程,非常类似于Java语言中的方法,它可以重复调用.当存储过程执行一次后,可以将语句缓存中,这样下次执行的时候直接使用缓存中的语句.这样就可以提高存储过程的性能. Ø ...

  4. android布局实践——模仿微信主界面

    这是目前微信6.0版本的主界面 先来分析一波: 1.(top.xml)界面头部有一个微信(6)消息提醒    一个搜索图标   一个更多的的图标+,中间还有一段空白,我们可以弄两个textView(其 ...

  5. 《微软互联网信息服务(IIS) 最佳实践》已上市,欢迎选购!

    本书内容涵盖了IIS6.0~IIS 10.0 的全部主流IIS 版本,是多年微软技术支持经验的结晶.祝您顺利排除Web 服务器的疑难杂症! 本书由微软亚太区全球技术支持中心IIS 方面的顶尖专家金鑫作 ...

  6. Ajax全面基础学习(二)

    两种配置ajax的方式 $.ajax('url',{ 配置ajax}); $.ajax({ url : 'url' 其他ajax配置}) ajax的回调函数 $.ajax('url',{ //请求成功 ...

  7. Android安全开发之ZIP文件目录遍历

    1.ZIP文件目录遍历简介 因为ZIP压缩包文件中允许存在“../”的字符串,攻击者可以利用多个“../”在解压时改变ZIP包中某个文件的存放位置,覆盖掉应用原有的文件.如果被覆盖掉的文件是动态链接s ...

  8. 【译】PHP的变量实现(给PHP开发者的PHP源码-第三部分)

    文章来自:http://www.aintnot.com/2016/02/12/phps-source-code-for-php-developers-part3-variables-ch 原文:htt ...

  9. 如何让你的JavaScript代码更加语义化

    语义化这个词在 HTML 中用的比较多,即根据内容的结构化选择合适的标签.其作用不容小觑: 赋予标签含义,让代码结构更加清晰,虽然我们可以在标签上添加 class 来标识,但这种通过属性来表示本体的形 ...

  10. 扫描仪API接入大全:Twain, WIA 或 两者不具有.[换个思路 春暖花开]

    最近做和扫描仪接入的扫描应用程序,深深感觉到了来自底层设备和WINDOWS协议的恶意.软件专业,对计算机深层次通知机制和协议,以及与之相连接的机器的接入协议和一些参数,当时并木有认真学习和了解,前一阵 ...