C++ 小白 学习记录12

第十二章 动态内存

  • 栈内存: 仅在其定义的程序块运行时才存在.
  • 静态内存: static对象在使用之前分配, 程序结束时销毁.
  • 内存池: 也叫自由空间或堆, 存储动态分配的对象.由程序控制, 不再使用时,必须显显式销毁

动态内存与智能指针

动态内存的管理 通过 new, delete管理

智能指针与常规指针的区别是, 智能指针负责自动释放指向的对象. 定义在memory头文件中

  • shared_ptr        允许多个指针指向同一个对象
  • unique_ptr        独占 所指向的对象
  • weak_ptr        弱引用, 指向shared_ptr所管理的对象.

shared_ptr

  • shared_ptr<string> p1 // 默认为空指针
  • shared_ptr<T> sp        定义一个智能指针, 默认为空
  • unique_ptr<T> up        定义一个独占式智能指针, 默认为空
  • p
  • *p
  • p->mem
  • p.get()        返回p中保存的指针, 若指针释放了对象, 返回的指针所指向的对象也不存在
  • swap(p,q)
  • p.swap(q)

shared_ptr 独有的操作

  • make_shared<T> (args) 返回一个智能指针, 指向动态分配的类型是T, 使用args初始化
  • shared_ptr<T> p(q) p复制 shared_ptr类型的q, q会递增, q中的指针必须能转换为T*
  • p=q, p和q必须能相互转换. 递减p原来的引用计数, 递增q引用计数, 如果p的引用计数变为0, 则将其管理的原内存释放.
  • p.unique()  若p.use_count() ==1 ? ture: false
  • p.use_count() 返回p共享对象的智能指针数量

make_shared 比较安全, 类似容器中的emplace

shared_ptr<int>p3 = make_shared<int>(42);

shared_ptr<string>p4 = make_shared<string>(10, '9') // 10个9组成的字符串

程序使用动态内存的三种原因:

  • 程序不知道自己需要使用多少对象
  • 程序不知道所需对象的准确类型
  • 程序需要在多个对象间共享数据

直接管理内存 new delete

int *p = new int ; // p 指向一个未初始化的int

int * p = new int(1024) // p 指向的对象的值为1024

string *ps = new string(10, '9') // *ps 为 10个9的字符串

auto p1 = new auto(obj) // p指向一个与obj类型相同的对象, 该对象使用obj进行初始化

const int * p = new const int(1024)

内存被耗尽时 new会失败, 然后抛出bad_alloc的异常.

int *p = new int // 如果分配失败, 抛出std::bad_alloc的错误

int *p = new (nothrow) int; // 分配失败, 返回空指针 不抛出错误

bad_alloc notthrow 都在new 头文件中

指针值和delete

delete只能释放new分配的内存, 不能多次释放, 否则其行为未定义

shared_ptr 和new 结合使用

接收指针参数的shared_ptr 的构造函数是explicit的, 因此 不能隐式的转换为一个智能指针. 必须使用直接初始化的方式定义:

shared_ptr<int> p1 = new int(1024);  // 错误的定义方式

shared_ptr<int>p1 (new int(1024))  // 正确的

默认情况下, 用来初始化智能指针的普通指针必须指向动态内存, 因为智能指针默认使用delete释放关联的对象.

定义和改变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所指向对象的所有权, q必须能转换为T*, p使用d代替delete
  • shared_ptr<T>p(p2,d) p复制shared_ptr p2, 使用d代替delete
  • p.reset()         若p是唯一一个指向其对象的shared_ptr, reset会释放此对象, 如果传递了内置指针q, 
  • p.reset(q)        会令p指向q, 否则会将p置空, 如果传递了d, 则会代替delete
  • p.reset(q,d)

智能指针和异常

智能指针和哑类

unique_ptr

unique_ptr 不支持普通的拷贝和赋值操作, 必须采用直接初始化的形式

unique_ptr<int> p (new int (42));

特有的操作

  • unique_ptr<T> u1     空unique_ptr 指向类型为T的对象, u1 默认使用delete, u2 使用D代替delete
  • unique_ptr<T,D> u2
  • unique_ptr<T,D>u(d)
  • u=nullptr        释放u指向的对象, 将u置空
  • u.release()        u放弃对指针的控制权, 返回指针, 并将u置空, 不会释放对象的内存
  • u.reset()        释放u指向的对象
  • u.reset(q)        令u指向内置指针q所指向的对象
  • r.reset(nullptr)

虽然不能赋值和拷贝, 可以通过release和reset将指针的所有权从一个unique_ptr转移给另一个

unique_ptr<string>p3 (new string("hello"));

unique_ptr<string>p2(p3.release());

p2.reset(p3.release());

release会切断unique_ptr和他原来管理的对象间的联系, 返回的指针通常被用来初始化另一个指针或给另一个智能指针赋值. 如果不保存该函数结果, 需要负责资源的释放.

p2.release()  // p2 不会释放内存, 而p2原来指向的对象的指针被丢失了

auto p = p2.release() // 正确的做法

可以拷贝unique_ptr的例外:

可以拷贝和赋值一个即将被销毁的unique_ptr. 如从函数返回:

unique_ptr<int> clone(int p) { return unique_ptr<int>(new int (p)); }

还可以返回一个局部对象的拷贝:

unique_ptr<int> clone(int p) {

unique_ptr<int> ret(new int (p));

return ret; }

unique_ptr 重载删除函数时, 与shared_ptr不同:

shared_ptr<connection>p(&c, end_connection);

unique_ptr<connection, decltype(end_connection)*>p(&c, end_connection);

weak_ptr

不控制所指向对象生存期的智能指针, 其指向一个shared_ptr管理的对象, 因其不控制对象生命周期, 也就不会改变shared_ptr的引用计数.

  • weak_ptr<T> w        指向T的空weak_ptr指针
  • weak_ptr<T> w(sp)      与shared_ptr sp指向相同对象的weak_ptr, T必须能转换为sp指向的类型
  • w=p        p可以是shared_ptr也可以是weak_ptr, w和p共享对象
  • w.reset() w置空
  • w.use_count() 与w共享的shared_ptr的数量
  • w.expired() return w.use_count() == 0 ? true:false
  • w.lock() 如果expired 为true 返回空shared_ptr, 否则返回一个指向w的对象的shared_ptr

由于对象可能不存在, 不能使用weak_ptr直接访问对象, 必须调用lock函数检查其指向的对象是否存在.

动态数组

new和数组

new T[ ] 分配的内存为 动态数组, 得到的是一个数组元素类型的指针. 不能对动态数组调用begin/end, 也不能使用返回for.

动态数组 并不是数组类型, 不能使用auto 分配数组.

初始化

int *pia = new int[10]()

int *pia2 = new int[2]{1,2} // 列表初始化, 也可以 花括号内的值数量少于[ ]内的数量, 如

int *pia3 = new int [10] {1,2 } // 除了1,2 其余使用默认初始化

int *pia4 = new int[3]{ 1, 2,3, 4} //如果{ }内的数量多于[ ] 内的数量, 则失败, 不会分配任何内存. 抛出bad_array_new_length 异常

动态数组的维度可以为0, 普通数组不行

例如 char arr[0] // 错误,  而 char *p = new char[0] , 则没有问题, 但是p不能解引用, 其实质是一个尾后指针.

释放动态数组

常规的释放内存的方式是delete p, 而动态数组的释放方式是 delete [ ] pa 以示区别, 动态数组在销毁时采用逆序方式.

智能指针和动态数组

unique_ptr<int []> up(new int[10]);

up.release() // 自动调用delete[ ] 销毁其指针

指向数组的unique_ptr 的操作中不支持成员访问运算符

  • unique_ptr<T [ ]> u  u指向一个动态分配的数组, 数组元素类型为T
  • unique_ptr<T [ ]> u (p) u指向内置指针p所指向的动态分配的数组, p必须能转换为类型T*
  • u[i] 返回u拥有的数组中位置为i处的对象, u必须指向一个数组

如果使用shared_ptr 管理动态数组, 必须重载删除函数

shared_ptr<int> sp(new int[10], [ ] (int *p) {delete [ ] p;});

allocator 类

用于解决new灵活性上的局限性. 将内存分配和对象构造分离.类似于vector 也是一种模版

  • allocator<T> a  定义一个名为a的allocator对象, 它可以为类型为T的对象分配内存
  • a.allocate(n) 分配一段原始的, 未构造的内存, 用于保存n个类型为T的对象
  • a.deallocate(p,n) 释放从T*指针p中地址开始的内存, 该内存保存了n个T对象. p必须是之前有allocator返回的指针, n必须是创建时的大小. 在调用deallocate之前, 用户必须对每个在块内存中创建的对象调用destroy.
  • a.construct(p, args) p必须是一个类型为T*的指针, 指向一块原始内存. args被传递给类型为T的构造函数, 用来在p指向的内存中构建一个对象.
  • a.destroy(p) p为T*类型的指针,  对p指向的对象执行析构函数.

allocator 分配的内存是未构造的.

allocator比较难理解: 

    allocator<string> alloc;  // 声明alloc 是用于分配string类型的allocator
	// 一段用alloc分配的内存, 此段内存用于保存10个string p指向该段内存中最后构造的元素之后的位置.
	// p相当于该段内存的起始位置,将来使用while循环时, 可以用于判断q是否已经从最后面的位置循环到此处
	// 	   while(q !=p) alloc.destroy(--q);
	// 所以不建议后段程序直接操作p的做法
	auto const p = alloc.allocate(10);  
	auto q = p;  // q指向最后构造的元素之后的位置, 此处因为还没有任何构建的元素, 所有q指向0位置
	// 以下之所以使用q指针赋值, 是因为p 被声明为const, 无法++p或者--p
	alloc.construct(q, "hi"); // *p 处的内容为hi
	q++;	// q 下移一个位置
	alloc.construct(q, "hello"); // *(p+1) 处的内容为 hello
	q++;
	alloc.construct(q, "world"); // *(p+1)处的内容为 world
	cout << "*q: " << *(q) << endl;
	//cout << "*q: " << *(++p) << endl;
	//cout << "*q: " << *(++p) << endl;

	//auto p = alloc.allocate(10);
	//alloc.construct(p, "hi"); // *p 处的内容为hi
	//p++;	// q 下移一个位置
	//alloc.construct(p, "hello"); // *(p+1) 处的内容为 hello
	//p++;
	//alloc.construct(p, "world"); // *(p+1)处的内容为 world
	//cout << "*q: " << *p << endl; // 输出 world
	//cout << "*q: " << *(--p) << endl; // 输出hello
	//cout << "*q: " << *(--p) << endl; // 输出 hi

    // 销毁
	while (q != p) {
		alloc.destroy(--q);
	}
	// 释放内存
	alloc.deallocate(p, 10);

拷贝和填充未初始化内存的算法

uninitialized_copy(b,e,b2) 从迭代器b和e指出的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中. b2指定的内存必须足够大到能装的下.

  • uninitialized_copy_n(b,n,b2) 从迭代器b指向的元素开始, 拷贝n个元素到b2开始的内存中
  • uninitialized_fill(b,e,t) 从迭代器b,e指定的原始内存范围中创建对象, 对象的值均为t的拷贝
  • uninitialized_fill_n(b,n,t) 从迭代器b指向的内存地址开始创建n个对象, b指向的内存必须足够大到能装的下
	vector<int> vec = { 1,2,3,4,5,6 };
	allocator<int> alloc;
	auto const p = alloc.allocate(vec.size() * 2);
	auto q = uninitialized_copy(vec.cbegin(), vec.cend(), p);
	uninitialized_fill_n(q, vec.size(), 41);
	for(auto p1 =p ; p1 !=p+2*vec.size(); ++p1)
		cout << "q: " << *p1 << endl;

使用标准库: 文本查询程序

ostream & print1(ostream& out, string s) {return out << s; }

class QueryResult1 {
public:
	QueryResult1(const string& kw, const set<int>& rowNums, const vector<string>& contents) :keyWord(kw), rowNumbers(rowNums), content(contents) {}
	string message() {
		string message = keyWord + " occurs " + to_string(rowNumbers.size()) + " times.";
		auto b = rowNumbers.begin();
		auto cb = content.begin();
		while (b != rowNumbers.end()) {
			message += "\r\n ( line " + to_string(*b) + " )" + *cb;
			++b;
			++cb;
		}
		return message;
	}
private:
	const string& keyWord;
	const set<int> & rowNumbers;
	const vector<string> & content;
};

class TextQuery1 {
public:
	TextQuery1(ifstream& in) {
		string line;
		unsigned int lineNumber = 0;
		while (getline(in, line)) {
			lineNumber++;
			vec_text.push_back(line);
			istringstream s(line);
			string strTemp;
			while (s >> strTemp) wordsNumbs[strTemp].emplace(lineNumber);
		}
	};
	string query(string wordForSearch) {
		set<int> allLines = wordsNumbs[wordForSearch]; // 此处 allLines可能为空
		QueryResult1 qr(wordForSearch, allLines, vec_text);
		return qr.message();
	}
private:
	vector<string> vec_text;
	map<string, set<int>> wordsNumbs;
};


// 书上的程序
class QueryResult;
class TextQuery {
public:
	using line_no = vector<string>::size_type;
	TextQuery(ifstream&);
	QueryResult query(const string&) const;
private:
	shared_ptr<vector<string>>file;
	map<string, shared_ptr<set<line_no>>>wm;
};
TextQuery::TextQuery(ifstream& is) :file(new vector<string>) {
	string text;
	while (getline(is, text)) {
		file->push_back(text);
		int n = file->size() - 1;
		istringstream line(text);
		string word;
		while (line >> word) {
			auto& lines = wm[word];
			if (!lines) lines.reset(new set<line_no>);
			lines->insert(n);
		}
	}
};

class QueryResult {
	using line_no = vector<string>::size_type;
	friend ostream& print(ostream&, const QueryResult&);
public:
	QueryResult(string s, shared_ptr<set<line_no>>p, shared_ptr<vector<string>>f) :sought(s), lines(p), file(f) {}

private:
	string sought;
	shared_ptr<set<line_no>> lines;
	shared_ptr<vector<string>> file;
};
QueryResult TextQuery::query(const string& sought) const {
	static shared_ptr<set<line_no>> nodata(new set<line_no>);
	auto loc = wm.find(sought);
	if (loc == wm.end()) return QueryResult(sought, nodata, file);
	else return QueryResult(sought, loc->second, file);
};

string make_plural2(size_t ctr, const string& word, const string& ending) {
	return (ctr > 1) ? word + ending : word;
}
ostream& print(ostream& os, const QueryResult& qr) {
	os << qr.sought << " occurs " << qr.lines->size() << " "
		<< make_plural2(qr.lines->size(), "time", "s") << endl;
	for (auto num : *qr.lines)
		os << "\t ( line " << num + 1 << " )"
		<< *(qr.file->begin() + num) << endl;
	return os;
};

// 使用StrBlob代替vectory<string>
class QueryResult3;
class TextQuery3 {
public:
	using line_no = vector<string>::size_type;
	TextQuery3(ifstream&);
	QueryResult3 query(const string&) const;
private:
	shared_ptr<StrBlob>file;
	map<string, shared_ptr<set<line_no>>>wm;
};
TextQuery3::TextQuery3(ifstream& is) :file(new StrBlob) {
	string text;
	while (getline(is, text)) {
		file->push_back(text);
		int n = file->size() - 1;
		istringstream line(text);
		string word;
		while (line >> word) {
			auto& lines = wm[word];
			if (!lines) lines.reset(new set<line_no>);
			lines->insert(n);
		}
	}
};
class QueryResult3 {
	using line_no = vector<string>::size_type;
	friend ostream& print3(ostream&, const QueryResult3&);
public:
	QueryResult3(string s, shared_ptr<set<line_no>>p, shared_ptr<StrBlob>f) :sought(s), lines(p), file(f) {}

private:
	string sought;
	shared_ptr<set<line_no>> lines;
	shared_ptr<StrBlob> file;
};
QueryResult3 TextQuery3::query(const string& sought) const {
	static shared_ptr<set<line_no>> nodata(new set<line_no>);
	auto loc = wm.find(sought);
	if (loc == wm.end()) return QueryResult3(sought, nodata, file);
	else return QueryResult3(sought, loc->second, file);
};
ostream& print3(ostream& os, const QueryResult3& qr) {
	os << qr.sought << " occurs " << qr.lines->size() << " "
		<< make_plural2(qr.lines->size(), "time", "s") << endl;
	for (auto num : *qr.lines)
		os << "\t ( line " << num + 1 << " )"
		<< *(qr.file->begin() + num) << endl;
	return os;
};
void runQueries3(ifstream& infile) {
	TextQuery3 tq(infile);  //保存文件 并建立查询map

	while (true) {
		cout << "enter word to search, or q to quit: ";
		string input;
		if (!(cin >> input) || input == "q") break;
		print3(cout, tq.query(input)) << endl;
	}
}

void runQueries(ifstream& infile) {
	TextQuery tq(infile);  //保存文件 并建立查询map

	while (true) {
		cout << "enter word to search, or q to quit: ";
		string input;
		if (!(cin >> input) || input == "q") break;
		print(cout, tq.query(input)) << endl;
	}
}

// 12.27
void runQueries1(ifstream& infile) {
	TextQuery1 tq(infile);  //保存文件 并建立查询map

	while (true) {
		cout << "enter word to search, or q to quit: ";
		string input;
		if (!(cin >> input) || input == "q") break;
		print1(cout, tq.query(input)) << endl;
	}
}
int main(int argc, char** argv) {

	// 12.27 带1的函数为12.27的作业
	ifstream in("E:\\c++\\primer\\primer_p5\\Debug\\english.txt");
	// runQueries1(in);
	// runQueries(in);
	// 3 for 12.32
	runQueries3(in);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yutao1131

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值