嗨喽,大家好呀,今天阿鑫给大家带来类和对象最终章以及内存管理上!经过半个多月的艰苦奋斗我们终于将要翻过类和对象这座山,下面让我们开始今天的博客学习吧!
类和对象最终与内存管理上
- 匿名对象
- c++内存管理方式
- operator new与operator delete函数
- 利用new实现一个简单链表
1.匿名对象
class A
{
public:
A(int b = 0)
:_a(b)
,_a1(b)
,_a2(b)
{
cout << "A(int b = 0)" << endl;
}
//explicit
//单参数类型转换
/*A(int a)
:_a(a)
{
cout << "A(int a)" << endl;
}*/
//多参数类型转换
A(int m, int n)
:_a1(m)
, _a2(n)
{
cout << "A(int m, int n)" << endl;
_a = 0;
}
A(const A& aa)//拷贝构造
:_a(aa._a)
{
cout << "A(const A& aa)" << endl;
}
~A()
{
cout << "~A()" << endl;
_a = -1;
_a1 = -1;
_a2 = -1;
}
private:
int _a;
int _a1;
int _a2;
};
A(2);
注意:匿名对象,生命周期只存在这一行
我们补充一下析构的顺序以及全局变量和静态变量构造与main函数的先后关系
- 对于后定义的对象先进行析构。
- 全局对象在main函数前就构造了,而静态对象生命周期是全局的但是第一次调用时才会进行构造,只会初始化一次
在开始我们的讲解之前,我们先来回顾一下c和c++的程序内存空间划分,并且用一道题目来回顾一下我们之前学习过的内容
答案:CCCAA
AAADAB
有问题的同学自行阅读这张图,相信就能解决你的问题。
2.C++内存管理方式
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
2.1 new/delete操作内置类型
下面给出示范
int main
{
//对于内置类型
int* p1 = new int[10];
int* p2 = new int[10];
delete p1;
delete[] p2;
return 0;
}
默认不初始化,但是可以初始化
对于自定义类型
class A
{
public:
A(int b = 0)
:_a(b)
{
cout << "A(int b = 0)" << endl;
}
//explicit
//单参数类型转换
/*A(int a)
:_a(a)
{
cout << "A(int a)" << endl;
}*/
//多参数类型转换
A(int m, int n)
:_a1(m)
, _a2(n)
{
cout << "A(int m, int n)" << endl;
_a = 0;
}
A(const A& aa)//拷贝构造
:_a(aa._a)
{
cout << "A(const A& aa)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
int _a1;
int _a2;
};
int main()
{
A* p1 = new A(3);
delete p1;
A* p2 = new A({4,5});
delete p2;
A* p3 = new A;//不传参调用默认构造
delete p3;
A* p4 = new A[10]{ 1,2,3,4,5 };//隐式类型转换
delete[] p4;
A* p5 = new A[10]{ 1,2,3,4,{5,6} };
delete[] p5;
return 0;
}
注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。
3 .operator new与operator delete函数
3 .1operator new与operator delete函数
new和delete是用户进行动态内存电请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
A* p1 = new A;
delete p1;
当我们在执行这两句代码时
第一句会转换成
operator new->(malloc)+构造函数
第二句会转换成
析构 + operator delete
并且是由顺序的,我们可以通过汇编语言来看到这个过程
3 .2operator new和构造函数的区别
当您在C++中使用new关键字来创建一个对象(比如一个栈类实例)时,以下是发生的事情的简要概述:
需要注意的是:内存分配时并没有对成员变量初始化,是编译器通过计算判断出需要创建的成员变量内存需要的大小,这个内存大小通常是根据对象的成员变量所确定,等到构造函数时才会真正对成员变量初始化
1.内存分配:new操作符首先调用operator new来为对象分配足够的内存空间。这个空间的大小通常等于对象的所有成员变量所占用的空间的总和,再加上任何可能由于内存对齐而产生的填充。
2.构造函数调用:一旦内存被成功分配,new操作符会调用对象的构造函数。构造函数负责初始化对象的成员变量。这可能包括为成员变量设置初始值,以及执行任何必要的初始化代码(比如为栈上的资源开辟空间)。
对于栈类来说,构造函数可能会执行以下操作:
初始化成员变量,比如栈顶指针或栈的大小。
为栈分配内部存储空间,这通常涉及到动态内存分配(比如使用new为数组分配空间)。
设置栈的初始状态(比如将栈顶指针设置为指向栈的底部)。
重要的是要理解,operator new只负责分配内存,而对象的实际初始化工作是由构造函数完成的。构造函数可以执行任何必要的初始化步骤,包括为对象的成员变量或内部资源分配空间。
下面是一个简单的栈类的例子,它展示了构造函数如何为栈的内部数组分配空间:
class Stack {
private:
int* elements; // 指向存储栈元素的数组的指针
size_t capacity; // 栈的容量
size_t size; // 栈中当前元素的数量
public:
// 构造函数
Stack(size_t cap) : capacity(cap), size(0) {
// 使用 new 为存储栈元素的数组分配内存
elements = new int[capacity];
// 此时,elements 指向一个包含 capacity 个 int 的数组,用于存储栈元素
}
// 析构函数
~Stack() {
// 释放存储栈元素的数组所占用的内存
delete[] elements;
}
// 其他栈操作函数...
void push(int value) {
if (size < capacity) {
elements[size++] = value;
} else {
// 处理栈满的情况,可能抛出异常或自动扩容等
}
}
int pop() {
if (size > 0) {
return elements[--size];
} else {
// 处理栈空的情况,可能抛出异常等
}
}
// ... 其他成员函数 ...
};
在这个例子中:
Stack 类的成员变量包括一个指向 int 数组的指针 elements、一个表示栈容量的 size_t 类型变量 capacity 和一个表示当前栈大小的 size_t 类型变量 size。
构造函数接受一个 cap 参数,并使用 new 为 elements 分配一个大小为 cap 的 int 数组。这个数组用于存储栈中的元素。
析构函数使用 delete[] 释放 elements 指向的数组所占用的内存。
push 和 pop 方法操作这个动态分配的数组,以实现栈的基本功能。
所以,operator new 只负责分配 Stack 对象本身的内存,而栈中实际存储元素的数组是通过构****造函数内部额外的 new 调用分配的。这允许我们根据需要在运行时动态地调整栈的大小。
4. 利用new实现一个简单链表
struct ListNode
{
struct ListNode* _next;
int _val;
struct ListNode(int val)
:_next(nullptr)
,_val(val)
{}
};
void func()
{
struct ListNode* node1 = new struct ListNode(1);// //得到的是一个ListNode类的指针,要用指定类型的指针接受
// //new失败了会抛异常,不需要再检查返回值
struct ListNode* node2 = new struct ListNode(2);
struct ListNode* node3 = new struct ListNode(3);
node1->_next = node2;
node2->_next = node3;
delete node1;
delete node2;
delete node3;
}
int main()
{
try
{
func();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}