C++ 三大特性 封装,继承,多态
封装
定义:封装就是将抽象得到的数据和行为相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成类,其中数据和函数都是类的成员,目的在于将对象的使用者和设计者分开,
以提高软件的可维护性和可修改性
特性:1. 结合性,即是将属性和方法结合 2. 信息隐蔽性,利用接口机制隐蔽内部实现细节,只留下接口给外界调用 3. 实现代码重用
继承
定义:继承就是新类从已有类那里得到已有的特性。 类的派生指的是从已有类产生新类的过程。原有的类成为基类或父类,产生的新类称为派生类或子类,
子类继承基类后,可以创建子类对象来调用基类函数,变量等
单一继承:继承一个父类,这种继承称为单一继承,一般情况尽量使用单一继承,使用多重继承容易造成混乱易出问题
多重继承:继承多个父类,类与类之间要用逗号隔开,类名之前要有继承权限,假使两个或两个基类都有某变量或函数,在子类中调用时需要加类名限定符如c.a::i = 1;
菱形继承:多重继承掺杂隔代继承1-n-1模式,此时需要用到虚继承,例如 B,C虚拟继承于A,D再多重继承B,C,否则会出错
继承权限:继承方式规定了如何访问继承的基类的成员。继承方式指定了派生类成员以及类外对象对于从基类继承来的成员的访问权限
继承权限:子类继承基类除构造和析构函数以外的所有成员
继承可以扩展已存在的代码,目的也是为了代码重用
继承也分为接口继承和实现继承:
普通成员函数的接口总是会被继承: 子类继承一份接口和一份强制实现
普通虚函数被子类重写 : 子类继承一份接口和一份缺省实现
纯虚函数只能被子类继承接口 : 子类继承一份接口,没有继承实现
访问权限图如下:
为了便于理解,伪代码如下,注意这个例子编译是不过的,仅是为了可以更简洁的说明继承权限的作用:
class Animal //父类
{
public:
void eat(){
cout<<"animal eat"<<endl;
}
protected:
void sleep(){
cout<<"animal sleep"<<endl;
}
private:
void breathe(){
cout<<"animal breathe"<<endl;
}
};
class Fish:public Animal //子类
{
public:
void test() {
eat(); //此时eat()的访问权限为public,在类内部能够访问
sleep(); //此时sleep()的访问权限为protected,在类内部能够访问
breathe(); //此时breathe()的访问权限为no access,在类内部不能够访问
}
};
int main(void) {
Fish f;
f.eat(); //此时eat()的访问权限为public,在类外部能够访问
f.sleep(); //此时sleep()的访问权限为protected,在类外部不能够访问
f.breathe() //此时breathe()的访问权限为no access,在类外部不能够访问
}
多态
定义:可以简单概括为“一个接口,多种方法”,即用的是同一个接口,但是效果各不相同,多态有两种形式的多态,一种是静态多态,一种是动态多态
动态多态: 是指在程序运行时才能确定函数和实现的链接,此时才能确定调用哪个函数,父类指针或者引用能够指向子类对象,调用子类的函数,所以在编译时是无法确定调用哪个函数
使用时在父类中写一个虚函数,在子类中分别重写,用这个父类指针调用这个虚函数,它实际上会调用各自子类重写的虚函数。
运行期多态的设计思想要归结到类继承体系的设计上去。对于有相关功能的对象集合,我们总希望能够抽象出它们共有的功能集合,在基类中将这些功能声明为虚接口(虚函数),
然后由子类继承基类去重写这些虚接口,以实现子类特有的具体功能。
运行期多态的实现依赖于虚函数机制。当某个类声明了虚函数时,编译器将为该类对象安插一个虚函数表指针,并为该类设置一张唯一的虚函数表,虚函数表中存放的是该类虚函数地址。
运行期间通过虚函数表指针与虚函数表去确定该类虚函数的真正实现。
优点: OO设计重要的特性,对客观世界直觉认识; 能够处理同一个继承体系下的异质类集合
vector<Animal*>anims;
Animal * anim1 = new Dog;
Animal * anim2 = new Cat;
//处理异质类集合
anims.push_back(anim1);
anims.push_back(anim2);
缺点:运行期间进行虚函数绑定,提高了程序运行开销;庞大的类继承层次,对接口的修改易影响类继承层次;由于虚函数在运行期才绑定,所以编译器无法对虚函数进行优化
虚函数
定义:用virtual关键字修饰的函数,本质:由虚指针和虚表控制,虚指针指向虚表中的某个函数入口地址,就实现了多态,作用:实现了多态,虚函数可以被子类重写,虚函数地址存储在虚表中
虚表:虚表中主要是一个类的虚函数的地址表,这张表解决了继承,覆盖的问题,保证其真实反应实际的函数,当我们用父类指针来指向一个子类对象的时候,虚表指明了实际所应调用的函数
基类有一个虚表,可以被子类继承,(当类中有虚函数时该类才会有虚表,该类的对象才有虚指针,子类继承时也会继承基类的虚表),子类如果重写了基类的某虚函数,那么子类继承于基类的虚表中该虚函数的地址也会相应改变,指向子类
自身的该虚函数实现,如果子类有自己的虚函数,那么子类的虚表中就会增加该项,编译器为每个类对象定义了一个虚指针,来定位虚表,所以虽然是父类指针指向子类对象,但因为此时子类
重写了该虚函数,该虚函数地址在子类虚表中的地址已经被改变了,所以它实际调用的是子类的重写后的函数,正是由于每个对象调用的虚函数都是通过虚表指针来索引的,也就决定了虚表指针的
正确初始化是非常重要的,即是说,在虚表指针没有正确初始化之前,我们是不能调用虚函数的,因为生成一个对象是构造函数的工作,所以设置虚指针也是构造函数的工作,编译器在构造函数
的开头部分秘密插入能初始化虚指针的代码, 在构造函数中进行虚表的创建和虚指针的初始化
一但虚指针被初始化为指向相应的虚表,对象就“知道”它自己是什么类型,但只有当虚函数被调用时这种自我认知才有用
类中若没有虚函数,类对象的大小正好是数据成员的大小,包含有一个或者多个虚函数的类对象。编译器会向里面插入一个虚指针,指向虚表,这些都是编译器为我们做的,我们完全不必关心
这些,所有有虚函数的类对象的大小是数据成员的大小加一个虚指针的大小;对于虚继承,若子类也有自己的虚函数,则它本身需要有一个虚指针,指向自己的虚表,另外子类继承基类时,
首先要通过加入一个虚指针来指向基类,因此可能会有两个或多个虚指针(多重继承会多个),其他情况一般是一个虚指针,一张虚表
每一个带有virtual函数的类都有一个相应的虚表,当对象调用某一virtual函数时,实际被调用的函数取决于该对象的虚指针所指向的那个虚表-编译器在其中寻找适当的函数指针。
效率漏洞:我们必须明白,编译器正在插入隐藏代码到我们的构造函数中,这些隐藏代码不仅必须初始化虚指针,而且还必须检查this的值(以免operator new返回零)和调用基类构造函数。放在一起,
这些代码可以影响我们认为是一个小内联函数的调用,特别是,构造函数的规模会抵消函数调用代价的减少,如果做大量的内联函数调用,代码长度就会增长,而在速度上没有任何好处,
当然,也许不会立即把所有这些小构造函数都变成非内联,因为它们更容易写为内联构造函数,但是,当我们正在调整我们的代码时,请务必去掉这些内联构造函数
虚函数使用:将函数声明为虚函数会降低效率,一般函数在编译期其相对地址是确定的,编译器可以直接生成imp/invoke指令,如果是虚函数,那么函数的地址是动态的,譬如取到的地址在eax寄存
器里,则在call eax之后的那些已经被预取到流水线的所有指令都将失效, 流水线越长,那么一次分支预测失败的代价越大,建议若不打算让某类成为基类,那么类中最好不要出现虚函数,
纯虚函数:含有至少一个纯虚函数的类叫抽象类,因为抽象类含有纯虚函数,所以其虚表是不健全的,在虚表不健全的情况下是不能实例化对象的,子类继承抽象基类后必须重写基类的所有纯虚函数
否则子类仍为纯虚函数子类将抽象基类的纯虚函数全部重写后会将虚表完善,此时子类才能实例化对象,纯虚函数只声明不定义,形如 virtual void print() = 0
静态多态:是在编译期就把函数链接起来,此时即可确定调用哪个函数或模板,静态多态是由模板和重载实现的,在宏多态中,是通过定义变量,编译时直接把变量替换,实现宏多态
优点: 带来了泛型编程的概念,使得C++拥有泛型编程与STL这样的武器; 在编译期完成多态,提高运行期效率; 具有很强的适配性和松耦合性,(耦合性指的是两个功能模块之间的依赖关系)
缺点: 程序可读性降低,代码调试带来困难;无法实现模板的分离编译,当工程很大时,编译时间不可小觑 ;无法处理异质对象集合
调用基类指针创建子类对象,那么基类应该有虚析构函数,因为如果基类没有虚析构函数,那么在删除这个子类对象的时候会调用错误的析构函数而导致删除失败产生不明确行为,
int main() {
Base *p = new Derive(); //调用基类指针创建子类对象,那么基类应有虚析构函数,不然当删除的时候会调用错误的析构函数而导致删除失败产生不明确行为,
delete p; //删除子类对象时,如果基类有虚析构函数,那么delete时会先调用子类的析构函数,然后再调用基类的析构函数,成功删除
return 0; //如果基类没有虚析构函数,那么就只会调用父类的析构函数,只删除了对象内的父类部分,造成一个局部销毁,可能导致资源泄露
} //注:只有当此类希望成为 基类时才会打算声明一个虚析构函数,否则不必要给此类声明一个虚函数
C++三大特性 封装 继承 多态的更多相关文章
-
Java三大特性(封装,继承,多态)
Java中有三大特性,分别是封装继承多态,其理念十分抽象,并且是层层深入式的. 一.封装 概念:封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别:将抽象得到的数据 ...
-
深入理解Java面向对象三大特性 封装 继承 多态
1.封装 封装的定义: 首先是抽象,把事物抽象成一个类,其次才是封装,将事物拥有的属性和动作隐藏起来,只保留特定的方法与外界联系 为什么需要封装: 封装符合面向对象设计原则的第一条:单一性原则,一个类 ...
-
OOP三大核心封装继承多态
OOP支柱 3 个核心:封装 继承 多态 封装就是将实现细节隐藏起来,也起到了数据保护的作用. 继承就是基于已有类来创建新类可以继承基类的核心功能. 在继承中 另外一种代码重用是:包含/委托,这种重用 ...
-
JAVA的三大特征 封装继承多态- 简单总结
简单总结一下 封装-即从很多类的抽取相同的代码 写在一个类里. 好处是 代码的重用,安全. 继承-减少代码的书写. 其好处也是 代码的重用. 多态- 把不同的子类对象都当作父类来看,可以屏蔽不同子类对 ...
-
初步理解Java的三大特性——封装、继承和多态
声明:整理自网络,如有雷同,请联系博主处理 一.封装 封装从字面上来理解就是包装的意思,专业点就是信息隐藏,是指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被 ...
-
转 OC温故:类的三大特性(封装,继承,多态)
原文标题:OC学习篇之---类的三大特性(封装,继承,多态) 我们都知道,面向对象程序设计中的类有三大特性:继承,封装,多态,这个也是介绍类的时候,必须提到的话题,那么今天就来看一下OC中类的三大特性 ...
-
Python 入门 之 面向对象的三大特性(封装 / 继承 / 多态)
Python 入门 之 面向对象的三大特性(封装 / 继承 / 多态) 1.面向对象的三大特性: (1)继承 继承是一种创建新类的方式,在Python中,新建的类可以继承一个或多个父类,父类又可以 ...
-
OC学习篇之---类的三大特性(封装,继承,多态)
之前的一片文章介绍了OC中类的初始化方法和点语法的使用:http://blog.csdn.net/jiangwei0910410003/article/details/41683873,今天来继续学习 ...
-
Python面向对象之:三大特性:继承,封装,多态以及类的约束
前言: python面向对象的三大特性:继承,封装,多态. 1. 封装: 把很多数据封装到⼀个对象中. 把固定功能的代码封装到⼀个代码块, 函数, 对象, 打包成模块. 这都属于封装的思想. 具体的情 ...
随机推荐
-
利用CocoaPods,在项目中导入AFNetworking类库
场景1:利用CocoaPods,在项目中导入AFNetworking类库 AFNetworking类库在GitHub地址是:https://github.com/AFNetworking/AFNetw ...
-
解决Android Studio 和 Android SDK Manager 无法在线更新的问题.
升级时提示 Connection failed. Please check your network connection and try again 修改安装目录下bin\studio.exe.vm ...
-
Android的两种上下文的区别
1.Activity.this,Activity是间接继承自Context 2.getApplicationContext()返回来的就是Context 3.getBaseContext()返回的也是 ...
-
codevs3304水果姐逛水果街
/* 线段树开到*4 *4 *4 *4 ! 维护 4个值 区间最大值 区间最小值 从左往右跑最大收益 从右往左跑最大收益 */ #include<iostream> #include< ...
-
Spring+Shiro的踩坑
今天想给某个Service的某些方法添加Cache,这个记为A,用的springboot,照常在方法上加上Cacheable注解,测试缓存生效,搞定.然后再在第二个Service,记为B,添加Cach ...
-
端口转发 Port Forwarding (一)
0x00First 最近发现一些好用的端口转发工具和技巧,计划认真梳理一下 SSH.NC.LCX.EW.FRP 0x01 SSH隧道端口转发 目前利用SSH隧道(SSH tunneling)进行端口转 ...
-
Java开发面试题整理(2019春招)
一.Java基础部分 1. HashMap和Hashtable各有什么特点,它们有什么区别?(必背题,超级重要) HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们 ...
-
自定义 Git - 配置 Git
用git config配置 Git,要做的第一件事就是设置名字和邮箱地址: $ git config --global user.name "John Doe" $ git con ...
-
cloudera项目源代码
以下项目都需要安装git,Linux的git还是比较容易安装的,windows的git安装参考项目区域:软件版本控制-在Windows中使用Git视频介绍 git相关软件安装参考win7安装 git软 ...
-
模拟源码深入理解Vue数据驱动原理(2)
我们说到如果监听的属性是个对象呢?那么这个对象中的其他属性岂不就是监听不了了吗?如下: 倘若user中的name.age属性变化,如何知道它们变化了呢?今儿,就来解决这一问题. 通过走读Vue源码,发 ...