发布于2021-07-24 20:25 阅读(809) 评论(0) 点赞(30) 收藏(0)
1).动态分配对象的生存期和它们在哪里创建是没有关系的,只有显式地被释放时,这些对象才会被释放。
2).为了解决动态对象能够被正确释放的问题,设置了智能指针。当一个对象应该被释放时,指向它的智能指针可以确保自动地释放它。
3).静态内存。
static
变量。static
数据成员4).栈内存。保存局部变量。
5).静态内存和栈内存,由编译器自动创建和销毁。
6).程序的自由空间,或者堆。程序可以用堆,存储动态分配(程序运行时分配)的对象(它的创建和销毁由程序代码显式表示)。
1).动态内存管理的运算符。
new
,在动态内存中为对象分配一个空间并返回一个指向该对象的指针。delete
,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。2).问题。
3).解决。两个智能指针,和一个伴随类。
shares_ptr
,允许多个指针同时指向一个对象。unique_ptr
,独占一个对象。weak_ptr
,它是一个弱引用。指向的是shared_ptr
所管理的对象。memory
中。1).智能指针实现的机制。依靠智能指针类里面的引用计数器成员。
引用计数何时增减。
ptr
被销毁;或者一个ptr
被赋予新值时;引用计数器减少。关于引用计数器。
2).介绍智能指针。
{
shard_ptr<string> p1;//指向string对象的空指针
shared_ptr<list<int>> p2;//指向int类型的list的空指针。
}
make_shared
函数构造智能指针并可以选择进行初始化。如果没有初始化,那么即使进行**值初始化。**这是分配和使用动态内存最安全的方法。该函数定义头文件memory
中。(使用时需要类型,类似于模板;与emplace
的类似,可以用参数来构造对象。){
auto p1 = make_shared<int>(42);//p指向一个值为42的int类型。
shared_ptr<string> p2 = make_shared<string>(10,'2');//p2指向内容为10个2的string类型。
shared_ptr<int> p3 = make_shared<int>();//p3指向一个值初始化为0的int类型
}
{
initializer_list<string> li = {};
make_shared<vector<string>>(li);
}
4).智能指针支持的操作。
操作名称 | 相关介绍 |
---|---|
shared_ptr和unique——ptr 都支持的操作。 | |
shared_ptr<T> sp; | 空智能指针。 |
unique<T> up; | |
p | 将p作为一个条件。非0返回true |
*p | 解引用 |
p->mem | 等价于(*p).mem |
p.get() | 返回与p指向同一个对象的内置指针。 |
swap(p,q) | 交换指针的值 |
q.swap§ |
5).shared_ptr
独有的操作。
操作名称 | 相关描述 |
---|---|
make_ptr<T>(agrs) | 返回一个指向动态分配的类型为T的对象的shared——ptr 。该对象用args 进行初始化。 |
shared_ptr<T> p(q) | 进行拷贝初始化。q 中的指针必须可以转换为T* ,p 是shared_ptr 类型的指针。 |
p = q | p,q都是shared_ptr ,并且它们所指向的类型必须可以进行相互转换。 |
p.use_count() | 返回的是与p共享对象的智能指针的数量,可能很慢,主要用于调式。 |
p.unique() | 如果,p.use_count()的数量为1,返回的是true ,否则返回的是false |
6).析构函数。
7).注意。以下情况引用计数不是为0。
{
void ues_factory(T args)
{
shared_ptr<F> p = make_ptr<F>(args);
return p;//由于进行了拷贝,引用计数递增了
//虽然p离开了局部作用域会被销毁。
//但是内存不会被释放。
}
}
8).当我们在一个容器中保存一些智能指针时,如果我们不需要这些指针了。只需要将他们erase
就可以进行删除。实际中这一点进行被遗忘。
9).为什么使用动态生存期的资源。
vector
,我们随着用户输入才知道,需要多少的空间。容器的动态内存机制。template<typename T>
)vector
拷贝给另一个vector
,虽然它们的内容是一样的,但是,它们不是共享的。一个vector
的生命期到了,它里面的元素也就释放了。我们要实现的是,当一个对象进行拷贝时,它所指向的底层数据是以被引用的方式被获取的,而不是底层数据的拷贝。练习,
const
版本的成员函数,返回值和this
类型都需要是const
。1).使用new关键字创建指针。
new
关键字分配的空间是没有命名的,返回的是指针指向该对象。{
//进行默认初始化
int *p = new int;
string *q = new string;
//进行值初始化
int *p = new int();
string *q = new string();
//对于类,不论是进行默认初始化还是值初始化都是调用默认构造函数,
// 对于内置类型,则由很大差异,前者是未定义,后者int则是0
//显式初始化。(进版本只能使用())
int *p = new int(12);
int *p(new int(42));
string *q = new string("hello world!");
string *q = new string(10,'a');
//在新版本中,还支持使用{}进行列表初始化
vector<int> *p = new vector<int>{1,2,3,4,5,6};
}
2).使用auto
关键字进行自动识别。
{
auto p = new auto(obj);//例如obj是一个int类型等。
//只能有一个初始化器。
auto q = new auto{a,b,c};//错误
}
3).申请对于const
对象的指针。
const
指针的释放方式是一样的。delete p;
即可。{
const string *p = new const string;
delete p;
}
const
对象必须进行初始化。可以是显式地,也可以是隐式地(例如,定义了默认构造函数的类类型。){
const int *p = new const int(12);
const string *q = new const string(" ");
const string *q = new const string;//隐式地进行初始化
}
4).关于定位new
与内存耗尽不能分配空间。
new
表达式传入额外参数,bad_alloc
和nothrow
都是定义在头文件new
中的。{
int *p = new int;//如果分配失败就抛出一个std::bad_alloc的错误。
int *p = new(nothrow) int;//如果分配失败就返回一个空指针。
}
5).delete
表达式
delete
执行两个操作,销毁给定指向的对象,然后释放内存。new
关键字分配的内存或者空指针进行delete
。否则结果是未定义的。{
int i,*p = &i,*q = nullptr;
double *s = new double(33),*t = s;
delete i;//错误,不是指针
delete p;//未定义,不是new产生的
delete q;//正确,释放空指针
delete s;//正确
delete t;//错误,重复释放一个空间。
}
delete
,即使指针被销毁了,它所指向的空间还是没有被释放。nullptr
。练习,
{
bool b()
{
int *p = new(nothrow) int;
return p;
}
}
1).shared_ptr
支持的操作。
操作名称 | 相关描述 |
---|---|
shared_ptr<T> p(q); | q是一个内置指针,p管理内置指针的空间。q必须是由new 分配的,并且可以转换为T* |
shared_ptr<T> p(u); | p从unique_ptr 中接管对象,将u置为空。也就是unique_ptr 和shared_ptr 之间的转换。 |
shared<T> p(q,d); | 同第一个操作,但是这里是使用可调用对象d来代替delete 。 |
shared_ptr<T> p(p2,d) | p2是shared_ptr p2 类型的拷贝,区别就是使用d来代替delete |
p.reset() | 如果p是唯一指向其对象的shared_ptr ,reset 会释放这个对象。并将p置为空。 |
p.reset(q) | 此时指向的是内置指针q |
p.reset(q,d) | 此时用d代替delete |
2).用内置指针初始化shared_ptr
。
explicit
的。所以我们不可以对它进行拷贝初始化。因为不能进行隐式地转换。delete
的。delete
。{
shared_ptr<int> p = new int(12);//错误,对于内置类型对shared_ptr的初始化,只能是直接初始化,不能是拷贝。
// 因为智能指针将会对该内置指针指向的空间进行接管。
shared_ptr<int> p(new int(12));
//使用reset,用内置指针对智能指针赋值
p.reset(new int(1024));
// 使用显式地转换也可以。
p = shared_ptr<int>(new int(12));
}
3).试图在函数返回值中使用内置指针对智能指针进行拷贝的错误。编译器不会执行从内置指针到智能指针的隐式转换。
{
shared_ptr<int> F(int q)
{
return new int(q);//错误,试图进行拷贝。
return shared_ptr<int>(new int(q));//正确。
}
}
4).试图在传参时,将一个内置指针传递给智能指针导致指针内存的释放。
{
int *p = new int(12);
void F(shared_ptr<int> q)
{
return;
}
F(p);//非法不能进行隐式转换。
F(shared_ptr<int>(p));
int i = *p;//错误,智能指针离开局部作用域时将内存释放。p此时就是一个空悬指针。
}
5).对一个内置指针绑定多个智能指针的错误。使用了get
函数。
get
返回的内置指针对一个智能指针进行赋值。除非你可以保证他不会delete
。{
shared_ptr<in> p(new int(12));
int *q = p.get();
{
shared_ptr<int>(q);
}
int i = *p;//错误内存已经被块作用域的智能指针释放了。
}
6).reset
的使用。
{
//配合unique成员,对单一分内容进行拷贝
// 实现单一处理
if (!p.unique())
p.reset(new string(*p));//内容不变
//多新的拷贝进行处理。
*p += " ";
}
7).归根结底就是,
练习,
{
shared_ptr<int> p(new int(12));
f(shared_ptr(p));//正确,可以
f(shared_ptr(p.get());//错误。
}
f(new int(12));//这是错误的,这也是需要隐式转换。
get
返回的指针是可以delete
,但是会导致,shared_ptr
变为空悬指针。1).在一个函数f中,如果程序异常结束了,且在函数体里面没有捕获到这一个异常。那么,
delete
,在异常抛出点之后,函数体退出时,它还没有运行到。使用内置类型的指针(局部变量)被销毁了。但是它的内存永远不会被释放。2).虽然大多数的类有析构函数,来清理随想使用的资源。但是有一些是没有定义良好的析构函数的。**如果它有析构,就像内置类型一样,根本不需要我们进行释放。**例如,c和c++都使用的网络库。这种情况下,我们需要主动地去释放这些空间。但是,
3).解决,使用智能指针。但需要自定义函数(删除器)重载delete
。因为他没有delete
操作,不是new
产生的动态内存。(shared_ptr
默认是使用delete
,默认管理的是动态内存。)
{
void end_connection(connnection *p)
{
disconnection(*p);
}
//创建一个智能指针。
destination d;
connection c = connect(&d);
//c传入的是指针。!!
shared_ptr<connection> p(&c,end_connection);
}
练习,
lambda
表达式作为重载函数。1).除了与shared_ptr
一样的操作,还支持以下操作。
操作名称 | 相关描述 |
---|---|
unique_ptr<T> u | 空的智能指针。使用delete 来释放空间 |
unique_ptr<T,D> u | u会调用D代替delete |
unique_ptr<T,D>u(d) | 空的指针,hi用类型为D的d来重载delete |
u = nullptr | 释放u指向的空间,并置为空指针 |
u.release() | u放弃控制权,并返回一个内置指针。u置为空指针。没有释放空间 |
u.reset() | 释放空间,并且u置为空。,如果u为空则无需释放。 |
u.reset(q) | 内置指针q。u释放所指向的空间,并且指向新的q。 |
u.reset(nullptr) | 效果同u.reset() |
2).release
只是放弃控制权,没有释放内存。
{
p.release();//错误,内存泄漏
auto q = p.release();//正确
// 但是不要漏了,delete q;
}
3).对unique_ptr
的定义以及初始化,赋值。
unique_ptr
之间只能交换控制权,不可以相互赋值,初始化。{
unique<int> u = new int(1);//错误
unique<int> u(new int(1));
unique<int> q(u);//错误
unique<int> p;
p = u;//错误。
}
4).在函数的返回值是可以拷贝unique_ptr
的。编译器会知道要返回的对象将要被销毁。
{
unique_ptr<int> f(int p)
{
unique_ptr<int> ret(new int(p));
return ret;
//或者也可以这样。
return unique_ptr<int>(new int(p));
}
}
5).**自定义操作版本的unique_ptr
和shared_ptr
是不一样的。与算法是一样的。
delete
操作,使得unique_ptr
的类型发生变化。{
void f(destination d)
{
connection c = connect(&d);
unique_ptr<connect,decltype(end_connection)*> u(&c,end_connection);
}
}
6).移交控制权。
const
的unique_ptr
才可以进行移交控制权。{
unique_ptr<string> p2(p1.release());//p2获获得p1的控制权,p1被置为空指针。
unique_ptr<string> p3(new string("test"));
p2.reset(p3.release());//p2所指向的空间,也就是p1原来的空间被释放了
//p3的控制权被转移到p2,p3被置为空指针。
}
7).关于auto_ptr
unique_ptr
的特点。练习,
unique_ptr
时,编译器给出的错误,并不一定是好理解的。unique_ptr
使用一个普通的指针进行构造,合法。但是行为是未定义。1).支持的操作。
操作名称 | 相关描述 |
---|---|
weak_ptr<T> w | 空指针 |
weak_ptr<T> w(sp) | 与shared_ptr 指向相同的对像的weak_ptr 。T必须可以转换为sp的类型。 |
w = p; | p可以是一个weak_ptr 或者是一个shared_ptr ,赋值后共享对象。 |
w.reset() | 将w置为空指针 |
w.use_count() | 与w共享的shared_ptr 的数量。 |
w.expired() | 如果w.use_count() 为0返回true ,反之返回的是false |
w.lock() | 如果w.expired() 返回true ,返回一个空的shared_ptr ,反之返回一个w的对象的shared_ptr |
2).定义以及初始化。
shared_ptr
初始化weak_ptr
{
auto p = make_shared<int>(42);
weak_ptr<int> q(p);
}
3).由于是弱引用,它的引用不计入引用计数中。因此它可能是无效的。使用时需要进行判断。使用local
{
if (shared_ptr q = wp.local())
{
//保证q是有效的
}
}
1).解决一次为多个对象分配、释放内存的问题。
2).虽然可以操作,但是在新版本中,使用标准库容器,有很多优势。
1).使用new
,创建一个动态的数组。
new int[];
new
返回的并不是一个数组,而是一个指向数组首元素的指针。因此不可以使用begin
或者end
函数(因为begin
和end
是基于数组的维度实现的。),也自然地不可以使用范围for
循环。[]
不要求是一个常量表达式{
int *p = new int[get_size()];
}
{
typedef int arr[12];
int *p = new arr;
//编译器编译时为
int *p = new int[12];
}
()
将会执行默认初始化。如果有()
将会执行值初始化。**由于是数组,我们不可以在()
中有初始化器,**因此它们不可以使用auto
来通过编译器自动识别类型。{
int *p = new int[12]();
//对于string,效果是一样的。
string *p = new string[12];
string *p = new string[12]();
}
{}
进行列表初始化。bad_array_new_length
的异常。这个异常和bad_alloc
都定义在头文件new
中。{
int *p = new int[12]{1,2,3,4};
string *p = new string[12]{"the","a",string(12,'1')};
}
2).动态分配一个大小为零的动态数组是合法的。
{
size_t n = get_size();
int *p = new int[n]();
for (int *q = p;q != p+n;++q)
//n可以为0
}
3).释放动态数组。
delete []p;
[]
;或者对于一个数组没有[]
,它们的行为都是没有定义的。但是编译器不会报错。{
delete p;//p指向一个动态分配的对象或者为空。
delete []p;//p必须指向一个动态分配的数组或者为空。
}
4).使用智能指针管理动态数组。
unique_ptr
,改变了它的类型。unique_ptr<int[]>
release
;书上的例子是否有误。{
unique_ptr<int[]> up(new int[12]);
//up.release();
//自动使用delete[]p销毁其指针。
}
unique_ptr<int[]>
类型改变,是因为它销毁内存空间时调用的是delete[]
5).unique_ptr
管理动态数组时支持的操作。
unique_ptr
不支持访问运算符号。.以及->
。因为不是单个对象,是数组。操作名称 | 相关描述 |
---|---|
unique_ptr<T[]> p | |
unique_ptr<T[]> p(q) | q指向的是一个动态数组。类型为T |
u[i] | 支持下标运算。u必须指向一个数组 |
6).使用shared_ptr
管理动态数组。
delete
运算为delete[]
{
shared_ptr<int> p = (new int[12],[](int *p){delete []p};);
//这里使用了lambda表达式删除器。
sp.reset();
}
shared_ptr
不支持下标运算,不支持算术运算。{
for (size_t n = 0;n != 10;++n)
*(sp.get()+n) = n;
//使用内置数组解决这个问题。
}
1).与new
和delete
的比较。
new
以及delete
的不同在于,它将内存分配的对象创建分开。allocate
这样做,也要有一定的开销。2).allocator支持的操作。
memory
。destroy
。{
cout << *p << endl;//错误,没有分配内存。
}
操作名称 | 相关描述 |
---|---|
allocator<T> a | 定义一个可以申请类型为T的内存空间的allocator 对象。 |
a.allocate(n) | 为类型T的申请一个原始的没有构造的内存。可以保存n个对象。返回的是指向这段连续空间的首元素。 |
a.deallocate(p,n) | 释放从p开始的n个内存空间。p必须是由allocate 的到的指针。n必须是p创建时的大小。在调用它之前必须先调用,destroy 清除创建的对象。 |
a.construct(p,args) | p必须时一个T*类型的指针,指向一块原始的内存。 |
{
allocator<string> alloc;//可以分配string内存的对象
auto p = alloc.allocate(n);//申请n个空间。
auto q = p;//方便后面destroy
alloc.construct(q++);//构造一个空的字符串
alloc.construct(q++,10,'c');
alloc.construct(q++,"hi");
//destroy
--q;//指向第一个元素。
while (q != p)
{
alloc.destroy(--q);
}
alloc.destroy(q);
//可以用于构造别的对象。
//释放内存。
alloc.deallocate(p,n);
}
3).拷贝以及填充未构造对象的算法。
算法 | 相关描述 |
---|---|
uninitialized_copy(b,e,d) | 输入范围拷贝到未构造的原始内存。d开始的内存应该足够大。 |
uninitialized_copy(b,n,b2) | 输入范围未b开始的n个元素。输入范围必须是未构造的。因为它执行的是构造,不是拷贝,与copy不一样 ;并且返回的是指向下一个没有构造的元素的指针。这一点与copy 相似。 |
uninitialized_fill(b,e,val) | b,e为未构造的内存空间。 |
uninitialized_fill_n(b,n,val) | b开始的空间必须足够大。至少要有n个空间。 |
{
auto p = alloc.allocate(v.size()*2);
auto q = uninitialized_copy(v.begin(),v.end(),p);
uninitialized_fill_n(q,v.size(),42);
}
1).思路设计。
vector<string>
存储文本中的每一行。istringstream
对输入的每一行进行分解。set
存储每一个单词出现的所有行号。map
将每一个单词和它的set
相互关联起来。shared_ptr
(设计两个类)set
,和vector
2).编写使用这个类的程序,观察类是否具有我们预想的功能。
{
void runQuires(ifsstream &infile)
{
TextQuery tq(infile);//保存文件并且,建立查询的map
//建立用户交互。
while (true)
{
cout << "enter the word to look for,or q to quit:";
string s;
//如果到了文件末尾或者用户输入了'q',就结束
if (!(cin >> s) || s == 'q') break;
//打印查询的结果
print(cout,tq.query(s))<< endl;
}
}
}
1).编写TextQuery
类。
{
class QueryResult;//先声明再使用作为成员函数query的返回值
class TextQuery{
public:
using line_no = std::vector<string>::size_type;
TextQuery(std::ifstream&);//接受文本输入内容。
QueryResult query(const std::string&)const;
//query只是查询功能。令他时const版本。
private:
std::shared_ptr<std::vector<std::string>> file;//建立一个共享的指针。
std::map<string,std::shared_ptr<set<line_no>>> wm;//建立映射关系。原文中这个是错误的。
};
}
2).构造一个TextQuery
对象。(要求构造处file
和wm
数据成员。)
{
TextQuery::TextQuery(std::ifstream &is) : file(new vector<string>)
//这里初始化一个shared_ptr指向空的vector
{
string text;
//每一次读一行
while (getline(is,tect))
{
file->push_back(text);//将这一行存入vector中
int n = file->size() - 1;//当前的行号。
//对于每一个单词都有该行号。
string word;
istringstream line(text);
while (line >> word)
{
auto &lines = wm[word];//存入,返回的时一个value_type,这里是指针的引用。如果word原来是不存在的,那么返回的是一个空指针。
if (!lines)//如果是第一次出现这个单词。
{
lines.reset(new set<ilne_no>);//分配一个新的set
lines->insert(n);//将该行号插入set
}
}
}
}
}
3).
{
class QueryResult{
friend std::ostream& print(std::ostream&,const string&);
public:
QueryResult(std::string s,
std::shared_ptr<std::vector<std::string>> f,
std::shared_ptr<std::set> p) :
sought(s),lines(p),file(f);
private:
std::string sought;//查询单词
std::shared_ptr<std::set<line_no>> lines;//出现的行号
std::shared_ptr<std::vector<std::string>> file;//输入的文件。
}
}
4).
{
QueryResult TextQuery::query(const string &sought) const
{
//设置成static,即便是没有找到也存在。
static shared_ptr<set<line_no>> nodata(new set<line_no>);
auto loc = wm.find(sought);//返回一个pair迭代器
if (loc == wm.end())
return QueryResult(sought,nodata,file);//未找到。
else
return QueryResult(sought,loc->second,file);
}
}
5).
{
//不必再一次声明友元。
ostream &print(ostream &os, const QueryResult &qr)
{
os << qr.sought << " occurs " << qr.lines->size() << " "
<< (qr.lines->size() > 1 ? "times" : "time") << endl;
for (auto num : *(qr.lines))//对set中的每一个行号
//当lines所指向的set为空时,循环一次也不执行。
{
//由于下标从零开始
os << "\t(line " << num+1 << ") "
<< *((*(qr.file)).begin() + num) <<
endl;
}
return os;
}
}
原文链接:https://blog.csdn.net/weixin_50559975/article/details/118827087
作者:小可爱不要爱
链接:http://www.pythonpdf.com/blog/article/306/63673a75be02ab5e072c/
来源:编程知识网
任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任
昵称:
评论内容:(最多支持255个字符)
投诉与举报,广告合作请联系vgs_info@163.com或QQ3083709327
免责声明:网站文章均由用户上传,仅供读者学习交流使用,禁止用做商业用途。若文章涉及色情,反动,侵权等违法信息,请向我们举报,一经核实我们会立即删除!