12.动态内存
最安全的使用和分配动态内存的方法是使用make_shared的标准库函数,形如make_shared<T>(args)
,返回一个shared_ptr智能指针,并指向类型为T的对象,用args参数进行初始化。(定义在头文件memory中)与顺序容器的成员emplace类似,都是直接使用其参数进行构造,而不是拷贝产生。
智能指针shared_ptr和普通指针不同的是,与之相连的会有一个计数器,这个计数器会记录有多少个地方对该对象进行了引用,当该计数器变为0时,它会自动销毁该对象。
使用动态内存的三种原因:
- 程序不知道一共需要多少个对象(容器)
- 程序不知道需要的对象的类型是什么
- 程序需要在多个对象间共享数据
直接管理内存
使用new和delete来直接管理内存,相较于智能指针而言,效果并不好容易遗忘等,具体使用如下:
\\可以使用new来申请获得一个无名的空间
int* p=new int;//默认初始化
string*p1 = new string("hello");
int* p3=new int(10);
vector<int> *pv=new vector<int>{1,2,3,4};//列表初始化
//也可以申请获得const类型的
const int *pi=new const int(10);//对于有默认构造函数的可以隐式初始化,
//其他的必须显式初始化
delete:释放由new申请得到的内存,对于不是指针的内容,还有已经释放过的指针指向内存使用会有潜在危害。如果忘记释放内存,会导致所谓的内存泄漏问题,只有到所有自由内存被用尽,才能发现问题;如果同一个内存被释放两次,会破坏自由内存;因此尽量使用智能指针来管理动态内存,避免使用new与delete来进行管理。
shared_ptr与new操作符混用
可以将一个由new、申请得到的动态内存,交付给智能指针来管理,如shared_ptr p(new int(10)),有智能指针p将管理一个值为10的内存空间。另外,必须使用直接初始化的方式进行如下操作是不允许的shared_ptr<int> pr=new int(1024);
会报错,这种间接操作是不允许的。
最好使用指针管理动态内存,即由new申请得到的,因为动态指针默认是使用delete来释放空间的,如果不是动态内存,则需要自己设计操作来完成释放操作,
例如shared_ptr<T> p(q,d)//q必须可以转换为<T>类型,p调用可调用对象d来代替delete
.
不要混合使用智能指针和普通指针————p413.
###unique_ptr
与shared_ptr类似只支持直接初始化,需要使用new申请返回的指针进行初始化,不允许赋值及拷贝,也即以下操作是不被允许的:
unique_ptr<int> p(p1)//错误,不允许拷贝,其中p1是另一个unique_ptr
p=p1//错误,不允许赋值
独享,一时刻只能有一个指针指向一个内存,不存在make_shared类似的函数返回一个unique_ptr的指针。另外,虽然unique_ptr不支持拷贝与赋值,但是它存在release方法和reset方法,可以将一段内存的所有权转移,以上代码目的的正确表达方式为:
unique_ptr<int> p(p1.release())//p1放弃对其指向内存的控制权,并将p1清空,
//转移所有权给p
unique_ptr<int> p3(new int(1024));
//或者使用reset,p3转移所有权给p
unique_ptr<int> p.reset(p3.release())//reset释放了p之前指向的内存
release函数会返回一个指针,以上是交给另一个智能指针管理,但是我们也可以把它赋给另一个普通指针,并用delete释放,但如下操作是错误的p.release()//p不会释放内存,且我们会丢失指针
,可以进行如下auto a=p.release() delete(a)
进行释放。
动态数组
动态数组并不是数组类型,利用new申请的一个数组,返回的是一个元素类型的指针,具体使用如下:
int* p=new int[42]//分配一个大小42的int数组,返回的指针指向第一个元素。
数组初始化
常用的初始化方式有:在申请的后面加小括号,表示进行空的值初始化,或者使用花括号进行列表初始化。
int* q=new int[10]()//对10个值进行值初始化
int* r=new string[10]{“hello”,“hi”,“c++”,string(10,‘c’)}//前四个元素按照列表进行初始化,后续的进行初始化,如果列表中元素多于数组内存则不会分配空间。
释放动态数组
在对动态数组进行释放时,和一般的delete不同的是需要在数组指针前加上一对[]
,形如delete []p
指示这是一个指向数组首元素的指针,之后将会逆序销毁数组中的元素,并销毁该指针。
- 智能指针与动态数组
可以使用智能指针unique_ptr来管理动态数组,但是需要在类型说明中加[]
,指明是一个数组指针,具体使用如下:
unique_ptr<int[ ]>up (new int[10]);
up.release()//默认使用delete[]进行销毁
并且可以使用下标运算符访问数组中的操作,例如up[3]
,访问数组中的第四个元素。
不建议使用shared_ptr进行管理。(需要自己提供删除器,不能使用下标运算符等)
allocator
在之前的new操作和delete中,申请内存和初始化操作,以及销毁和清空内存的操作都是在一起的,这就产生了不灵活的问题,在memory中allocator类提供了仅仅申请内存,不进行初始化的操作,具体初始化需要自己亲自执行,可以解决该问题,具体使用如下:
allocator<string> allo;//创建一个可以申请string类型的allocator对象
auto const p=allo.allocate(n);//分配n个未初始化的string
//初始化
auto q=p;
allo.construct(q++,"hello");
allo.construct(q++,"world");
//销毁,销毁的必须是自己构造的内存
while(q!=p){
allo.destroy(q);
--q;
}
allo.deallocate(p,n)//p必须是创建时返回的指针,n必须是创建时申请的大小。
//必须先执行上面的destroy操作,才能释放内存给系统
文本查询程序设计
设计思路:
- 使用vector将待查询的文件中的每一行视为vector的一个元素,进行存储,便于后续通过行号输出对应内容。
- 使用一个map存储对应单词和其出现的行数,行数存储在一个set中,作为键值对中的值进行存储。具体形式为
map<string,set<int> >
.
最后的显示效果为:
please enter a word to explore: to
to occurs 3 times
to (line 2 ) You can certainly do this if you want to, but there doesn’t seem to be any
to (line 4 ) data (which is a pointer) but rather the thing data points to, which is
to (line 5 ) perfectly legal to do with a const pointer. by David Schwartz.
please enter a word to explore: q
另外官方设计还采用shared_ptr管理两个类之间数据共享的问题,在自己的实现中没有使用。程序链接为
官方Header|Implemenation|Test
自己的实现Header|Implemenation|Test
小结
