C++Primer13.5节练习

练习13.39:

#include <iostream>
#include <string>
using namespace std;
#include <vector>
#include <algorithm>
#include <set>

//类vector类内存分配策略的简化实现
class StrVec {
public:
	//默认初始化
	StrVec();
	//拷贝构造函数
	StrVec(const StrVec&);
	//拷贝赋值运算符
	StrVec& operator=(const StrVec&);
	//析构函数
	~StrVec();
	//拷贝元素
	void push_back(const std::string&);
	//数组大小和容量
	size_t size()const;
	size_t capacity()const;
	//获得数组的首元素和尾后元素
	std::string* begin()const;
	std::string* end()const;

	void reserve(size_t n);
	void resize(size_t n);

private:
	//静态成员,用来分配元素
	static std::allocator<std::string>alloc;
	//工具函数,被添加元素的函数所使用
	void chk_n_alloc();
	//工具函数,被拷贝构造函数、赋值运算符和析构函数使用
	std::pair<std::string*, std::string*>alloc_n_copy(const std::string*, const std::string*);
	//销毁元素并释放内存
	void free();
	//获得更多内存并拷贝已有元素
	void reallocate();
	//指向数组首元素的指针
	std::string* elements;
	//指向数组第一个空闲元素的指针
	std::string* first_free;
	//指向数组尾后位置的指针
	std::string* cap;
};

std::allocator<string> StrVec::alloc;

size_t StrVec::capacity()const
{
	return cap - elements;
}

size_t StrVec::size()const
{
	return first_free - elements;
}

string* StrVec::begin()const
{
	return elements;
}

string* StrVec::end()const
{
	return first_free;
}

//push_back
void StrVec::push_back(const string& s)
{
	chk_n_alloc();
	//此时有足够的空间,创建元素,拷贝
	alloc.construct(first_free, s);
	++first_free;
}

//工具函数,被拷贝构造函数、赋值运算符和析构函数使用
pair<string*, string*> StrVec::alloc_n_copy(const string* s1, const string* s2)
{
	//拷贝传递参数指定范围的元素
	//创建元素的副本
	auto data = alloc.allocate(s2 - s1);
	//uninitialized_copy(b,e,b2)作用是从迭代器b和e指出的输出范围中拷贝元素到迭代器b2指定的未构造的原始内存中
	// 使用uninitialized_copy(b,e,b2)函数对未构造的函数进行拷贝
	//返回一个pair,分别指向拷贝后的数组首元素和最后元素后面的元素
	return { data,uninitialized_copy(s1,s2,data) };
}

//工具函数free,destory元素,释放StrVec内存
void StrVec::free()
{
	//不能传递给deallocate一个空指针,如果elements为空,函数就什么也不做
	if (elements)
	{
		//逆序销毁元素,收回空间
		for (string* p = first_free; p != elements;)
		{
			alloc.destroy(--p);
		}
		alloc.deallocate(elements, cap - elements);
	}
}

//默认初始化
StrVec::StrVec() :elements(nullptr), first_free(nullptr), cap(nullptr) 
{ 

}

//拷贝构造函数
StrVec::StrVec(const StrVec& sv)
{
	//调用alloc_n_copy分配空间以容纳与sv中一样多的元素
	auto newData = alloc_n_copy(sv.begin(), sv.end());
	elements = newData.first;
	first_free = cap = newData.second;
}

//拷贝赋值运算符
StrVec& StrVec::operator=(const StrVec& sv)
{
	//为了正确处理自赋值,在释放已有元素前调用alloc_n_copy
	//分配内存,大小和sv中占用的元素一样多
	auto data = alloc_n_copy(sv.begin(), sv.end());
	free();
	elements = data.first;
	first_free = cap = data.second;
	return *this;
}

//reallocate成员
void StrVec::reallocate()
{
	//分配当前大小两倍的内存空间
	auto newCapacity = size() ? 2 * size() : 1;
	//分配新内存
	auto newData = alloc.allocate(newCapacity);
	//将数据从旧内存移到新内存
	//指向数组下一个空闲位置
	auto dest = newData;
	//指向旧数组中下一个元素
	auto elem = elements;
	for (size_t i = 0; i != size(); ++i)
	{
		alloc.construct(dest++, std::move(*elem++));
	}
	//一旦我们移动完元素就释放旧内存空间
	free();
	//更新我们的数据结构,执行新元素
	elements = newData;
	first_free = dest;
	cap = elements + newCapacity;

}


//析构函数
StrVec::~StrVec()
{
	free();
}
void StrVec::reserve(size_t n)
{
	//分配大小为n的内存
	if (capacity() < n)
	{
		auto newCapacity = n;
		//创建新内存
		auto newData = alloc.allocate(n);
		//将数据从旧内存移到新内存
		//指向数组下一个空闲位置
		auto dest = newData;
		//指向旧数组中下一个元素
		auto elem = elements;
		//将数据移动
		for (size_t i = 0; i != size(); ++i)
		{
			alloc.construct(dest++, std::move(*elem));
		}
		//一旦我们移动完元素就释放旧内存空间
		free();
		//更新我们的数据结构,执行新元素
		elements = newData;
		first_free = dest;
		cap = elements + newCapacity;
	}
}

void StrVec::resize(size_t n)
{
	//n的大小大于size(),以默认值填充
	if (size() < n)
	{
		size_t num = n - size();
		for (size_t i = 0; i != num; ++i)
		{
			alloc.construct(first_free++, std::string());
		}
	}


	//n的大小小于size(),删除
	if (size() > n)
	{
		//从n开始destory内存
		for (auto p = elements + n; p != first_free; ++p)
		{
			alloc.destroy(p);
		}
		//更新first_free
		first_free = elements + n;
	}
}

int main()
{

	system("pause");
	return 0;
}

练习13.40:

//接受初始值列表的构造函数
StrVec::StrVec(initializer_list<string>l)
{
	//使用alloc_n_copy赋值元素,创建内存
	auto newData = alloc_n_copy(l.begin(), l.end());
	elements = newData.first;
	first_free = newData.second;
}

练习13.41:

我们在construct调用中使用后置递增运算first_free++,由于first_free指向的是最后一个元素后面的位置,添加新元素时,新元素应该被添加到first_free指向的位置中,然后递增,使first_free指向第一个空闲位置,便于下次元素插入

如果使用前置递增运算++first_free,那么调用construct后首先会将first_free递增,使其指向第一个空闲位置后面的位置,然后将新元素添加到first_free指向的位置中,与我们设计不符

练习13.42:

将vector<string>替换为其简易版本,其他不变,测试,总体代码如下

#include <iostream>
#include <string>
using namespace std;
#include <algorithm>
#include <fstream>
#include <memory>
#include <set>
#include <map>
#include <sstream>



//类vector类内存分配策略的简化实现
class StrVec {
public:
	//默认初始化
	StrVec();
	//接受初始值列表的构造函数
	StrVec(initializer_list<std::string>);
	//拷贝构造函数
	StrVec(const StrVec&);
	//拷贝赋值运算符
	StrVec& operator=(const StrVec&);
	//析构函数
	~StrVec();
	//拷贝元素
	void push_back(const std::string&);
	//数组大小和容量
	size_t size()const;
	size_t capacity()const;
	//获得数组的首元素和尾后元素
	std::string* begin()const;
	std::string* end()const;

	void reserve(size_t n);
	void resize(size_t n);

private:
	//静态成员,用来分配元素,工具人,不需要每个对象都分配,设置为静态成员
	static std::allocator<std::string>alloc;
	//工具函数,被添加元素的函数所使用
	void chk_n_alloc();
	//工具函数,被拷贝构造函数、赋值运算符和析构函数使用
	std::pair<std::string*, std::string*>alloc_n_copy(const std::string*, const std::string*);
	//销毁元素并释放内存
	void free();
	//获得更多内存并拷贝已有元素
	void reallocate();
	//指向数组首元素的指针
	std::string* elements;
	//指向数组第一个空闲元素的指针
	std::string* first_free;
	//指向数组尾后位置的指针
	std::string* cap;
};

//静态成员变量需要在类外定义,但不一定要初始化
std::allocator<std::string> StrVec::alloc;

//reallocate成员
void StrVec::reallocate()
{
	//分配当前大小两倍的内存空间
	auto newCapacity = size() ? 2 * size() : 1;
	//分配新内存
	auto newData = alloc.allocate(newCapacity);
	//将数据从旧内存移到新内存
	//指向数组下一个空闲位置
	auto dest = newData;
	//指向旧数组中下一个元素
	auto elem = elements;
	for (size_t i = 0; i != size(); ++i)
	{
		alloc.construct(dest++, std::move(*elem++));
	}
	//一旦我们移动完元素就释放旧内存空间
	free();
	//更新我们的数据结构,执行新元素
	elements = newData;
	first_free = dest;
	cap = elements + newCapacity;

}

size_t StrVec::capacity()const
{
	return cap - elements;
}

void StrVec::chk_n_alloc()
{
	if (size() == capacity())
	{
		reallocate();
	}
}

size_t StrVec::size()const
{
	return first_free - elements;
}

string* StrVec::begin()const
{
	return elements;
}

string* StrVec::end()const
{
	return first_free;
}

//push_back
void StrVec::push_back(const string& s)
{
	chk_n_alloc();
	//此时有足够的空间,创建元素,拷贝
	alloc.construct(first_free++, s);
}

//工具函数,被拷贝构造函数、赋值运算符和析构函数使用
pair<string*, string*> StrVec::alloc_n_copy(const string* s1, const string* s2)
{
	//拷贝传递参数指定范围的元素
	//创建元素的副本
	auto data = alloc.allocate(s2 - s1);
	//uninitialized_copy(b,e,b2)作用是从迭代器b和e指出的输出范围中拷贝元素到迭代器b2指定的未构造的原始内存中
	// 使用uninitialized_copy(b,e,b2)函数对未构造的函数进行拷贝
	//返回一个pair,分别指向拷贝后的数组首元素和最后元素后面的元素
	return { data,uninitialized_copy(s1,s2,data) };
}

//工具函数free,destory元素,释放StrVec内存
void StrVec::free()
{
	//不能传递给deallocate一个空指针,如果elements为空,函数就什么也不做
	if (elements)
	{
		//逆序销毁元素,收回空间
		for (auto p = first_free; p != elements;)
		{
			alloc.destroy(--p);
		}
		alloc.deallocate(elements, cap - elements);
	}
}

//默认初始化
StrVec::StrVec() :elements(nullptr), first_free(nullptr), cap(nullptr)
{

}

//拷贝构造函数
StrVec::StrVec(const StrVec& sv)
{
	//调用alloc_n_copy分配空间以容纳与sv中一样多的元素
	auto newData = alloc_n_copy(sv.begin(), sv.end());
	elements = newData.first;
	first_free = cap = newData.second;
}

//拷贝赋值运算符
StrVec& StrVec::operator=(const StrVec& sv)
{
	//为了正确处理自赋值,在释放已有元素前调用alloc_n_copy
	//分配内存,大小和sv中占用的元素一样多
	auto data = alloc_n_copy(sv.begin(), sv.end());
	free();
	elements = data.first;
	first_free = cap = data.second;
	return *this;
}

//接受初始值列表的构造函数
StrVec::StrVec(initializer_list<string>l)
{
	//使用alloc_n_copy赋值元素,创建内存
	auto newData = alloc_n_copy(l.begin(), l.end());
	elements = newData.first;
	first_free = newData.second;
}


//析构函数
StrVec::~StrVec()
{
	free();
}

void StrVec::reserve(size_t n)
{
	//分配大小为n的内存
	if (capacity() < n)
	{
		auto newCapacity = n;
		//创建新内存
		auto newData = alloc.allocate(n);
		//将数据从旧内存移到新内存
		//指向数组下一个空闲位置
		auto dest = newData;
		//指向旧数组中下一个元素
		auto elem = elements;
		//将数据移动
		for (size_t i = 0; i != size(); ++i)
		{
			alloc.construct(dest++, std::move(*elem));
		}
		//一旦我们移动完元素就释放旧内存空间
		free();
		//更新我们的数据结构,执行新元素
		elements = newData;
		first_free = dest;
		cap = elements + newCapacity;
	}
}

void StrVec::resize(size_t n)
{
	//n的大小大于size(),以默认值填充
	if (size() < n)
	{
		size_t num = n - size();
		for (size_t i = 0; i != num; ++i)
		{
			alloc.construct(first_free++, std::string());
		}
	}


	//n的大小小于size(),删除
	if (size() > n)
	{
		//从n开始destory内存
		for (auto p = elements + n; p != first_free; ++p)
		{
			alloc.destroy(p);
		}
		//更新first_free
		first_free = elements + n;
	}
}

class QueryResult;
class TextQuery
{
public:
	using line_no = int;
	//构造函数
	TextQuery(std::ifstream&);
	QueryResult query(const std::string&)const;

private:
	std::shared_ptr<StrVec>file;
	std::map<std::string, std::shared_ptr<set<line_no>>>wm;
};

TextQuery::TextQuery(ifstream& is) :file(new StrVec)
{
	string text;
	while (getline(is, text))
	{
		file->push_back(text);
		size_t n = file->size() - 1;
		istringstream line(text);
		string word;
		while (line >> word)
		{
			//如果单词不在wm中,以之为下标在wm中添加一项
			auto& lines = wm[word];
			if (!lines)
			{
				//此时lines是新插入map中的一项,string部分为word,现在创建set
				lines.reset(new set<line_no>);
			}
			//将行号插入set中
			lines->insert(n);
		}
	}
}

class QueryResult
{
	friend ostream& print(ostream& os, const QueryResult& qr);
public:
	using line_no = int;
	//构造函数
	QueryResult(std::string s, std::shared_ptr<std::set<line_no>>p, std::shared_ptr<StrVec>f) :
		sought(s), lines(p), file(f) { }

private:
	//查询单词
	std::string sought;
	//出现的行号
	std::shared_ptr<std::set<line_no>> lines;
	//输入文件
	std::shared_ptr<StrVec> file;
};

//query函数实现
QueryResult TextQuery::query(const string& sought)const
{
	//如果未找到sought,我们返回一个指向此set的指针
	static shared_ptr<set<line_no>>nodata(new set<line_no>);
	//使用find而不是下标运算符来查找单词,避免将单词添加到wm中!
	auto loc = wm.find(sought);
	if (loc == wm.end())
	{
		//未找到
		return QueryResult(sought, nodata, file);
	}
	else
	{
		return QueryResult(sought, loc->second, file);
	}
}

string make_plural(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_plural(qr.lines->size(), "time", "s") << endl;
	//打印单词出现的每一行
	for (auto num : *qr.lines)
	{
		//避免行号从0开始给用户造成困惑
		os << "\t(line" << num + 1 << ")" << *(qr.file->begin() + num) << endl;
	}
	return os;
}

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 infile("text.txt");
	runQueries(infile);

	system("pause");
	return 0;
}

测试结果:

 text.txt文件内为:

 练习13.43:

//重写free
void StrVec::free()
{
	//不能传递给deallocate一个空指针,如果elements为空,函数就什么也不做
	if (elements)
	{
		for_each(elements, first_free, [](string& p) {alloc.destroy(&p); });
		alloc.deallocate(elements, cap - elements);
	}
}

elements和first_free是string*类型,因此它们指出的范围中的元素是string类型。因此,lambda的参数s应该是string&类型,在lambda的函数体中应该取s的地址,用来调用destroy

lambda实现更好,使用lambda我们不需要控制指针的增减,只需要控制指针的范围即可

练习13.44:

简化版的string与简化版的vector<string>类似,其中的元素变为char,将StrVec的位置改为String,操作的元素为char即可,代码如下:

#include <iostream>
#include <string>
using namespace std;
#include <algorithm>
#include <fstream>
#include <memory>
#include <set>
#include <map>
#include <sstream>



//标准库string的简化实现
class String{
public:
	//默认初始化
	String();
	//接受C风格字符串指针参数
	String(const char*);
	//接受初始值列表的构造函数
	String(initializer_list<char>);
	//拷贝构造函数
	String(const String&);
	//拷贝赋值运算符
	String& operator=(const String&);
	//析构函数
	~String();
	//添加元素
	void push_back(const char&);
	//数组大小和容量
	size_t size()const;
	size_t capacity()const;
	//获得数组的首元素和尾后元素
	char* begin()const;
	char* end()const;

	void reserve(size_t n);
	void resize(size_t n);

private:
	//静态成员,用来分配元素,工具人,不需要每个对象都分配,设置为静态成员
	static std::allocator<char>alloc;
	//工具函数,被添加元素的函数所使用
	void chk_n_alloc();
	//工具函数,被拷贝构造函数、赋值运算符和析构函数使用
	std::pair<char*, char*>alloc_n_copy(const char*, const char*);
	void free();
	//获得更多内存并拷贝已有元素
	void reallocate();
	//指向数组首元素的指针
	char* elements;
	//指向数组第一个空闲元素的指针
	char* first_free;
	//指向数组尾后位置的指针
	char* cap;
};

//静态成员变量需要在类外定义,但不一定要初始化
std::allocator<char> String::alloc;

//reallocate成员
void String::reallocate()
{
	//分配当前大小两倍的内存空间
	auto newCapacity = size() ? 2 * size() : 1;
	//分配新内存
	auto newData = alloc.allocate(newCapacity);
	//将数据从旧内存移到新内存
	//指向数组下一个空闲位置
	auto dest = newData;
	//指向旧数组中下一个元素
	auto elem = elements;
	for (size_t i = 0; i != size(); ++i)
	{
		alloc.construct(dest++, std::move(*elem++));
	}
	//一旦我们移动完元素就释放旧内存空间
	free();
	//更新我们的数据结构,执行新元素
	elements = newData;
	first_free = dest;
	cap = elements + newCapacity;

}

size_t String::capacity()const
{
	return cap - elements;
}

void String::chk_n_alloc()
{
	if (size() == capacity())
	{
		reallocate();
	}
}

size_t String::size()const
{
	return first_free - elements;
}

char* String::begin()const
{
	return elements;
}

char* String::end()const
{
	return first_free;
}

//push_back
void String::push_back(const char& c)
{
	chk_n_alloc();
	//此时有足够的空间,创建元素,拷贝
	alloc.construct(first_free++, c);
}

//工具函数,被拷贝构造函数、赋值运算符和析构函数使用
pair<char*, char*> String::alloc_n_copy(const char* c1, const char* c2)
{
	//拷贝传递参数指定范围的元素
	//创建元素的副本
	auto data = alloc.allocate(c2 - c1);
	//uninitialized_copy(b,e,b2)作用是从迭代器b和e指出的输出范围中拷贝元素到迭代器b2指定的未构造的原始内存中
	// 使用uninitialized_copy(b,e,b2)函数对未构造的函数进行拷贝
	//返回一个pair,分别指向拷贝后的数组首元素和最后元素后面的元素
	return { data,uninitialized_copy(c1,c2,data) };
}


//free
void String::free()
{
	//不能传递给deallocate一个空指针,如果elements为空,函数就什么也不做
	if (elements)
	{
		for_each(elements, first_free, [](char& p) {alloc.destroy(&p); });
		alloc.deallocate(elements, cap - elements);
	}
}

//默认初始化
String::String() :elements(nullptr), first_free(nullptr), cap(nullptr)
{

}

//接受C风格字符串指针参数
String::String(const char* str)
{
	//确定数组大小
	auto newCapacity = strlen(str);
	//创建内存
	auto newData = alloc.allocate(newCapacity);
	elements = newData;
	//复制元素
	auto p = uninitialized_copy(str, str + newCapacity, newData);
	//更新指针
	first_free = p;
	cap = elements + newCapacity;
}

//拷贝构造函数
String::String(const String& cv)
{
	//调用alloc_n_copy分配空间以容纳与sv中一样多的元素
	auto newData = alloc_n_copy(cv.begin(), cv.end());
	elements = newData.first;
	first_free = cap = newData.second;
}

//拷贝赋值运算符
String& String::operator=(const String& cv)
{
	//为了正确处理自赋值,在释放已有元素前调用alloc_n_copy
	//分配内存,大小和sv中占用的元素一样多
	auto data = alloc_n_copy(cv.begin(), cv.end());
	free();
	elements = data.first;
	first_free = cap = data.second;
	return *this;
}

//接受初始值列表的构造函数
String::String(initializer_list<char>l)
{
	//使用alloc_n_copy赋值元素,创建内存
	auto newData = alloc_n_copy(l.begin(), l.end());
	elements = newData.first;
	first_free = newData.second;
}


//析构函数
String::~String()
{
	free();
}

void String::reserve(size_t n)
{
	//分配大小为n的内存
	if (capacity() < n)
	{
		auto newCapacity = n;
		//创建新内存
		auto newData = alloc.allocate(n);
		//将数据从旧内存移到新内存
		//指向数组下一个空闲位置
		auto dest = newData;
		//指向旧数组中下一个元素
		auto elem = elements;
		//将数据移动
		for (size_t i = 0; i != size(); ++i)
		{
			alloc.construct(dest++, std::move(*elem));
		}
		//一旦我们移动完元素就释放旧内存空间
		free();
		//更新我们的数据结构,执行新元素
		elements = newData;
		first_free = dest;
		cap = elements + newCapacity;
	}
}

void String::resize(size_t n)
{
	//n的大小大于size(),以默认值填充
	if (size() < n)
	{
		size_t num = n - size();
		for (size_t i = 0; i != num; ++i)
		{
			alloc.construct(first_free++, char());
		}
	}


	//n的大小小于size(),删除
	if (size() > n)
	{
		//从n开始destory内存
		for (auto p = elements + n; p != first_free; ++p)
		{
			alloc.destroy(p);
		}
		//更新first_free
		first_free = elements + n;
	}
}

int main()
{
	system("pause");
	return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
c primer 15.9作业是关于异常处理的内容。在这一中,主要介绍了C语言中的错误处理机制和异常处理方式。 异常处理是一种程序设计中的重要思想,它允许应对出现的各种错误或异常情况,从而增加程序的健壮性和可靠性。C语言中的异常处理主要通过使用错误码和错误处理函数来实现。 在进行异常处理时,通常需要先定义一些错误码,用于标识可能出现的异常情况。C语言提供了一些标准的错误码,如errno.h头文件中定义的错误码,还可以根据需要自定义错误码。 接下来,我们需要在程序中合适的位置进行错误检测并进行异常处理。可以使用if语句或者switch语句等条件语句来检测错误码,并根据不同的错误码执行相应的错误处理代码。 错误处理代码的内容可以根据具体情况而定,它可以是打印错误信息、修复错误、返回错误码等操作。在处理完异常后,可以继续执行后续的程序逻辑,或者返回到调用处继续处理。 除了使用错误码和错误处理函数进行异常处理外,C语言还提供了一种特殊的异常处理方式,即信号处理。信号处理是通过捕捉和处理操作系统发送的信号来实现的,通过注册信号处理函数,可以在程序遇到特定信号时执行相应的处理代码。 总之,C语言中的异常处理是一种重要的错误处理机制,可以提高程序的可靠性和健壮性。通过定义错误码、错误处理函数和信号处理,可以有效地捕捉和处理各种异常情况。在编写C程序时,合理地使用异常处理机制是至关重要的。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小白学C++.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值