C++ 动态内存 学习笔记

C++ 动态内存 学习笔记(一)

在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。

  • :在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

  • :就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。

  • 自由存储区:就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。

  • 全局/静态存储区:全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。

  • 常量存储区:这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。

动态内存与智能指针

由于newdelete的使用笔记繁琐,为了更容易(同时也更安全)地使用动态内存,新的标准库提供了两种智能指针,类型来管理动态对象。智能指针地行为类似常规指针,重要的区别是它负责自动释放所指向的对象。

  • shared_ptr:允许多个指针指向同一个对象
  • unique_ptr:所指向的对象不能被其他指针指向
  • weak_ptr:一种弱引用,指向shared_ptr所管理的对象

shared_ptr类

定义方法:

#include<memory>

shared_ptr<string> p1; // 空指针,可以指向string
shared_ptr<vector<int>> p2; // 空指针,可以指向int的vector

if(p1 && p1->empty())
{
    *p1 = "hi"; // 解引用p1,将一个新值赋予string
}

在这里插入图片描述
在这里插入图片描述

make_shared函数

这是最安全的分配和使用动态内存的方法。定义方法:

// 指向一个值为42的int的shared_ptr
shared_ptr<int> p3 = make_shared<int>(42);
// 指向一个值为"9999999999"的string
shared_ptr<string> p4 = make_shared<string>(10, '9');
// 指向一个值初始化的int,即值为0
shared_ptr<int> p5 = make_shared<int>();

auto p6 = make_shared<vector<string>>();
shared_ptr的拷贝和赋值

当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象:

// 指向一个值为42的int的shared_ptr
shared_ptr<int> p = make_shared<int>(42);
auto q(p); // p和q指向相同对象,此对象有两个引用者

每个shared_ptr都有一个关联的计数器,通常称其为引用计数。无论何时我们拷贝一个shared_ptr,计数器都会递增。例如,当用一个shared_ptr初始化另一个shared_ptr,或将它作为参数传递给一个参数以及作为函数的返回值时(被拷贝),它所关联的计数器就会递增。

当我们给shared_ptr赋予一个新值或是shared_ptr被销毁时,计数器就会递减。一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。

shared_ptr自动销毁所管理的对象

当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会通过析构函数来自动销毁此对象。

shared_ptr还会自动释放相关联的内存
#include<iostream>
#include<string>
#include<vector>
#include<memory>
using namespace std;

class student
{
    public:
        int age;
        int name;
        int sex;
};

// book函数返回一个shared_ptr,指向一个动态分配的对象
shared_ptr<student> book(student arg)
{
    // shared_ptr负责释放内存
    return make_shared<student>(arg);
} // 由于shared_ptr作为返回值,它指向的内存的值被保持

void use_book(student arg)
{
    shared_ptr<student> p = book(arg);
} // p离开了作用域,它指向的内存会被自动释放掉

int main()
{
    // 指向一个值为42的int的shared_ptr
    student Tom;
    use_book(Tom);
    return 0;
}

总而言之,对于一块内存,shared_ptr类保证只要有任何shared_ptr对象引用它,它就不会被注释掉。但是,保证shared_ptr在无用之后不再保留就非常重要类。如果我们忘记了销毁程序不再需要的shared_ptr,程序仍会正确指向,但会浪费内存。
shared_ptr在无用之后仍然保留的一种可能情况是,我们将它存放在一个容器中,随后重排了容器,从而不再需要某些元素。在这种情况下,我们应该确保用erase删除那些不再需要的shared_ptr元素。

直接管理内存
使用new动态分配喝初始化对象
int *p1 = new int; // pi指向一个动态分配的、未初始化的无名对象
int *p2 = new int(1024); // pi指向的对象的值未1024
int *p3 = new int(); // 值初始化未0

int a = 10;
auto p4 = new auto(a); // p4指向一个与a类型相同的对象,该对象用obj进行初始化

const int *p5 = new const int(1024); // 分配并初始化一个const int
内存耗尽

一旦一个程序用光了它所有可用的内存,new表达式就会失败。默认情况下,如果new不能分配所要求的内存空间,它会抛出一个类型未bad_alloc的一次。我们可以改变使用new的方式来阻止它抛出异常:

// 如果分配失败,new抛出一个std::bad_alloc
int *p1 = new int;
// 如果分配失败,new返回一个空指针
int *p2 = new (nothrow) int;
释放动态内存
delete p1;

注:虽然一个const对象的值不能被改变,但它本身是可以被销毁的。

shared_ptr和new结合使用(虽然可以,但是推荐使用make_shared而不是new)

我们可以用new返回的指针来初始化智能指针:

shared_ptr<int> p2(new int(42)); // p2指向一个值为42的int

我们必须将shared_ptr显式绑定到一个想要返回的指针上:

shared_ptr<int> clone(int p)
{
	return shared_ptr<int>(new int(p));
}

默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用delete释放它所关联的对象。
在这里插入图片描述
在这里插入图片描述

其他shared_ptr操作

我们可以用reset来将一个新的指针赋予一个shared_ptr:

p.reset(new int(1024));

与赋值类似,reset会更新引用技术,如果需要的话,会释放p指向的对象。reset成员经常与unique一起使用,来控制多个shared_ptr共享的对象。在改变底层对象之前,我们检查自己是否是当前对象仅有的用户。如果不是,在改变之前要制作一份新的拷贝:

if(!p.unique())
	p.reset(new string(*p)); // 我们不是唯一用户;分配新的拷贝
*p += newVal; // 现在我们知道自己是唯一的用户,可以改变对象的值

unique_ptr

与shared_ptr不同,没有类似make_shared的标准库函数返回一个unique_ptr。当我们定义一个unique_ptr时,需要将其绑定到一个new返回的指针上。:

unique_ptr<double> p1; // 可以指向一个double的unique_ptr
unique_ptr<double> p2(new double(42.0));

由于一个unique_ptr拥有它指向的对象,因此unique_ptr不支持普通的拷贝或赋值操作:

unique_ptr<double> p2(new double(42.0));

错误:
unique_ptr<double> p3(p3);

unique_ptr<double> p3;
错误:
p3 = p2;

在这里插入图片描述
虽然我们不能拷贝或赋值unique_ptr,但可以通过调用release或reset将指针的所有权从一个(非const)unique_ptr转移给另一个unique:

将所有权从p1转移给p2
unique_ptr<string> p2(p1.release()); release将p1置为空
unique_ptr<string> p3(new string("Trex"));

将所有权从p3转移给p2
p2.reset(pe.release()); reset释放了p2原来指向的内存
传递unique_ptr参数和返回unique_ptr

不能拷贝unique_ptr的规则有一个例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr。

unique_ptr<int> clone(int p)
{
	unique_ptr<int> ret(new int(p));
	// ...
	return ret;
}

weak_ptr

weak_ptr可以看出shared_ptr的辅助工具。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦shared_ptr被销毁,对象就会被释放(不管是否有weak_ptr指向对象)。
在这里插入图片描述

auto p = make_shared<int>(42);
weak_ptr<int> wp(p); // wp弱共享p,p的引用计数未改变

动态数组

大多数应用都没有直接访问动态数组的需求。当一个应用需要可变数量的对象时,我们通常采用更简单、更快速并且更安全的——STL容器。因此这里暂时不学动态数组了…哈哈哈哈

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值