深度探索C++对象模型之第一章:关于对象之对象的差异
一、三种程序设计范式:
C++程序设计模型支持三种程序设计范式(programming paradiams).
- 程序模型(procedural model)
char boy[] = "ccpang";
char *p_son; p_son = new char[strlen(boy) + ];
strcpy(p_son,boy); if(!strcmp(p_son,boy))
take_to_disneyland(boy);
- 抽象数据模型(abstract data type model)
此模型的抽象是和一组表达式(public接口)一起提供,那时其运算定义仍然隐而未明的。
String girl = "Anna"
String daughter; //String ::operator();
daughter = girl; //String::operator == ();
if(girl ==daughter)
take_to_disneyland(girl);
- 面向对象模型(object-orlented model)
此模型中有一些彼此相关的类型,通过一个抽象的基类(用以提供共同接口)被封装起来。Library_materials class就是一个例子,真正的子类例如Book、Video、等均可以从那里派生而来。
void check_in (Library_materials *pmat)
{
if(pmat ->late())
pmat->fine();
pmat->check_in(); if(Lender *plend = pmat->reserved())
pmat->notify(plend);
}
只以一种程序设计范式写代码,有助于整体行为的良好稳固。如果混合了不同的范式,就可能会带来让人吃惊的后果。常出现的问题如下所示:
以一个基类的具体实例来完成某种多态(polymorphism)情况是时:
Library_materials thing1; //class Book : public Library_materials{....};
Book book; //thing1不是一个book,book被裁减了(sliced)
thing1 = book; //调用赋值运算符,只对基类部分进行操作,派生类的其他部分将被忽略。 //调用的是Library_materials::check_in()
thing1.check_in(); //通过基类的指针或引用来完成多态局面:
Library_materials &thing2 = book; //基类的指针或引用可以指向派生类 //现在使用的是Book::check_in()
thing2.check_in();
虽然你可以直接或间接(赋值或引用)处理继承体系中的一个基类对象,但是只有通过指针或引用的间接处理,才支持面向对象程序设计所需的多态性质。thing2的定义和运用符号面向对象编程的良好习惯。而thing1的定义和运用则不是面向对象的习惯,它反映的是一个抽象数据类型范式的良好习惯。thing1的行为是好是坏,取决于程序员的意图。
在面向对象编程范式中,需要处理的一个未知实例,虽然它的类型有所界定,但是却存在无数种可能,它的类型受限于其继承体系,然而该体系理论上没有深度和广度的限制。原则上,被指定的对象的真实类型在每一个特定执行点之前,是无法解析的。在C++中,只有通过指针和引用操作才能完成。相反,在抽象数据类型范式中,程序员处理的是一个拥有固定而单一类型的实例,它在编译时期就已经完全定义好了。例如下面:
//描述对象:不确定类型 //基类的一个指针或引用,可能指向一个基类对象或者基类对象的派生类(子类型)
Librar_materials *px = retrieve_some_material();
Librar_materials &rx = *px; //dx是一个基类对象,它的值是px所指向的值。赋值运算符。
Librar_materials dx = *px;
值得说明的是:这样的行为虽然或许未如你所预期,却是良好的行为。虽然对于对象的多态操作,要求此对象必须可以由一个指针或引用来存取,但是有指针和引用并不是多态。
在C++中,多态只存在于公有类的继承体系中(public class),px只能指向某个类型的基类对象,或是根据public继承关系派生而来的一个子类型。非公有的派生行为以及类型为void*的指针可以说是多态的,但是它们并没有被语言所明确的支持,也就是它们必须由程序员通过显式的转换操作来管理。
二、C++支持多态的方法:
- 隐式转换操作
//例如将一个派生类指针转换为一个指向其基类型的指针
shape *ps = new circle();
- 由虚函数机制
ps->rotate();
- 由dynamic_cast和typeid运算符
if(circle *pc = dynameic_cast<circle*>() ps)
多态的主要用途是经由一个共同的接口来影响类型的封装,这个接口通常被定义在一个抽象的基类中。
void rotate(X datum,cosnt X *pointer,cosnt X &reference)
{
//在执行期之前,无法决定到底调用哪一个rotate()实例
(*pointer).rotate();
reference.rotate(); //
//datum.rotate(); } main(){
Z z; //Z是X的一个子类型
rotate(z,&z,z);
return ;
}
在上例中,经pointer 和reference完成的两个函数调用操作会被动态完成,而经过datum完成的函数调用操作则不经过virtual机制。
三、表示一个类对象需要多少内存:
- 非静态数据成员的总和大小
- 加上由于alignment的需求而填补上去的空间。(alignment就是将数值调整到某数的倍数)
- 加上为了支持virtual而由内部产生的任何额外负担(overhead)
四、指针的类型:
一个指向ZooAnimal的指针和一个指向整数的指针和一个指向模板数组的指针有什么不同呢?
ZooAnimal *px;
int *pi;
Array<String> *pta;
答案是没有什么不同。指向不同类型的指针间的差异,既不在其指针表示法不同,也不在其内容(代表一个地址)不同,而是在其所寻址出来的对象不同。也就是说,指针类型会教导编译器如何解释某个特定的地址中的内存内容及其大小。
举个例子:以下是一个ZooAnimal的声明:
class ZooAnimal {
public:
ZooAnimal();
virtual ~ZooAnimal(); //。。。
virtual void rotate(): protected:
int loc;
String name;
}; ZooAnimal za("pig");
ZooAnimal *pza = &za;
ZooAnimal在内存中的布局:
那么一个指向地址1000而类型为void*的指针,将涵盖怎样的地址空间呢?答案是不知道,这就是为什么一个void*指针只能够持有一个地址而不能通过它操作所指对象。
综上得到,转换只是一种编译器指令,大部分情况下它并不改变一个指针所含的真正地址,它只会影响被指出的内存的大小和其内容。
五、加上多态之后:
如下所示一个Bear类,它继承自ZooAnimal:
class Bear :public ZooAnimal{
public;
Bear();
~Bear();
void rotate():
virtual void dance(); protected:
enum Dances{...};
Dances dances_know;
int cell_blook: }; Bear b("panda");
Bear *Pb = &b;
Bear &rb = *pb;
不管是指针(pointer)或引用(reference)都只需要一个word(在32位机器上是4-bytes)的空间。Beard对象需要24个bytes,也就是ZooAnimal的16个bytes再加上Bear所带来的8bytes.如下图所示:
如果把一个Bear对象放在地址1000处,那么一个Bear指针和一个ZooAnimal指针有什么不同? 这就是将一个派生类赋给基类的指针
Bear b;
ZooAnimal *Pz = &b; //pz是一个指向派生类Bear的基类指针
Bear *pb = &b; //pb是一个指向Bear的Bear指针
答案是它们都会指向Bear对象的第一个字节,但是pb所涵盖的地址包含整个Bear对象,而pz所涵盖的地址只包含Bear对象中的ZooAnimal实例。(将Bear的部分截取掉了)
除了ZooAnimal实例中出现的成员,你不能用pz来直接处理Bear的任何members。唯一例外是通过virtual机制。
pz ->cell_block;//这是不合法的,虽然我们知道pz指向一个Bear对象,但是cell_block不是ZooAnimal的一个成员; //经过一个显示转换操作就可以了
(static_cast<Bear*>(pz))->cell_block; //下面这样更好
if(Bear *pb2 = dynamic_cast<Bear*>(pz))
pb2->cell_block;
当我们使用pz->rotate();时,pz的类型将在编译时期决定以下两点:
- 固定的可用接口,也就是说,pz只能调用ZooAnimal的public接口
- 该接口的access level。
在每一个执行点,pz所指向的对象类型可以决定rotate()所调用的实例。类型信息的封装并不是维护在pz上,而是维护在link之中,这个link存在于对象的vptr和vptr所指的虚函数表之间。
六、面向对象程序设计并不支持对对象的直接处理:
{
ZooAnimal za;
ZooAnimal *pza; Bear b;
Panda *pp = new Panda; pza = &b;
}
其内存布局可能如下图所示:
注意:new开辟的是Heap Memory(堆内存),其他都是在栈上开辟的。
将za或b的地址或pp所指的内容(也是地址),指定给pza,显然不是问题。一个指针或引用之所以支持多态,是因为它们并不引发内存中任何“与类型有关的内存委托操作(type-dependent commitment)”,会受到改变的只有它们所指向的内存的“大小和内容解释方式而已”
然而如果将一个Bear对象附给一个ZooAnimal,会违法其定义中受契约保护的“资源需求量”。将一个Bear对象指定给za,则会溢出它所分配得到的内存。(za = Bear)
而当一个基类对象被初始化为一个派生类对象时(会引起切割),派生类对象就会被切割以塞入较小的基类内存中,派生类型将没有留下任何痕迹,多态将不再呈现。
总而言之,多态允许你继一个抽象的public接口之后,封装相关的类型。但是要付出的代价就是额外的间接性——不论是在“内存的获得”,还是类型的决断上。C++通过类的指针和引用来支持多态,这种程序设计风格就被称为面向对象。
C++也支持具体的抽象数据类型风格,如今被称为基于对象(object-based(OB)),例如String,一种非多态的数据类型。String class可以展示封装的非多态形式:它提供一个public接口和一个private实现品:包括数据和算法,但是不支持类型的扩充。一个OB设计可能比OO设计速度更快且空间更紧凑:速度快是因为所有的函数调用都是编译时期完成的,对象构建起来并不需要设置virtual机制;空间紧凑是因为每一个类对象不需要负担传统上为了支持virtual机制而需要的额外负担,不过OB没有弹性。
深度探索C++对象模型之第一章:关于对象之对象的差异的更多相关文章
- 深度探索C++对象模型之第一章:关于对象之C++对象模型
一.C和C++对比: C语言的Point3d: 数据成员定义在结构体之内,存在一组各个以功能为导向的函数中,共同处理外部的数据. typedef struct point3d { float x; f ...
- 深度探索C++对象模型之第一章:关于对象之关键词所引起的差异
————如果不是为了努力维护与C之间的兼容性,C++远比现在简单的多. 如果一个程序员渴望学习C++,但是他却发现书中没有熟悉的struct,一定会苦恼,将这个主题包含到C++里,可以提供语言转移时的 ...
- 《深度探索C++对象模型》第一章 | 关于对象
C++对象模式 非静态数据成员放置在每个类对象内,静态数据成员则被放置在所有类对象之外.静态和非静态的成员函数也被放置在所有类对象之外.每个类产生一堆指向虚函数的指针,放在虚表(vtbl)中.每个类对 ...
- 深度探索C++对象模型之第二章:构造函数语意学之Copy constructor的构造操作
C++ Standard将copy constructor分为trivial 和nontrivial两种:只有nontrivial的实例才会被合成于程序之中.决定一个copy constructor是 ...
- 《深度探索C++对象模型》第二章 | 构造函数语意学
默认构造函数的构建操作 默认构造函数在需要的时候被编译器合成出来.这里"在需要的时候"指的是编译器需要的时候. 带有默认构造函数的成员对象 如果一个类没有任何构造函数,但是它包含一 ...
- 深度探索C++对象模型第四章:函数语义学
C++有三种类型的成员函数:static/nonstatic/virtual 一.成员的各种调用方式 C with class 只支持非静态成员函数(Nonstatic member function ...
- 深度探索C++对象模型之第二章:构造函数语意学之成员初始值列表
当我们需要设置class member的初值时,要么是经过member initialization list ,要么在construcotr内. 一.先讨论必须使用member initializa ...
- 深度探索C++对象模型之第二章:构造函数语意学之Default constructor的构造操作
C++新手一般由两个常见的误解: 如果任何class没有定义默认构造函数(default constructor),编译器就会合成一个来. 编译器合成的的default constructor会显示的 ...
- 拾遗与填坑《深度探索C++对象模型》3.3节
<深度探索C++对象模型>是一本好书,该书作者也是<C++ Primer>的作者,一位绝对的C++大师.诚然该书中也有多多少少的错误一直为人所诟病,但这仍然不妨碍称其为一本好书 ...
随机推荐
- JQuery中内容操作函数、validation表单校验
JQuery:内容体拼接(可以直接拼接元素节点和内容节点) JQuery实现: 方案1:A.append(B); == B.appendTo(A);A的后面拼接B 方案2: A.prepend(B); ...
- webstorm vue eslint 自动修正配置
原文:https://medium.com/@netczuk/even-faster-code-formatting-using-eslint-22b80d061461 https://stackov ...
- Spring MVC源码分析(二):SpringMVC的DispatcherServlet的设计与实现
概述 DispatcherServlet是SpringMVC的一个前端控制器,是MVC架构中的C,即controller的实现,用于拦截这个web应用的所有请求,具体为在web.xml中配置这个s ...
- hdu6393 /// 树链剖分
题目大意: 给定n q 在n个点n条边的图中 进行q次操作 0 k x 为修改第k条边的值为x 1 x y 为查询x到y的最短路 https://blog.csdn.net/nka_kun/artic ...
- Center OS 7安装 Apollo
声明: 每个人的情况都不一样,所以大家在看教程的时候自行斟酌,最好先扫一遍,再来根据自身情况进行操作.同时,遇到的问题也可能不尽相同,要灵活处理. 了解: Apollo是从原始ActiveMQ的基础构 ...
- AN之数据集
一.数据集: 首先介绍数据集参数: 英文简称 英文全称 中文全称 单位 换成正常单位 说明1 说明2 Time Time 时间 小时:分钟 Temp Temperature 温度 摄氏度 ...
- CSIC_716_20191129【 单例模式 的五种实现方式】
单例模式 单例模式:在确定类中的属性和方法不变时,需要反复调用该类的情况. 让所有通过该类实例化出的对象,都指向同一个内存地址. 优点:节省内存空间. 单例模式有五种表现形式: 1.通过class ...
- Vue Router基础
路由 安装 vue-router 起步 <router-link to="/foo">Go to Foo</router-link> <router- ...
- CDH 下线节点
rm -rf /var/web/com/public 杜绝这种删除方式 停止集群下线的节点 步骤: 1 停止主机上的角色 2 解除授权 3 从集群中删除 4 为了避免数据丢失,必须一台一台的进行,如 ...
- thinkphp读取配置
无论何种配置文件,定义了配置文件之后,都统一使用系统提供的C方法(可以借助Config单词来帮助记忆)来读取已有的配置. 获取已经设置的参数值:C('参数名称') 例如, $model = C('UR ...