zoukankan      html  css  js  c++  java
  • c++ StrVec等效vector(string)的类

    c++ StrVec等效vector<string>的类

    知识点

    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
    
  • 相关阅读:
    http的8种请求方式
    死锁
    进程与线程
    vuex
    路由懒加载
    SPA单页面富应用
    组件中的data为什么必须是函数
    v-for中key的作用
    关于排序的常识总结
    关于树的常见操作-C++面试
  • 原文地址:https://www.cnblogs.com/yuandonghua/p/15636003.html
Copyright © 2011-2022 走看看