【C++】string模拟实现

目录

1.构造函数

2.拷贝构造

3.operator=

4.operator[]

5.迭代器 iterator

 6.比较大小(ASCII码值)

7.扩容 reserve 

8.尾插

(1)尾插字符

(2)尾插字符串

9.+=字符/字符串

10.插入 insert

11.resize

12.erase

 13.流插入<<

 14.流提取>>


1.构造函数

#include <iostream>
using namespace std;
namespace Byzbw
{
	class string
	{
	public:
		string()//无参构造函数
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{}

		string(const char* str)//带参构造函数
			:_str(str)
			,_size(strlen(str))
			,_capacity(_size)
		{}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

若是写两个构造函数,一个无参一个带参,调用带参函数会将str传给_str,属于权限放大,将会报错

为了解决这个问题,我们可以将char* _str改为const char* _str,但这样就无法修改_str所指向的内容,调用operater[]会报错。

#include <iostream>
using namespace std;
namespace Byzbw
{
	class string
	{
	public:
		string()//无参构造函数
			:_str(new char[1])//为了适配delete[]的析构
			,_size(0)
			,_capacity(0)
		{}

		string(const char* str)//带参构造函数
			:_size(strlen(str))
		{
			_capacity = _size;
			_str = new char[_capacity + 1];//+1是为了给\0位置
			strcpy(_str, str);
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

 为考虑后续扩容,且无参构造函数都用delete[]析构,所以用new[]开辟空间

优化为全缺省函数 :

string(const char* str = " ")
    :_size(strlen(str))
{
    if(_size == 0)
    {
        _capacity = 3;
    }
    else
    {
        _capacity = _size;
    }
    _str = new char[_capacity + 1];//开辟空间
    strcpy(_str,str);//将str拷贝给_str
}

不可以将缺省值设为nullptr,strlen(str)对str的解引用是遇到'\0'才终止的,解引用nullptr会报错,只要将缺省值设为空字符串,结尾默认就是\0

2.拷贝构造

浅拷贝

 拷贝构造如果不自己写,编译器会自动生成,对内置类型进行值拷贝或浅拷贝,所以用编译器自己生成的拷贝构造就会报错

两个指针指向同一块空间,发生浅拷贝,修改一个会影响另一个,而且会析构两次 


 深拷贝

 创建一个同样大小的空间,并把原先的数据拷贝下来放入新空间,使s1和s2指向两个不同的空间,互不影响

string(const string& s)
		:_size(s._size)
		,_capacity(s._capacity)
	{
		_str = new char[_capacity + 1];//开辟空间
		strcpy(_str, s._str);//将s._str拷贝给_str
	}

3.operator=

赋值运算符也是默认成员函数,如果不写会自动调用值拷贝或浅拷贝

string& operator=(const string& s)//s3 = s2的情况
{
	if (this != &s)//排除赋值相同的情况
	{
		char* tmp = new char[s._capacity + 1];//用临时变量tmp接收开辟的空间
		strcpy(tmp, s._str);
		delete[] _str;
		_str = tmp;
		_size = s._size;
		_capacity = s._capacity;
	}
	return *this;
}

 就算new开辟空间失败,也不会影响原本的空间

4.operator[]

由于可能出现string和const string两种类型,所以写两个函数构成的函数重载

char& operator[](size_t pos)
{
    return _str[pos];
}    
char& operator[](size_t pos) const
{
    return _str[pos];
}

 调用print函数调用operator[]() const

 普通遍历调用operator[]()

5.迭代器 iterator

   	typedef char* iterator;
	typedef const char* const_iterator;
	iterator begin()
	{
		return _str;
	}
	iterator end()
	{
		return _str + _size;
	}
	iterator begin() const
	{
		return _str;
	}
	iterator end() const
	{
		return _str + _size;
	}

begin()指向字符串的第一个位置

end()指向字符串最后一个字符的下一个位置 

为了满足不同情况,分别写了const和非const类型的迭代器

 6.比较大小(ASCII码值)

	bool operator==(const string& s) const //s1==s2
	{
		return strcmp(_str, s._str) == 0;
	}
	bool operator<(const string& s) const //s1<s2
	{
		return strcmp(_str, s._str) < 0;
	}
	bool operator<=(const string& s) const //s1<=s2
	{
		return *this < s || *this == s;
	}
	bool operator>(const string& s) const //s1>s2
	{
		return !(*this <= s);
	}
	bool operator>=(const string& s) const //s1>=s2
	{
		return *this > s || *this == s;
	}
	bool operator!=(const string& s) const //s1!=s2
	{
		return !(*this == s);
	}

写出==和<的比较,其他的就可以复用这两个函数实现比较

避免将const修饰的成员传给非const修饰的成员而出现报错,给外部加上const修饰this指针

建议内部不用修改成员数据的函数,最好都写成const类型

7.扩容 reserve 

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

8.尾插

(1)尾插字符

void push_back(const char ch)
{
    if(_size + 1 > _capacity)
    {
        reserve(_capacity * 2);
    }
    _str[_size] = ch;//把ch赋值给最后一位的字符
    _size++;
    _str[_size] = '\0';
}

(2)尾插字符串

void append(const char* str)
{
    int len = strlen(str);
    if(_size + len > _capacity)
    {
        reserve(_size + len);
    }
    strcpy(_str + _size,str);//在原来的字符串后\0的位置开始把str字符串拷贝进去
    _size += len;
}

9.+=字符/字符串

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

10.插入 insert

插入字符

void insert(size_t pos,char ch)//在pos位置插入字符ch
{
    if(_size + 1 > _capacity)
    {
        reserve(_capacity * 2);
    }
    size_t end = _size + 1;
    while(end > pos)
    {
        _str[end] = _str[end - 1];
        end--;
    }
    _str[pos] = ch;
    _size++;
}

 因为pos和end都是size_t类型,若是while的判断条件为end >= pos且pos为0时

当发生end--,end变为负数,计算的是其补码,因此while的判断一直成立,无法结束循环

插入字符串

void insert(size_t pos,char* str)//在po位置前插入字符串
{
    size_t len = strlen(str);
    if(_size + len > _capacity)
    {
        reverse(_size + len);
    }
    size_t end = _size + len + 1;
    while(end > pos + len - 1)
    {
        _str[end] = _str[end - len];
        end--;
    }
    strncpy(_str + pos,str,len);
    _size += len;
}

11.resize

void resize(size_t n,char ch)
{
    if(n <= _size)//删除保留前n个
    {
        _size = 0;
        _str[n] = '\0';
    }
    else//n > _size    
    {
        if(n > _capacity)//扩容
        {
            reserve(n);
        }   
        int i = _size;
        while(i < n)//将剩余空间初始化为ch
        {
            _str[i] = ch;
            i++;
        }     
        _size = n;
        _str[_size] = '\0';
    }
}    

12.erase

static const size_t npos = -1;
string& erase(size_t pos = 0,size_t len = npos)
{
    if(len == npos || pos + len > _size)
    {
        _str[pos] = '\0';
        _size = pos;
    }
    else
    {
        strcpy(_str + pos,_str + pos + len);//'\0'也一起copy过去了
        _size -= len;
    }
    return *this;
}

当pos+len的长度>总长度len==npos时,将pos后的剩余长度全部删除

当pos+len的长度<总长度时,使用strcpy函数拷贝,覆盖要被删除的字符

 13.流插入<<

流插入不一定是友元函数,只要不调用类的私有成员变量,就不用友元

ostream& operator<<(ostream& out, const string&s)
{
    for (int i = 0; i < s.size(); i++)
    {
        out << s[i] << " ";
    }
    return out;
}

 14.流提取>>

错误写法

istream& operator>>(istream& in,string& s)
{
    char ch;
    in >> ch;
    while(ch != ' ' && ch != '\n')
    {
        s += ch;
        in >> ch;
    }
    return in;
}

如果这么写,当我们输入‘ ’‘\n’时,C++会默认是字符之间的间隔而不读取,达不到预期,

循环也无法停止

使用get就可以避免这样的错误发生

istream& operator>>(istream& in,string& s)
{
    char ch = in.get();
    while(ch != ' ' && ch != '\n')
    {
        s += ch;
        ch = in.get();
    }
    return in;
}

 下一个问题随之出现,如果我们原本的字符串中有内容,当我们使用自己写的流插入后,出现的结果与库中string类的结果不符

库中string类流提取是直接刷新对象字符串的内容,而我们自己写的流提取是在尾部追加,

所以我们可以在流提取前加一个clear函数

//在string类里写
void clear()
{
    _str[0] = '\0';
    _size = 0;
}
istream& operator>>(istream& in,string& s)
{
    s.clear();
    char ch = in.get();
    while(ch != ' ' && ch != '\n')
    {
        s += ch;
        ch = in.get();
    }
    return in;
}

 接下来考虑到如果一次性提取过多字符,让对象进行了多余的扩容,造成资源的浪费

我们开辟一个缓冲数组,用于储存字符,当数组满了再进行扩容

istream& operator>>(istream& in,  string& s)
{
    s.clear();
    char ch = in.get();
    char buf[128];
    size_t index = 0;
    while (ch != ' ' && ch != '\n')
    {
        buf[index++] = ch;
        if (index == 127)//为防止频繁的扩容
        {
            buf[127] = '\0';
            s += buf;
            index = 0;
        }
        ch = in.get();
    }
    if (index != 0)
    {
        buf[index] = '\0';
        s += buf;
    }
    return in;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值