c++学习笔记:动态内存管理类StrVec类的实现

来源:互联网 发布:windows10基本软件 编辑:程序博客网 时间:2024/06/03 21:39

vector类将其元素保存在连续的内存中,vector会预先分配足够的内存来保存可能需要的更多元素。vector每次添加元素时都会检查是否有可用的空间容纳更多的元素,所以每个添加元素的成员函数在添加元素之前都会检查当前所有的内存是否有可用的空间。如果有,就会在下一个可用的位置构造一个对象。如果没有,vector类就会重新分配空间,把原来的元素移动到新的空间去,再释放旧内存。根据这样的特性,所以要用allocator类来获得一个块未构造的原始内存,这样就可以根据需要是否需要构造还是保留。

allocator类的成员函数:

allocate(n) :

分配一块原始的未构造的内存,内存大小为n。

deallocate/(p, n):

释放内存指针p中地址开始的内存,这块内存保存着n个对象。p必须是allocate返回的指针,n必须是创建时所要求的大小。注意!!必须先调用destroy销毁元素之后,才能用deallocate释放内存。而且,不能给deallocate函数传递一个空指针。

construct(p, args):

p指向一个未构造的内存,args是用来构造一个对象的值。

destroy(p):

销毁指针p指向的对象

注意!destroy是销毁元素,不释放内存。释放内存需要用deallocate函数。


vector类的成员解释:

size 和 capacity的区别:

size指明的是当前vector已经保存的元素数目。而capacity则是在不分配新内存的前提下,容器最多能保存多少个元素。

resize 和 reserve的区别:

resize 只改变的是容器中元素的数目,不改变容器的容量。而reserve改变的是容器的容量。

头文件:

  1 #ifndef _STRVEC_H_  2 #define _STRVEC_H_  3 #include <string>  4 #include <utility>  5 #include <memory>  6 #include <initializer_list>  7   8 class Strvec  9 { 10 public: 11     /*三个指针都初始化为空,表明没有元素。当添加元素的时候使用push_back()函数,会自动 12      * 调用check_n_alloc()函数检查,然后再调用reallocate()函数分配空间,构造元素*/ 13     Strvec() : element(nullptr), first_free(nullptr), cap(nullptr) {} 14     Strvec(std::initializer_list<std::string>); //接受列表初始化的构造函数 15     Strvec(const Strvec &); 16     Strvec &operator=(const Strvec &); 17     ~Strvec(); 18     void push_back(const std::string &); 19     size_t size() const { return first_free - element; }//当前容器中元素的数目 20     size_t capacity() const { return cap - element; } //当前容器的容量 21     void reserve(int); 22     void resize(size_t); 23     void resize(size_t, const std::string &); 24     bool empty() const; 25     std::string *begin() const { return element; } 26     std::string *end() const { return first_free; } 27 private: 28     static std::allocator<std::string> alloc; //默认构造函数隐式初始化,静态成员在类外初始化 29     void check_n_alloc();//用来检查是否有可用内存的函数 30     void free(); 31     void reallocate(); 32     /*pair的作用是它的first成员指向新分配内存块的第一个元素的位置,second成员指向内存块 33      * 末尾之后的位置,这样就能确定这一块内存的所在*/ 34     std::pair<std::string *, std::string *> alloc_n_copy(const std::string *, const std::string *); 35     std::string *element; //指向分配内存的首元素 36     std::string *first_free; //指向分配内存尾元素的下一个位置 37     std::string *cap; //指向内存块末尾之后的位置 38 }; 39  40 #endif

定义静态成员和check_n_alloc()函数。

check_n_alloc函数首先判断,然后再决定是否调用reallocate函数分配新的内存。

  3 std::allocator<std::string> Strvec::alloc; //必须类外定义静态成员,初始化也是  4   5 void Strvec::check_n_alloc()  6 {     7     if (size() == capacity()) //检查当前的内存是否已经全部构造  8         reallocate();         //如果没有可用的空间,则调用reallocate()函数重新分配一块更大的内存  9 }                             //然后把元素移到新内存,释放旧内存 


reallocate() 函数

首先检查当前的内存容量,如果当前容量不为空则分配当前容量的两倍内存。如果为空,就分配容纳一个元素的内存。它的工作顺序是这样的:

1.先分配一块新的,更大的内存

2.把旧内存的元素移动到新内存当中去

3.释放旧内存的空间,然后更新确定内存块的三个指针

 29 void Strvec::reallocate() 30 {    31     auto newcapacity = size() ? 2 * size() : 1; //确定新内存的大小 32     auto newdata = alloc.allocate(newcapacity);//开辟内存空间 33     auto newconst = newdata; //开辟内存返回的指针不能移动,所以需要一个辅助指针 34     auto old = element; //同理,element也不能移动,需要辅助指针 35      36     //将旧内存的元素移动到新内存之中 37     for (size_t i = 0; i != size(); i++) 38         //通常写std::move, 不为move提供using声明 39         //而且不能漏掉std::move,否则会调用string的拷贝构造函数 40         alloc.construct(newconst++, std::move(*old++)); 41      42     free(); //销毁旧内存 43     //更新数据指针,指向新的内存块 44     element = newdata; 45     first_free = newconst; 46     cap = element + newcapacity; 47 }



push_back()函数

上面有提到。每次添加元素的时候都要对容器的内存进行检查是否有可用的内存,如果有则在未构造的内存构造一个对象。否则重新分配内存,在添加元素。所以push_back函数先调用check_n_alloc函数检查是否需要重新分配内存,如果需要,则调用reallocate函数。

 11 void Strvec::push_back(const std::string &str) 12 {    13     check_n_alloc(); //先检查是否还有可用的空间 14     alloc.construct(first_free++, str); //在first_free指向的位置构造一个元素,使用后置自增 15 }                                       //在原来的位置构造,再移到下一个未构造的位置

free()函数

先用destroy销毁元素,再用deallocate释放内存空间。而且是逆序销毁元素,因为指向首元素的内存是用来确定内存的,不能移动。

 17 void Strvec::free() 18 { //不能给deallocate函数传递一个空指针,否则销毁一个不存在的内存,出现错误 19     if (element){  20         auto p = first_free; //按逆序销毁元素 21         while (p != element) 22             alloc.destroy(--p); //因为p刚开始指向的是未构造的内存,所以要先递减 23                                 //注意destroy只是销毁元素,不释放内存                    24         alloc.deallocate(element, cap - element); //释放内存 25         //element必须是由allocate返回的指针,并且cap-element必须是创建时的大小 26     } 27 }


alloc_n_copy()函数:

这个函数返回一个pair,两个指针分贝指向新空间的开始位置和尾后位置。尾后位置指的是,内存末尾的下一个位置。

uninitialized_copy(b, e, b2)是allocator类的伴随算法,作用是:

从迭代器 b 到 e 的范围中拷贝到迭代器b2指向的未构造的原始内存中。

在c++11标准中,pair 可以用一个花括号初始化。而不需要显示的用make_pair<std::string *, std::string *> 或者std::pair<std::string *, std::string *> ().

 49 std::pair<std::string *, std::string *> 50 Strvec::alloc_n_copy(const std::string *begin, const std::string *end) 51 {    52     auto data = alloc.allocate(end - begin); //分配副本的空间 53     //uninitialized_copy把范围begin--end的元素拷贝到data指向的未构造内存中 54     //返回指向最后一个构造元素之后的位置。所以data指向内存块的首位置 55     //uninitialize_copy的返回值指向内存块的尾后位置 56     return {data, uninitialized_copy(begin, end, data)};//用花括号初始化一个pair 57 }

拷贝,构造,析构成员:

 59 Strvec::Strvec(std::initializer_list<std::string> list) 60 { //列表初始化构造函数 61     auto new_capacity = list.size() * 2; //初始化分配两倍的空间 62     auto newdata = alloc.allocate(new_capacity); 63     auto p = newdata; 64      65     for (auto begin = list.begin(); begin != list.end(); begin++) 66         alloc.construct(p++, *begin); //构造元素 67      68     //更新数据结构 69     element = newdata; 70     first_free = p;  71     cap = element + new_capacity; 72 } 73  74 Strvec::Strvec(const Strvec &obj) //拷贝构造函数 75 {    76     auto newdata = alloc_n_copy(obj.begin(), obj.end()); 77      78     element = newdata.first; 79     first_free = cap = newdata.second;  80     //因为开辟的内存已经全部被构造,所以cap和first_free都指向一个地方 81 } 82  83 Strvec &Strvec::operator=(const Strvec &obj) //拷贝赋值运算符 84 {    85     //先备份右对象的一份拷贝,再销毁左对象的内存,再赋值 86     //这样及时左右对象都是同一个对象的时候也能正确赋值 87     auto temp = alloc_n_copy(obj.begin(), obj.end()); 88  89     free(); 90     //因为alloc_n_copy返回的是一个pair,所以它的first数据成员就是指向 91     //首元素,而second成员就是指向尾后位置 92     element = temp.first; 93     first_free = cap = temp.second; 94  95     return *this; //拷贝赋值运算符记得返回自身的引用 96 } 97  98 Strvec::~Strvec() //析构函数,直接调用free函数释放内存 99 {100     free();101 }

reverse()函数

reverse(n) 函数是分配至少能容纳n个元素的内存空间。只有当给定的大小超过当前容量,reserve才会改变vector的容量,否则reserve什么也不做。调用reserve之后,capacity将会等于或者大于传递给reverse的大小。

103 void Strvec::reserve(int n)104 {   105     //reverse函数的作用是:当要求的大小大于当前容量时,则重新分配内存106     //否则什么也不会做107     if (n > capacity()){108         auto newdata = alloc.allocate(n);109         auto newconst = newdata;110         auto old = element;111 112         for (size_t i = 0; i != size(); i++)113             alloc.construct(newconst++, std::move(*old++));114 115         free(); //重新分配一块大内存之后记得销毁原来的内存116         element = newdata;117         first_free =  newconst;118         cap = element + n;119     }120 }


resize()函数(重载)

函数重载回忆:

在同一作用域内的几个名字相同,但是形参列表不同(形参数量不同或形参类型不同)的函数,称之为重载函数。

注意!不以函数的返回类型区分。

122 void Strvec::resize(size_t n) //只提供一个参数的版本123 {124     //只提供大小的版本,如果大于当前元素数目,则进行值初始化125     //否则,销毁大于提供大小的那部分元素。只销毁元素,不释放内存126     //因为resize不改变容器的容量127     if (n > size())128         while (size() < n)129             push_back("");130     else if (n < size())131         while (size() > n)132             alloc.destroy(--first_free);133 }134 135 void Strvec::resize(size_t n, const std::string &s) //提供两个参数的版本136 { //提供初始值的版本,如果大小小于之前的,则什么也不做137     if (n > size())138         while (size() < n)139             push_back(s);140 }

empty() 函数


142 bool Strvec::empty() const143 {144     if (size() > 0)145         return true;146     else147         return false;148 }


类的实现:

  1 #include "strvec.h"  2   3 std::allocator<std::string> Strvec::alloc; //必须类外定义静态成员,初始化也是  4   5 void Strvec::check_n_alloc()  6 {     7     if (size() == capacity()) //检查当前的内存是否已经全部构造  8         reallocate();         //如果没有可用的空间,则调用reallocate()函数重新分配一块更大的内存  9 }                             //然后把元素移到新内存,释放旧内存  10  11 void Strvec::push_back(const std::string &str) 12 {    13     check_n_alloc(); //先检查是否还有可用的空间 14     alloc.construct(first_free++, str); //在first_free指向的位置构造一个元素,使用后置自增 15 }                                       //在原来的位置构造,再移到下一个未构造的位置 16  17 void Strvec::free() 18 { //不能给deallocate函数传递一个空指针,否则销毁一个不存在的内存,出现错误 19     if (element){  20         auto p = first_free; //按逆序销毁元素 21         while (p != element) 22             alloc.destroy(--p); //因为p刚开始指向的是未构造的内存,所以要先递减 23                                 //注意destroy只是销毁元素,不释放内存                    24         alloc.deallocate(element, cap - element); //释放内存 25         //element必须是由allocate返回的指针,并且cap-element必须是创建时的大小 26     } 27 } 28  29 void Strvec::reallocate() 30 {    31     auto newcapacity = size() ? 2 * size() : 1; //确定新内存的大小 32     auto newdata = alloc.allocate(newcapacity);//开辟内存空间 33     auto newconst = newdata; //开辟内存返回的指针不能移动,所以需要一个辅助指针 34     auto old = element; //同理,element也不能移动,需要辅助指针 35      36     //将旧内存的元素移动到新内存之中 37     for (size_t i = 0; i != size(); i++) 38         //通常写std::move, 不为move提供using声明 39         //而且不能漏掉std::move,否则会调用string的拷贝构造函数 40         alloc.construct(newconst++, std::move(*old++)); 41      42     free(); //销毁旧内存 43     //更新数据指针,指向新的内存块 44     element = newdata; 45     first_free = newconst; 46     cap = element + newcapacity; 47 } 48  49 std::pair<std::string *, std::string *> 50 Strvec::alloc_n_copy(const std::string *begin, const std::string *end) 51 { 52     auto data = alloc.allocate(end - begin); //分配副本的空间 53     //uninitialized_copy把范围begin--end的元素拷贝到data指向的未构造内存中 54     //返回指向最后一个构造元素之后的位置。所以data指向内存块的首位置 55     //uninitialize_copy的返回值指向内存块的尾后位置 56     return {data, uninitialized_copy(begin, end, data)};//用花括号初始化一个pair 57 } 58  59 Strvec::Strvec(std::initializer_list<std::string> list) 60 { //列表初始化构造函数 61     auto new_capacity = list.size() * 2; //初始化分配两倍的空间 62     auto newdata = alloc.allocate(new_capacity); 63     auto p = newdata; 64  65     for (auto begin = list.begin(); begin != list.end(); begin++) 66         alloc.construct(p++, *begin); //构造元素 67  68     //更新数据结构 69     element = newdata; 70     first_free = p; 71     cap = element + new_capacity; 72 } 73  74 Strvec::Strvec(const Strvec &obj) 75 { 76     auto newdata = alloc_n_copy(obj.begin(), obj.end()); 77  78     element = newdata.first; 79     first_free = cap = newdata.second; 80     //因为开辟的内存已经全部被构造,所以cap和first_free都指向一个地方 81 } 82  83 Strvec &Strvec::operator=(const Strvec &obj) 84 { 85     //先备份右对象的一份拷贝,再销毁左对象的内存,再赋值 86     //这样及时左右对象都是同一个对象的时候也能正确赋值 87     auto temp = alloc_n_copy(obj.begin(), obj.end()); 88  89     free(); 90     //因为alloc_n_copy返回的是一个pair,所以它的first数据成员就是指向 91     //首元素,而second成员就是指向尾后位置 92     element = temp.first; 93     first_free = cap = temp.second; 94  95     return *this; 96 } 97  98 Strvec::~Strvec() 99 {100     free();101 }102 103 void Strvec::reserve(int n)104 {105     //reverse函数的作用是:当要求的大小大于当前容量时,则重新分配内存106     //否则什么也不会做107     if (n > capacity()){108         auto newdata = alloc.allocate(n);109         auto newconst = newdata;110         auto old = element;111 112         for (size_t i = 0; i != size(); i++)113             alloc.construct(newconst++, std::move(*old++));114 115         free(); //重新分配一块大内存之后记得销毁原来的内存116         element = newdata;117         first_free =  newconst;118         cap = element + n;119     }120 }121 122 void Strvec::resize(size_t n)123 {124     //只提供大小的版本,如果大于当前元素数目,则进行值初始化125     //否则,销毁大于提供大小的那部分元素。只销毁元素,不释放内存126     //因为resize不改变容器的容量127     if (n > size())128         while (size() < n)129             push_back("");130     else if (n < size())131         while (size() > n)132             alloc.destroy(--first_free);133 }134 135 void Strvec::resize(size_t n, const std::string &s)136 { //提供初始值的版本,如果大小小于之前的,则什么也不做137     if (n > size())138         while (size() < n)139             push_back(s);140 }141 142 bool Strvec::empty() const143 {144     if (size() > 0)145         return true;146     else147         return false;148 }


测试:

  1 #include <iostream>  2 #include "strvec.h"  3 using namespace std;  4   5 void Verify_iterator(Strvec &obj)  6 {     7     for (auto begin = obj.begin(); begin != obj.end(); begin++)  8         cout << *begin << endl;  9 } 10  11 void Verify_resize(Strvec &obj) 12 {    13     int a = 2, b = 6; 14      15     obj.resize(a); 16     Verify_iterator(obj); 17     obj.resize(b); 18     Verify_iterator(obj); 19     obj.resize(8, "hello"); 20     Verify_iterator(obj); 21 } 22  23 void Verify_reserve(Strvec &obj) 24 { 25     obj.reserve(50); 26     cout << obj.capacity() << endl; 27 } 28  29 void Verify_push_back(Strvec &obj) 30 { 31     string str = "push_back"; 32  33     obj.push_back(str); 34     Verify_iterator(obj); 35 } 36  37 int main() 38 { 39     Strvec obj{"you", "are", "mine"}; 40  41     cout << "test iterator" << endl; 42     Verify_iterator(obj); 43     cout << "test reserve" << endl; 44     Verify_reserve(obj); 45     cout << "test push_abck" << endl; 46     Verify_push_back(obj); 47     cout << "test resize" << endl; 48     Verify_resize(obj); 49  50     cout << "test obj_1" << endl; 51     Strvec obj_1; 52     obj_1.push_back("hello world"); 53     Verify_iterator(obj_1); 54  55     cout << "test empty" << endl; 56     if (obj_1.empty()) 57         cout << "not empty" << endl; 58  59     return 0; 60 }