类和对象最终与内存管理上

在这里插入图片描述

嗨喽,大家好呀,今天阿鑫给大家带来类和对象最终章以及内存管理上!经过半个多月的艰苦奋斗我们终于将要翻过类和对象这座山,下面让我们开始今天的博客学习吧!

类和对象最终与内存管理上

  1. 匿名对象
  2. c++内存管理方式
  3. operator new与operator delete函数
  4. 利用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函数的先后关系

  1. 对于后定义的对象先进行析构。
  2. 全局对象在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;
}

今天的博客就分享到这里啦!期待我们的下一次相遇,觉得阿鑫博客写的还可以的记得留下你的三连,拜拜!

  • 23
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值