从C++开始的编程生活(8)——内部类、匿名对象、对象拷贝时的编译器优化和内存管理

发布于:2025-09-11 ⋅ 阅读:(24) ⋅ 点赞:(0)

前言

本系列文章承接C语言的学习,需要有C语言的基础才能学会哦~
第8篇主要讲的是有关于C++的内部类匿名对象对象拷贝时的编译器优化内存管理
C++才起步,都很简单!!


目录

前言

内部类

性质

匿名对象

性质

※对象拷贝时的编译器优化

内存管理

内存分区

​编辑

内核空间

静态区/数据段

常量区/代码段

虚拟进程地址空间

动态内存管理函数

C++内存管理

基本语法

operator new和operator delete函数

new和delete的原理

定位new表达式(placement-new)

※内存池(了解即可,后面会深度学)


内部类

一个类定义在另一个类里,这个类就是内部类。

//简单代码
class A
{
    public:
    
    private:
    class B
    {
        private:
            int _b;
    }
}

性质

内部类默认是外部类的友元
本质是一种封装方式。若类B实现出来是为了给类A使用,那么可以把B设置为A的内部类。
内部类受访问限定符限制,被privact和protect修饰的内部类只能被外部类使用。

匿名对象

没有标识符标识的对象为匿名对象,反之为有名对象。

//有名对象,d1为其名
Date d1(1999,9,9);
//匿名对象
Date(2000,1,1);

性质

匿名对象的生命周期只有一行代码。也就是匿名对象构造后,下一步就会被析构(一次性筷子)。
②匿名对象具有常性,只能被const引用
被const引用后,匿名对象的生命周期被延长至与该引用同步。

※对象拷贝时的编译器优化

现代编译器会在不影响代码正确性的情况下,提高效率,在底层进行优化实现。
不同的编译器,优化方式不同,以下优化方式以VS2019的debug为例。
越新的编译器,优化越好,甚至会有跨多行合并优化代码。

例1:隐式类型转换

A aa1 = 1;//隐式类型转换

语法上:该隐式类型转换先以1为参数构造临时对象,在将临时对象拷贝构造给aa1。
实际上:直接以1为参数构造新对象aa1。
从而减少对内存的使用,提高代码运行效率。

例2:传值传参

void func(A aa)
{
    //·······
}
int main()
{
    func(1)
    return 0;
}

语法上:该传值传参先以1为参数构造临时对象,在将临时对象拷贝构造给aa,然后传入func。
实际上:直接以1为参数构造对象aa传入func。
从而减少对内存的使用,提高代码运行效率。

例3:传值返回

A f2()
{
    A aa;
    return aa
}

int main()
{
    A aa2 = f2();
    return 0;
}

语法上:构造f2函数局部域的一个对象aa,然后返回aa时将aa拷贝构造为临时对象,接着临时对象在拷贝构造给aa2。(构造->拷贝->拷贝)。
实际上:构造f2函数局部域的一个对象aa,再直接拷贝给aa2。

内存管理

内存分区

内存分区主要分为四个区(还有其他不常用的区,这里省去):栈区、堆区、静态区和常量区

内核空间

用户代码,不可以读写。

局部变量,参数,返回值,对象,函数调用等等要在栈区开辟栈帧,主要存储临时的、局部的变量(栈区占比不大,M为单位)。栈向下增长,越后开辟的空间地址越小。

malloc、new等动态内存管理开辟的空间,在堆区(占比较大,以G为单位)。堆向上增长,越后开辟的空间地址越大(可能会在动态内存管理时被打乱顺序)

静态区/数据段

全局数据和静态数据放在静态区。

常量区/代码段

存放常量和编译完成的指令

虚拟进程地址空间

地址空间是虚拟的,需要页表等算法进行映射,这个部分知识涉及操作系统。

int a;
static int b = 1;
int main()
{
    int num[] = {1,2,3,4};
    static int c = 1;
    char arr[] = "abcdefg";
    const char* p1 = "abcdefg";
    char* p2 = (char*)malloc(sizeof(char) * 10);
}

如上代码
a、b、c存放在静态区
num、arr数组存放在栈区
p2存放在堆区
“abcdefg”存放在常量区

动态内存管理函数

C语言动态内存管理函数 malloc / calloc / realloc / free 依旧可以使用

tips:malloc / calloc / realloc的区别?------>calloc会在malloc的基础上,初始化开辟空间;realloc重新分配更多的空间,也覆盖malloc的功能。

C++内存管理

C++有新的方式进行内存管理,使用 new / delete 操作符进行内存管理。

基本语法

int main()
{
    //动态申请1个int类型空间
    int *ptr1 = new int;
    //动态申请1个int类型空间,并初始化为10
    int *ptr2 = new int(10);
    
    //释放开辟的int类型空间
    delete ptr1;
    delete ptr2;

    //动态申请3个int类型的空间
    int ptr3 = new int[3];
    //动态申请3个int类型的空间,并依次初始化为1,2,3
    int ptr4 = new int[3]{1,2,3};
    //动态申请5个int类型的空间,并依次初始化为1,2,3,为指定初始化的空间,默认初始化为0
    int ptr5 = new int[5]{1,2,3};

    //释放开辟的int类型数组
    delete[] ptr3;
    delete[] ptr4;
    delete[] ptr5;

    return 0;
}
    

以上为new和delete的使用,改为malloc和free基本没有区别。

而new开辟自定义类型:

//malloc开辟
A* p1 = (A*)malloc(sizeof(A));
//new开辟
A* p2 = new A;
A* p3 = new A(10);
A* p4 = new A[10];//申请空间,构造10次
A* p5 = new A[2]{1,2};//申请空间,构造2次
A* p6 = new A[2]{p2,p3};//申请空间,构造2次

区别:
new之于malloc,不只是申请空间,还会调用其默认构造函数。
错误时不返回NULL,而是抛出异常。
不需要手动填入申请空间大小

而delete释放自定义类型:

delete p2;
delete p3;
delete[] p4;//申请空间,析构10次
delete[] p5;//申请空间,析构2次
delete[] p6;//申请空间,析构2次

区别:delete之于free,不只是释放空间,还会调用其析构函数。

综上,对于自定义类型,还是优先使用new和delete。

operator new和operator delete函数

这不是对操作符new和delete的重载,它们实际上是两个全局函数使用new和delete时,在底层会调用相应的全局函数

在底层源码中,new实际上是对malloc的再次扩展封装。
malloc错误后会抛出一个nullptr,new会遇到malloc抛出的空指针后,首先执行用户提供的应对措施,如果不提供,就抛出一个异常。(异常为后面的内容,了解即可)。

在底层源码中,delete实际上是对free的再次扩展封装。
进行错误检查等处理后,再通过free释放空间

operator new和operator delete也可以直接调用。

new和delete的原理

调用new:①调用operator new函数,开辟空间。②若开辟自定义类型空间,最后依次调用其默认构造函数。

调用delete:①若是释放自定义类型空间,首先依次调用其析构函数。②调用operator delete函数,释放空间。

定位new表达式(placement-new)

作用是在已分配的原始内存空间调用构造函数初始化一个对象。

A *p1 = (A*)operator new(sizeof(A));
//new + 需构造的指针 + 对象类型 + 初始化参数
new(p1)A(10);

为什么要这样做呢?因为不是new开辟的对象,不可以直接初始化,必须要用这种格式

p1->A(10);//错误的

但是,析构可以直接调用。

p1->~A();

※内存池(了解即可,后面会深度学)

从堆区里一次性开辟出较大的内存空间作为内存池,通过数据结构管理起来,申请空间从内存池取出,可以减少频繁申请堆区,因为在堆区只能依次申请空间,效率低。

❤~~本文完结!!感谢观看!!接下来更精彩!!欢迎来我博客做客~~❤


网站公告

今日签到

点亮在社区的每一天
去签到