【C++从入门到踹门】 第七篇:模拟string


在这里插入图片描述


string的使用

首先熟悉string的使用,可以参考string使用手册

string是存放字符串的字符,而且要实现动态的内存管理
参考标准库的string,于是设有三个私有成员变量:

class mystring
{
    char* _str;//指向存放字符串的堆
    size_t _size;//实际字符串长度
    size_t _capacity;//容量
};

默认成员函数

构造函数

缺省参数的构造函数,默认参数为空字符串(结尾带‘\0’),_capacity和_size都预设为实际字符串的长度,不包含\0

mystring(const char* str = "") : _size(strlen(str)), _capacity(_size)
	{
		//开辟空间
		_str = new char[_capacity + 1];
		strcpy(_str,str);
	}

//或者将缺省构造函数拆分:默认的构造函数和普通带参的构造函数

拷贝构造函数

错误示例:

mystring(const mystring& s):_size(s._size),_capacity(s._capacity)
{
    _str = s._str;
}

这里涉及到了深浅拷贝的问题,错误示例则为浅拷贝(值拷贝)。

浅拷贝:单纯的拷贝值,假如值是一个指针,那么拷贝指针和源指针将指向同一块内存区域

在这里插入图片描述

假如对其中一者的字串进行改动,势必会影响另一个同样使用该空间的对象,而且程序结束调用析构函数,那么同一块空间就将被释放两次。

所以应使用深拷贝,拷贝对象将源对象堆空间里的值也一并拷走。这样两个对象相互独立。

正确写法:

mystring(const mystring& s):_size(s._size),_capacity(s._capacity)
{
	//开辟空间
	_str = new char[_capacity];
	strcpy(_str, s._str);
}

在这里插入图片描述

另外一种写法是利用源对象的_str指针构造一个临时对象temp,然后将temp与*this的内容互换,实现夺舍。

mystring(const mystring&s):_str(nullptr),_size(0),_capacity(0)
{
    mystring temp(s._str);//构造临时对象
    swap(temp._str,_str);
    swap(temp._size,_size);
    swap(temp._capacity,_capacity);

}

赋值重载函数

开辟空间,然后将值转移

mystring& operator=(const mystring& s)
{
	if (this != &s)
	{
		char* temp = new char[s._capacity + 1];//保证new不会失败,再delete自身
		strcpy(temp, s._str);
		delete[] _str;
		_str = temp;
		_size = s._size;
		_capacity = s._capacity;
	}

	return *this;
}

考虑拷贝函数的第二种写法(复用构造),这里的赋值重载可以复用拷贝函数构造临时对象

为方便交换,先对swap封装(如果直接用库里的swap将多次调用构造函数)

	void swap( mystring& s2)
	{
		std::swap(_str, s2._str);
		std::swap(_size, s2._size);
		std::swap(_capacity, s2._capacity);
	}
mystring& operator=(const mystring& s)
{
	//复用拷贝构造,生成临时对象,然后把*this和临时对象互换
	mystring temp(s);
	swap(temp);
	return *this;
}

再进一步,函数传参设置为传值,那么在传值时就会自动调用拷贝构造

mystring& operator=(mystring s)
{
	swap(s);
    return *this;
}

析构函数

这里申请了空间,默认的析构不会帮助我们delete,所以我们需要在构造函数中手动释放空间

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

迭代器相关函数

begin和end 以及 rbegin和rend

由于string使用的是连续空间,所以其迭代器为其原生指针。

typedef char* iterator;
typedef const char* const_iterator;

	iterator begin()
	{
		return _str;
	}

	const_iterator begin()const
	{
		return _str;
	}

	iterator end()
	{
		return _str+_size;
	}

	const_iterator end()const
	{
		return _str+_size;
	}

容量管理相关函数

size 、 capacity

查阅当前对象的实际大小和容量,由于不用对其内容进行修改,可用const进行修饰。

⚠注意:this指针本身就以用顶层const进行修饰,我们后续添加的是底层const修饰,锁住的是*this。

size_t size()const
{
	return _size;
}

size_t capacity()const
{
	return _capacity;
}

reserve 、 resize

  • reserve:只扩容,不会缩容
// reserve ——扩容
void reserve(size_t n)
{
	//只有当n>_capacity 才会扩容
	if (n > _capacity)
	{
		char* temp = new char[n + 1];
		strncpy(temp, _str,_size+1);
		delete[] _str;
		_str = temp;
		_capacity = n;
	}
}

这里不用strcpy是怕遇到当中有\0的字符串,如"hello\0world"只会拷贝到"hello\0",数据丢失。

  • resize
    n>容量时,进行扩容,并用给定字符(不给定则用缺省值)填充,n<实际大小,则将有效字符串长度缩至n,但是容量保持不变。
//resize   
void resize(size_t n, char c = '\0')
{
	if (n < _size)
	{
		_str[n] = '\0';
		_size = n;
	}

	else
	{
		reserve(n);
		memset(_str + _size, c, sizeof(char) * (n - _size));
		_size = n;
		_str[_size] = '\0';
	}
}

clear 、 empty

void clear()
{
	resize(0);
}

bool empty()
{
	return strcmp(_str, "") == 0;
}

寻找与访问字串相关函数

operator[]

//返回引用,方便修改
char& operator[](size_t pos)
{
	assert(pos < _size);
	return _str[pos];
}

//传入常量字符串,用于只读操作
const char& operator[](size_t pos)const
{
	assert(pos < _size);
	return _str[pos];
}

c_str

char* c_str()
{
    return _str;
}

const char* c_str()const
{
    return _str;
}

find

//找单个字符
size_t find(const char ch,size_t pos=0)const
{
    while(pos < _size)
    {
        if (_str[pos] == ch)
        {
            return pos;
        }
        pos++;
    }
    return npos;
}

//找子串
size_t find(const char* s, size_t pos = 0)const
{
    const char* ret = strstr(_str + pos,s);
    if (ret)
    {
        return ret - _str;
    }
    else
    {
        return npos;
    }
}

rfind

//找字符
//我们将母串逆置,然后复用find,最后处理下返回值即可。
size_t rfind(const char ch, size_t pos = npos)const
{
    mystring temp(*this);
    reverse(temp.begin(), temp.end());
    if (pos >= _size)
    {
        pos = _size - 1;
    }
    size_t rpos = _size - pos - 1;
    //*this从pos往前找,temp应从rpos往后找
    size_t ret = temp.find(ch, rpos);
    if (ret == npos)
    {
        return npos;
    }
    else
    {
        return _size - ret - 1;
    }

}

//找子串
//这里使用逆置也可以,不过还需另辟空间将子串逆置,然后在逆置的母串中查找
//我们再换种方法,顺着找到最后一个匹配字串即可
size_t rfind(const char* s, size_t pos = npos)const
{
    size_t locate = find(s);//此时locate有两种情况1.npos 2.第一个位置的下标(可能在pos的前后)
    size_t ret=locate;
    while (locate < pos)
    {
        ret = locate;
        locate = find(s, ret + 1);
    }
    if (ret <= pos)
    {
        return ret;
    }
    else
    {
        return npos;
    }
}

修改字串函数

这类函数的难点在于扩容与移位:复用reserve函数,扩容变得轻而易举。

push_back

在_size位置上添加字符,勿忘最后另需添加\0

//push_back
void push_back(char ch)
{
    //检查扩容
    if (_size == _capacity)
    {
        reserve(_capacity == 0 ? 4 : 2 * _capacity);
    }
    _str[_size] = ch;
    _size++;
    _str[_size] = '\0';
}

append

考虑扩容的大小,_size亦需实时变化

//append 的扩容 需要根据插入字符串的大小考虑扩容的大小
void append(const char* s)
{
    size_t len = strlen(s);
    if(len+_size>_capacity)
    {
            reserve(_capacity == 0 ? len : len + _size);
    }
    strcpy(_str + _size, s);
    _size += len;
}

+=

复用push_back ,append

//+=
mystring& operator+=(char ch)
{
    push_back(ch);
    return *this;
}

mystring& operator+=(const char* s)
{
    append(s);
    return *this;
}

pop_back

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

insert

//插入单个字符
mystring& insert(size_t pos, char ch)
{
    assert(pos <= _size);//_str[_size]='\0'
    //考虑扩容
    if (_size == _capacity)
    {
        reserve(_capacity = 0 ? 4 : 2 * _capacity);
    }
    if (pos == _size)
    {
        push_back(ch);
    }
    else
    {
        memmove(_str + pos + 1, _str + pos, _size - pos+1);
        _str[pos] = ch;
        _size += 1;
    }
    return *this;
}

//插入字符串
mystring& insert(size_t pos, const char* s)
{
    assert(pos <= _size);
    int len = strlen(s);
    //考虑扩容
    if (_size+len> _capacity)
    {
        reserve(_size+len);
    }
    _size += len;
    memmove(_str + pos + len, _str + pos, _size - pos + 1);
    strncpy(_str + pos, s, len);
    return *this;

}

erase

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

关系运算符重载

这类函数我们放置全局函数

bool operator<(const mystring& s1, const mystring& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) < 0;
}

bool operator==(const mystring& s1, const mystring& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) == 0;
}

bool operator>(const mystring& s1, const mystring& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) > 0;
}

bool operator>=(const mystring& s1, const mystring& s2)
{
	return !(s1 < s2);
}

bool operator<=(const mystring& s1, const mystring& s2)
{
	return !(s1 > s2);
}

流运算符重载

由于流提取符和流插入符方向的特性,不方便使其作为成员函数(因为this指针将始终位于符号左侧),所以这里设为全局函数。

//输入>>
istream& operator>>(istream& in, mystring& s)
{
	//先清空字符串原本的内容
	s.clear();
	char ch;
	ch=in.get();
	while (ch != ' ' && ch != '\n')//遇到" "和"\0"结束读取
	{
		s += ch;
		 ch=in.get();
	}
	return in;
}

istream& getline(istream& in, mystring& s)
{
	//先清空字符串原本的内容
	s.clear();
	char ch;
	ch = in.get();
	while (ch != '\n')//读取一行,直到换行符结束
	{
		s += ch;
		ch = in.get();
	}
	return in;
}

//输出<<
ostream& operator<<(ostream& out,const mystring&s)
{
	//out << s.c_str();//这种写法遇到当中有\0的字符串就嗝屁了

	for (auto ch : s)
	{
		out << ch;
	}
	return out;
}

青山不改 绿水长流
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值