C++ 动态内存 学习笔记(一)
在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。
-
栈:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
-
堆:就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
-
自由存储区:就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。
-
全局/静态存储区:全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。
-
常量存储区:这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。
动态内存与智能指针
由于
new
和delete
的使用笔记繁琐,为了更容易(同时也更安全)地使用动态内存,新的标准库提供了两种智能指针,类型来管理动态对象。智能指针地行为类似常规指针,重要的区别是它负责自动释放所指向的对象。
- 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容器。因此这里暂时不学动态数组了…哈哈哈哈