文章目录
C++primer-学习心得-第12章-动态内存
除了静态内存和堆内存,每个程序还拥有一个内存池。这部分内存被称为*空间或堆。程序用堆来存储动态分配的对象,即那些程序运行时分配的对象。动态 对象的生存期由程序来控制。
12.1 动态内存与智能指针
管理动态内存的运算符:new:在动态内存中为对象分配空间并返回一个指向该对象的指针,可以同时对其初始化;delete:接受一个动态对象的指针,销毁该对象并释放其内存。
动态内存的使用时非常容易出问题的,非常常见的一个问题是我们在用的时候容易忘了最后释放内存,这样会产生内存泄漏,有时也可能出现还有指针引用内存的情况下先释放内存的情况,这样就会产生引用非法内存的指针。这两种情况实在是很难注意到,尤其对于初学者。为了更方便更安全的使用动态内存,新的标准库提供了两种智能指针类型来管理动态对象。智能指针负责自动释放所指向的对象。这两种智能指针:shared_ptr允许多个指针指向同一个对象,unique_ptr则“独占”所指向对象。另外还有一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。三种类型都定义在memory头文件中。
1.shared_ptr类
智能指针也是模板。
- shared_ptr<T>sp:空智能指针
- make_shared<T>(args):返回一个shared_ptr,指向一个动态分配的类型为T的对象并用args初始化对象
- shared_ptr<T>p(q):p是q的拷贝,递增q中的计数器
- p=q:递减p的引用计数递增q的引用计数
- p.unique():若p.use_count()为1返回true,否则false
- p.use_count():返回与p共享的智能指针数量
最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。
shared_ptr<int>p1=make_shared<int>(42);//p1指向一个值为42的int
shared_ptr<string>p2=make_shared<string>(10,'9');
shared_ptr<int>p3=make_shared<int>();//指向值为0的int
auto p6=make_shared<vector<string>>();
我们也可以使用auto来定义。
每个shared_ptr都有一个关联的计数器,称为引用计数。只要我们拷贝一个shared_ptr,计数器就会递增。一旦一个shared_ptr的计数器为0,它就会自动释放自己所管理的对象。如
auto r=make_shared<int>(42);//r指向的int只有一个引用者
r=q;//r指向另一个对象,r原来指向的对象的引用计数递减为0,自动释放
当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会通过一个特殊的成员函数–析构函数(destructor)自动销毁此对象。类似于构造函数控制初始化一样,**析构函数控制该类型的对象销毁时的操作。**析构函数一般用来 释放对象所分配的资源。
一般以下三种情况下程序会使用动态内存:
- 程序不知道自己需要使用多少对象
- 程序不知道所需对象的准确类型
- 程序需要在多个对象间共享数据
练习12.2
#include<string>
#include<iostream>
#include<memory>
#include<vector>
using namespace std;
class strblobss {
public:
typedef vector<string>::size_type st;
strblobss(initializer_list<string>i1) :data(make_shared<vector<string>>(i1)) {};
strblobss():data(make_shared<vector<string>>()) {}
st size()const { return data->size(); }
bool empty()const { return data->empty(); }
void push_back(const string& s) { data->push_back(s); }
void pop_back() {
check(0, "pop_back on empty strblob");
return data->pop_back();
};
string& front() {
check(0, "front on empty strblob");
return data->front();
};
const string& front() const {
check(0, "front on empty strblob");
return data->front();
};
string& back() {
check(0, "back on empty strblob");
return data->back();
};
const string& back()const {
check(0, "back on empty strblob");
return data->back();
};
private:
shared_ptr<vector<string>>data;
void check(st i, const string& msg)const {
if (i >= data->size())
throw out_of_range(msg);
};
};
int main() {
strblobss strs;
string str;
while (cin >> str)
strs.push_back(str);
while (!strs.empty()) {
cout << strs.back() << endl;
strs.pop_back();
}
}
2.直接管理内存
int *p1=new int;
int *p2=new int(1024);
string *p3=new string;
string *p4=new string(10,'9');
string *p5=new string();
auto p6=new auto(obj);
auto p7=new auto{a,b,c};
const int *p8=new const int(1024);
const string *p9=new const string;
如果一个程序用光了它所有的可用的内存,new 表达式就会失败,抛出一个类型为bad_alloc的异常。可以用以下方式阻止它抛出异常
int *p10=new (nothrow) int;//如果分配失败返回一个空指针
这种形式称为定位new。nothrow定义在头文件new中。
释放动态内存:
delete p;/指向一个动态分配的对象或者一个空指针
delete一个指针之后,指针值变为无效了,但很多机器上指针仍然保存着动态内存的地址。delete之后,指针变为空悬指针。如果我们需要保留指针,可以在delete之后将nullptr赋给指针。
练习12.6
#include<string>
#include<iostream>
#include<memory>
#include<vector>
using namespace std;
vector<int>* getptr() {
vector<int>*vi = new vector<int>;
return vi;
}
void inputit(vector<int>& vi) {
int i;
while (cin >> i)
vi.push_back(i);
}
void printit(vector<int>& vi) {
for (const auto c : vi)
cout << c << endl;
}
int main() {
vector<int>*vi = getptr();
inputit(*vi);
printit(*vi);
delete vi;
}
练习12.7
#include<string>
#include<iostream>
#include<memory>
#include<vector>
using namespace std;
shared_ptr<vector<int>> getptr() {
return make_shared<vector<int>>();
}
void inputit(vector<int>& vi) {
int i;
while (cin >> i)
vi.push_back(i);
}
void printit(vector<int>& vi) {
for (const auto c : vi)
cout << c << endl;
}
int main() {
shared_ptr<vector<int>>vi = getptr();
inputit(*vi);
printit(*vi);
}
3.shared_ptr和new结合使用
定义和改变shared_ptr的其他方法
- shared_ptr<T>p(q):p管理内置指针q指向的对象;q必须指向new分配的内存且能够转换为T*类型
- shared_ptr<T>p(u):p从unique_ptr u哪里接管了对象的所有权,将u置为空
- shared_ptr<T>p(q,d):p接管了内置指针q指向的对象的所有权。p将使用可调用对象d来代替delete
- shared_ptr<T>p(p2,d):p是shared_ptr p2的拷贝
- p.reset():若p是唯一指向其对象的shared_ptr,reset会释放此时的对象。
- p.reset(q):令p指向q
- p.reset(q,d)
注意不要混用普通指针和智能指针。
4. 智能指针和异常
正确使用智能指针必须坚持一些基本规范:
- 不使用相同的内置指针初始化(或reset)多个智能指针
- 不delete get()返回的指针
- 不使用get()初始化或reset另一个智能指针
- 如果使用get()返回的指针,记住最后一个对应的智能指针销毁后你的指针会变为无效的
- 如果使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器
5. unique_ptr
- unique_ptr<T> u1
- unique_ptr<T,D>u2
- unique_ptrT,D>u(d)
- u=nullptr
- u.release()
- u.reset()
- u.reset(q)
- u.reset(nullptr)
由于一个unique_ptr拥有它指向的对象,因此unique_ptr不支持普通的拷贝或赋值操作。
6.weak_ptr
weak_ptr是一种不控制所指向对象的智能指针,指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。所以这种指针的特点:weak。
- weak_ptr<T>w
- weak_ptr<T>w(sp)
- w=p
- w.reset()
- w.use_count():与w共享对象的shared_ptr的数量
- w.expired():若w.use_count()=0则true否则false
- w.lock():若expired为true返回一个空shared_ptr否则返回一个指向w的对象的shared_ptr
练习12.20
这个题还是有点难度的,花了我不少时间
#include<string>
#include<iostream>
#include<memory>
#include<vector>
#include<fstream>
using namespace std;
class strblobpt;
class strblobss {
public:
typedef vector<string>::size_type st;
friend class strblobpt;
strblobss(initializer_list<string>i1) :data(make_shared<vector<string>>(i1)) {};
strblobss() :data(make_shared<vector<string>>()) {}
st size()const { return data->size(); }
bool empty()const { return data->empty(); }
void push_back(const string& s) { data->push_back(s); }
void pop_back() {
check(0, "pop_back on empty strblob");
return data->pop_back();
};
string& front() {
check(0, "front on empty strblob");
return data->front();
};
const string& front() const {
check(0, "front on empty strblob");
return data->front();
};
string& back() {
check(0, "back on empty strblob");
return data->back();
};
const string& back()const {
check(0, "back on empty strblob");
return data->back();
};
strblobpt begin();// { return strblobpt(*this); }
strblobpt end();/* {
auto ret = strblobpt(*this, data->size());
return ret;
}*/
private:
shared_ptr<vector<string>>data;
void check(st i, const string& msg)const {
if (i >= data->size())
throw out_of_range(msg);
};
};
class strblobpt {
public:
strblobpt():curr(0){}
strblobpt(strblobss& a, size_t sz = 0) :wptr(a.data), curr(sz) {}
string& deref()const {
auto p = check(curr, "dereference past end");
return (*p)[curr];
};
strblobpt& incr() {
check(curr, "increment past end of strblopt");
++curr;
return *this;
};
private:
shared_ptr<vector<string>>check(size_t i, const string&msg)const {
auto ret = wptr.lock();
if (!ret)
throw runtime_error("unbound strblobptr");
if (i >= ret->size())
throw out_of_range(msg);
return ret;
};
weak_ptr<vector<string>>wptr;
size_t curr;
};
strblobpt strblobss::begin() {
return strblobpt(*this);
}
strblobpt strblobss::end() {
auto ret = strblobpt(*this, data->size());
return ret;
}
int main() {
strblobss strs;
ifstream in("main.cpp");
string line;
while (getline(in,line))
strs.push_back(line);
/*
while (!strs.empty()) {
cout << strs.back() << endl;
strs.pop_back();
}
*/
strblobpt strp=strs.begin();
while (&strp.deref()!=&strs.back()) {
cout << strp.deref() << endl;
strp.incr();
}
cout << strp.deref() << endl;
}
12.2 动态数组
new和delete是一次分配/释放一个对象,但有时我们需要一次为很多对象分配内存,标准库中包含一个名为allocator的类,允许我们将分配和初始化分离。
1.new和数组
int *pia=new int[get_size()];
typedef int arrT[42];
int *p=new arrT;
通常称new T[]分配的内存为“动态数组”,但实际上我们这样做的时候并没有得到数组类型的对象,而是得到指向数组的指针。我们仅仅是得到 了一个指针。
int *p=new int[4]{0,1,2,3};
string *ps=new string[10]{"a","b","the"};
释放动态数组
delete []pa;//pa必须指向一个动态分配的数组或空
智能指针和动态数组
unique_ptr<int[]>up(new int[10]);
up.release();
练习12.23
#include<string>
#include<iostream>
#include<memory>
#include<vector>
#include<cstring>
#include<fstream>
using namespace std;
//
int main() {
const char* c1 = "hello";
const char* c2 = "world";
char* r = new char[strlen(c1) + strlen(c2) +2];
strcpy_s(r,strlen(c1)+1,c1);
strcat_s(r, 12 , c2);//容易出错的地方
cout << r << endl;
string s1 = "hello";
string s2 = "world";
strcpy_s(r, s1.size() + s2.size()+2, (s1 + s2).c_str());
cout << r << endl;
delete[] r;
return 0;
}
练习12.24
#include<string>
#include<iostream>
#include<memory>
#include<vector>
#include<cstring>
#include<fstream>
using namespace std;
//这里我直接参考了下标准答案了,偷个懒
int main() {
char* r = new char[10];
string s;
//cout << strlen(r) << endl;
if (cin >> s)
if (s.size() + 1 < 10) {
strcpy_s(r, s.size() + 1, s.c_str());
cout << r << endl;
}
else
cout << "invalid" << endl;
delete[] r;
return 0;
}
2. allocator类
new存在的一些不足的地方是它将内存分配和对象构造组合在一起,而delete将对象析构和内存释放组合在一起。当分配一大块内存时我们通常希望内存分配和对象构造分离。
allocator是一个模板。
- allocator<T> a
- a.allocate(n)
- a.deallocate(p,n)
- a.construct(p,args)
- a.destroy§
allocator<string>alloc;
auto const p=alloc.allocate(n);
auto q=p;
alloc.construct(q++);
alloc.construct(q++,10,'c');
alloc.construct(q++,"hi");
while(q!=p)
alloc.destroy(--q);
标准库为allocator类定义了两个伴随算法,可以在未初始化内存中创建对象。他们都定义在头文件memory中。
- uninitialized_copy(b,e,b2):
- uninitialized_copy_n(b,e,b2)
- uninitialized_fill(b,e,t)
- uninitialized_fill_n(b,n,t)
#include<string>
#include<iostream>
#include<memory>
#include<vector>
#include<cstring>
#include<fstream>
using namespace std;
int main() {
allocator<string> alloc;
auto const p = alloc.allocate(10);
auto q = p;
string str;
for (int i = 0; i < 9; i++) {
cin >> str;
auto qq = q;
alloc.construct(q++, str);
cout << *qq << endl;
}
while (q != p) {
alloc.destroy(--q);
}
return 0;
}
12.3使用标准库:文本查询程序
我们现在运用学到的知识来实现一个简单的文本查询程序:给定某个单词和文本,程序查询这个单词在文本中一共出现的次数并记录相关的行最后输出单词出现的总次数并列出相关的行。
步骤:
- 先按行读取给定的文件并将其存储至vector<string>中
- 可以用istringstream来将每行分解为单词
- 可以用set来保存单词出现的行号
- 用map将每个单词和set关联起来
本来我们可以直接用这些容器来编写程序的,但为了用到我们刚学的知识,我们把问题抽象化,选择更花哨的方案。我们先定义一个保存输入文件的类:TextQuery,包含一个vector和一个map。可以想象后面我们会需要在类之间共享数据。我们用QueryResult类来表达查询结果。
在设计一个类之前我们先考虑这个类需要实现什么样的效果:
void runQueries(ifstream &infile){
TextQuery tq(infile);
while(true){
cout<<"enter word to look for,or q to quit: ";
string s;
if(!(cin>>s)||s=="q")
break;
print(cout,tq.query(s))<<endl;
}
}
下面我给出我花了很长时间完成的个人的版本,还是有点难度的,感觉没用到本章多少的知识,但能够确实的解决所求的问题,也是按照要求创建了两个类,如果不创建两个类可以更快解决问题~~
#include<string>
#include<iostream>
#include<memory>
#include<vector>
#include<cstring>
#include<fstream>
#include<set>
#include<map>
#include<sstream>
using namespace std;
class TextQuery {
public:
//构造函数
TextQuery():row(0) {}
TextQuery(ifstream& in) {
string s;
while (getline(in, s)) {
text.push_back(s);
}
}
//查询函数
vector<string> query(string s);
private:
vector<string>text;
map<string,set<int>>words;
size_t row;
};
class QueryResult {
public:
QueryResult() {}
QueryResult(vector<string>* vss, set<int>su,string s) :vs(vss), su(su),s(s) {}
vector<string>& getresult() {
int times = su.size();
string head = s + " occurs " +to_string(times) + " times";
result.push_back(head);
for (auto c : su) {
result.push_back(" (line " + to_string(c) + ") " + (*vs)[c]);
}
return result;
}
private:
vector<string>*vs;
set<int>su;
vector<string>result;
string s;
};
vector<string> TextQuery::query(string s) {
for (string::size_type i = 0; i < text.size();i++) {
istringstream ins(text[i]);
string ss;
while (ins >> ss) {
if (ss == s)
{
words[s].insert(i);
}
}
}
QueryResult qs(&text, words[s], s);
return qs.getresult();
}
ostream& print(ostream& o,const vector<string>& vs) {
for (const auto c : vs)
cout << c <<"\n";
return o;
}
void runQueries(ifstream& infile) {
TextQuery tq(infile);
while (true) {
cout << "enter word to look for,or q to quit: ";
string s;
if (!(cin >> s) || s == "q")
break;
print(cout, tq.query(s)) << endl;
}
}
int main() {
ifstream in("target.txt");
runQueries(in);
}
这个我就不想做过多说明了,毕竟完全是自己个人思考出来的,重要的是这个探索的过程,实际能解决这个问题的答案是很多的,但自己想方设法写代码最终能够正常运行并达到预期的结果就足够了,我甚至不想细看书上给出的标准的代码,越是有点难度的问题越是要自己探索,当然这是建立在把书上的知识点弄清楚的基础上。
xhh22900 发布了32 篇原创文章 · 获赞 9 · 访问量 1266 私信 关注