C/C++知识点总结(4)

C++的类型转换规则:

  • 对于数值类型而言:当一个较小数值类型赋值给一个较大数值类型的时候,C++支持隐式的类型转换,不会有任何的损失;

  • 对于数值类型而言,当一个较大数值类型赋值给一个较小数值类型时候,由于较小数值类型内存空间有限,有信息丢失,这种转换被视为不安全,但仍旧可以隐式进行;

  • 对于对象类型而言,派生类对象中的信息肯定包含基类对象,将一个派生类对象赋值给基类对象的时候,肯定也有信息丢失,但是这不会影响我们使用基类对象的信息,所以这种转换被视为安全的,可以隐式进行;

  • 对于对象类型而言,反过来,如果将基类对象赋值非派生类对象,由于没有足够的信息来填充派生类对象的对应空间,这样的转换被视为不安全的;如果是按值传
    递,则不能进行显示转换(除非定义转换构造函数或者转换运算符重载函数),如果是指针或者索引类型则可以进行显示转换(强制转换,可行,但无意义);对于对象类型而言,反过来,如果将基类对象赋值非派生类对象,由于没有足够的信息来填充派生类对象的对应空间,这样的转换被视为不安全的;如果是按值传
    递,则不能进行显示转换(除非定义转换构造函数或者转换运算符重载函数),如果是指针或者索引类型则可以进行显示转换(强制转换,可行,但无意义);

对象值类型之间的类型转换:

  • 对于不同类型之间的赋值classB=classA而言,如果没有对应的赋值运算符重载函数,则首先通过转换构造函数转换,再使用赋值运算符重载函数赋值;

  • 对于不同类型之间的混合运算classB=classA+classC而言,如果没有正确参数对应的友元函数,则需要首先使用转换构造函数进行类型转换,然后调用友元函数;

  • 对于相同类型之间的初始化classB b=classB而言,调用的是拷贝构造函数,所以对于不同种类型没有作用;

  • C++对于内部数值类型的类型检查不严,认为这些类型都可以相互隐式转化,从这个角度看C++是弱类型语言;对于基类和派生类而言,派生类可通过隐式转换
    到达基类(通过拷贝构造函数或者赋值运算符重载函数实现),但是基类必须通过强制转换才可以到达派生类(这些规则对于类对象类型,引用类型和指针类型都适
    用),不相关类型之间的转换只能通过特殊的程序特征设计才能实现,所以从这个角度看C++是强类型语言;

C++中有三种强制类型转换方式:

  • 转换构造函数:当提供非目标对象类型的类型时,将调用转换构造函数创建一个临时对象,此临时对象一般情况下会用于拷贝构造函数(参数传递,初始化)或重载
    赋值运算符函数(赋值)的参数。如果在转换构造函数前添加explicit关键字,则需要显示使用强制转换符号才能显示进行转换。

    Base::Base(int init) {……}

    有一函数void setOther(Base b),则如果传入参数为setOther(5);实际的调用为

    setOther(Base(5));

    依赖转换构造函数的隐式转换仅限于按值传递参数类型,因为转换构造函数构造一个对象本身,所以对于指针和引用类型的参数不能依赖这个机制;


  • 转换运算符重载函数:不能有在函数头上定义返回值,但是函数体内需要有return返回值,定义:operator const char* () const
    {return ptr;} 其中的const char*就是最终转到的类型,使用:(char *)typeinst; typeinst
    就是定义转换运算符函数的类型,此类函数的功能与static_cast<>
    ()类似。尽管没有显示声明返回值,但是函数体内也需要使用return语句返回对应函数名的类型;

    operator const char*() const {……return const ptr;}

    operator char*() {……return ptr;}


  • 指针(引用)之间的转换:隐式转换的规则仅适用于数值类型(和有转换构造函数的类型),任何类型的变量都可以接受强制类型转换的初始化或者赋值,但大多数情况下是使用新的类型解释机制去解释原有的内存数据,尽管没有意义,但是编译器却允许这样的显示转换;

  • 当使用基类对象作为右值赋值给作为左值的派生类对象时,即使使用强制转换也是不合法的;仅有定义针对基类对象的赋值运算符重载函数才可以(派生类对象有部分数据需要额外赋值)

    1 class Base {}; class Derived :public Base {}
    2 Derived d; Base b;
    3 d=b;//错误,一定需要赋值运算符重载函数
    4 d=(Derived)b;//错误,一定需要转换操作符重载函数

    解决办法是:
            之一:为Derived添加赋值运算符重载函数
                    void
    Derived::operator=(const Base
    &b)
                    void
    Derived::operator=(const Derived
    &d)

            之二:为Derived添加转换构造函数
                    Derived::Derived(const
    Base &
    b)

            之三:为Base添加转换操作符重载函数
                    Base::Derived()
    {……return derived;}

对象类型之间的转换,无论是基类对象转换成派生类对象,还是派生类对象转换成基类对象,都是生成新对象的过程,需要调用对应的构造函数,并且可能会丢失信
息(派生类转换到基类的时候会出现信息丢失)。而指针或者索引之间的转换则只是访问模式的转换,对于对象本身来讲并没有实质上的变化,因此不同类别之间的
指针和索引可以相互转换,并且转换是可逆的;

虚函数机制的使用:

  • 只适用于指针和索引类型

  • 其本身不能是静态函数,不能使用对象变量或者作用域运算符调用

  • 并且一般是基类类型指针(索引)指向派生类类型对象的时候使用

  • 继承模式为public(隐式转换只有在公共派生时才有效)

  • 虚函数不能声明为static,private

  • 派生类中对应的函数需要与基类中的函数的名字,参数列表,返回值都完全相同;

    1 class Base {virtual void func() {……}};
    2 class Derived1 : public Base {void func() {…1…}};
    3 class Derived2 : public Base {void func() {…2…}};
    4 Base* comBase=new Derived1(); comBase->func();//调用1
    5 comBase=new Derived2(); comBase->func();//调用2

    则comBase会根据具体索引不同的派生类类型,调用不同的func()方法

四类函数定义:

  • 1类:基类中定义,派生类中没有定义

  • 2类:基类中没有定义,派生类中进行了定义

  • 3类:基类中,派生类中都进行了定义(相同函数名)

  • 4类:虚函数

三类索引情况:(针对四类函数定义中的四项)

  • 基类指针索引基类对象:1

  • 派生类指针索引派生类对象:某些public继承子基类的1,2和3中的派生类定义(静态绑定);

  • 基类指针索引派生类对象:1,3中的基类定义(静态绑定)和4中的派生类定义(动态绑定);

对C++类的内存模型的理解:

  • static类函数和static类成员数据是独立于类而言,也就是存储于静态存储区;

  • 非static类函数是针对类而言,也就是所有相同类型的实例都共享同一份非static类函数列表的内存,这份数据在类的第一个实例创建的时候载入内
    存,直到最后一个类的实例销毁的时候才销毁,表示一个类的数据;非static类函数以Table的形式出现,每一个slot表示函数的调用地址;如果是
    派生类,则Table会以基类的Table作为模板(因此包含private,
    protected和public的函数指针),然后将派生类的类函数地址存储到新的Table中(顺序查找是否有同名函数,如果有则实现函数覆盖;如果
    没有,则加入Table的末尾);由于使用Base::fun2()就可以访问基类的方法,所以可能不是简单的同名覆盖;

  • 非static类成员数据是针对实例而言,也就是相同类型的实例在内存中有一份独立的成员数据拷贝,在非static类函数体内通过*this指针进行成
    员数据的访问;如果是派生类,则其内存中会包含所有基类的数据成员(因此包含所有private, protected和public的数据成员);

  • virtual函数是针对实例而言,含有虚函数的类与不含有虚函数的类在内存中同样具有以Table形式存储指向类函数的指针的列表,派生类的类函数地址
    同样会存储于Table中(顺序查找是否有同名函数,如果有则实现函数覆盖;如果没有,则加入Table的末尾);不同的是,类实例的内存的起始位置会使
    用4bytes(一个指针大小)存储指向这个Table的指针,因此这个Table更名为Virtual
    Table;并且表明为virtual的函数会使用运行时查找的方式到VTable中查找函数,其他函数仍旧使用静态绑定的方式确定类函数调用地址;

  • 如果是多继承的情况,则按照继承顺序依次存储每一个基类的VT_Ptr和数据,最后才是自身的数据;


     1 class Base {
    2 private:
    3 int data0;
    4 public:
    5 int data1;
    6 static int data2;
    7 public:
    8 void fun1();
    9 void fun2();
    10 void fun3();
    11 virtual void fun4();
    12 virtual void fun5();
    13 virtual void fun6();
    14 static void fun7();
    15 };
    16
    17 class Derived : public Base{
    18 public :
    19 int data3;
    20 static int data4;
    21 public :
    22
    23 void fun2();
    24 void fun3(int);
    25
    26 void fun5();
    27 void fun6(int);
    28 void fun8();
    29 static void fun9();
    30 };
    31
    32 class solo {
    33 private:
    34 int data0;
    35 public :
    36 int data1;
    37
    38 };
    39 Base类的类函数Table(由于有虚函数,所有也叫VTable):
    40
    41 Base::fun1 Base::fun2 Base::fun3 Base::fun4 Base::fun5 Base::fun6
    42
    43 Derived类的类函数Table(由于有虚函数,所有也叫VTable):
    44
    45 Base::fun1 Derived::fun2 Derived::fun3 Base::fun4 Derived::fun5 Derived::fun6 Derived::fun8

    static函数存储于程序静态存储区,所以独立于类的类函数Table;

    派生类的Table直接复制自基类的Table,并将自己定义的函数添加到Table的末尾(如fun8),如果有同名函数则直接进行覆盖(这里不管是否参数列表相同,也不管是否为virtual函数,如fun2和fun5,fun3和fun6)

    Base类的类实例内存结构:
    VT_Ptr1 data0 data1

    Derived类的类实例内存结构:

    VT_Ptr2 data0 data1 data3

    static成员数据存储于程序静态存储区,所以独立于类的实例内存;

    派生类的类实例包含所有基类的数据成员;并且不会覆盖重名的成员数据,而只是简单的追加

    VT_Ptr1表示指向Base类在内存中的类函数Table的指针;VT_Ptr2表示指向指向Derived类在内存中的类函数Table的指针;仅仅virtual函数才是动态动态调用,其他函数则是静态绑定

虚拟继承和虚基类:用于避免多继承环境中的二义性和内存使用效率

  • 问题:当B和C拥有共同的基类A,同时D同时继承B和C,这样当D调用A中的方法时产生二义性(不知道选择B继承路线还是C继承路线,在D的内存数据中有两份A的类数据),其实问题在于B和C各自拥有一份A基类的内存拷贝,不仅有二义性且内存耗用大

    1 class Top {};
    2 class Left : virtual public Top {};
    3 class Right : virtual public Top {};
    4 class Bottom : public Left, public Right {};







     

    Bottom* b=new Bottom(); b指向Left::Top::a;

    Left*
    bl=b;bl指向Left::Top::a;但是当Right*
    br=b;时,br需要指向Right::Top::a才逻辑正确,需要手动进行偏移;

    当Top*
    bt=b;时,编译不通过,产生歧义。
     


  • 虚继承中,每个类实例中仍旧有4bytes的指针指向虚函数表,并且Derived3的实例中有两个4bytes的VT指针;

    解决:class B: virtual A{}; class C: virtual
    A{};这样的定义使得内存中A基类仅有一份数据,所以的子类都使用同一份拷贝


C/C++知识点总结(4),布布扣,bubuko.com

时间: 2024-10-10 18:56:31

C/C++知识点总结(4)的相关文章

H5移动端知识点总结

移动开发基本知识点 一. 使用rem作为单位html { font-size: 100px; }@media(min-width: 320px) { html { font-size: 100px; } }@media(min-width: 360px) { html { font-size: 112.5px; } }@media(min-width: 400px) { html { font-size: 125px; } }@media(min-width: 640px) { html { f

Spring知识点回顾(01)

Spring知识点回顾(01) 一.依赖注入 1.声明Bean的注解 @Component @Service @Repository @Controller 2.注入Bean的注解 @Autowired @Inject @Resource 二.加载Bean 1.xml方式 - applicationcontext.xml : Beans, Bean, Component-Scan 2.注解方式 - @Configuration,@ComponentScan,@Bean 用@Configurati

Javascript易错知识点

? JS易错知识点总结: == 和 === 的区别: ==:判断两个变量的值是否相等. ===:判断两个变量的类型和值是否都相等,两个条件同时满足时,表达式为True. switch中break的作用: 如果一个case后面的语句,没有写break,那么程序会向下执行,而不会退出: 例如:当满足条件的case 2下面没有break时,case 3也会执行 1 var num = 2; 2 switch(num){ 3 case 1: 4 alert('case 1'); 5 break; 6 c

老男孩教育每日一题-2017年5月11-基础知识点: linux系统中监听端口概念是什么?

1.题目 老男孩教育每日一题-2017年5月11-基础知识点:linux系统中监听端口概念是什么? 2.参考答案 监听端口的概念涉及到网络概念与TCP状态集转化概念,可能比较复杂不便理解,可以按照下图简单进行理解? 将整个服务器操作系统比喻作为一个别墅 服务器上的每一个网卡比作是别墅中每间房间 服务器网卡上配置的IP地址比喻作为房间中每个人 而房间里面人的耳朵就好比是监听的端口 当默认采用监听0.0.0.0地址时,表示房间中的每个人都竖起耳朵等待别墅外面的人呼唤当别墅外面的用户向房间1的人呼喊时

JavaScript一些重要知识点结合题目的表现!

function Foo() { //① 声明一个Foo的函数 getName = function () { alert (1); }; return this; } Foo.getName = function () { alert (2);}; ② 为Foo创建一个叫getName的静态属性存储一个匿名函数 Foo.prototype.getName = function () { alert (3);}; ③为Foo的原型对象创建一个叫getName的匿名函数 var getName =

学完了js的知识,一起分享总结知识点

又一个知识点学完了,到了总结学习效果和知识总结的时间了.js这个编程语言相对于html和css的逻辑性要强一些,也比较不容易上手.概念性的知识点不难理解,就是实际的操作并不容易,需要通过学习和借鉴案列来理解和帮助并提高实践操作的能力,把理论知识更好的结合到实践当中去,这样才能更有利于去理解和提高自己,做到知识的真正转化,缺乏理论概念性的支撑,有时真的很难上手,尤其是对于刚学习新手而言.所以需要总结知识点,大家可以互相分享一下学习的方式方法,知识总结,通过这种方式方法,相信可以有效的帮助解决一些学

C#高级知识点概要(1) - 委托和事件

作者:linybo 要成为大牛,必然要有扎实的基本功,不然时间再长项目再多也很难有大的提升.本系列讲的C# 高级知识点,是非常值得去撑握的,不仅可以让你写代码时游刃有余,而且去研究和学习一些开源项目时,也不会显得那么吃力了. 希望大家记住,这里讲的所有的知识点,不仅仅是了解了就可以了,还要会灵活用,一定要多思考,撑握其中的编程思想. 本文讲的是委托和事件,这两个词可能你早就耳熟能详,但你是否真正撑握了呢? 本系列讲的C#高级知识点都是要求开发时能达到可以徒手写出来的水平(不依赖搜索引擎.找笔记等

关于Less,你必知的知识点

这是一篇关于Less学习教程 http://www.maiziedu.com/course/497/,讲解了Less的语法,Less的混合等知识点. 1. 关于 less sass 的预编译处理器 LESS 将 CSS 赋予了动态语言的特性,如 变量, 继承, 运算, 函数. LESS 既可以在 客户端 上运行 (支持IE 6+, Webkit, Firefox),也可以借助Node.js或者Rhino在服务端运行. less 编译使用前期使用koala 编译 2. less的 语法 A): 注

JavaScript 总结几个提高性能知识点

前段时间花时间看了大半的<High Performance JavaScript>这本书啊,然后就开始忙项目了,庆幸最忙的一周已经熬过去了.由于空不出时间,这个月写的学习笔记也不多,忙完最苦X的一周,这两天晚上也算是挑灯夜读了...终于是在残血之际将这本书shut down了... 既然读完了,总归是要学到些什么的.说说对这本书的看法先吧,整体的来说,内容还是不错的,就是感觉有点老了(作为前端小白,也可能是自身水平有限,未能体会到其中真意).看这本书的过程中也是写了挺多代码用以测试的,并且对本

Oracle 相关知识点结构图

最近在学Oracle数据库,制作了些结构图方便记忆!主要涉及到Oracle数据类型,Oracle的表操作以及Oracle的游标,还有的之后再分享...... Oracle 数据类型 因为图片上只能看到结构,一些知识点看不了,建议大家点击这个链接,去看源文件:http://naotu.baidu.com/viewshare.html?shareId=atvuh8jmlb4g Oracle表操作 链接:http://naotu.baidu.com/viewshare.html?shareId=atv