【C++】string的模拟实现

目录

1、面试时写的string

1.1 传统写法的String类

1.2 现代写法的String类

2、真正的string类

2.1 size、capacity

2.2 构造函数、拷贝构造、析构函数、赋值运算符重载

2.3 operator[]

2.4 c_str

2.5 operator<<、operator>>、getline

2.6 迭代器

2.7 push_back、append、+=、reserve

2.8 resize

2.9 insert、erase

2.10 find

2.11 relational operators


1、面试时写的string

在面试时,限于时间,不可能要求具备std::string的功能,但至少要求能正确管理资源,即完成构造,析构,拷贝构造,赋值运算符重载,这时候类的成员变量只需要一个_str

1.1 传统写法的String类

注意,只要是接收字符常量的指针变量都要加const

但此时拷贝构造会报错

说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。

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

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

1.2 现代写法的String类

现代写法的String针对拷贝构造和赋值运算符重载进行了改进,让深拷贝不用自己做,让别人做,再交换

tmp是临时对象会自动释放是因为会自动调用析构函数

2、真正的string类

实现一个能够进行增删查改的string

2.1 size、capacity

在上面的string中,成员变量中并没有siae、capacity,这样子没办法完成扩容,是不完美的

现在,我们需要在成员变量中增加_size,_capacity,_size是字符数组中有效字符的个数,_capacity是这个字符数组最多可以存放多少个有效字符

2.2 构造函数、拷贝构造、析构函数、赋值运算符重载

因为这里增加了_size和_capacity,所以相较于上面有一些变化

在拷贝构造和赋值运算符重载中,swap只会将字符串交换,但是_size和_capacity并没有改变,所以还需要修改_size和_capacity

2.3 operator[]

2.4 c_str

插个题外话

2.5 operator<<、operator>>、getline

这三个需要写在类外,不需要让其称为友元,因为s[i]、s.size()等调用了成员函数,不是直接访问成员变量

2.6 迭代器

end返回的是\0

在string中,迭代器是指针,但是不是所有迭代器都是指针,也正因为返回的指针,所以迭代器是可读可写的

此时会发现只要支持了迭代器,就能支持范围for,因为范围for最终被替换成迭代器,此过程是编译器进行的,所以迭代器中的begin和end必须是这样写,若变成Begin等,编译器会报错

2.7 push_back、append、+=、reserve

此时会发现,在push_back和append的扩容操作中,有很多重复的操作,所以可以将其封装成函数,且这个函数就reserve

2.8 resize

2.9 insert、erase

2.10 find

2.11 relational operators

class String
{
public:
	typedef char* iterator;
	iterator begin()
	{
		return _str;
	}
	iterator end()
	{
		return _str + _size;
	}
	String(const char* str = "")//最好还是合二为一用全缺省
	{//这里一定要加const,因为像"hello"是字符串常量,不能权限放大
		if (nullptr == str)
		{
			assert(false);
			return;
		}
		_size = strlen(str);
		_capacity = _size;
		_str = new char[_capacity + 1];
		strcpy(_str, str);
	}
	~String()
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}
	String(const String& s)
		: _str(nullptr)
		//这里一定要给空指针,否则为随机值,下面交换后要析构strTmp时会报错
	{
		String strTmp(s._str);//构造函数
		_size = s._size;
		_capacity = s._capacity;
		swap(_str, strTmp._str);
	}
	String& operator=(String s)//直接利用传参时来拷贝构造
	{
		swap(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;
		return *this;
	}
	size_t size() const
	{
		return _size;
	}
	size_t capacity() const
	{
		return _capacity;
	}
	char& operator[](size_t i)
	{
		assert(i < _size);
		return _str[i];
	}
	const char& operator[](size_t i) const//还需要给一个const版本
	{
		assert(i < _size);
		return _str[i];
	}
	const char* c_str() const
	{
		return _str;
		//拿到指向字符串的指针就可以直接cout输出
	}
	void reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* newstr = new char[n + 1];
			strcpy(newstr, _str);
			delete[] _str;
			_str = newstr;
			_capacity = n;
		}
	}
	void push_back(char ch)
	{
		if (_size == _capacity)
		{
			size_t newcapacity = _capacity == 0 ? 2 : _capacity * 2;
			//使用三目操作符是为了防止空字符串调用
			reserve(newcapacity);
		}
		_str[_size] = ch;
		++_size;
		_str[_size] = '\0';
		//插入后,一定要在插入之后的一个位置加一个\0
		//也可以用insert
		//insert(_size,ch);
	}
	void append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			size_t newcapacity = _size + len;
			reserve(newcapacity);
		}
		strcpy(_str + _size, str);
		_size += len;
		//insert(_size,str);
	}
	String& operator+=(char ch)
	{
		this->push_back(ch);
		return *this;
	}
	String& operator+=(const char* str)
	{
		this->append(str);
		return *this;
	}
	void rasize(size_t n, char ch='\0')
	{
		if (n < _size)
		{
			_str[n] = '\0';
			_size = n;
		}
		else
		{
			if (n > _capacity)
			{
				reserve(n);
			}
			for (size_t i = _size; i < n; i++)
			{
				_str[i] = ch;
			}
			_size = n;
			_str[_size] = '\0';
		}
	}
	String& insert(size_t pos, char ch)
	{
		assert(pos <= _size);//=即尾插
		if (_size == _capacity)
		{
			size_t newcapacity = _capacity == 0 ? 2 : _capacity * 2;
			reserve(newcapacity);
		}
		//insert(1,'a')是在下标为1的位置插入一个a,所以从\0开始挪动
		int end = _size;
		while (end >= (int)pos)
			//pos用size_t是因为下标不可能有负数
			//要对pos强转是因为当end变成-1时能够出循环
		{
			_str[end + 1] = _str[end];
			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);
		}
		//挪动数据
		int end = _size;
		while (end >= (int)pos)
		{
			_str[end + len] = _str[end];
			end--;
		}
		strncpy(_str + pos, str, len);
		//不能使用strcpy,因为这样会把\0也拷贝过去
		//可以使用strncpy,因为可以控制长度
		//当然也可以自己写循环,将str的数据放到pos往后len个空间中
		_size += len;
		return *this;
	}
	String& erase(int pos, size_t len = npos)
	{
		if (len >= _size - pos)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			int i = pos + len;
			while (i <= _size)
			{
				_str[i - len] = _str[i];
				i++;
			}
			_size -= len;
		}
		return *this;
	}
	size_t find(char ch, size_t pos = 0)
	{
		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
			{
				return i;
			}
		}
		return npos;
	}
	size_t find(const char* str, size_t pos = 0)
	{
		char* p = strstr(_str, str);
		//string有\0是为了兼容C,所以可以使用C的函数
		if (p == nullptr)
		{
			return npos;
		}
		else
		{
			return p - _str;
			//p-_str就是下标
		}
	}
	bool operator<(const String& s) const
	{
		int ret = strcmp(_str,s._str);
		return ret < 0;
	}
	bool operator==(const String& s) const
	{
		int ret = strcmp(_str, s._str);
		return ret == 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);
	}
	bool operator!=(const String& s) const
	{
		return !(*this == s);
	}
private:
	char* _str;
	int _size;//已经有多少个有效字符
	int _capacity;//最多能存多少个有效字符,\0不是有效字符
	static size_t npos;
};
size_t String::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)
{
	while (1)//遇到空格或换行就结束
	{
		char ch;
		//in >> ch;
		//这样子是错的,会死循环,因为cin是编译器实现的
		//只要遇到空格或换行就自动忽略,去读下一个
		//若没有下一个则ch为空,不会加入if而跳出循环
		ch = in.get();//in.get是无论什么都读
		if (ch == ' ' || ch == '\n')
		{
			break;
		}
		else
		{
			s += ch;
		}
	}
	return in;
}
istream& getline(istream& in, String& s)
{
	while (1)
	{
		char ch;
		ch = in.get();
		if (ch == '\n')
		{
			break;
		}
		else
		{
			s += ch;
		}
	}
	return in;
}

相关推荐

  1. MFC中CString用法及使用示例

    2024-04-23 20:16:02       38 阅读
  2. string模拟实现

    2024-04-23 20:16:02       31 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-23 20:16:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-23 20:16:02       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-23 20:16:02       82 阅读
  4. Python语言-面向对象

    2024-04-23 20:16:02       91 阅读

热门阅读

  1. Redis雪崩

    2024-04-23 20:16:02       38 阅读
  2. python多线程详解

    2024-04-23 20:16:02       30 阅读
  3. Ubuntu搭建RP2040开发环境-1

    2024-04-23 20:16:02       38 阅读
  4. Springboot2.7解决静态资源302问题

    2024-04-23 20:16:02       39 阅读
  5. LeetCode 42. 接雨水 - PHP

    2024-04-23 20:16:02       32 阅读
  6. 2023年图灵奖揭晓

    2024-04-23 20:16:02       31 阅读