C++ 类(第三章)

前言

本章介绍一下C++类的内存方面的知识

一、C++ 封闭类

一个类包含成员对象时,该类成为封闭类。

成员对象意思是一个类的成员变量是另一个类的对象

封闭类对象的生成过程:

  1. 先执行成员对象的构造函数
  2. 在执行封闭类的构造函数

成员对象的构造函数调用顺序与其在封闭类中的声明顺序有关,与初始值列表无关

封闭类对象的消亡过程:

  1. 先执行封闭类析构函数
  2. 再执行成员对象的析构函数

参考:c++——成员对象和封闭类

二、C++类也是一种作用域

类中的普通成员只能通过对象访问;
类中的静态成员可以通过对象、也可以通过类访问;
类中typedef定义的类型只能通过类进行访问。

//c++作用域的研究
#include <iostream>
#include <string>
using namespace std;

class A{
public:
    typedef int INT;
    static void show(){cout<<"show()"<<endl;}
    void work(){cout<<"work()"<<endl;}
};

int main(){
    A a;
    a.work();
    a.show();
    A::show();
    A::INT n=10;
    cout<<n<<endl;
    //a.INT m=10;报错
    return 0;
}

类的成员函数定义在外部,一旦遇到类名,剩余部分都在类的作用域之内,如果该函数的返回值类型定义在类的内部则需要指明那个类定义过他。例如上面的代码修改为:

class A{
private:
	int m;
public:
    typedef int INT;
    void show(INT N);
    INT work();
};

void A::show(INT N){//A作用域内的INT
	cout<<"show()"<<endl;
	m=100;//A作用域内的m
}

A::INT A::work(INI P){
	cout<<"work()"<<endl;
	return p;
}

参考:类其实也是一种作用域

三、C++ 类的内存模型

我们首先要明确C++类存在的部分:

  • 成员变量
  • 成员函数
  • 构造函数
  • 析构函数
  • 静态成员变量
  • 静态成员函数
  • 虚函数(派生类重写,通过动态绑定实现运行时动态)
  • 纯虚函数(含有纯虚函数的类为抽象类,不能被实例,纯虚函数可以有函数体,定义在类外)
  • 虚析构函数

对于对象而言,其内存模型不包含函数(在上一章中介绍过C++ 成员函数的编译过程),函数单独存放,通过地址调用对象,其内存模型和占用的空间大小受以下几个因素影响:

  • 成员变量
  • 虚表指针(只是一个指针,指向虚函数表,不占用对象内存,指针大小与机器位数有关)
  • 虚基类表指针(有几个基类,就有几个该指针)
  • 字节对齐

派生类如果有和基类重名的成员,调用时会覆盖掉基类的成员,也可以显示声明出基类成员,在派生类对象的内存中,基类的成员变量仍然会占有内存。派生类对象继承基类时,会把自身的虚函数加入到第一个基类的虚函数表中;如果重写了基类的虚函数,就会把对应的虚函数表中的地址替换成派生类的虚函数地址。虚函数表位于只读数据段(.rodata),也就是C++内存模型的常量区,虚表指针在对象的内存中

主要参考这篇C++ 对象内存模型文章,但是我发现这篇文章有些地方说法是不对的,下面的实验没有改变,我从新组织了一下讲解。推荐看看下面四篇博客。
C++ 继承详解
C++ 虚函数详解
虚析构函数详解
C++中的纯虚函数
C++中的虚函数表和虚函数在内存中的位置

3.1 类的大小实验:

对于没有任何函数成员和数据成员的类,编译器给予一个字节的标志位

//无成员的类
class A
{
 
};

int main()
{
    cout << sizeof(A) << endl;
    A a;
    cout << sizeof(a) << endl;
}
//输出
1
1

仅包含虚函数的类,其对象增加一个虚表指针(32位机器是4,64位机器是8),指向虚函数表。

class A{
public:
    virtual void base(){
        cout << 1 << endl;
    }
};
int main(){
    cout << sizeof(A) << endl;
    A a;
    cout << sizeof(a) << endl;
}
//输出结果
8
8

包含普通成员的类按成员变量顺序以及字节对齐存放在内存中,涉及继承则基类在前,派生类在后。下面例子为单一继承,可以看到基类首先会包含一个虚表指针,成员变量按字节对齐的方式最终为16,派生类继承了基本的成员(派生类的虚函数放在了基本的虚函数表中,所有派生类对象只有一个虚表指针),加上自身的成员变量,按字节对齐(基类在前,派生类在后)最后为24

class A{ 
private:
    char c;
    int i;
public:
    virtual void base()
    {
        cout << 1 << endl;
    }
};
class B :public A{
private:
    char c;
    int i;
public:
    virtual void drive()
    {
        cout << 2 << endl;
    }
};
int main(){
    cout << sizeof(A) << endl;
    A a;
    cout << sizeof(a) << endl;
    B b;
    cout << sizeof(B) << endl;
    cout << sizeof(b) << endl;
}
//输出
16
16
24
24

如果类不包含虚函数,则按常规的字节对齐方式进行对齐。double c[0]虽然不占空间,但通知编译器以8字节对齐

class C{
private:
    char cr;
    short b;
    double c[0];
};
int main(){
    cout<<sizeof(C)<<endl;
    C c;
    cout<<sizeof(c)<<endl;
}
//输出
8
8

存在多个基类时,派生类对象将包含多个虚函数表(两个基类就有两个虚函数表),每一张存放自身虚函数与从对应基类继承过来的虚函数。其派生类内存占用为从两个基类继承而来的数据成员+自身数据成员+2个虚表指针:
(4+4)x2+4+4+(2x8)=40;

class A{
private:
    char c;
    int i;
public:
    virtual void base(){
        cout << 1 << endl;
    }
};
class B{
private:
    char c;
    int i;
public:
    virtual void base(){
        cout << 1 << endl;
    }
};
class C :public A,public B{
private:
    char c;
    int i;
public:
    virtual void drive(){
        cout << 2 << endl;
    }
};
int main(){
    cout << sizeof(C) << endl;
    C c;
    cout << sizeof(c) << endl;
}
//输出
40
40

总结

本来还想写写继承、虚函数之类的,上面推荐的博客已经写的特别完善了,接下来我就看看书,没什么补充就接着看其他内容了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值