C++入门之stl六大组件--String库函数的模拟实现

文章目录

前言

一、String类的模拟实现

1.构造

1)无参构造

2)有参构造

2.拷贝构造

3.赋值运算符重载

3.析构

4.运算符重载

1)operator[  ]

2)operator>

3)operator==

3)operator>=

4)operator<

5)operator<=

6)operator!=

5.一些接口实现

1)resize

2)reverse

3)begin+end

4)pushback

5)append

6)operator+=

7)insert

8)swap

9)c_str

10)find

11)clear

12)erase

6.流插入和流提取

1)operator<<

2)operator>>

总结


前言

本章为了深入了解string类的底层原理,用数据结构的思想来管理资源模拟实现出string类的一些接口。具体来说,1.能像int类型那样定义变量,并且支持赋值,赋值。2.能用做函数的参数类型以及返回类型。3.能用做标准库容器的元素类型,即vector/list/deque的value_type。(摘自coolshell)


一、String类的模拟实现

上章主要对string类以及接口简单的介绍,在很多面经中,面试官让学生来模拟实现string类,主要实现string类的构造、拷贝构造、运算符重载以及析构函数,本章将一一介绍思想以及实现方法。

首先要明确,string类是由一个字符串进行封装构造的,要在这个类中实现增删查改,就需要和顺序表一样的size和capacity,分别表示有效字符串的个数,和这块容量的大小。

class string
{
    pubilc:
        //实现一些方法
    private:
        //成员变量
        char * _str;
        size_t _size;   //存多少个有效字符
        size_t _capacity;  //这段空间的大小
}

1.浅拷贝

也称为位拷贝,编译器只是将对象中的值拷贝过来,如果对象中管理资源,最后就会导致多个对象共享一份资源,当一个对象销毁时,就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为仍有效,所以继续对这项资源进行操作时就会发生访问违规。

2.深拷贝

如果一个类中涉及到资源管理,其拷贝构造函数、赋值运算符重载以及析构必须要显示给出。可以采用深拷贝解决浅拷贝的问题,即:每个对象都有一份独立的资源,不和其他对象共享。

1.构造

1)无参构造

目的:构造空的string类对象,即空字符串

string s1; //无参初始化 

实现思想:使用无参构造,就是传入一个空字符串,在构造列表中,如果给str传入一个nullptr,当我们使用c_str()//返回str字符串的时候,就会访问到空指针,cout无法打印。

string()
    :_str(new char[1])   // 这里 new char 也是一个 加[]是和析构delete[]匹配
    ,_size(0)
    ,_capacity(0)
{
    _str[0] = '\0';    //字符串里只有一个\0
}
    

2)有参构造

string s2("hello world");
string(const char * str)
     //如果这里直接使用初始化列表new出来,就需要不停的扩容
    //:_str(str)    //如果这里直接用const str给了str 后面要对str进行修改 则不能修改
    //,
    _size(strlen(str)) //初始化顺序得按声明顺序来,为了避免这种,只初始化一个
    //,_capacity(strlen(str)+1)
{
    _capacity = _size;
    _str = new char[_capacity+1]; //new出char类型的capacity+1个大小的字节  +1是给\0
    strcpy(str,str); //按字节拷贝过来
}

private:
    char * str;
    size_t _size;
    size_t _capacity;


void test()
{
    string s1("hello world");
}

2.拷贝构造

拷贝构造,即新对象要和旧对象的size,capacity都相同,可以在构造列表中直接赋值,使用strcpy拷贝过来字符串

string(const string&s)
    :_size(s._size)
    ,_capacity(s._capacity)
{
    _str = new char[s._capacity+1];  //_str 是一个字符数组 new capacity个空间
    strcpy(_str,s._str);
}

3.赋值运算符重载

这里单独写出来是和拷贝构造进行对比

参数类型使用引用,返回值也使用引用,目的是支持连续赋值。同时要检查是否自己给自己赋值,返回*this。这里存在一个问题:被赋值的对象和赋值的对象容量的大小可能不相同,可能大于,小于或者等于,这里存在要再开空间的情况。编译器的处理方法是:先开辟一个临时空间和拷贝对象相同,然后拷贝进来字符串,再销毁掉被赋值对象原来的空间,将临时对象拷贝的str赋值给被赋值对象。这里开临时空间的原因是:需要创建一段内存,拷贝传入对象的内容,为了防止再析构的时候,对同一段内存重复释放,导致程序崩溃

string& operator=(const string&s)
{
    //防止自己赋值给自己
    if(*this!= &s)
    {
        //先开空间,成功了再拷贝
        char * tmp = new char[s._capacity+1];
        strcpy(tmp,s._str);
    
        //释放掉原来的空间
        delete[] _str;
        //再拷贝给_str;
        _str = tmp;
        _size = s._size;
        _capacity = s._capacity;
    }
    
    return * this;
}

3.析构

析构中主要完成资源的清理,注意这里_str要指向一个Nullptr

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

4.运算符重载

1)operator[  ]

函数功能:返回pos位置的字符

const size_t & operator[](size_t pos) const   //传入的this不能修改
{
    return _str[pos];
}


char & operator[](size_t pos)
{
    assert(pos<_size);
    return _str[pos];
}

2)operator>

函数功能:比较两个字符串的大小,这里比的是ascii码,使用strcmp函数

bool operator>(string&s)const
{
    return strcmp(_str,s._str)  >0 ;
}

3)operator==

bool operator==(string& s) const
{
    return strcmp(str,s._str) == 0;
}

3)operator>=

bool operator>=(string& s) const
{
    return *this>s || *this ==s;
}

4)operator<

bool operator<(const string* s) const
{

    return !(this>=s);
}

5)operator<=

bool operator<=(const string&s) const
{
    return *this < s || *this == s;
}

6)operator!=

bool operator!=(const string &s) const
{
    return !(*this ==s);
}

5.一些接口实现

1)resize

函数功能:1.将str中的有效字符改成n个,多出的空间用字符c填充(开空间+初始化)

void resize(size_t n,char ch= '\0')
{
 if(n<=_size)
    {
        _str[n] = '\0';
        _size = n;
    }
  else
    {
        if(n> _capacity)
        {
           reserve(n);
        }

        size_t i = _size;
        while(i<n)
        {
            _str[i] = ch;
            ++i;
        }
        _size = n;
        _str[_size] = '\0';
    
       }
}

2)reverse

函数功能:为字符串预留空间,新增的空间用\0占位。先new出一段临时空间,将原来空间的字符串拷贝进去,再释放掉原来的空间,新增的空间赋值给_str。如果比原来空间小,也不缩容。

void reserve(size_t n,char ch='\0')
{
    char * tmp = new char[n+1];
    strcpy(tmp,_str);
    delete[] str;
    _srt = tmp;
    
    _capacity = n;
}

3)begin+end

函数功能:使用迭代器,begin获取第一个字符,end获取最后一个字符的下一个位置

char* begin()
{
    return _str;
}

char* end()
{
    return _str+_size;
}

4)pushback

函数功能:在字符串后面增加一个字符。增加一个字符就要判断容量是否够,如果不够,扩容2倍,再把字符放在_size的位置

void pushback(char ch)
{
    if(_size+1 >_capacity)
    {
        reserve(2*_capacity);
    }
    
    _str[size] = ch;
    ++size;

    _str[_size] = '\0'; //注意这里有\0
}

5)append

函数功能:在字符串后面增加一个字符串

void append(const char * str)
{
    size len = strlen(str);
    if(_size+len>_capacity)
    {
        reverse(2*capacity);
    }

   strcpy(_str+_size,str);

    _size+= len;
}

6)operator+=

1.在原来字符串后面增加一个字符

2.在原来字符串后面增加一个字符串

string& operator+=(char ch)
{
    push_back(ch);
    retrun *this;
}

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

7)insert

1.函数功能:在字符串的任意位置增加一个字符

2.函数功能:在字符串的任意位置增加一个字符串

string& insert(size_t pos,const char ch)
{
    assert(pos<_size);
    //插入要检查容量
    int len = strlen(str);
    if(1+_size >_capacity)
        reverse(2*_capacity);
    //开始插入 先要挪动数据 插入一个字符 就挪动一个位置
    //现在要插入len个字符,就要挪动len个位置
    size_t _end = _size;
    while(pos < end)
    {
        _str[end+1] = _str[end]
        --end;
    }
    //放上数据
      _str[pos] = ch;
      ++size;
   
    return *this;
}
string& insert(size_t pos, const char * str)
{
    assert(pos<_size);
    int len = strlen(str);
    if(_size+len >_capacity)
    {
        reverse(2*_capacity);
    }
    
    size_t end = _size;
    //挪动数据
    while(end>pos)
    {
        _str[end+len] = _str[end];
        --end;
    }

    strnpy(_str+pos,_str,len);
    return * this;
}

8)swap

函数功能:交换两个string类中的字符串,并且size和capacity也要参与交换

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

9)c_str

返回string类对象中的字符串,直接返回即可

char& c_str(string& s)
{
    return s._str;
}

 

10)find

1.函数功能:从npos位置开始查找字符c,返回该字符在字符串中的位置

2.函数功能:从npos位置开始查找字符串,返回该字符在字符串中的位置

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

    return npos;
}
size_t find(char * str,pos = 0)
{
    assert(pos<_size);
    
    //strstr(const char* str1, const char*str2) 在str1中寻找是否存在str2,如果存在返回str2的地址,不存在返回null
    char * p = strstr(_str+pos,str);
    if( p == nullptr)
        return npos;
    else
        return p- _ str; 
}

11)clear

函数功能:将_str中的值都置空,有效字符就是0,容量不变

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

12)erase

函数功能:删除pos位置的len个字符,如果len>strlen(_str),从pos位置有多少删多少

//比如现在有一段字符串 1234567890  10个字符 删除pos = 5位置的两个字符
或者删除pos位置的10个字符 或者删除npos个字符(全删除)
// pos = 5 ;len = 2  1234890 890三个数字挪动到pos = 5的位置 
// pos = 5 ; len = 10 pos = 5的位置开始删除 _str[pos] = '\0'; 
//这两种都只改变_size,不用改变_capacity 
string& eraser(size_t pos,size_t len= npos)
{
    assert(pos<_size);
    
    //注意这里 npos = -1 pos+len 就溢出了 所以需要单独判断
    if(len == npos || pos+len >= _size)
    {
        _str[pos] = '\0';
        _size = pos;
    }

    else
    {
        strcpy(_str+pos,_str+pos+len);
        _size -=len;
    }

    return *this;
}

6.流插入和流提取

1)operator<<

cout<<string<<endl;

ostream& operator<<(ostream& out, string& s)
{
    //遍历这个字符串打印
    for(auto ch:s)
    {
        out<<ch;
    }
        
    return out;
}

2)operator>>

cout>>string

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

instream& operator>>(istream& in , string& s)
{
    //每次清空
    s.clear();
    //从缓冲区拿到数据
    char ch = in.get();
    size_t i= 0;
    while(ch != ' ' && ch == '\n')
    {
        s+= ch;
        buff[i++] = ch;
        //拿到127个清空数组,然后再加后面的字符串
        if(i == 127)
        {
            buff[127] = '\0';
            s+= buff; 
            i= 0;
        }
        
        ch = in.get();
        
    }
    
    return in;
 }

总结

本文主要模仿stl库中的一些源码,技术有限,如有错误请指正。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值