动态内存:之前的程序使用对象有着严格定义的生存期,会自动销毁。C++支持动态分配对象,动态分配对象的生存期和他们在哪里创建是无关的,只有当显式的被释放,这些对象才会销毁。
标准库定义了智能指针对象可以自动释放内存。
new在动态内存中分配空间并且返回一个指向该对象的指针,delete接受一个动态对象的指针销毁该对象并释放相关的内存
使用动态内存的原因:
1.程序不知需要多少对象2.程序不知道对象类型3.程序需要在多个对象之间共享数据————允许多个对象共享相同状态
忘记释放内存会导致内存泄漏,释放了被指针指向的内存会导致非法指针。
标准库定义的智能指针可以自动释放指向的对象,share_ptr允许多个指针指向同一个对象,unique_ptr独占所指向的对象。
share_ptr类
默认初始化的智能指针保存一个空指针。
智能指针可以解引用。
shared_ptr独有操作:make_shared<T>(args) 返回一个ptr指向一个动态分配的类型为T的对象,用args初始化
shared_ptr<T>p(q) p 是shared_ptr q的拷贝此操作会递增q里的计数器
p.unqiue() 返回引用计数是否为1
p.use_count() 速度慢,返回和p共享对象的智能指针数目
make_shared 函数
最安全的分配和使用动态内存的方法是调用一个make_shared的函数,此函数在动态内存中分配一个对象并且初始化,返回指向这个对象的shared_ptr,和智能指针一样,包含在memory中
shared_ptr<int> p = make_shared<int>(42);
类似emplace 用参数来构造给定类型的对象,传递的参数必须和<T>的某个构造函数相匹配,然后进行值初始化
shared_ptr 的拷贝和赋值
当进行拷贝或者赋值操作每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象,可以认为每个shared_ptr都有一个关联的计数器,通常称为引用计数,无论何时我们copy一个shared_ptr,计数器将会递增,如:用一个s_ptr初始化另外一个s_ptr,或者把它作为一个参数传递给一个函数 ,或者把它作为函数的返回值,他的关联计数器就会增加。
而当我们给s_ptr赋予一个新的值或者,离开作用域,计数器就会减少。
一旦s_ptr计数器变为0,它就会自动释放自己管理的对象。(调用该对象的析构函数)
s_ptr在无用之后仍然保存的一种情况:将它放在一个容器中随后重排了容器。
定义STRBLOB类
定义一个管理string 的类,实现我们所希望的数据共享。
将一个vector保存在一个类定义对象中,为了避免它在离开作用域时被销毁,将其保存在动态内存中。
用make_shared 和shared_ptr 来构造类对象
直接管理内存:new delete
new 返回一个指向该对象的指针,对象可以采用传统构造方式也可以使用列表初始化。
用new分配const 对象 是合法的! 一个动态分配的const对象必须初始化!
当内存耗尽 new表达式就会失败,抛出一个bad_alloc 的一场,我们可以改变使用new的方式来阻止它抛出异常:
int *p2 = new (nothrow) int;如果分配失败返回一个空指针
释放动态内存 delete 将内存归还给系统
动态对象生存期直到被释放为止
delete之后重置指针:在delete一个动态对象之后,指针就变成了空悬指针,有一种方法可以避免该问题,在将要离开作用于之前释放关联的内存。或者给他赋值null
shared_ptr 和new 结合使用
可以使用new返回的指针来初始化智能指针.接受指针参数的智能指针构造函数是explicit 的,所以不能将一个内置指针隐式转换为一个只能指针,必须直接初始化形式
shared_ptr<int> p2(new int(1024));
p1的初始化隐式地要求编译器用一个new返回的int*来创建一个shared_ptr ,由于我们不能进行内置指针到智能指针的隐式转换,必须直接初始化.必须将shared_ptr显式绑定到一个想要返回的指针上.
返回语句中那个不能隐式转换一个内置普通指针!
必须shared_ptr<int> clone(int p)
{
return shared_ptr<int>(new int(p));
}
在默认情况下一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用delete来销毁构建他的对象.
不要混合使用智能指针和普通指针:使用一个内置指针来访问一个智能指针负责的对象是很危险的,因为我们无法知道对象何时会被销毁。
shared_ptr可以协调对象的析构,但是仅仅是自身的copy之间.
如果将一个内置指针初始化的shared_ptr作为参数传入,会导致错误的释放空间。
不要使用get初始化另一个智能指针或者为智能指针赋值
get用来将指针的访问权限传递.int *q = p.get();在使用q的时候要注意不能释放!
p.get()获得一个普通指针
如果执行以下代码会发生什么?
auto sp = make_shared<int>();
auto p = sp.get();
delete p;
sp的引用计数仍然为1,但是它指向的int对象已经被释放了
unique_ptr
和shared_ptr初始化的形式相同,必须直接初始化。
weak_ptr 是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr管理的对象,绑定到shared_ptr不会改变其引用计数,一旦最后一个只想对象的shared_ptr被销毁那么对象会被释放
调用lock()函数检查对象是否已经被释放
通过使用weakptr不会影响shared_ptr指向的对象的生存期,但是可以阻止用户访问一个不再存在的vector的企图.
动态数组
依次分配多个内存空间,new,allocator。大多数应用应该直接使用标准容器。new和数组:在类型名之后加一对方括号,在其中指明要分配的对象的数目.new分配多个数据,返回一个指向第一个数据的指针。
动态数组并不是数组类型! 得到了一个数组元素类型的指针。
初始化动态分配对象的数组
string *psa = new string[10]();10个空string
动态分配一个空数组是合法的:
可以用任意表达式来确定要分配的对象数目:
size_t n = getsize();int *p = new int[n];当我们用new分配一个大小为0的数组,new返回一个合法的非空指针!(对于零长度来说,这个指针和尾后指针一样) ,可以像使用为后迭代器一样使用。
释放动态数组
delete [] p;释放p指向的数组中的元素,逆序销毁。
allocator 类
new有一些灵活性上的局限,其中一方面表现在它将内存分配和对象构造组合在了一起,delete将对象析构和内存释放组合在了一起,造成不必要的浪费,我们分配单个对象时希望把内存分配和对象初始化组合在一起。因为在这种情况下我们几乎可以肯定对象的值。
当分配一大块内存时,我们通常计划在这块内存上按需构造对象,再次情况下,我们希望将内存分配和对象构造分离,这意味着可以分配大块内存,但只在真正需要的时候执行对象创建操作。
allocator分配的内存是未构造的,我们按需要在次内存中构造对象。construct 函数接受一个指针和零个或者多个额外函数在给定位置构造一个元素,额外参数用来初始化构造对象。
当我们用完对象之后,对每个构造的函数调用destroy来销毁。使用allocate的内存必须用construct构建对象,使用未构造的内存行为是未定义的。
使用标准库:
文本查询程序—— 在一个给定的文本里面查询单词:输出结果出现的次数和所在行的列表,如果一个单词在一行中出现多次那么只列出一次