精华内容
下载资源
问答
  • C++对象、对象数组的排序

    千次阅读 多人点赞 2015-06-24 12:20:35
    C++对象、对象数组的排序

    C++动态对象数组及其排序(一)


    相信在学习完对象数组后,大家肯定回想起了数组当中的动态数据的申请以及需要排序的问题。同样的,类(class)中同样有着排序的问题,其内容大概都是 类似的:要求用户乱序输入N个类的对象信息,然后再按类的某个成员变量为索引(比如工号)顺序或倒序排序。

    问题分析:
    1、对象必须按用户输入的个数进行申请;
    2、一系列对象数组的排序问题;

    需要按用户输入的个数来申请相应个数个对象,这需要申请对象数组,而且还得是动态的。这就需要像申请动态数组一样申请数组对象。其中,建立的类的名字当成对象指针的数据类型,用new运算符开辟N个类类型的对象。例:


    #include<iostream>
    using namespace std;
    class A
    {
    public:
        int key;
        char id[10];	
    };
    
    void main()
    {
    	int n;	
    	cin>>n;  //让用户统计需要开辟的对象个数,并输入这里的n
    	A *p=new A[n];	//创建n个A的对象
    	
    }
    这样就开辟了个对象,每个对象的名字分别是p[0]至p[n-1]。就像一般的对象名,可以通过点操作符对其中的内容进行访问:

    p[i].key=10;

    但是这里要注意,类的析构函数需要在程序结束、或显式调用时,才会把每个对象所占的空间析构。对于要求比较高的编程环境,这是非常致命的。由于new出的对象内存占用贯穿整个程序,会消耗大量计算机资源;然而这个动态对象很有可能只使用一次。所以要养成良好的习惯,在使用完动态数据后及时删除。
    删除new出的空间使用delete标识符,然而仅仅写delete p;是不够的,结果只是删除了p[0]这一个对象,而剩下的n-1个对象仍会造成内存泄露。因此要这样写:

    delete[] p;


    到此为止我们就按要求申请了n个(n为输入值)动态对象数组。



    接下来我们为这个类构建了输入输出的成员函数,用来更好地描述一系列对象数组排序的问题。把上面的类修改如下:
    class A
    {
    public:
        int key;
        char id[10];
        void out();
        void in();	
    };
    void A::out()
    {
    cout<<key<<endl<<id;
    }
    
    void A::in()
    {
    cin>>key;
    cin>>id;
    }
    
    

    主函数申请动态数组对象,并逐一输入:

    void main()
    {
    int n;
    cin>>n;
    A *p=new A[n];
    
    
    for(int i=0;i,n;i++)
    {
    p[i].in();
    }//未结束



    到此为止用户已经乱序输入了n个对象数组,所有的数据都在内存当中,接下来我们要对其按成员变量key进行排序。方法还是冒泡排序,不同的是临时变量的数据类型和if的判定内容
    这里得讲一个常规,如果对一个类定义了两个或多个对象,则这些同类的对象之间可以互相赋值,或者说,一个对象的值可以赋给另一个同类的对象。这里所指的对象的值是指对象中所有数据成员的值。也就是说我们可以通过赋值运算符“ = ”直接用一个对象来赋值另一个同类的对象,于是这个冒泡里用于临时存放数据的对象可声明为A temp,于是按照冒泡排序的算法有如下代码:

    for(int j=0;j<n;j++)
    	for(int k=0;k<n-j-1;k++)
    	{
    		if(p[k].key>p[k+1].key)//这个判定条件的话就是升序了
    		{
    			A temp=p[k];
    			p[k]=p[k+1];
    			p[k+1]=temp;
    		}
    	}
    
    

    对象之间的赋值要遵循以下规则:
    1、对象的赋值只对其中的数据成员赋值,而不对成员函数赋值。数据成员是占存储空间的,不同对象的数据成员占有不同的存储空间,赋值的过程是将一个对象的数据成员在存储空间的状态复制给另一对象的数据成员的存储空间。而不同对象的成员函数是同一个函数代码段,不需要、也无法对它们赋值。
    2、类的数据成员中不能包括动态分配的数据,否则在赋值时可能出现严重后果。

    有的同学可能会想到通过交换指针来完成冒泡,然而着这里是不行的。p是这段空间的首地址,并不能间接访问到对象所有内容,所以如*(p+1)这种方式访问对象是错的。因为p[n]本身就是一个对象而非指针,所以访问每个对象只需用p[i]这种方式就可以了。
    好的,经以上的步奏以后,就可以以下标升序的形式输出每个对象,并切每个对象按要求的key升序输出:
    for(int s=0;s<n;s++)
    {
    	p[s].out();
    }
    delete[] p;
    
    }//main函数结束









    若有不正确的地方欢迎留言指正





    展开全文
  • c++对象初始化

    千次阅读 2018-03-27 00:24:14
    c++对象初始化方法,与其他语言有所不同: 1显式调用构造函数,返回类对象,注意这里与Java等语言不同,调用构造函数并不必需new关键字,new在c++里有其他含义 User user3 = User(age, name);2隐式调用构造函数 User...

    c++对象初始化方法,与其他语言有所不同:

    1显式调用构造函数,返回类对象,注意这里与Java等语言不同,调用构造函数并不必需new关键字,new在c++里有其他含义

     

    User user3 = User(age, name);

    2隐式调用构造函数

     

     

    User user2(age, name);

    隐式调用构造函数,看起来像调用名为user2的函数,但其实是调用了User构造函数,这个方式效果与第一种相同,但是不直观,第一眼容易认错

     

    3.使用大括号初始化语法

     

    User user1 = { age,name };

    本质上还是调用构造函数,但是参数使用列表按顺序传递,大括号初始化还可以用于数组、结构体等,也不很直观

     

    4.动态分配内存

     

    User * user4 = new User(age, name);

    使用new关键字调用构造函数,返回对象指针,此时存储内存为动态分布,必须在合适时候删除

     

     


     

     

    展开全文
  • 图说C++对象模型:对象内存布局详解

    千次阅读 多人点赞 2018-02-20 22:09:07
    何为C++对象模型?2.文章内容简介3.理解虚函数表3.1.多态与虚表3.2.使用指针访问虚表4.对象模型概述4.1.简单对象模型4.2.表格驱动模型4.3.非继承下的C++对象模型5.继承下的C++对象模型5.1.单继承5.2.多继承6.虚继承...

    转自:http://mp.blog.csdn.net/postedit

     

    正文

    0.前言

    文章较长,而且内容相对来说比较枯燥,希望对C++对象的内存布局、虚表指针、虚基类指针等有深入了解的朋友可以慢慢看。
    本文的结论都在VS2013上得到验证。不同的编译器在内存布局的细节上可能有所不同。
    文章如果有解释不清、解释不通或疏漏的地方,恳请指出。

    1.何为C++对象模型?

    引用《深度探索C++对象模型》这本书中的话:

    有两个概念可以解释C++对象模型:

    1. 语言中直接支持面向对象程序设计的部分。
    2. 对于各种支持的底层实现机制。

    直接支持面向对象程序设计,包括了构造函数、析构函数、多态、虚函数等等,这些内容在很多书籍上都有讨论,也是C++最被人熟知的地方(特性)。而对象模型的底层实现机制却是很少有书籍讨论的。对象模型的底层实现机制并未标准化,不同的编译器有一定的自由来设计对象模型的实现细节。在我看来,对象模型研究的是对象在存储上的空间与时间上的更优,并对C++面向对象技术加以支持,如以虚指针、虚表机制支持多态特性。

    2.文章内容简介

    这篇文章主要来讨论C++对象在内存中的布局,属于第二个概念的研究范畴。而C++直接支持面向对象程序设计部分则不多讲。文章主要内容如下:

    • 虚函数表解析。含有虚函数或其父类含有虚函数的类,编译器都会为其添加一个虚函数表,vptr,先了解虚函数表的构成,有助对C++对象模型的理解。
    • 虚基类表解析。虚继承产生虚基类表(vbptr),虚基类表的内容与虚函数表完全不同,我们将在讲解虚继承时介绍虚函数表。
    • 对象模型概述:介绍简单对象模型、表格驱动对象模型,以及非继承情况下的C++对象模型。
    • 继承下的C++对象模型。分析C++类对象在下面情形中的内存布局:
      1. 单继承:子类单一继承自父类,分析了子类重写父类虚函数、子类定义了新的虚函数情况下子类对象内存布局。
      2. 多继承:子类继承于多个父类,分析了子类重写父类虚函数、子类定义了新的虚函数情况下子类对象内存布局,同时分析了非虚继承下的菱形继承。
      3. 虚继承:分析了单一继承下的虚继承、多重基层下的虚继承、重复继承下的虚继承。
    • 理解对象的内存布局之后,我们可以分析一些问题:
      1. C++封装带来的布局成本是多大?
      2. 由空类组成的继承层次中,每个类对象的大小是多大?

    至于其他与内存有关的知识,我假设大家都有一定的了解,如内存对齐,指针操作等。本文初看可能晦涩难懂,要求读者有一定的C++基础,对概念一有一定的掌握。

    3.理解虚函数表

    3.1.多态与虚表

    C++中虚函数的作用主要是为了实现多态机制。多态,简单来说,是指在继承层次中,父类的指针可以具有多种形态——当它指向某个子类对象时,通过它能够调用到子类的函数,而非父类的函数。

    class Base {     virtual void print(void);    }
    class Drive1 :public Base{    virtual void print(void);    }
    class Drive2 :public Base{    virtual void print(void);    }
    Base * ptr1 = new Base; 
    Base * ptr2 = new Drive1;  
    Base * ptr3 = new Drive2;
    ptr1->print(); //调用Base::print()
    prt2->print();//调用Drive1::print()
    prt3->print();//调用Drive2::print()

    这是一种运行期多态,即父类指针唯有在程序运行时才能知道所指的真正类型是什么。这种运行期决议,是通过虚函数表来实现的。

    3.2.使用指针访问虚表

    如果我们丰富我们的Base类,使其拥有多个virtual函数:

    class Base
    {
    public:
     
        Base(int i) :baseI(i){};
    
        virtual void print(void){ cout << "调用了虚函数Base::print()"; }
    
        virtual void setI(){cout<<"调用了虚函数Base::setI()";}
    
        virtual ~Base(){}
     
    private:
     
        int baseI;
    
    };

    当一个类本身定义了虚函数,或其父类有虚函数时,为了支持多态机制,编译器将为该类添加一个虚函数指针(vptr)。虚函数指针一般都放在对象内存布局的第一个位置上,这是为了保证在多层继承或多重继承的情况下能以最高效率取到虚函数表。

    当vprt位于对象内存最前面时,对象的地址即为虚函数指针地址。我们可以取得虚函数指针的地址:

    Base b(1000);
    int * vptrAdree = (int *)(&b);  
    cout << "虚函数指针(vprt)的地址是:\t"<<vptrAdree << endl;

    我们运行代码出结果:

    我们强行把类对象的地址转换为 int* 类型,取得了虚函数指针的地址。虚函数指针指向虚函数表,虚函数表中存储的是一系列虚函数的地址,虚函数地址出现的顺序与类中虚函数声明的顺序一致。对虚函数指针地址值,可以得到虚函数表的地址,也即是虚函数表第一个虚函数的地址:

        typedef void(*Fun)(void);
        Fun vfunc = (Fun)*( (int *)*(int*)(&b));
        cout << "第一个虚函数的地址是:" << (int *)*(int*)(&b) << endl;
        cout << "通过地址,调用虚函数Base::print():";
        vfunc();
    • 我们把虚表指针的值取出来: *(int*)(&b),它是一个地址,虚函数表的地址
    • 把虚函数表的地址强制转换成 int* : ( int *) *( int* )( &b )
    • 再把它转化成我们Fun指针类型 : (Fun )*(int *)*(int*)(&b)

    这样,我们就取得了类中的第一个虚函数,我们可以通过函数指针访问它。
    运行结果:

    同理,第二个虚函数setI()的地址为:

     (int * )(*(int*)(&b)+1)

    同样可以通过函数指针访问它,这里留给读者自己试验。

    到目前为止,我们知道了类中虚表指针vprt的由来,知道了虚函数表中的内容,以及如何通过指针访问虚函数表。下面的文章中将常使用指针访问对象内存来验证我们的C++对象模型,以及讨论在各种继承情况下虚表指针的变化,先把这部分的内容消化完再接着看下面的内容。

    4.对象模型概述

    在C++中,有两种数据成员(class data members):static 和nonstatic,以及三种类成员函数(class member functions):static、nonstatic和virtual:

    现在我们有一个类Base,它包含了上面这5中类型的数据或函数:

    class Base
    {
    public:
     
        Base(int i) :baseI(i){};
      
        int getI(){ return baseI; }
     
        static void countI(){};
     
        virtual ~Base(){}
    
        virtual void print(void){ cout << "Base::print()"; }
    
        
     
    private:
     
        int baseI;
     
        static int baseS;
    };

    那么,这个类在内存中将被如何表示?5种数据都是连续存放的吗?如何布局才能支持C++多态? 我们的C++标准与编译器将如何塑造出各种数据成员与成员函数呢?

    4.1.简单对象模型

    说明:在下面出现的图中,用蓝色边框框起来的内容在内存上是连续的。
    这个模型非常地简单粗暴。在该模型下,对象由一系列的指针组成,每一个指针都指向一个数据成员或成员函数,也即是说,每个数据成员和成员函数在类中所占的大小是相同的,都为一个指针的大小。这样有个好处——很容易算出对象的大小,不过赔上的是空间和执行期效率。想象一下,如果我们的Point3d类是这种模型,将会比C语言的struct多了许多空间来存放指向函数的指针,而且每次读取类的数据成员,都需要通过再一次寻址——又是时间上的消耗。
    所以这种对象模型并没有被用于实际产品上。

    4.2.表格驱动模型

    这个模型在简单对象模型的基础上又添加一个间接层,它把类中的数据分成了两个部分:数据部分与函数部分,并使用两张表格,一张存放数据本身,一张存放函数的地址(也即函数比成员多一次寻址),而类对象仅仅含有两个指针,分别指向上面这两个表。这样看来,对象的大小是固定为两个指针大小。这个模型也没有用于实际应用于真正的C++编译器上。

    4.3.非继承下的C++对象模型

    概述:在此模型下,nonstatic 数据成员被置于每一个类对象中,而static数据成员被置于类对象之外。static与nonstatic函数也都放在类对象之外,而对于virtual 函数,则通过虚函数表+虚指针来支持,具体如下:

    • 每个类生成一个表格,称为虚表(virtual table,简称vtbl)。虚表中存放着一堆指针,这些指针指向该类每一个虚函数。虚表中的函数地址将按声明时的顺序排列,不过当子类有多个重载函数时例外,后面会讨论。
    • 每个类对象都拥有一个虚表指针(vptr),由编译器为其生成。虚表指针的设定与重置皆由类的复制控制(也即是构造函数、析构函数、赋值操作符)来完成。vptr的位置为编译器决定,传统上它被放在所有显示声明的成员之后,不过现在许多编译器把vptr放在一个类对象的最前端。关于数据成员布局的内容,在后面会详细分析。
      另外,虚函数表的前面设置了一个指向type_info的指针,用以支持RTTI(Run Time Type Identification,运行时类型识别)。RTTI是为多态而生成的信息,包括对象继承关系,对象本身的描述等,只有具有虚函数的对象在会生成。

    在此模型下,Base的对象模型如图:

    先在VS上验证类对象的布局:

    Base b(1000);

    可见对象b含有一个vfptr,即vprt。并且只有nonstatic数据成员被放置于对象内。我们展开vfprt:

    vfptr中有两个指针类型的数据(地址),第一个指向了Base类的析构函数,第二个指向了Base的虚函数print,顺序与声明顺序相同。
    这与上述的C++对象模型相符合。也可以通过代码来进行验证:

    void testBase( Base&p)
    {
        cout << "对象的内存起始地址:" << &p << endl;
        cout << "type_info信息:" << endl;
        RTTICompleteObjectLocator str = *((RTTICompleteObjectLocator*)*((int*)*(int*)(&p) - 1));
     
     
        string classname(str.pTypeDescriptor->name);
        classname = classname.substr(4, classname.find("@@") - 4);
        cout <<  "根据type_info信息输出类名:"<< classname << endl;
     
        cout << "虚函数表地址:" << (int *)(&p) << endl;
     
        //验证虚表
        cout << "虚函数表第一个函数的地址:" << (int *)*((int*)(&p)) << endl;
        cout << "析构函数的地址:" << (int* )*(int *)*((int*)(&p)) << endl;
        cout << "虚函数表中,第二个虚函数即print()的地址:" << ((int*)*(int*)(&p) + 1) << endl;
     
        //通过地址调用虚函数print()
        typedef void(*Fun)(void);
        Fun IsPrint=(Fun)* ((int*)*(int*)(&p) + 1);
        cout << endl;
        cout<<"调用了虚函数"
        IsPrint(); //若地址正确,则调用了Base类的虚函数print()
        cout << endl;
     
        //输入static函数的地址
        p.countI();//先调用函数以产生一个实例
        cout << "static函数countI()的地址:" << p.countI << endl;
     
        //验证nonstatic数据成员
        cout << "推测nonstatic数据成员baseI的地址:" << (int *)(&p) + 1 << endl;
        cout << "根据推测出的地址,输出该地址的值:" << *((int *)(&p) + 1) << endl;
        cout << "Base::getI():" << p.getI() << endl;
     
    }
    Base b(1000);
    testBase(b);

    结果分析:

    • 通过 (int *)(&p)取得虚函数表的地址
    • type_info信息的确存在于虚表的前一个位置。通过((int)(int*)(&p) - 1))取得type_infn信息,并成功获得类的名称的Base
    • 虚函数表的第一个函数是析构函数。
    • 虚函数表的第二个函数是虚函数print(),取得地址后通过地址调用它(而非通过对象),验证正确
    • 虚表指针的下一个位置为nonstatic数据成员baseI。
    • 可以看到,static成员函数的地址段位与虚表指针、baseI的地址段位不同。

    好的,至此我们了解了非继承下类对象五种数据在内存上的布局,也知道了在每一个虚函数表前都有一个指针指向type_info,负责对RTTI的支持。而加入继承后类对象在内存中该如何表示呢?

    5.继承下的C++对象模型

    5.1.单继承

    如果我们定义了派生类

    class Derive : public Base
    {
    public:
        Derive(int d) :Base(1000),      DeriveI(d){};
        //overwrite父类虚函数
        virtual void print(void){ cout << "Drive::Drive_print()" ; }
        // Derive声明的新的虚函数
            virtual void Drive_print(){ cout << "Drive::Drive_print()" ; }
        virtual ~Derive(){}
    private:
        int DeriveI;
    };

    继承类图为:

    一个派生类如何在机器层面上塑造其父类的实例呢?在简单对象模型中,可以在子类对象中为每个基类子对象分配一个指针。如下图:

    简单对象模型的缺点就是因间接性导致的空间存取时间上的额外负担,优点则是类的大小是固定的,基类的改动不会影响子类对象的大小。

    在表格驱动对象模型中,我们可以为子类对象增加第三个指针:基类指针(bptr),基类指针指向指向一个基类表(base class table),同样的,由于间接性导致了空间和存取时间上的额外负担,优点则是无须改变子类对象本身就可以更改基类。表格驱动模型的图就不再贴出来了。

    在C++对象模型中,对于一般继承(这个一般是相对于虚拟继承而言),若子类重写(overwrite)了父类的虚函数,则子类虚函数将覆盖虚表中对应的父类虚函数(注意子类与父类拥有各自的一个虚函数表);若子类并无overwrite父类虚函数,而是声明了自己新的虚函数,则该虚函数地址将扩充到虚函数表最后(在vs中无法通过监视看到扩充的结果,不过我们通过取地址的方法可以做到,子类新的虚函数确实在父类子物体的虚函数表末端)。而对于虚继承,若子类overwrite父类虚函数,同样地将覆盖父类子物体中的虚函数表对应位置,而若子类声明了自己新的虚函数,则编译器将为子类增加一个新的虚表指针vptr,这与一般继承不同,在后面再讨论。

    我们使用代码来验证以上模型

    typedef void(*Fun)(void);
     
    int main()
    {
        Derive d(2000);
        //[0]
        cout << "[0]Base::vptr";
        cout << "\t地址:" << (int *)(&d) << endl;
            //vprt[0]
            cout << "  [0]";
            Fun fun1 = (Fun)*((int *)*((int *)(&d)));
            fun1();
            cout << "\t地址:\t" << *((int *)*((int *)(&d))) << endl;
     
            //vprt[1]析构函数无法通过地址调用,故手动输出
            cout << "  [1]" << "Derive::~Derive" << endl;
     
            //vprt[2]
            cout << "  [2]";
            Fun fun2 = (Fun)*((int *)*((int *)(&d)) + 2);
            fun2();
            cout << "\t地址:\t" << *((int *)*((int *)(&d)) + 2) << endl;
        //[1]
        cout << "[2]Base::baseI=" << *(int*)((int *)(&d) + 1);
        cout << "\t地址:" << (int *)(&d) + 1;
        cout << endl;
        //[2]
        cout << "[2]Derive::DeriveI=" << *(int*)((int *)(&d) + 2);
        cout << "\t地址:" << (int *)(&d) + 2;
        cout << endl;
        getchar();
    }

    运行结果:

    这个结果与我们的对象模型符合。

    5.2.多继承

    5.2.1一般的多重继承(非菱形继承)

    单继承中(一般继承),子类会扩展父类的虚函数表。在多继承中,子类含有多个父类的子对象,该往哪个父类的虚函数表扩展呢?当子类overwrite了父类的函数,需要覆盖多个父类的虚函数表吗?

    • 子类的虚函数被放在声明的第一个基类的虚函数表中。
    • overwrite时,所有基类的print()函数都被子类的print()函数覆盖。
    • 内存布局中,父类按照其声明顺序排列。

    其中第二点保证了父类指针指向子类对象时,总是能够调用到真正的函数。

    为了方便查看,我们把代码都粘贴过来

    class Base
    {
    public:
     
        Base(int i) :baseI(i){};
        virtual ~Base(){}
     
        int getI(){ return baseI; }
     
        static void countI(){};
     
        virtual void print(void){ cout << "Base::print()"; }
     
    private:
     
        int baseI;
     
        static int baseS;
    };
    class Base_2
    {
    public:
        Base_2(int i) :base2I(i){};
    
        virtual ~Base_2(){}
    
        int getI(){ return base2I; }
    
        static void countI(){};
    
        virtual void print(void){ cout << "Base_2::print()"; }
     
    private:
     
        int base2I;
     
        static int base2S;
    };
     
    class Drive_multyBase :public Base, public Base_2
    {
    public:
    
        Drive_multyBase(int d) :Base(1000), Base_2(2000) ,Drive_multyBaseI(d){};
     
        virtual void print(void){ cout << "Drive_multyBase::print" ; }
     
        virtual void Drive_print(){ cout << "Drive_multyBase::Drive_print" ; }
     
    private:
        int Drive_multyBaseI;
    };

    继承类图为:

    此时Drive_multyBase 的对象模型是这样的:

    我们使用代码验证:

    typedef void(*Fun)(void);
     
    int main()
    {
        Drive_multyBase d(3000);
        //[0]
        cout << "[0]Base::vptr";
        cout << "\t地址:" << (int *)(&d) << endl;
     
            //vprt[0]析构函数无法通过地址调用,故手动输出
            cout << "  [0]" << "Derive::~Derive" << endl;
     
            //vprt[1]
            cout << "  [1]";
            Fun fun1 = (Fun)*((int *)*((int *)(&d))+1);
            fun1();
            cout << "\t地址:\t" << *((int *)*((int *)(&d))+1) << endl;
     
     
            //vprt[2]
            cout << "  [2]";
            Fun fun2 = (Fun)*((int *)*((int *)(&d)) + 2);
            fun2();
            cout << "\t地址:\t" << *((int *)*((int *)(&d)) + 2) << endl;
     
     
        //[1]
        cout << "[1]Base::baseI=" << *(int*)((int *)(&d) + 1);
        cout << "\t地址:" << (int *)(&d) + 1;
        cout << endl;
     
     
        //[2]
        cout << "[2]Base_::vptr";
        cout << "\t地址:" << (int *)(&d)+2 << endl;
     
            //vprt[0]析构函数无法通过地址调用,故手动输出
            cout << "  [0]" << "Drive_multyBase::~Derive" << endl;
     
            //vprt[1]
            cout << "  [1]";
            Fun fun4 = (Fun)*((int *)*((int *)(&d))+1);
            fun4();
            cout << "\t地址:\t" << *((int *)*((int *)(&d))+1) << endl;
     
        //[3]
        cout << "[3]Base_2::base2I=" << *(int*)((int *)(&d) + 3);
        cout << "\t地址:" << (int *)(&d) + 3;
        cout << endl;
     
        //[4]
        cout << "[4]Drive_multyBase::Drive_multyBaseI=" << *(int*)((int *)(&d) + 4);
        cout << "\t地址:" << (int *)(&d) + 4;
        cout << endl;
     
        getchar();
    }

    运行结果:

    5.2.2 菱形继承

    菱形继承也称为钻石型继承或重复继承,它指的是基类被某个派生类简单重复继承了多次。这样,派生类对象中拥有多份基类实例(这会带来一些问题)。为了方便叙述,我们不使用上面的代码了,而重新写一个重复继承的继承层次:

    class B
     
    {
     
    public:
     
        int ib;
     
    public:
     
        B(int i=1) :ib(i){}
     
        virtual void f() { cout << "B::f()" << endl; }
     
        virtual void Bf() { cout << "B::Bf()" << endl; }
     
    };
     
    class B1 : public B
     
    {
     
    public:
     
        int ib1;
     
    public:
     
        B1(int i = 100 ) :ib1(i) {}
     
        virtual void f() { cout << "B1::f()" << endl; }
     
        virtual void f1() { cout << "B1::f1()" << endl; }
     
        virtual void Bf1() { cout << "B1::Bf1()" << endl; }
     
     
     
    };
     
    class B2 : public B
     
    {
     
    public:
     
        int ib2;
     
    public:
     
        B2(int i = 1000) :ib2(i) {}
     
        virtual void f() { cout << "B2::f()" << endl; }
     
        virtual void f2() { cout << "B2::f2()" << endl; }
     
        virtual void Bf2() { cout << "B2::Bf2()" << endl; }
     
    };
     
     
    class D : public B1, public B2
     
    {
     
    public:
     
        int id;
     
     
     
    public:
     
        D(int i= 10000) :id(i){}
     
        virtual void f() { cout << "D::f()" << endl; }
     
        virtual void f1() { cout << "D::f1()" << endl; }
     
        virtual void f2() { cout << "D::f2()" << endl; }
     
        virtual void Df() { cout << "D::Df()" << endl; }
     
    };

    这时,根据单继承,我们可以分析出B1,B2类继承于B类时的内存布局。又根据一般多继承,我们可以分析出D类的内存布局。我们可以得出D类子对象的内存布局如下图:

    D类对象内存布局中,图中青色表示b1类子对象实例,黄色表示b2类子对象实例,灰色表示D类子对象实例。从图中可以看到,由于D类间接继承了B类两次,导致D类对象中含有两个B类的数据成员ib,一个属于来源B1类,一个来源B2类。这样不仅增大了空间,更重要的是引起了程序歧义:

    D d;
     
    d.ib =1 ;               //二义性错误,调用的是B1的ib还是B2的ib?
     
    d.B1::ib = 1;           //正确
     
    d.B2::ib = 1;           //正确

    尽管我们可以通过明确指明调用路径以消除二义性,但二义性的潜在性还没有消除,我们可以通过虚继承来使D类只拥有一个ib实体。

    6.虚继承

    虚继承解决了菱形继承中最派生类拥有多个间接父类实例的情况。虚继承的派生类的内存布局与普通继承很多不同,主要体现在:

    • 虚继承的子类,如果本身定义了新的虚函数,则编译器为其生成一个虚函数指针(vptr)以及一张虚函数表。该vptr位于对象内存最前面。
      • vs非虚继承:直接扩展父类虚函数表。
    • 虚继承的子类也单独保留了父类的vprt与虚函数表。这部分内容接与子类内容以一个四字节的0来分界。
    • 虚继承的子类对象中,含有四字节的虚表指针偏移值。

    为了分析最后的菱形继承,我们还是先从单虚继承继承开始。

    6.1.虚基类表解析

    在C++对象模型中,虚继承而来的子类会生成一个隐藏的虚基类指针(vbptr),在Microsoft Visual C++中,虚基类表指针总是在虚函数表指针之后,因而,对某个类实例来说,如果它有虚基类指针,那么虚基类指针可能在实例的0字节偏移处(该类没有vptr时,vbptr就处于类实例内存布局的最前面,否则vptr处于类实例内存布局的最前面),也可能在类实例的4字节偏移处。
    一个类的虚基类指针指向的虚基类表,与虚函数表一样,虚基类表也由多个条目组成,条目中存放的是偏移值。第一个条目存放虚基类表指针(vbptr)所在地址到该类内存首地址的偏移值,由第一段的分析我们知道,这个偏移值为0(类没有vptr)或者-4(类有虚函数,此时有vptr)。我们通过一张图来更好地理解。

    虚基类表的第二、第三...个条目依次为该类的最左虚继承父类、次左虚继承父类...的内存地址相对于虚基类表指针的偏移值,这点我们在下面会验证。

    6.2.简单虚继承

    如果我们的B1类虚继承于B类:

    //类的内容与前面相同
    class B{...}
    class B1 : virtual public B

    根据我们前面对虚继承的派生类的内存布局的分析,B1类的对象模型应该是这样的:

    我们通过指针访问B1类对象的内存,以验证上面的C++对象模型:

    int main()
    {
    B1 a;
        cout <<"B1对象内存大小为:"<< sizeof(a) << endl;
     
        //取得B1的虚函数表
        cout << "[0]B1::vptr";
        cout << "\t地址:" << (int *)(&a)<< endl;
     
        //输出虚表B1::vptr中的函数
        for (int i = 0; i<2;++ i)
        {
            cout << "  [" << i << "]";
            Fun fun1 = (Fun)*((int *)*(int *)(&a) + i);
            fun1();
            cout << "\t地址:\t" << *((int *)*(int *)(&a) + i) << endl;
        }
     
        //[1]
        cout << "[1]vbptr "  ;
        cout<<"\t地址:" << (int *)(&a) + 1<<endl;  //虚表指针的地址
        //输出虚基类指针条目所指的内容
        for (int i = 0; i < 2; i++)
        {
            cout << "  [" << i << "]";
     
            cout << *(int *)((int *)*((int *)(&a) + 1) + i);
     
            cout << endl;
        }
     
     
        //[2]
        cout << "[2]B1::ib1=" << *(int*)((int *)(&a) + 2);
        cout << "\t地址:" << (int *)(&a) + 2;
        cout << endl;
     
        //[3]
        cout << "[3]值=" << *(int*)((int *)(&a) + 3);
        cout << "\t\t地址:" << (int *)(&a) + 3;
        cout << endl;
     
        //[4]
        cout << "[4]B::vptr";
        cout << "\t地址:" << (int *)(&a) +3<< endl;
     
        //输出B::vptr中的虚函数
        for (int i = 0; i<2; ++i)
        {
            cout << "  [" << i << "]";
            Fun fun1 = (Fun)*((int *)*((int *)(&a) + 4) + i);
            fun1();
            cout << "\t地址:\t" << *((int *)*((int *)(&a) + 4) + i) << endl;
        }
     
        //[5]
        cout << "[5]B::ib=" << *(int*)((int *)(&a) + 5);
        cout << "\t地址: " << (int *)(&a) + 5;
        cout << endl;

    运行结果:

    这个结果与我们的C++对象模型图完全符合。这时我们可以来分析一下虚表指针的第二个条目值12的具体来源了,回忆上文讲到的:

    第二、第三...个条目依次为该类的最左虚继承父类、次左虚继承父类...的内存地址相对于虚基类表指针的偏移值。

    在我们的例子中,也就是B类实例内存地址相对于vbptr的偏移值,也即是:[4]-[1]的偏移值,结果即为12,从地址上也可以计算出来:007CFDFC-007CFDF4结果的十进制数正是12。现在,我们对虚基类表的构成应该有了一个更好的理解。

    6.3.虚拟菱形继承

    如果我们有如下继承层次:

    class B{...}
    class B1: virtual public  B{...}
    class B2: virtual public  B{...}
    class D : public B1,public B2{...}

    类图如下所示:

    菱形虚拟继承下,最派生类D类的对象模型又有不同的构成了。在D类对象的内存构成上,有以下几点:

    • 在D类对象内存中,基类出现的顺序是:先是B1(最左父类),然后是B2(次左父类),最后是B(虚祖父类)
    • D类对象的数据成员id放在B类前面,两部分数据依旧以0来分隔。
    • 编译器没有为D类生成一个它自己的vptr,而是覆盖并扩展了最左父类的虚基类表,与简单继承的对象模型相同。
    • 超类B的内容放到了D类对象内存布局的最后。

    菱形虚拟继承下的C++对象模型为:

    下面使用代码加以验证:

    int main()
    {
        D d;
        cout << "D对象内存大小为:" << sizeof(d) << endl;
     
        //取得B1的虚函数表
        cout << "[0]B1::vptr";
        cout << "\t地址:" << (int *)(&d) << endl;
     
        //输出虚表B1::vptr中的函数
        for (int i = 0; i<3; ++i)
        {
            cout << "  [" << i << "]";
            Fun fun1 = (Fun)*((int *)*(int *)(&d) + i);
            fun1();
            cout << "\t地址:\t" << *((int *)*(int *)(&d) + i) << endl;
        }
     
        //[1]
        cout << "[1]B1::vbptr ";
        cout << "\t地址:" << (int *)(&d) + 1 << endl;  //虚表指针的地址
        //输出虚基类指针条目所指的内容
        for (int i = 0; i < 2; i++)
        {
            cout << "  [" << i << "]";
     
            cout << *(int *)((int *)*((int *)(&d) + 1) + i);
     
            cout << endl;
        }
     
     
        //[2]
        cout << "[2]B1::ib1=" << *(int*)((int *)(&d) + 2);
        cout << "\t地址:" << (int *)(&d) + 2;
        cout << endl;
     
        //[3]
        cout << "[3]B2::vptr";
        cout << "\t地址:" << (int *)(&d) + 3 << endl;
     
        //输出B2::vptr中的虚函数
        for (int i = 0; i<2; ++i)
        {
            cout << "  [" << i << "]";
            Fun fun1 = (Fun)*((int *)*((int *)(&d) + 3) + i);
            fun1();
            cout << "\t地址:\t" << *((int *)*((int *)(&d) + 3) + i) << endl;
        }
     
        //[4]
        cout << "[4]B2::vbptr ";
        cout << "\t地址:" << (int *)(&d) + 4 << endl;  //虚表指针的地址
        //输出虚基类指针条目所指的内容
        for (int i = 0; i < 2; i++)
        {
            cout << "  [" << i << "]";
     
            cout << *(int *)((int *)*((int *)(&d) + 4) + i);
     
            cout << endl;
        }
     
        //[5]
        cout << "[5]B2::ib2=" << *(int*)((int *)(&d) + 5);
        cout << "\t地址: " << (int *)(&d) + 5;
        cout << endl;
     
        //[6]
        cout << "[6]D::id=" << *(int*)((int *)(&d) + 6);
        cout << "\t地址: " << (int *)(&d) + 6;
        cout << endl;
     
        //[7]
        cout << "[7]值=" << *(int*)((int *)(&d) + 7);
        cout << "\t\t地址:" << (int *)(&d) + 7;
        cout << endl;
     
        //间接父类
        //[8]
        cout << "[8]B::vptr";
        cout << "\t地址:" << (int *)(&d) + 8 << endl;
     
        //输出B::vptr中的虚函数
        for (int i = 0; i<2; ++i)
        {
            cout << "  [" << i << "]";
            Fun fun1 = (Fun)*((int *)*((int *)(&d) + 8) + i);
            fun1();
            cout << "\t地址:\t" << *((int *)*((int *)(&d) + 8) + i) << endl;
        }
     
        //[9]
        cout << "[9]B::id=" << *(int*)((int *)(&d) + 9);
        cout << "\t地址: " << (int *)(&d) +9;
        cout << endl;
     
        getchar();
    }
     

    查看运行结果:

    7.一些问题解答

    7.1.C++封装带来的布局成本是多大?

    在C语言中,“数据”和“处理数据的操作(函数)”是分开来声明的,也就是说,语言本身并没有支持“数据和函数”之间的关联性。
    在C++中,我们通过类来将属性与操作绑定在一起,称为ADT,抽象数据结构。

    C语言中使用struct(结构体)来封装数据,使用函数来处理数据。举个例子,如果我们定义了一个struct Point3如下:

    typedef struct Point3
    {
        float x;
        float y;
        float z;
    } Point3;

    为了打印这个Point3d,我们可以定义一个函数:

    void Point3d_print(const Point3d *pd)
    {
        printf("(%f,%f,%f)",pd->x,pd->y,pd_z);
    }

    而在C++中,我们更倾向于定义一个Point3d类,以ADT来实现上面的操作:

    class Point3d
    {
        public:
            point3d (float x = 0.0,float y = 0.0,float z = 0.0)
                : _x(x), _y(y), _z(z){}
    
            float x() const {return _x;}
            float y() const {return _y;}
            float z() const {return _z;}
        
        private:
            float _x;
            float _y;
            float _z;
    };
    
        inline ostream&
        operator<<(ostream &os, const Point3d &pt)
        {
            os<<"("<<pr.x()<<","
                <<pt.y()<<","<<pt.z()<<")";
        }

    看到这段代码,很多人第一个疑问可能是:加上了封装,布局成本增加了多少?答案是class Point3d并没有增加成本。学过了C++对象模型,我们知道,Point3d类对象的内存中,只有三个数据成员。

    上面的类声明中,三个数据成员直接内含在每一个Point3d对象中,而成员函数虽然在类中声明,却不出现在类对象(object)之中,这些函数(non-inline)属于类而不属于类对象,只会为类产生唯一的函数实例。

    所以,Point3d的封装并没有带来任何空间或执行期的效率影响。而在下面这种情况下,C++的封装额外成本才会显示出来:

    • 虚函数机制(virtual function) , 用以支持执行期绑定,实现多态。
    • 虚基类 (virtual base class) ,虚继承关系产生虚基类,用于在多重继承下保证基类在子类中拥有唯一实例。

    不仅如此,Point3d类数据成员的内存布局与c语言的结构体Point3d成员内存布局是相同的。C++中处在同一个访问标识符(指public、private、protected)下的声明的数据成员,在内存中必定保证以其声明顺序出现。而处于不同访问标识符声明下的成员则无此规定。对于Point3类来说,它的三个数据成员都处于private下,在内存中一起声明顺序出现。我们可以做下实验:

    void TestPoint3Member(const Point3d& p)
    {
     
        cout << "推测_x的地址是:" << (float *) (&p) << endl;
        cout << "推测_y的地址是:" << (float *) (&p) + 1 << endl;
        cout << "推测_z的地址是:" << (float *) (&p) + 2 << endl;
     
        cout << "根据推测出的地址输出_x的值:" << *((float *)(&p)) << endl;
        cout << "根据推测出的地址输出_y的值:" << *((float *)(&p)+1) << endl;
        cout << "根据推测出的地址输出_z的值:" << *((float *)(&p)+2) << endl;
     
    }
        //测试代码
        Point3d a(1,2,3);
        TestPoint3Member(a);

    运行结果:

    从结果可以看到,_x,_y,_z三个数据成员在内存中紧挨着。

    总结一下:
    不考虑虚函数与虚继承,当数据都在同一个访问标识符下,C++的类与C语言的结构体在对象大小和内存布局上是一致的,C++的封装并没有带来空间时间上的影响。

    7.2.下面这个空类构成的继承层次中,每个类的大小是多少?

    今有类如下:

    
    class B{};
    class B1 :public virtual  B{};
    class B2 :public virtual  B{};
    class D : public B1, public B2{};
    
    int main()
    {
        B b;
        B1 b1;
        B2 b2;
        D d;
        cout << "sizeof(b)=" << sizeof(b)<<endl;
        cout << "sizeof(b1)=" << sizeof(b1) << endl;
        cout << "sizeof(b2)=" << sizeof(b2) << endl;
        cout << "sizeof(d)=" << sizeof(d) << endl;
        getchar();
    }

    输出结果是:

    解析:

    • 编译器为空类安插1字节的char,以使该类对象在内存得以配置一个地址。
    • b1虚继承于b,编译器为其安插一个4字节的虚基类表指针(32为机器),此时b1已不为空,编译器不再为其安插1字节的char(优化)。
    • b2同理。
    • d含有来自b1与b2两个父类的两个虚基类表指针。大小为8字节。

    转载请注明原出处:http://www.cnblogs.com/QG-whz/p/4909359.html

    展开全文
  • C++对象数组与对象指针

    万次阅读 2016-02-13 15:13:14
    C++对象数组 1.1数组不仅可以由简单的变量组成,而且还可以由对象组成。 1.2对象数组的初始化: A.如果构造函数只有一个参数,在初始化数组时可以在花括号里提供实参。Student stud[3]={10,18,12}; B.如果构造...

    C++对象数组

    1.1数组不仅可以由简单的变量组成,而且还可以由对象组成。

    1.2对象数组的初始化:

    A.如果构造函数只有一个参数,在初始化数组时可以在花括号里提供实参。Student stud[3]={10,18,12};

    B.如果构造函数有多个参数,在初始化数组时,分别在数组中写出构造函数并指定实参。

    Student Stud[3]={

    Student(1,11,111);

    Student(2,12,112);

    Student(3,13,113);

    }

    A情况也可以使用B的方式,只不过A的方式更为简便。

    C++对象指针

    1.1什么是对象指针?
    建立对象时,编译系统会为每一个对象分配一定的存储空间,以存放成员,对象空间的起始地址就是对象指针。
    1.2可以通过对象指针来访问对象的数据成员,及其成员函数。
    1.3指向对象数据成员的指针:
    int *p=&t1.hour;
    1.4指向对象成员函数的指针
    void (Time::*p2)()=&Time::get_time;
    注意是Time(类名),而不是对象名。因为成员函数不是存放在对象的空间中的,而是存放在对象外的空间中的。
    而且括号不能省略。因为括号的优先级高于*。

    C++ this指针

    1.1每一个成员函数里面有一个特殊的指针,叫this,它是指向本类对象的指针,它的值是当前所在对象的地址。
    如Volume函数计算(height*width*length),实际上是在计算(this->height*this->width*this->length);
    1.2 this指针是被隐式调用的,它是作为函数参数传给成员函数的。如:
    int Box::volume()
    {
    return (height*width*length);
    }
    C++把它处理为:
    int Box::volume(Box *this)
    {
    return (this->height*this->width*this->length);
    }
    1.3我们使用的时候没必要加上this,因为这些都是编译系统会帮你自动实现。




    展开全文
  • 4. C++对象模型和this指针4.1 成员变量和成员函数分开存储4.2 this指针概念4.3 空指针访问成员函数4.4 const修饰成员函数 4. C++对象模型和this指针 4.1 成员变量和成员函数分开存储   在C++中,类内的成员变量和...
  • C++对象的删除

    千次阅读 2014-03-08 19:01:18
    C++对象A中嵌套有C++对象B,在删除该对象A时,应先删除B,再删除A,顺寻不能颠倒,否则会出现违法访问的错误
  • C++对象内存模型

    千次阅读 2016-05-12 20:12:42
    但是C++提供了类,可以实现较好的数据和处理数据的算法的封装性,这种封装性相比较C语言而言会带来一些成本,这主要受制于是C++对象为支持相应特性而实现的内存模型。 C++中分别有static和nonstatic两种数据成员,...
  • C/C++对象的序列化

    万次阅读 2017-08-28 20:33:51
    C/C++对象的序列化
  • WIndows对象 C++对象 的关系

    千次阅读 2011-02-04 22:03:00
    C++对象则是真正的“面向对象”思想中的“类的对象”。 在windows编程中,除了普通的“类的对象”外,用得最多的“C++类的对象”应该是MFC对象了(如果你是用MFC编程的话),MFC对象是指“封装了...
  • C++对象模型之简述C++对象的内存布局

    万次阅读 多人点赞 2015-05-22 02:28:26
    那么,它们如何影响C++对象在内存中的分布呢? 当存在继承的情况下,其内存分布又是如何呢? 下面就一个非常简单的类,通过逐渐向其中加入各种成员,来逐一分析上述两种成员变量及三种成员函数对类的对象的内存...
  • C++对象的赋值和复制

    千次阅读 2016-02-13 17:12:24
    C++对象的赋值 1.1对象之间的赋值是用“=”运算符来实现的,“=”在c++中扩展为重载运算符来实现对象间的赋值。 t1=t2; 1.2对象赋值是对数据成员的赋值,而不是对成员函数的赋值。因为数据成员占用内存空间,而成员...
  • v8 JavaScript中绑定c++对象

    千次阅读 2015-04-29 17:47:58
    v8作为js解释器,也提供了对外接口用于绑定c++对象到js中。 这里面比较有名的就属nodejs了。 这里大致讲述如何根据js绑定c++对象。 v8中绑定c++对象 从需求入手。 我们要实现如下的js调用,...
  • c++对象作为函数参数

    千次阅读 2016-02-01 12:38:32
    c++对象作为函数参数也分为值传递和引用传递: 对象也可以作为函数的参数传递给函数,其转递方法与传递其他类型的数据一样,可采用值传递和地址传递两种方法。 值传递时是把对象的拷贝而不是本身传递给函数...
  • C++对象模型之详述C++对象的内存布局

    万次阅读 多人点赞 2015-06-08 10:16:48
    本文主要讨论继承对于对象的内存分布的影响,包括:继承后类的对象的成员的布局、继承对于虚函数表的影响、virtual函数机制如何实现、运行时类型识别等。由于在C++中继承的关系比较复杂,所以本文会讨论如下的继承...
  • c++ 对象指针

    千次阅读 2019-09-29 16:05:31
    c++ 中 指针依然是广泛存在的,尤其是指向对象的指针,没有他就不能实现某些功能. #include <iostream> using namespace std; //类通常定义在函数外面 class Student{ public: //类包含的变量 char *...
  • C++对象的内存模型

    千次阅读 2015-06-25 08:27:35
    看了C++ under the hood之后,C++对象的内存模型是这样的:涉及到虚的,就需要额外的存储空间。虚这里指的是虚函数和虚继承,额外的存储是对象内部需要存储虚函数表指针和虚基类表指针。 一个类的内存的分布在VC下是...
  • C++ 对象数组

    千次阅读 2018-02-21 19:48:20
    C++中的对象数组为我们提供了同时实例化多个相同对象的功能,避免我们多次进行实例化相同的对象。例如表示多维图形时的坐标等。声明时的方法与实例化单个对象类似。下面以一个二维三角形为例:class Coordinate { ...
  • C++对象 NULL

    千次阅读 2017-08-15 20:54:16
    c++中引用的概念跟java中的不一样,c++中的引用必须绑定到一个实际存在的对象,不能绑定到NULL; 也就是说在c++中 Object &obj=NULL; //这样的句子是绝对违法的而下面这种Object &obj=*(new Object());这样的句子,...
  • C++ 对象的生命周期

    千次阅读 2013-03-21 22:25:36
    转:C++ 对象的生命周期 stack heap global local static 标签: stack heap global local static 分类: CPlusPlus2011-04-15 23:27 C++ 对象的生命周期 ...
  • 深度探索C++对象模型电子书pdf下载

    千次阅读 2019-03-14 23:47:33
    深度探索C++对象模型下载链接: https://pan.baidu.com/s/1UEI9YBSsCnnOyC91-VomWg 提取码获取方式:关注下面微信公众号,回复关键字:1163
  • 当加载一个 QML 对象到一个 C++ 应用程序中时,直接嵌入可以从 QML 代码中使用的一些 C++ 数据是非常有用的。例如,对嵌入的对象调用一个 C++ 函数,或者使用一个 C++ 对象实例作为 QML 视图的数据模型。 通过 ...
  • 介绍:RapidjsonRapidjson库是C++对象序列化到Json字符串的非常好的工具,以效率著称,腾讯的人写的。官方网站:点击打开链接本文全部资源百度云这个库的缺点(个人拙见):1 暴露的细节相对较多:容器,迭代器,...
  • C++对象到bool值的转换

    千次阅读 2018-05-24 22:17:00
    C++对象到bool值的隐式转换 问题 最近在使用pugixml,在阅读源码自带的例程时(docs/samples/load_error_handling.cpp),发现有段代码比较迷惑, 这里生成了一个xml_parse_result的对象result,用于接受doc....
  • c++对象模型知识久负盛名,在c++界具有很高的美誉度,这方面知识的学习,更是被诸多颇具开发实力的行业前辈倾力推荐! 本门课程内容将涉及到很多不被常人所知的 c++对象内部工作原理、底层的一些具体实现机制方面的...
  • C++对象之间通信

    千次阅读 2010-12-11 21:08:00
    一直学习C++并使用C++完成了一个一个作 业和项目,然而对C++类对象之间的通信总有种不知所措就好像人吃了饭... 在将对象消息传递方法之前,我们先声明C++对象之间消息传递区别于windows消息传递。这是两个
  • 关于C++对象内存布局的资料和书籍也有很多,比如陈皓老师的博客: 1、C++对象的内存布局(上) 2、C++对象的内存布局(下) 白杨: RTTI、虚函数和虚基类的实现方式、开销分析及使用指导 左手为你画猜: C++类...
  • C++对象的生成和消亡时刻分析

    千次阅读 2017-06-04 17:14:22
    对于不同性质的C++对象的生成和消亡时间是不同,比如全局对象、静态对象、局部对象等; 1. 对于全局对象,C++规定 全局对象的构建将比程序进入点(main函数,WinMain函数)更早,全局类对象的构造函数都是在main...
  • C++对象的动态建立和释放

    万次阅读 2016-02-13 17:04:30
    C++对象动态建立和释放 1.1用new运算符之后,返回一个指向新对象的指针。 Box *pt=new Box(1,2,3); 1.2通过pt来访问这个对象 cout height; cout volume(); 1.3用delete运算符来释放对象。 delete pt; 1.4...
  • C++ 对象移动

    千次阅读 2018-12-03 10:57:04
    右值和右值引用 左值持久,右值短暂 凡是有名字的都是左值,变量都是左值 ...本身并不移动对象,只是将对象的类型转换为右值 调用此函数即保证后面不会再使用传入的对象 参数 stack&amp;amp;amp; ope...
  • 生成一个C++对象的成本

    千次阅读 2011-12-19 11:30:47
    又常常用PYTHON,或者阅读些JAVA的代码,感觉C的开发者们由于C语言在软件工程上的先天缺陷,导致开发效率不高,所以决定拿出C++来看看用用,准备把libevent封装出一个类ACE的C++实现,首先来复读下C++对象模型吧。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 178,776
精华内容 71,510
关键字:

c++对象

c++ 订阅