当前位置:首页 > 公众号精选 > C语言与CPP编程
[导读]一、关于对象C语言是程序性的,语言本身并没有支持数据和函数之间的关联性C中可能采取抽象数据类型,或者是多层次的类结构完成C的封装并没有增加多少成本,每一个成员函数虽然在class中声明,但是却不出现在每个对象中每一个非内联的成员函数只会诞生一个函数实例每个内联函数会在其每一个使用...


一、关于对象

  • C 语言是程序性的,语言本身并没有支持数据和函数之间的关联性


  • C 中可能采取抽象数据类型,或者是多层次的类结构完成


  • C 的封装并没有增加多少成本,每一个成员函数虽然在class中声明,但是却不出现在每个对象中


    • 每一个非内联的成员函数只会诞生一个函数实例
    • 每个内联函数会在其每一个使用者身上产生一个函数实例
  • C 在布局以及存储时间上主要的额外负担是由virtual引起的


    • 虚函数机制用以支持一个有效率的“执行期绑定”
    • 虚基类用来实现“多次出现在继承关系中的基类,有一个单一而被共享的实例”
  • 还有一些多重继承下的额外负担,发生在一个派生类和其第二或后继之基类的转换之间


1.1 C 对象模式

  • C 对象模型有以下几点
    • 每个类中存放一个指针称为vptr,指向虚函数表
    • 表中每个都指向一个虚函数
    • 非静态数据成员放在类对象内
    • 静态数据成员放在类对象外
    • 静态和非静态成员函数也放在类对象外
    • 虚函数则不同
C 对象模型

1.2 关键词所带来的差异

  • int ( *pq ) ( ); //声明


  • 当语言无法区分那是一个声明还是一个表达式时,我们需要一个超越语言范围的规则,而该规则会将上述式子判断为一个“声明“


  • struct和class可以相互替换,他们只是默认的权限不一样


  • 如果一个程序员需要拥有C声明的那种struct布局,可以抽出来单独成为struct声明,并且和C 部分组合起来


1.3 对象的差异

  • C 支持三种程序范式:程序模型、抽象数据类型模型、面向对象模型


    • 面向对象模型在继承体系中 ,有时候编译期间无法确定指针或引用所指类型
  • C 支持的多态类型:


    1. 经由一组隐式的转化操作:如派生类指针转化为指向父类的指针
    2. 经由虚函数机制
    3. 经由dynamic_cast 和 typeid运算符
  • 一个class所占的大小包括:


    • 其非静态成员所占的大小
    • 由于内存对齐填补上的大小
    • 加上支持虚函数而产生的大小
  • 指针的类型,只能代表其让编译器如何解释其所指向的地址内容,和它本身类型无关,所以转换其实是一种编译器指令,不改变所指向的地址,只影响怎么解释它给出的地址


  • 当一个基类对象被初始化为一个子类对象时,派生类就会被切割用来塞入较小的基类内存中,派生类不会留下任何东西,多态也不会再呈现。


二、构造函数语意学

2.1 默认构造函数的构造操作

  • 以下四种情况下,会合成有用的构造函数:


    • 类声明(或继承)一个虚函数
    • 类派生自一个继承串链,其中有一个或更多的虚基类
    • 带有默认构造函数的成员函数对象,不过这个合成操作只有在构造函数真正需要被调用时才发生,但只是调用其成员的默认构造函数,其他则不会初始化
    • 如果一个派生类的父类带有默认构造函数,那么子类如果没有定义构造函数,则会合成默认构造函数,如果有的话但是没有调用父类的,则编译器会插入一些代码调用父类的默认构造函数
    • 带有一个虚函数的类
    • 带有一个虚基类的类
  • C 新手常见的两个误解:


    • 任何class如果没有定义默认构造函数,就会被合成出来一个
    • 编译器合成出来的默认构造函数会显式设定类中的每一个数据成员的额 默认值

2.2 拷贝构造函数的构造操作

  • 有三种情况会调用拷贝构造函数:


    • 对一个对象做显式的初始化操作
    • 当对象被当作参数交给某个函数
    • 当函数传回一个类对象时
  • 如果类没有声明一个拷贝函数,就会有隐式的声明和隐式的定义出现,同默认构造函数一样在使用时才合成出来


  • 什么情况下一个类不展现“浅拷贝语意”:


    • 编译器会合成一个拷贝构造函数,安插一些代码用来设定虚基类指针和偏移的初值,对每个成员执行必要的深拷贝初始化操作,以及执行其他的内存相关工作
    • 编译器会显式的设定新类的虚函数表,而不是直接拷贝过来指向同一个
    • 这两个编译器都会合成拷贝构造函数并且安插进那个成员和基类的拷贝构造函数
    • 当类内含有一个成员类而后者的类声明中有一个拷贝构造函数(例如内含有string成员变量)
    • 当类继承自一个基类而基类中存在拷贝构造函数
    • 当类声明了一个或多个虚函数
    • 当类派生自一个继承串链,其中有一个或多个虚基类

2.3 程序转化语意学

  • 在将一个类作为另一个类的初值情况下,语言允许编译器有大量的自由发挥的空间,用来提升效率,但是缺点是不能安全的规划拷贝构造函数的副作用,必须视其执行而定


  • 拷贝构造的应用,编译器会多多少的进行部分转换,尤其是当一个函数以值传递的方式传回一个对象,而该对象有一个合成的构造函数,此外编译器也会对拷贝构造的调用进行调优,以额外的第一参数取代NRV(Named Return Value)


2.4 成员们的初始化队伍

  • 四种情况下你需要使用成员初始化列表
    • 当初始化一个引用成员变量
    • 当初始化一个const 成员变量
    • 当调用一个基类的构造函数,而它拥有一组参数
    • 当调用一个类成员变量的构造函数,而它拥有一组参数
class Word{
String _name;
int _cnt;
public:
Word(){
_name = 0;
_cnt = 0;
}
/*使用成员列表初始化可以解决
Word() : _name(0),_cnt(0){

}
*/

}
上式不会报错,但是会有效率问题,因为这样会先产生一个临时的string对象,然后将它初始化,之后以一个赋值运算符将临时对象指定给_name,再摧毁临时的对象


  • 成员初始化列表中的初始化顺序是按照类中的成员变量声明的顺序,与成员初始化列表的排列顺序无关

3、Data语意学

class X{};
class Y : public virtual X {};
class Z : public virtual X {};
class A : public Y,public Z {};

sizeof(X) //1
sizeof(Y) //4
sizeof(Z) //4
sizeof(A) //8
  • X为1是因为编译器的处理,在其中插入了1个char,为了让其对象能在内存中有自己独立的地址


  • Y,Z是因为虚基类表的指针


  • A 中含有Y和Z所以是8


  • 每一个类对象大小的影响因素:


    • 非静态成员变量的大小
    • virtual特性
    • 内存对齐

3.1 数据成员的绑定

  • 如果类的内部有typedef,请把它放在类的起始处,因为防止先看到的是全局的和这个typedef相同的冲突,编译器会选择全局的,因为先看到全局的

3.2 数据成员的布局

  • 非静态成员变量的在内存中的顺序和其声明顺序是一致的
  • 但是不一定是连续的,因为中间可能有内存对齐的填补物
  • virtual机制的指针所放的位置和编译器有关

3.3 成员变量的存取

  • 静态变量都被放在一个全局区,与类的大小无关,正如对其取地址得到的是与类无关的数据类型,如果两个类有相同的静态成员变量,编译器会暗自为其名称编码,使两个名称都不同
  • 非静态成员变量则是直接放在对象内,经由对象的地址和在类中的偏移地址取得,但是在继承体系下,情况就会不一样,因为编译器无法确定此时的指针指的具体是父类对象还是子类对象

3.4 继承下的数据成员

  • 在下面给定的两个类中依次讨论不同情况:



原本的数据模型
  • 在单一继承没有虚函数的情况下布局图



单一继承且无虚函数
  • 这种情况下常见错误:
    • 可能会重复设计一些操作相同的函数,我们可以把某些函数写成inline,这样就可以在子类中调用父类的某些函数来实现简化
    • 把数据放在同一个类中和继承起来的内存布局可能不同,因为每个类需要内存对齐


    • 叠在一起的内存布局


分层继承的布局
可见内存大了100%


  • 容易出现的不易发现的问题:


继承下易犯错误
  • 当加上多态之后,对空间上增加的额外负担包括:

    • 析构函数的调用顺序是反向的,从子类到父类
    • 导入一个虚函数表,表中的个数是声明的虚函数的个数加上一个或两个slots(用来支持运行类型识别)


    • 在每个对象中加入vptr,提供执行期的链接,使每一个类能找到相应的虚函数表


    • 加强构造函数,使它能够为vptr设定初值,让它指向对应的虚函数表,这可能意味着在派生类和每一个基类的构造函数中,重新设定vptr的值


    • 加强析构函数,使它能够消抹“指向类的相关虚函数表”的vptr,vptr很可能以及在子类析构函数中被设定为子类的虚表地址。


    • 以下是三种情况不同的继承下会有不同的布局


    • Vptr放在尾端


    • vptr放在前端




含虚函数的数据分布
  • 多重继承

  • **单一继承特点:**派生类和父类对象都是从相同的地址开始,区别只是派生类比较大能容纳自己的非静态成员变量


  • 多重继承下会比较复杂




多重继承关系
  • 一个派生对象,把它的地址指定给最左边的基类,和单一继承一样,因为起始地址是一样的,但是后面的需要更改,因为需要加上前面基类的大小,才能得到后面基类的地址


多重继承数据分布
  • 虚继承

  • STL标准库中使用的虚继承:


  • 虚继承关系图


  • 虚继承关系:





    虚继承例子


  • 虚继数据在内存中的分布


  • 虚继承数据模型


  • 虚继承数据模型2


3.5 对象成员的效率

  • 程序员如果关心程序效率,应该实际测试,不要光凭推论、常识判断或假设。
  • 优化操作并不一定总是能够有效运行,我不止一次以优化方式来 编译一个已通过编译的正常程序,却以失败收场

3.6 指向数据成员的指针

  • vptr通常放在起始处或尾端,与编译器有关,C 标准允许放在类中的任何位置


  • 取某个类成员变量的地址,通常取到得的是在类的首地址的偏移位置


    • 例如
本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

电感是导线内通过交流电流时,在导线的内部及其周围产生交变磁通,导线的磁通量与生产此磁通的电流之比。电感器也叫电感线圈,是利用电磁感应原理制成的,由导线在绝缘管上单层或多层绕制而成的,导线彼此互相绝缘,而绝缘管可以是空心的...

关键字: 电感 磁通量 电感器

根据交通运输部水运科学研究院提出的智慧港口的概念,智慧港口是利用新一代信息技术,将港口相关业务和管理创新深度融合,使港口更加集约、高效、便捷、安全、绿色,创新港口发展模式,实现港口科学可持续发展。

关键字: 智慧港口 信息技术 业务

近年来,世界主要汽车大国纷纷加强新能源汽车战略谋划、强化政策支持、完善产业布局,新能源汽车已成为全球汽车产业转型发展的主要方向和促进世界经济持续增长的重要引擎。2021年,全国新能源汽车实现产量354.5万辆,销量352...

关键字: 新能源 汽车 引擎

2007-2021年,全球针状焦行业专利申请人数量及专利申请量总体呈现增长态势。虽然2021年全球针状焦行业专利申请人数量及专利申请量有所下降,但是这两大指标数量仍较多。整体来看,全球针状焦技术处于成长期。

关键字: 针状焦行业 专利申请人 增长态势

按企业主营业务类型分,我国智能家居行业竞争派系可分为传统家电企业、互联网企业以及其他企业三派。传统家电企业代表有海尔智家、美的集团、格力电器等,具有供应链和销售渠道,制造能力和品牌优势突出;互联网企业代表有小米集团、百度...

关键字: 智能家居 互联网企业 供应链

军工电子是集红外技术、激光技术、半导体及嵌入式技术与虚拟仿真技术为一体的综合性军工技术体系,是国防信息化建设的基石。军工电子行业包含在军工行业内,专注于军工行业电子产品布局。根据其军工产品的不同可分为卫星导航、通信指挥、...

关键字: 军工电子 嵌入式技术 信息化建设

我国汽车零配件行业细分种类众多,从汽车零配件主要产品来看,发动机系统行业内有潍柴动力、华域汽车等主要从业企业;在车身零部件领域内,福耀玻璃、中策橡胶具有一定的规模优势;行驶系统领域内有中策橡胶提供的轮胎以及华为等企业提供...

关键字: 汽车零配件 发动机 行驶系统

茶饮料是指以茶叶或茶叶的水提取液、浓缩液、茶粉(包括速溶茶粉、研磨茶粉)或直接以茶的鲜叶为原料添加或不添加食品原辅料和(或)食品添加剂,经加工制成的液体饮料。根据国家标准《茶饮料(GB/T 21733-2008)》的规定...

关键字: 茶饮料 茶叶的水 食品添加剂

全球液压行业专利技术在21世纪初得到初步发展,这一时期液压专利申请人数量和申请量处于较低水平。2011-2012年,液压行业专利技术的发展总体处于成长期,2012年以后中全球液压行业专利技术申请量或申请人数量整体处于波动...

关键字: 液压行业 专利授权 技术类型

从上市企业的总市值情况来看,2022年7月28日,中芯国际、紫光国微和韦尔股份总市值遥遥领先,中芯国际总市值达到3238.21亿元,紫光国微总市值达到1358.77亿元,韦尔股份总市值达到1277.07亿元;其次是兆易创...

关键字: 上市企业 集成电路 行业

编辑精选

技术子站

关闭