前言
本系列文章承接C语言的学习,需要有C语言的基础才能学会哦~
第8篇主要讲的是有关于C++的内部类、匿名对象、对象拷贝时的编译器优化和内存管理。
C++才起步,都很简单!!
目录
operator new和operator delete函数
内部类
一个类定义在另一个类里,这个类就是内部类。
//简单代码
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();
※内存池(了解即可,后面会深度学)
从堆区里一次性开辟出较大的内存空间作为内存池,通过数据结构管理起来,申请空间从内存池取出,可以减少频繁申请堆区,因为在堆区只能依次申请空间,效率低。
❤~~本文完结!!感谢观看!!接下来更精彩!!欢迎来我博客做客~~❤