【C++】string类的模拟实现

举报
平凡的人1 发表于 2022/12/15 11:10:47 2022/12/15
【摘要】 @[toc] 一、string类的构造、拷贝构造、赋值重载以及析构 1.构造函数分为无参和带参这两种构造函数。无参构造函数默认构造空字符串"",所以我们只需要给一个缺省值即可。string(const char* str = ""){ _size = strlen(str); _capacity = _size; _str = new char[_capacity + 1]...

@[toc]

一、string类的构造、拷贝构造、赋值重载以及析构

1.构造函数

分为无参和带参这两种构造函数。无参构造函数默认构造空字符串"",所以我们只需要给一个缺省值即可。

string(const char* str = "")
{
    _size = strlen(str);
    _capacity = _size;
    _str = new char[_capacity + 1];
	strcpy(_str, str);
}

对于这里的capacity问题,这里是字符的个数,不包括\0,所以要给\0预留位置。

2.拷贝构造

对于拷贝构造和赋值是默认成员函数,不写编译器会自动生成,对于内置类型完成浅拷贝,对于自定义类型调用拷贝构造

对于string类型来说,如果不写拷贝构造会导致浅拷贝问题(只完成值拷贝)

image-20221116080701479

所以我们需要进行深拷贝:

  • 传统写法
string(const string& s)
		{
			_str = new char[s._capacity + 1];
			_capacity = s._capacity;
			_size = s._size;

			strcpy(_str, s._str);
		}
  • 现代写法

传统写法比较循规蹈矩,现代写法更加灵活,拷贝构造的现代写法可以通过构造出tmp,然后把tmp和s2进行交换(swap)

注意:我们需要把s2的_str置为nullptr,如果不置为空,tmp会变成随机值,tmp是局部变量出作用域时会调用析构函数

void swap(string& s)
{
    std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}
string(const string& s)
        :_str(nullptr)
        ,_size(0)
        ,_capacity(0)
        {
			string tmp(s._str);
			swap(tmp);//this->swap(tmp)
		}

3.swap问题

对于上面现代写法swap的问题:标准库有一个swap,string也有一个swap,有什么区别?

s1.swap(s2);
swap(s1,s2);

image-20221116100220826

image-20221116100242124

第二个swap交换代价比较大,需要三次深拷贝(拷贝+赋值+赋值),造成空间损耗,所以我们可以提供一个成员函数swap交换string,直接交换,swap中的swap要指定作用域std::,否则需要从局部找,再去全局找,发现参数不匹配

4.赋值重载

默认生成的赋值重载也会导致浅拷贝,所以我们需要实现深拷贝。同时,对于赋值重载,我们不要直接去进行销毁,有可能自己给自己赋值,导致自身进行销毁。同时,为了安全起见,我们最好利用tmp来进行赋值

  • 传统写法
string& operator =(const string& s)
{
    if (this != &s)
    {
        char* tmp = new char[s._capacity + 1];
        strcpy(tmp, s._str);
        delete[] _str;
        _str = tmp;
		_size = s._size;
		_capacity = s._capacity;
    }
    return *this;
}
  • 现代写法
string& operator = (const string& s)
{
    if (this != &s)
    {
        //string tmp(s._str);
		string tmp(s);
		swap(tmp);
    }
    return *this;
}

但是此方法仍然可以简化,不需要临时tmp,直接进行传值传参,更加简洁

	//直接传值传参
string& operator = (string s)
{
    swap(s);
    return *this;
}

5.析构函数

析构函数比较简单,直接delete[]释放空间即可

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

二、常用接口

下面几个常用的接口实现比较简单,我们先一起来看一看:

1.c_str

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

2.[]

//普通对象:可读可写
char& operator[](size_t pos)
{
    assert(pos < _size);
	return _str[pos];
}
//const对象:可读不可写
const char& operator[](size_t pos) const
{
    assert(pos < _size);
    return _str[pos];
}

3.迭代器和范围for

  • 迭代器

迭代器有普通迭代器以及const修饰的迭代器,所以我们可以实现两种不同的迭代器,其中,const迭代器可读不可写

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

iterator end()
{
	return _str + _size;
}

const_iterator begin() const
{
	return _str;
}
const_iterator end() const
{
	return _str + _size;
}
  • 范围for

实现完迭代器之后,对于范围for我们自然可以直接使用:

image-20221110093019376

4.size和capacity

直接返回值即可,比较简单

size_t size() const
{
    return _size;
}

size_t capacity() const
{
	return _capacity;
}

三、插入

1.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;
    }
}
  • resize

resize需要分情况:

1.元素个数大于容量,需要扩容,多出来的用’\0’(默认情况下)来进行填充

2.元素个数小于原有的,需要删除

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

2.push_back

尾插一个字符,我们需要考虑扩容问题,我们需要判断capacity是否为0的情况,同时,尾插之后’\0’要重新处理

void push_back(char ch)
{
    //开辟2倍空间
    if (_size == _capacity)
    {
        size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newCapacity);
    }
    _str[_size] = ch;
    ++_size;
    _str[_size] = '\0';
}

3.append

这里开多少空间取决于插入字符串的长度,我们需要计算,然后决定开多少空间(直接开2倍可能不够用)

//2倍不一定够用
void append(const char* str)
{
    size_t len = strlen(str);
    if (_size + len > _capacity)
    {
        reserve(_size + len);
	}
    strcpy(_str + _size, str);
	_size += len;
}

4.+=

实现了push_back和append之后,对于+=来说,简直就是手到擒来,直接调用即可

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

5.insert

insert的问题比较多

  • 插入字符

image-20221116125328193

image-20221110103116787

这里存在着一个很大的问题:

pos=0的时候,–end会变成-1(但是不要忽略了,end的类型是size_t,怎么可能是-1,此时有人会说了,可以把end改成int类型,但是实际上这样子会发生隐式类型提升,范围小往大的提升,也就是int会提升为size_t,还是没解决问题)这里太坑了,悄悄提升

所以解决的方式有两种:

1.强转

image-20221116125803359

2.把=号给去掉

string& insert(size_t pos, char ch)
{
    assert(pos <= _size);
    if (_size == _capacity)
    {
        size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newCapacity);

	}
    //移动数据
	size_t end = _size + 1;
    while (end > pos)
    {
        _str[end] = _str[end-1];
        --end;
	}
    _str[pos] = ch;
    ++_size;
    return *this;
}

image-20221116130041030

  • 插入字符串

插入字符串,要把插入的字符串拷贝过来,但是不要把’\0’顺便拷贝过来,所以不要用strcpy而是要用strncpy

同时,要防止越界问题

string& insert(size_t pos, const 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)
    {
        _str[end] = _str[end - len];
        --end;
	}
    strncpy(_str + pos, str, len);
    _size += len;
	return *this;
}

四、删除

1.erase

说到erase,自然要跟npos联系起来,npos是string类的静态成员变量,静态成员变量要在类外定义的:

size_t string::npos = -1

普通成员对象可以给缺省值,在构造函数初始化列表完成初始化,但是静态成员变量不会在初始化列表阶段进行初始化,静态成员变量不属于某个具体的对象,属于整个类,所以需要在类外初始化

但是有一个**==特例==**,const静态成员变量可以在声明时定义(只针对整型):

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

		const static size_t npos = -1;

erase实现:

建议这个地方自己画个图辅助理解

1.如果len太长,直接把pos之后的删除即可

2.只需要删除部分,挪动数据

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;
}

2.clear

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

五、查找

1.find

从pos处开始查找字符或者字符串,找到返回下标值,没找到则返回npos

对于字符串的查找可以调用strstr

size_t find(const char ch, size_t pos = 0)
{
    assert(pos < _size);
	while (pos < _size)
    {
        if (_str[pos] == ch)
        {
            return pos;
		}
        ++pos;
	}
    return npos;
}
size_t find(const char* str, size_t pos = 0)
{
    const char* ptr = strstr(_str + pos, str);
    if (ptr == nullptr)
    {
        return npos;
    }
    else
    {
        return ptr - _str;
    }
}

六、运算符重载

流插入<<和流提取>>

对于流插入和流提取我们之前就在日期类接触了。不能重载成成员,会让this指针抢占第一个位置问题。所以需要定义成全局的

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

对于<<和c_str()的区别:<<按照size进行打印,跟\0没有关系,而c_str()遇到\0结束

image-20221110205012838

所以用流插入读取比较好一点。

  • >>

scanf和cin一样,都拿不到’ ‘和’\0’

所以要读取一个 一个的字符,我们可以用get函数

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 buff[128] = { '\0' };
    size_t i = 0;
    char ch = in.get();
    while (ch != ' ' && ch != '\n')
    {
        if (i == 127)
        {
            s += buff;
            i = 0;
        }
        buff[i++] = ch;
        ch = in.get();
    }
    if (i >= 0)
    {
        buff[i] = '\0';
        s += buff;
    }
    return in;
}

七、总体代码

#pragma once
#include <assert.h>
namespace hwc
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

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

		/*string()
		{
			_str = new char[1];
			_str[0] = '\0';
			_capacity = _size = 0;
		}*/
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		//s2(s1)
		//传统写法
		/*string(const string& s)
		{
			_str = new char[s._capacity + 1];
			_capacity = s._capacity;
			_size = s._size;

			strcpy(_str, s._str);
		}*/
		
		//现代写法

		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		string(const string& s)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			string tmp(s._str);
			swap(tmp);//this->swap(tmp)
		}

	/*	string& operator =(const string& s)
		{
			if (this != &s)
			{
				char* tmp = new char[s._capacity + 1];
				strcpy(tmp, s._str);
				delete[] _str;
				_str = tmp;
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}*/

		//现代写法
		//string& operator = (const string& s)
		//{
		//	if (this != &s)
		//	{
		//		//string tmp(s._str);
		//		string tmp(s);
		//		swap(tmp);
		//	}
		//	return *this;
		//}
		//直接传值传参
		string& operator = (string s)
		{
			swap(s);
			return *this;
		}
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}
		const char* c_str() const
		{
			return _str;
		}

		size_t size() const
		{
			return _size;
		}
		size_t capacity() const
		{
			return _capacity;
		}
		//可读可写
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		//const对象,可读不可写
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp,_str);
				delete[] _str;

				_str = tmp;
				_capacity = n;
			}
		}

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

		}
		//开辟2倍
		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newCapacity);
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}
		//2倍不一定够用
		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			strcpy(_str + _size, str);
			_size += len;
		}
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

		string& insert(size_t pos, char ch)
		{
			assert(pos <= _size);
			if (_size == _capacity)
			{
				size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newCapacity);

			}
			//移动数据
			size_t end = _size+1;
			while (end >pos)
			{
				_str[end] = _str[end-1];
				--end;
			}
			_str[pos] = ch;
			++_size;
			return *this;
		}
		string& insert(size_t pos, const 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)
			{
				_str[end] = _str[end - len];
				--end;
			}
			strncpy(_str + pos, str, len);
			_size += len;
			return *this;
		}

		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 clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

		size_t find(const char ch, size_t pos = 0)
		{
			assert(pos < _size);
			while (pos < _size)
			{
				if (_str[pos] == ch)
				{
					return pos;
				}
				++pos;
			}
			return npos;
		}
		size_t find(const char* str, size_t pos = 0)
		{
			const char* ptr = strstr(_str + pos, str);
			if (ptr == nullptr)
			{
				return npos;
			}
			else
			{
				return ptr - _str;
			}
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;

		const static size_t npos = -1;
	};


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

	/*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 buff[128] = { '\0' };
		size_t i = 0;
		char ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			if (i == 127)
			{
				s += buff;
				i = 0;
			}
			buff[i++] = ch;
			ch = in.get();
		}
		if (i >= 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}



	void test_string1()
	{
		string s1("hello world");
		string::iterator it1 = s1.begin();
		while (it1 != s1.end())
		{
			cout << *it1 << " ";
			++it1;
		}
		cout << endl;
		for (auto ch : s1)
		{
			cout << ch << " ";
		}
		cout << endl;
	}

	void test_string2()
	{
		string s1("hello");
		s1 += ' ';
		s1 += "world hello world";

		string s2;
		s2 += 'X';
	}

	void test_string3()
	{
		string s1("hello world");
		s1.insert(0, 'X');
		cout << s1.c_str() << endl;
		s1.insert(12, 'Y');
		cout << s1.c_str() << endl;
		s1.insert(13,"CWH");
		cout << s1.c_str() << endl;
		s1.insert(0, "hwc");
		cout << s1.c_str() << endl;

	}
	void test_string4()
	{
		string s1("hello world");
		s1.erase(3, 4);
		cout << s1.c_str() << endl;
	}

	void test_string5()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;
		s1.resize(16, 'X');
		cout << s1.c_str() << endl;

		string s2("hello world");
		s2.resize(5);
		cout << s2.size() << endl;
		cout << s2.capacity() << endl;
		cout << s2.c_str() << endl;
	}

	void test_string6()
	{
		string s1("hello world");
		cout << s1 << endl;
		cout << s1.c_str() << endl;

		s1.insert(5, 'X');
		cout << s1.size() << endl;
		cout << s1.capacity()<< endl;

		cout << s1 << endl;
		cout << s1.c_str() << endl;

		string s2;
		cin >> s2;
		cout << s2 << endl;
	}

	void test_string7()
	{
		string s1("hello world");
		string s2(s1);

		cout << s1 << endl;
		cout << s2 << endl;

		string s3("HWC");
		s1 = s3;
		cout << s1 << endl;
		cout << s3 << endl;

		s1.swap(s2);
		swap(s1, s2);
		cout << s1 << endl;
		cout << s2 << endl;

	}
}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。