知识点
1、静态成员变量要在类外定义和初始化
2、allocator类是使用和uninitialized_copy的配合使用,实现string空间的分配和strings数据的拷贝(拷贝string的时候调用的string的拷贝构造函数,string的拷贝构造函数会拷贝string指向的char数据)。
3、拷贝构造函数需要拷贝StrVec类的成员也要拷贝StrVec指向的string。
4、拷贝赋值运算符,拷贝了右侧对象StrVec指向的数据,同时销毁左侧StrVec指向的数据
5、重新分配空间的时候,只拷贝string而不拷贝string指向的空间,通过alloc.construct(dst++, str::move(src++))实现。
alloc_n_copy()和free()函数
在拷贝构造函数,拷贝赋值运算符都要用到空间分配和指向数据的拷贝,所以定义一个alloc_n_copy()函数
在拷贝赋值运算符和析构函数中都要释放指向的空间,所以定义free()函数
一般类中都应该定义这两个private函数,方便时序拷贝构造函数、拷贝赋值运算符和析构函数。
StrVec.h
#include <string> #include <memory> #include <utility> // pair move #include <initializer_list> // initializer_list #include <algorithm> // for_each #ifndef STRVEC__H #define STRVEC__H using namespace std; class StrVec { public: StrVec():b(nullptr),e(nullptr),cap(nullptr){} StrVec(const StrVec &); StrVec &operator=(const StrVec &); ~StrVec(); void push_back(const string &); size_t size() const {return e - b;} size_t capacity() const {return cap - b;} void reserve(const size_t &); void resize(const size_t &); void resize(const size_t &, const string &); string *begin() const {return b;} string *end() const {return e;} private: static allocator<string> alloc; void chk_n_alloc() {if(size() == capacity()) reallocate();} pair<string*,string*> alloc_n_copy(const string*, const string*); void free(); void reallocate(); string *b; string *e; string *cap; } #endif
StrVec.cpp
#include "StrVec.h" // 静态成员变量定义和默认初始化。 // 静态成员变量不属于任何对象,不能在类的构造函数中被构造和初始化,必须在类的外部定义和初始化 // 不要重复写static // 静态函数可以在类内部定义(没有初始化一说) allocator<string> StrVec::alloc; // 列表初始化的构造函数 // initializer_list元素是const的,但是initializer_list不是const的,要接收{"abc","hello"}这样为const的列表 // 要求initializer_list也为const类型的。 StrVec:StrVec(const initializer_list<string> &strs) { auto p = alloc_n_copy(strs.begin(), strs.end()); b = p.first; e = cap = p.second; } // 分配能容纳拷贝数据的空间,并把数据拷贝到新空间。 // 返回分配空间的首尾地址 // 这里是真实的数据拷贝。 pair<string*,string*> Strvec::alloc_n_copy(const string *b_ptr, const string *e_ptr) { auto p = alloc.allocate(e_ptr - b_ptr); return {p, uninitialized_copy(b_ptr, e_ptr, p)};// 调用了string的拷贝构造函数,拷贝构造函数中,重新为string指向的char分配了空间并拷贝。 } // 释放StrVec分配的空间 // 先destroy StrVec中的成员,再把整个分配空间都释放(deallocate) void StrVec::free() { if(e) { for(auto p = e; p != b;) alloc.destroy(--p); alloc.deallocate(cap-b); } } // 使用for_each实现的free函数 //void StrVec::free() { // if(e) { // for_each(b, e, [](string &str)->void{destroy(&str);}); // alloc.deallocate(b, cap - b); // } //} // 重新分配更多的空间(string空间),并把原数据(char)移动到新空间中 // 利用string的移动函数,可以不要拷贝string执行的char数据,而只拷贝string指向char数据的首指针? // 这里只分配string的空间,而不分配string指向char的空间 // 移动后,销毁原string的空间,而不销毁string指向char的空间,应为char的空间被新的string指向了。 void StrVec::reallocate() { size_t newcapacity = size() ? 2*size() : 1; // 如果有数据就分配原来两倍数据的空间,如果没有就只分配1个string auto p = alloc.allocate(newcapacity); auto dst = p; // dst和src要递增,要保存一个p和b的备份用于拷贝string成员。 auto src = b; for(size_t i=0; i != size(); ++i) alloc.construct(dst++, std::move(*src++)); b = p; e = dst;// p + size(); cap = p + newcapacity; } // 拷贝构造函数 // 拷贝StrVec指向的string, 同时拷贝一下StrVec的成员变量。 // 这个是真实的拷贝,alloc_n_copy拷贝StrVec指向的string,uninitialized_copy拷贝string指向的char StrVec::StrVec(const StrVec &s) { auto p = alloc_n_copy(s.begin(), s.end()); b = p.first; e = cap = p.second; } // 拷贝赋值运算符 // 真实的拷贝右侧对象指向的数据和成员变量到左侧对象 // 释放左侧对象指向空间,比拷贝构造函数多一个释放指向空间的过程。 StrVec &StrVec::operator=(const StrVec &s) { auto p = alloc_n_copy(s.begin(), s.end()); free(); b = p.first; e = cap = p.second; } // 析构函数,释放StrVec指向空间,成员对象会自动析构。 StrVec::~StrVec() { free(); } // 往StrVec中添加一个string // 先检查还有没没有构造的空间,没有就分配一些,然后在第一个没有构造的空间上构造string. void StrVec::push_back(const string &str) { chk_n_alloc(); alloc.construct(e++, str); } // 如果size>n,销毁后面的size-n个数据 // 如果size<=n,在模块构造n-size个数据 void StrVec::resize(const size_t &n) { if(n > capacity()) { // n > capacity,要使用的空间比现有空间多,就要分配空间 auto p = alloc.allocate(n); auto dst = p; auto src = b; size_t i = 0; for(; i != size(); ++i) alloc.construct(dst++, std::move(src++)); for(; i != n; ++i) alloc.construct(dst++);//使用string的默认构造函数构造。 free(); b = p; e = cap = dst; } else if(n > size()) { // size < n < capacity,要使用的空间比现有少,但是比使用的空间多,在现有的空间上构造数据即可 while(e < b+n) alloc.construct(e++); } else { // n < size,要使用的空间比使用的还有少,要销毁模块的数据。 while(e > b+n) alloc.destroy(--e); } } // 如果size>n,销毁后面的size-n个数据 // 如果size<=n,在模块构造n-size个数据 void StrVec::resize(const size_t &n, const string &str) { if(n > capacity()) { // n > capacity,要使用的空间比现有空间多,就要分配空间 auto p = alloc.allocate(n); auto dst = p; auto src = b; size_t i = 0; for(; i != size(); ++i) alloc.construct(dst++, std::move(src++)); for(; i != n; ++i) alloc.construct(dst++, str);//使用string的拷贝构造函数。 free(); b = p; e = cap = dst; } else if(n > size()) { // size < n < capacity,要使用的空间比现有少,但是比使用的空间多,在现有的空间上构造数据即可 while(e < b+n) alloc.construct(e++, str); } else { // n < size,要使用的空间比使用的还有少,要销毁模块的数据。 while(e > b+n) alloc.destroy(--e); } } // 修改容器的容量,如果capacity()<n时会分配新空间,但是capacity()>=n时什么也不做 void StrVec::reserve(const size_t &n) { if(capacity() < n) { auto p = alloc.allocate(n); auto dst = p; auto src = b; for(size_t i=0; i<size(); ++i) alloc.const(dst++, std::move(src++)); free(); b = p; e = dst; cap = b + n; } }
测试程序
string a = "name"; string b = "hello"; string c = "world"; StrVec str; // 测试默认构造函数 str.push_back(a); // 测试push_back str.push_back(b); str.push_back(c); for(const auto &v : str) cout<<v<<endl; // 输出3行,name/hello/world StrVec str2(str); // 测试拷贝构造函数 for(const auto &v : str2) cout<<v<<endl; // 输出3行,name/hello/world StrVec str3; str3 = str; // 测试拷贝构赋值运算符 for(const auto &v : str3) cout<<v<<endl; // 输出3行,name/hello/world cout<<"size:"<<str.size()<<",capacity:"<<str.capacity()<<endl; // 输出size:3,capacity:4 str.reserve(10); cout<<"size:"<<str.size()<<",capacity:"<<str.capacity()<<endl; // 输出size:3,capacity:10 str.resize(10); cout<<"size:"<<str.size()<<",capacity:"<<str.capacity()<<endl; // 输出size:10,capacity:10 for(const auto &v : str) cout<<v<<endl; // 输出3行,name/hello/world和7个空行 str.resize(2); cout<<"size:"<<str.size()<<",capacity:"<<str.capacity()<<endl; // 输出size:2,capacity:10 for(const auto &v : str) cout<<v<<endl; // 输出3行,name/hello str.resize(12,"xx"); cout<<"size:"<<str.size()<<",capacity:"<<str.capacity()<<endl; // 输出size:2,capacity:10 for(const auto &v : str) cout<<v<<endl; // 输出3行,name/hello和10行"xx" StrVec str4 = {"hello", "list", "strVec"}; // 测试列表构造函数 for(const auto &v : str4) cout<<v<<endl; // 输出3行,hello/list/strVec