首页 > 编程语言 >C++ 对象模型

C++ 对象模型

时间:2024-10-06 21:34:49浏览次数:7  
标签:调用 函数 Base1 对象 模型 Derived C++ int public

  1. 对象的内存布局
    • 非虚函数类对象
      • 对于不包含虚函数的类,对象的内存布局相对简单,其成员变量按照声明的顺序依次存储。例如:
        class SimpleClass {
        private:
            int num;
            double d;
        public:
            SimpleClass(int n, double dd) : num(n), d(dd) {}
        };
        
      • SimpleClass对象的内存中,首先存储int类型的num,然后存储double类型的d。假设int占4字节,double占8字节,那么一个SimpleClass对象的大小至少为12字节(考虑内存对齐等因素可能会有所增加)。
    • 包含虚函数的类对象
      • 如果一个类包含虚函数,对象的内存布局会包含一个虚函数表指针(vptr)。这个指针通常位于对象内存的开头(不同编译器可能有不同的实现方式)。例如:
        class VirtualClass {
        private:
            int num;
            double d;
        public:
            VirtualClass(int n, double dd) : num(n), d(dd) {}
            virtual void someVirtualFunction() {}
        };
        
      • VirtualClass对象的内存中,首先是虚函数表指针(通常在32位系统中占4字节,64位系统中占8字节),然后是成员变量numd。虚函数表是一个函数指针数组,存储了类的虚函数地址。当调用虚函数时,通过这个虚函数表指针找到虚函数表,再根据表中的指针调用相应的函数。
  2. 继承关系中的对象模型
    • 单继承
      • 在单继承关系中,派生类对象的内存布局是基类部分在前,派生类部分在后。例如:
        class Base {
        private:
            int baseNum;
        public:
            Base(int n) : baseNum(n) {}
        };
        
        class Derived : public Base {
        private:
            double derivedNum;
        public:
            Derived(int n, double d) : Base(n), derivedNum(d) {}
        };
        
      • Derived对象的内存中,首先是Base类的baseNum,然后是Derived类的derivedNum。如果Base类包含虚函数,那么Derived对象也会包含虚函数表指针(并且这个虚函数表指针会被正确初始化以反映Derived类的虚函数情况)。
    • 多继承
      • 多继承的对象模型较为复杂。例如,有类Base1Base2和派生类Derived
        class Base1 {
        private:
            int num1;
        public:
            Base1(int n) : num1(n) {}
        };
        
        class Base2 {
        private:
            double num2;
        public:
            Base2(double n) : num2(n) {}
        };
        
        class Derived : public Base1, public Base2 {
        private:
            char ch;
        public:
            Derived(int n, double d, char c) : Base1(n), Base2(d), ch(c) {}
        };
        
      • Derived对象的内存中,首先是Base1的部分(num1),然后是Base2的部分(num2),最后是Derived类特有的部分(ch)。如果基类包含虚函数,处理虚函数表指针会更加复杂,不同编译器可能采用不同的策略来确保正确的虚函数调用。
  3. 对象的构造与析构
    • 构造函数顺序
      • 在对象创建时,首先调用基类的构造函数,然后按照继承顺序依次调用派生类的构造函数。在构造函数内部,成员变量的初始化按照声明的顺序进行。例如,对于前面提到的Derived类:
        Derived(int n, double d, char c) : Base1(n), Base2(d), ch(c) {}
        
      • 首先调用Base1的构造函数初始化Base1部分,然后调用Base2的构造函数初始化Base2部分,最后初始化Derived类特有的ch成员。
    • 析构函数顺序
      • 析构函数的调用顺序与构造函数相反。当Derived类对象被销毁时,首先调用Derived类的析构函数,然后按照与继承顺序相反的顺序依次调用基类的析构函数。并且,如果基类的析构函数是虚函数,那么通过基类指针删除派生类对象时,可以确保正确调用派生类的析构函数。例如:
        Base1* ptr = new Derived(1, 2.0, 'a');
        delete ptr;
        
      • 如果Base1的析构函数不是虚函数,只会调用Base1的析构函数,可能导致Derived类资源无法正确释放;如果Base1的析构函数是虚函数,则会先调用Derived类的析构函数,再调用Base1的析构函数,正确释放资源。
  4. 函数调用机制(特别是虚函数调用)
    • 非虚函数调用
      • 对于非虚函数,函数调用在编译时就确定了。编译器根据函数名和作用域直接生成调用代码。例如:
        class NonVirtualClass {
        public:
            void nonVirtualFunction() {}
        };
        
        int main() {
            NonVirtualClass obj;
            obj.nonVirtualFunction();
            return 0;
        }
        
      • 编译器直接知道obj.nonVirtualFunction()应该调用NonVirtualClass类中的nonVirtualFunction函数,不需要在运行时进行额外的查找。
    • 虚函数调用
      • 虚函数的调用是在运行时确定的。当通过基类指针或引用调用虚函数时,会根据对象实际的类型来决定调用哪个函数。例如:
        class Base {
        public:
            virtual void virtualFunction() {
                std::cout << "Base::virtualFunction" << std::endl;
            }
        };
        
        class Derived : public Base {
        public:
            void virtualFunction() override {
                std::cout << "Derived::virtualFunction" << std::endl;
            }
        };
        
        int main() {
            Base* ptr = new Derived();
            ptr->virtualFunction();
            return 0;
        }
        
      • 这里ptrBase类的指针,但实际指向Derived类对象。当调用ptr->virtualFunction()时,由于virtualFunction是虚函数,会在运行时根据ptr实际指向的对象类型(即Derived类)来调用Derived类中的virtualFunction函数。这种机制实现了多态性,使得代码更加灵活和可扩展。

标签:调用,函数,Base1,对象,模型,Derived,C++,int,public
From: https://www.cnblogs.com/androidsuperman/p/18449442

相关文章

  • 总结C/C++中内存区域划分
    目录1.C/C++程序内存分配主要的几个区域:2.内存分布图1.C/C++程序内存分配主要的几个区域:1、栈区2、堆区3、数据段(静态区)4.代码段2.内存分布图 如图:static修饰静态变量成员——放在静态区intglobalVar是全局变量——放在静态区全局变量&&静态变量 放在......
  • Vue3 watch方法----监视对象
    使用watch时,如果想监视对象的内部属性值。需要使用watch的第三个参数的配置对象,手动开启深度监视。//使用watch时如果想监视对象内部的属性值,需要使用watch的第三个参数,手动开启深度监视watch(person,(newValue,oldValue)=>{console.log('person发生了变化',newValue,o......
  • C++ 算法学习——1.8 悬线法
    1.问题引入:对于一个矩形图,图中放置着不少障碍,要求出最大的不含障碍的矩形。2.分析:显然一个极大矩形是左右上下都被障碍挡住,无法再扩大的矩形,此时障碍也包括边界。3.方法:悬线法考虑以当前点所在行为下界,以往上能达到的最大距离为高度,正上方所有点的往左最大距离的最小值和往右......
  • c++中的读写锁
    读写锁是一种特殊的锁机制,允许多个线程同时读取共享数据,但在写入共享数据时,只有一个线程可以进行写操作,其他线程必须等待。这种机制对于读多写少的场景非常有效,可以提高并发性能。以下是通过shared_lock、unique_lock、shared_mutex和mutex的解释来说明读写锁的实现和应用。......
  • VC++ 6.0的安装及使用
    1.安装双击运行程序vc6_cn_full.exe进行安装如果需要更改安装目录,选择浏览进行安装地址的修改,否则点击下一步程序第一次启动会弹出提示框,可去掉“启动时显示提示”选项框,下一次就不会弹出该提示框    2. 一个简单的demo初学者建议选择“一个空程序”去创建控......
  • Java - 11 类与对象
    Java-11类与对象类类[属性,行为]->对象[属性,行为]publicclassTest{ publicstaticvoidmain(String[]args){Catcat1=newCat();//创建对象cat1.name="大宝";cat1.age="3";cat1.color="orange";......
  • 大模型~合集7
    我自己的原文哦~  https://blog.51cto.com/whaosoft/11566532# 语言模型是否会规划未来tokenTransformer本可以深谋远虑,但就是不做,语言模型是否会规划未来token?这篇论文给你答案。「别让YannLeCun看见了。」YannLeCun表示太迟了,他已经看到了。今天要介绍的这篇......
  • Java内存模型
    1.硬件的效率与一致性物理机遇到的并发问题与虚拟机中的情况有很多相似之处,物理机对并发的处理方案对虚拟机的实现也有相当大的参考意义。“让计算机并发执行若干个运算任务”与“更充分地利用计算机处理器的效能”之间的因果关系,看起来理所当然,实际上它们之间的关系并没有想象......
  • C++ explicit&noexcept关键字
    C++explicit&noexcept关键字explicit关键字在C++中,explicit关键字用于避免编译器在特定情况下进行隐式类型转换。它主要作用于构造函数和转换函数,防止不必要或意外的类型转换发生,从而提高代码的安全性和可读性。1.作用于构造函数当一个构造函数只接受一个参数时,它通常会......
  • c++ 键盘/鼠标交互
    c++键盘/鼠标交互鼠标操作点击加上如下宏定义#include<windows.h>#defineKEY_DOWN(VK_NONAME)((GetAsyncKeyState(VK_NONAME)&0x8000)?1:0)#defineKEY_UP(VK_NONAME)((GetAsyncKeyState(VK_NONAME)&0x8000)?0:1)如果获取左键的点击,可以使用如下的代码:KEY_D......