std::string基本功能和操作的实现

类的成员变量

class string
    {
    public:
				typedef char* iterator;
				typedef const char* const_iterator;
		private:
        char* _str;
        size_t _size;
        size_t _capacity;
        static const size_t npos;
        //可以初始化,只针对整型,double等类型不行
//        static const size_t npos=-1;
		};
		const size_t string::npos=-1;

公有成员变量:

重命名模版类型T*指针为迭代器iterator和const类型的const_iterator迭代器

私有成员变量:

char str*为字符数组,string类中存储字符的指针就是它;

_size和_capacity分别是字符个数和空间大小,因为不可能是负数所以直接用size_t定义;

npos则是string类中默认赋值为-1的static const静态常量,无法被修改,且初始化需要在class类外部初始化。

Tip:整型的静态常量可以在私有区域初始化,但是只针对整型

基本的函数

const char* c_str()
        {
            return _str;
        }

size_t size() const //后面加const只有在类成员函数中修饰*this,无法改变this中的值
        {
            return _size;
        }

c_str()是用C语言的方式返回字符串,即不带’\0’。

size()就是返回_size的大小,后面加上const修饰*this,无法改变this中的值。

string::swap()的实现

void swap(string& s)
        {
            std::swap(_str,s._str);
            std::swap(_size,s._size);
            std::swap(_capacity,s._capacity);
        }

直接掉用std库中的swap函数交换string类中的对应成员变量。

find()查找字符下标

单个字符查找:

        size_t find(char ch, size_t pos = 0)
        {
            assert(pos < _size);
            for (size_t i = pos; i < _size; ++i)
            {
                if (_str[i] == ch)
                {
                    return i;
                }
            }

            return npos;
        }

断言越界,循环查找即可。

字符串查找:

//char *strstr(const char *haystack, const char *needle)
        //haystack -- 要被检索的 C 字符串。
        //needle -- 在 haystack 字符串内要搜索的小字符串。
        size_t find(const char* str,size_t pos=0)
        {
            assert(pos < _size);
            char* p=strstr(_str+pos,str);//找到str的起始地址
            if(p== nullptr)
            {
                return npos;
            }
            else
            {
                return p - _str;//str起始地址减去_str首地址就是第几个
            }
        }

使用strstr找到str的起始地址,找不到返回nullptr给指针p,返回npos值;

找到就返回str首地址p减去_str的首地址的值,即是str的位置。

初始化列表

string(const char* str="")//全缺省初始化 避免空str无法被输出和崩溃
        :_size(strlen(str))
        {
            _capacity=_size==0 ? 3 : _size*2; //防止空间大小为0导致双倍扩容失败
            _str=new char[_capacity+1];//为_str扩容初始化
            strcpy(_str,str);
        }

初始化采用全缺省初始化,如果用户不给值,则初始化为空字符串,这样做可以避免空str无法被输出和崩溃。

_size的大小取决于形参str的大小,放入初始化列表,值为str的长度。

_capacity的大小跟着_size走且应不为0,需要判断是否str为空,如果为空则另外扩容3个空间,防止空间大小为0导致双倍扩容失败。

_str扩容初始化后的大小应该为**_capacity+1**,因为字符串后还要有一个’\0’结束字符串读取,所以要多开一个空间,最后在用strcpy函数把形参str中的数据拷贝到类成员_str中。

至此一个string类型初始化才算完成。

拷贝构造

分为s2(s1)括号拷贝构造和s2=s1 赋值重载深拷贝

拷贝构造:

string(const string& s)
        :_size(s._size)
        ,_capacity(s._capacity)
        {
            _str=new char[s._capacity+1];
            strcpy(_str,s._str);
        }

如果没有拷贝构造,当用一个已存在的s1去初始化新的s2,只是把s1的地址拷贝给s2,这时s1和s2共用一个地址,当主函数结束,这个地址会被调用两次析构函数,被析构两次就会报错。

所以我们需要拷贝构造,初始化_size和_capacity让它们和s的空间大小一致,然后再重新开一片同样大小的空间给_str,再把s用strcpy复制过去即可,这样地址就不会冲突。

赋值重载深拷贝:

string& operator=(const string& s)
        {
            if(this != &s)
            {
                //如果开空间失败不会影响this._str
                char* tmp = new char[s._capacity + 1];
                strcpy(tmp, s._str);//再把拷贝值拷贝进tmp中
                delete[] _str;//释放原来的值,如果不用tmp的话,无法释放之前的地址空间
                _str = tmp; //再指向tmp的地址
                _size = s._size;
                _capacity = s._capacity;
            }
            return *this;
        }

重载操作符=,将const常量s拷贝给_str。

这里使用tmp做中间值,即使开空间失败也不会影响_str的空间,如果不用tmp,而直接用_str开空间,会导致无法释放_str开空间之前的地址空间。

然后再把s中的值用strcpy拷贝给tmp,再去释放原来的_str空间,此时_str是空指针。再将tmp地址赋值给_str,让_str指向拷贝后的数据空间,最后赋值字符串大小和空间大小即可。

析构函数

~string()
        {
            delete[] _str;
            _str= nullptr;
            _size=_capacity=0;
        }

析构函数没什么可说的,释放掉_str空间后再置成nullptr值即可,容量大小也置0。

operator操作符重载

[ ]重载

char& operator[](size_t pos)    //给普通用户调用
        {
            assert(pos<_size);
            return _str[pos];
        }
  //函数重载
const char& operator[](size_t pos) const//给const成员调用
        {
            assert(pos<_size);
            return _str[pos];
        }

最简单的操作符重载

第一个重载用于普通数据,重载需要的参数为pos,即数组的下标位置,断言防止越界访问,返回的值是_str[pos],_str本身就是一个数组,可以用中括号[ ]访问数据。

第二个重载是专门给const类型使用,有时候数组中的数据如_str[0]需要传给一个函数,此时希望数组中的数据不被改变,需要加上const,这时const类型就能调用重载了。前面的const是用来限制返回值不被修改,后面的const是限制隐式参数this不被修改。

Tip:const成员调用非const函数是权限放大,无法调用,普通用户调用const是权限缩小

比较符号重载

//数据不改变的重载最好加上const,让const成员也能用
        bool operator>(const string& s) const
        {
            return strcmp(_str,s._str)>0;
        }
        bool operator==(const string& s) const
        {
            return strcmp(_str,s._str)==0;
        }
        bool operator>=(const string& s) const
        {
            return *this >s || *this ==s;   //复用
        }
        bool operator<(const string& s) const
        {
            return !(*this >=s);
        }
        bool operator<=(const string& s) const
        {
            return *this <s || *this ==s;
        }
        bool operator!=(const string& s) const
        {
            return !(*this==s);
        }

写出前面两个重载后,以防粗心写错,下面那些重载都可以复用前面两个。

加上const后const类型的成员也可以使用这些重载。

迭代器的实现

//迭代器底层实现
        
        iterator begin()
        {
            return _str;//首地址
        }
        iterator end()
        {
            return _str+_size;//末地址
        }
        //const迭代器实现
        //const迭代器只能读 但是const_iterator类型变量可以变 例如++it
        const_iterator begin() const
        {
            return _str;//首地址
        }
        const_iterator end() const
        {
            return _str+_size;//末地址
        }

分为两种迭代器,一种是可以改变指向数据值的迭代器;一种是const类型迭代器,无法改变值。

reserve和resize扩容

reserve扩容

void reserve(size_t n)//扩容 容量小于现有大小,不改变容量
        {
            if(n>_capacity)//防止缩容
            {
                char* tmp=new char[n+1];
                strcpy(tmp,_str);
                delete[] _str;
                _str=tmp;
                _capacity=n;
            }
        }

reserve扩容需要传入一个扩容大小的值n,当n比原来的容量大时才进行扩容操作,且reserve不接受缩容操作。
扩容不是原地扩容,函数中扩容借助了tmp的帮助来拷贝_str中的数据,然后改变_str指向来完成深拷贝。
如果不用tmp,而直接用_str开空间,会导致无法释放_str开空间之前的地址空间。

resize扩容

void resize(size_t n,char ch='\0')
        {
            if(n<=_size)//resize的大小小于原有的长度,直接屏蔽后面
            {
                _size=n;
                _str[_size]='\0';
            }
            else
            {
                if(n>_capacity)
                {
                    reserve(n);
                }
                size_t i=_size;
                while(i<n)
                {
                    _str[i]=ch;
                    ++i;
                }
                _size=n;
                _str[_size]='\0';//末尾加上'\0'结束
            }
        }

resize扩容也需要传入一个扩容大小的值n,但是resize扩容可以自定义扩容后的空间中的值:ch,这个val值可以缺省,默认为’\0’。它的扩容和reserve相同,只不过支持空间初始化和删除多余n个的数据。
当需要扩容的容量n小于原本的大小,那么就删除原本数据,直到留下n个数据;扩容的容量大于原本空间时,则初始化后面所有的值为ch值,注意,这里用了一个变量i去取_size的值,不用_size是因为循环中不能改变_size的大小。

push_back尾插一个字符

void push_back(char ch)//增加一个字符
        {
            if(_size+1>_capacity)
            {
                reserve(_capacity*2);//双倍扩容
            }
            _str[_size]=ch;//_size是最后'\0'位置
            ++_size;
            _str[_size]='\0';
        }

插入的时候一定要判断插入后的数据是否超出空间大小,是否需要扩容,然后在最后的位置插入值,_size往后移动并赋值’\0’,如果不加这个’\0’会越界访问未知的数据!

append插入字符串

void append(const char* str)
        {
            size_t len=strlen(str);
            if(_size+len>_capacity)
            {
                reserve(_size+len);
            }
            strcpy(_str+_size,str);
            _size=_size+len;
        }

和push_back大同小异,用strcpy函数,strcpy会在最后加上’\0’,这里就不需要手动加。

以上两种插入方式都是不常用的,常用的是重载+=赋值

+=重载

string& operator+=(char ch)
        {
            push_back(ch);
            return *this;
        }
string& operator+=(const char* str)
        {
            append(str);
            return *this;
        }

+=重载虽然是复用push_back和append,但是实际用起来简单明了。

插入和删除

insert在pos位置插入字符

string& insert(size_t pos,char ch)
        {
            assert(pos<=_size);
            if(_size+1>_capacity)
            {
                reserve(_capacity*2);
            }
            size_t end=_size+1;
            while(end > pos)//无符号0-1时会变成最大值
            {
                _str[end]=_str[end-1];//往后挪
                --end;
            }
            _str[pos]=ch;
            ++_size;

            return *this;
        }

string& insert(size_t pos,char* str)
        {
            assert(pos<=_size);
            size_t len=strlen(str);
            if(_size+len>_capacity)//扩容
            {
                reserve(_size+len);
            }
            size_t end=_size+len;
            while(end>pos+(len-1))//len-1和下标位置对齐
            {
                _str[end]=_str[end-len];//挪动数据
                --end;
            }
            strncpy(_str+pos,str,len);//在pos位置插入str中len个字符,不插入'\0'
            _size+=len;

            return *this;
        }

首先需要断言pos大小,不能超出_size范围,利用函数重载,分为字符和字符串两种接口。

这里需要注意的是循环判断的问题,在end>pos处,end和_size比加上了1,当end=0时如果是_size那么就会变成-1,而_size是无符号整型,会直接变成最大值,永远大于pos,所以要借用end作为循环判断的条件之一,下面的end>pos+(len-1)亦是如此。

erase删除pos位置的len个字符

string& erase(size_t pos,size_t len=npos)
        {
            assert(pos<_size);
            if(len == npos || pos+len >= _size)
            {
                _str[pos]='\0';
                _size=pos;
            }
            else
            {
                strcpy(_str+pos,_str+pos+len);
                _size-=len;
            }
            return *this;
        }

首先断言pos大小,不能删除_size之后不存在的数据。

默认缺省参数len为npos=-1,即是全部删除。

如果len等于npos或者pos位置后的字符长度少于len,那么可以直接把pos位置的字符置‘\0’,屏蔽后面所有字符即可。

否则就从pos位置开始复制pos+len位置的字符串,如下
std::string基本功能和操作的实现_第1张图片

流提取和流插入

流提取cout:

ostream& operator<<(ostream& out,const string& s)
    {
        for(auto ch:s)
        {
            out << ch;
        }
        return out;
    }

流提取重载很简单,只需要用循环遍历插入到out容器中即可。

流插入cin:

istream& operator>>(istream& in,string& s)
    {
        s.clear();//清空后_size=0,插入时插入的位置是_size位置
        char ch=in.get();//get()获取字符到流插入中
        char buff[128];//防止一直扩容,直接存入buff数组中,一次性插入
        size_t i=0;
        while( ch != ' ' && ch != '\n' )
        {
            buff[i++]=ch;
            if(i==127)
            {
                buff[127]='\0';
                s+=buff;  //+=重载中,会在插入字符后在末尾插入一个'\0'
                i=0;
            }
            ch=in.get();
        }
        if(i!=0)//不够127个字符时
        {
            buff[i]='\0';
            s+=buff;
        }
        return in;
    }
}

流插入有些麻烦,当进行到循环判断时,它会把\n和空格也一起当作字符插入,主要是因为这俩没有金缓冲区,被当作了分隔字符的判断,为了解决这个问题,使用get()函数获取字符到流插入in中,再赋值给ch字符变量。

为了防止频繁扩容,创建一个buff数组,把获取到值存入数组中,一次性插入,可以减少扩容次数。

当buff中的字符达到了127个,那么就开始插入到s中,记得把buff最后一个字符置’\0’否则以后编译器访问s._str会越界访问未知变量。

不够127个时,直接在最后一个字符后到位置插入’\0’即可。

到此string的基本功能都已经实现,附上完整的源代码:

#include 
#include 
namespace lty
{
    class string
    {
    public:
        //string(const char* str=nullptr)//不行,cout无法输出空指针的.c_str
        //string(const char* str='\0')
        //string(const char* str="\0")
        string(const char* str="")//全缺省初始化 避免空str无法被输出和崩溃
        :_size(strlen(str))
        {
            _capacity=_size==0 ? 3 : _size*2; //防止空间大小为0导致双倍 扩容失败
            _str=new char[_capacity+1];//为_str扩容初始化
            strcpy(_str,str);
        }
        //s2(s1)深拷贝
        string(const string& s)
        :_size(s._size)
        ,_capacity(s._capacity)
        {
            _str=new char[s._capacity+1];
            strcpy(_str,s._str);
        }
        //s2=s1深拷贝重载
        string& operator=(const string& s)
        {
//            if(this!=&s)
//            {
//                _size = s._size;
//                _capacity = s._capacity;
//                _str = new char[s._capacity + 1];
//                strcpy(_str, s._str);
//            }
//                return *this;

            if(this != &s)
            {
                //如果开空间失败不会影响this._str
                char* tmp = new char[s._capacity + 1];
                strcpy(tmp, s._str);//再把拷贝值拷贝进tmp中
                delete[] _str;//释放原来的值,如果不用tmp的话,无法释放之前的地址空间
                _str = tmp; //再指向tmp的地址
                _size = s._size;
                _capacity = s._capacity;
            }
            return *this;
        }
        ~string()
        {
            delete[] _str;
            _str= nullptr;
            _size=_capacity=0;
        }
        //在前后都加上const,返回值无法修改且this中的值也无法修改!
        const char& operator[](size_t pos) const//给const成员调用
        {
            assert(pos<_size);
            return _str[pos];
        }
        //函数重载
        char& operator[](size_t pos)    //给普通用户调用
        {
            assert(pos<_size);
            return _str[pos];
        }
        const char* c_str()
        {
            return _str;
        }

        //const成员调用非const函数是权限放大,普通用户调用是const权限缩小
        size_t size() const //后面加const只有在类成员函数中修饰*this,无法改变this中的值
        {
            return _size;
        }

        //迭代器底层实现
        typedef char* iterator;
        iterator begin()
        {
            return _str;//首地址
        }
        iterator end()
        {
            return _str+_size;//末地址
        }
        //const迭代器实现
        //const迭代器只能读 但是const_iterator类型变量可以变 例如++it
        typedef const char* const_iterator;
        const_iterator begin() const
        {
            return _str;//首地址
        }
        const_iterator end() const
        {
            return _str+_size;//末地址
        }

        //string比较大小重载
        //数据不改变的重载最好加上const,让const成员也能用
        bool operator>(const string& s) const
        {
            return strcmp(_str,s._str)>0;
        }
        bool operator==(const string& s) const
        {
            return strcmp(_str,s._str)==0;
        }
        bool operator>=(const string& s) const
        {
            return *this >s || *this ==s;   //复用
        }
        bool operator<(const string& s) const
        {
            return !(*this >=s);
        }
        bool operator<=(const string& s) const
        {
            return *this <s || *this ==s;
        }
        bool operator!=(const string& s) const
        {
            return !(*this==s);
        }


        //插入字符
        void reserve(size_t n)//扩容 容量小于现有大小,不改变容量
        {
            if(n>_capacity)//防止缩容
            {
                char* tmp=new char[n+1];
                strcpy(tmp,_str);
                delete[] _str;
                _str=tmp;
                _capacity=n;
            }
        }
        void push_back(char ch)//增加一个字符
        {
            if(_size+1>_capacity)
            {
                reserve(_capacity*2);//双倍扩容
            }
            _str[_size]=ch;//_size是最后'\0'位置
            ++_size;
            _str[_size]='\0';
        }
        void append(const char* str)
        {
            size_t len=strlen(str);
            if(_size+len>_capacity)
            {
                reserve(_size+len);
            }
            strcpy(_str+_size,str);
            _size=_size+len;
        }

        string& operator+=(char ch)
        {
            push_back(ch);
            return *this;
        }
        string& operator+=(const char* str)
        {
            append(str);
            return *this;
        }

        void resize(size_t n,char ch='\0')
        {
            if(n<=_size)//resize的大小小于原有的长度,直接屏蔽后面
            {
                _size=n;
                _str[_size]='\0';
            }
            else
            {
                if(n>_capacity)
                {
                    reserve(n);
                }
                size_t i=_size;
                while(i<n)
                {
                    _str[i]=ch;
                    ++i;
                }
                _size=n;
                _str[_size]='\0';//末尾加上'\0'结束
            }
        }

        string& insert(size_t pos,char ch)
        {
            assert(pos<=_size);
            if(_size+1>_capacity)
            {
                reserve(_capacity*2);
            }
            size_t end=_size+1;
            while(end > pos)//无符号0-1时会变成最大值,且又因为无符号和整形相比较会发生整形提升,所以用这种方式
            {
                _str[end]=_str[end-1];//往后挪
                --end;
            }
            _str[pos]=ch;
            ++_size;

            return *this;
        }

        string& insert(size_t pos,char* str)
        {
            assert(pos<=_size);
            size_t len=strlen(str);
            if(_size+len>_capacity)//扩容
            {
                reserve(_size+len);
            }
            size_t end=_size+len;
            while(end>pos+(len-1))//len-1和下标位置对齐
            {
                _str[end]=_str[end-len];//挪动数据
                --end;
            }
            strncpy(_str+pos,str,len);//在pos位置插入str中len个字符,不插入'\0'
            _size+=len;

            return *this;
        }

        //删除下标位置len个字符
        string& erase(size_t pos,size_t len=npos)
        {
            assert(pos<_size);
            if(len == npos || pos+len >= _size)
            {
                _str[pos]='\0';
                _size=pos;
            }
            else
            {
                strcpy(_str+pos,_str+pos+len);
                _size-=len;
            }
            return *this;
        }

        void swap(string& s)
        {
            std::swap(_str,s._str);
            std::swap(_size,s._size);
            std::swap(_capacity,s._capacity);
        }

        //找字符下标
        size_t find(char ch, size_t pos = 0)
        {
            assert(pos < _size);
            for (size_t i = pos; i < _size; ++i)
            {
                if (_str[i] == ch)
                {
                    return i;
                }
            }

            return npos;
        }

        //char *strstr(const char *haystack, const char *needle)
        //haystack -- 要被检索的 C 字符串。
        //needle -- 在 haystack 字符串内要搜索的小字符串。
        size_t find(const char* str,size_t pos=0)
        {
            assert(pos < _size);
            char* p=strstr(_str+pos,str);//找到str的起始地址
            if(p== nullptr)
            {
                return npos;
            }
            else
            {
                return p - _str;//str起始地址减去_str首地址就是第几个
            }
        }

        void clear()
        {
            _str[0] = '\0';
            _size = 0;
        }

    private:
        char* _str;
        size_t _size;
        size_t _capacity;
        static const size_t npos;
        //可以初始化,只针对整型,double等类型不行
//        static const size_t npos=-1;
    };
    const size_t string::npos=-1;
    ostream& operator<<(ostream& out,const string& s)
    {
        for(auto ch:s)
        {
            out << ch;
        }
        return out;
    }
    istream& operator>>(istream& in,string& s)
    {
        s.clear();//清空后_size=0,插入时插入的位置是_size位置
        char ch=in.get();//get()获取字符到流in中
        char buff[128];//防止一直扩容,直接存入buff数组中,一次性插入
        size_t i=0;
        while( ch != ' ' && ch != '\n' )
        {
            buff[i++]=ch;
            if(i==127)
            {
                buff[127]='\0';
                s+=buff;  //+=重载中,会在插入字符后在末尾插入一个'\0'
                i=0;
            }
            ch=in.get();
        }
        if(i!=0)//不够127个字符时
        {
            buff[i]='\0';
            s+=buff;
        }
        return in;
    }
}

你可能感兴趣的:(c++)