【C++】模拟实现string常用接口

模拟实现string

1. 构造+析构+拷贝构造函数

注意事项:

  • 因为c++模板库里面有string这个类,以防重名,给自己模拟的string加上namespace
  • 在构造函数,需要单独重载一个应对传参传空的构造函数
  • 这里因为初始化列表有一个顺序问题,以防出现问题,还是在大括号里面写

以下代码进行了三次优化

#include <stdio.h>
#include <string>

namespace wyj
{
	class string
	{
	public:
        //构造函数
        string()
			:_size(0),
			_capacity(0),
			_str(new char[1])
		{
			_str[0] = '\0';
		}
        
        string(char* str = "")//这里是空字符串只有'\0'
			:_size(strlen(str)),
			_capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
        
        /*  拷贝构造
        第一版本
		string(const string& s)
			:_size(s._size),
			_capacity(s._capacity)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, s._str);
		}
		*/
        
        //拷贝构造版本
		string(const string& s)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			string tmp(s._str);//这里是利用前面string的构造函数
			swap(_str, tmp._str);
			swap(_size, tmp._size);
			swap(_capacity, tmp._capacity);
		}

        //析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
	private:
		char* _str;//字符串地址
		size_t _size;//字符串长度
		size_t _capacity;//字符串开辟空间大小
	};
}


2. 赋值重载=

考虑3个问题 以s1 = s2 为例

  • 如果s2 比 s1 大,需要增加容量
  • 如果s2 比 s1 小,空间大不适合
  • 如果s2 是 s1 本身,则不能直接销毁s2

但这里还有一个细节,如果new开辟失败了呢?

string& operator=(const string& s)
{
	if (this != &s)
	{
		char* tmp = new char[strlen(s._str) + 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);//这里使用拷贝构造
		swap(_str, tmp._str);//这里将原来字符串的与tmp交换
        _size = s._size;
		_capacity = s._capacity;
	}
    
	return *this;
}
//结束之后,出了作用域,会调用tmp的析构函数
//将tmp原来指向字符串销毁
//正好保留需要的

更进一步优化:

虽然对相同的对象有影响,但总体来说,代码量比上面的小了很多

string& operator=(string s)
{
	swap(_str, s._str);
	swap(_size, s._size);
	swap(_capacity, s._capacity);
    return *this;
}

但我们发现,拷贝构造和赋值重载有相同的地方

//拷贝构造
string(const string& s)
	:_str(nullptr)
	,_size(0)
    ,_capacity(0)
{
	string tmp(s._str);//这里是利用前面string的构造函数		swap(_str, tmp._str);
    swap(_size, tmp._size);
    swap(_capacity, tmp._capacity);
}
//赋值重载=
string& operator=(string s)
{
    swap(_str, s._str);
    swap(_size, s._size);
	swap(_capacity, s._capacity);
    return *this;
}

image-20220309123134696

可以将这里单独拿出来写一个swap函数

正好string标准库里面也有swap

image-20220309123814650

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

提问:string类的成员函数swap和标准库std里面的swap区别是什么

string专门针对类的,对成员变量交换效率高

再看看std模板库里的swap

image-20220309131833976

这里一看进行了3次string的拷贝


测试:

#define _CRT_SECURE_NO_WARNINGS
#include <string>
#include <assert.h>
#include <iostream>
using namespace std;

namespace wyj
{
	class string
	{
	public:
		
		typedef char* iterator;

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return (_str + _size);
		}

		string(const char* str = "")
			:_size(strlen(str)),
			_capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		
		//拷贝构造
		//string(const string& s)
		//	:_size(strlen(s._str)),
		//	_capacity(_size)
		//{
		//	_str = new char[_capacity + 1];
		//	strcpy(_str, s._str);
		//}

	
		//string(const string& s)
		//	:_str(nullptr)
		//{
		//	string tmp(s._str);//这里是利用前面string的构造函数
		//	swap(_str, tmp._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);//这里是利用前面string的构造函数
			swap(tmp);
		}
		

		//string& operator=(const string& s)
		//{
		//	if (this != &s)
		//	{
		//		char* tmp = new char[strlen(s._str) + 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);
		//		swap(_str, tmp._str);
		//		_size = s._size;
		//		_capacity = s._capacity;
		//	}
		//	return *this;
		//}

		string& operator=(string s)
		{
			swap(s);
			return *this;
		}
		
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}

		size_t size() const
		{
			return _size;
		}

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

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

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



	private:
		char* _str;//字符串地址
		size_t _size;//字符串长度
		size_t _capacity;//字符串开辟空间大小
	};

	void test1()
	{
		string s1("hello");
		string s2("abc");
		s2 = s1;
	}
}


image-20220308134806068


3. c_str 返回字符串地址

image-20220308143703114

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

4. size 字符串长度

image-20220308143935371

size_t size() const
{
	return _size;
}

5. 迭代器

string的迭代器是个char*指针

范围for本质就是迭代器

image-20220308150325439最主要的是前几个

typedef char* iterator;

iterator begin()
{
	return _str;
}

iterator end()
{
	return (_str + _size);
}

6. 增

  • reserve 扩容函数

image-20220309134923012

//改变容量
void reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		_str = tmp;
		_capacity = n;
	}
}

  • push_back 以字符增加
//增加
void push_back(char c)
{
	if (_size = _capacity)
	{
		reserve(_capacity == 0 ? 4 : 2 * _capacity);
	}
	_str[_size] = c;
	_size++;
	_str[_size] = '\0';
}



  • append 以字符串增加
string& append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}
	strcpy(_str + _size, str);
	_size += len;
}

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

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

测试:

void test1()
{
	string s1("hello");
	s1 += " world";
	s1.print();
}

image-20220309144112726


  • resize 既然提到了reserve看一下resize

区别:resize可以初始化,已经有字符的不再初始化,只初始化空白的地方,如果不够会开空间

image-20220309144510435

void resize(size_t n, char c = '\0')
{
	if (n > _size)
	{
		if (n > _capacity)
		{
			reserve(n);
		}
		memset(_str + _size, c, n - _size);
	}
	_str[n] = '\0';
	_size = n;
}

  • insert 在指定位置添加字符/字符串

image-20220309162257076

  • 添加字符
string& insert(size_t pos, const char ch)
{
	assert(pos <= _size);

	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : 2 * _capacity);
	}

	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		end--;
	}

	_str[pos] = ch;
	_size += 1;
	return *this;
}
  • 添加字符串
string& insert(size_t pos, const char* s)
{
	assert(pos <= _size);
	size_t len = strlen(s);

	if (len + _size > _capacity)
	{
		reserve(len + _size);
	}

	size_t end = _size + len;
	while (end > pos)
	{
		_str[end] = _str[end - len];
		end--;
	}
	strncpy(_str + pos, s, len);
	_size += len;

	return *this;
}

7. 删

  • erase 删除

image-20220309183550080

string& erase(size_t pos = 0, 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;
}

8. 查

  • 查找字符

image-20220309153729372

size_t find(char c, size_t pos = 0) const
{
	for (size_t i = pos; pos < _size; ++i)
	{
		if (_str[i] == c)
		{
			return i;
		}
	}
	return npos;
}

image-20220309154141649


  • 如果是查找字符串呢

image-20220309161353851

size_t find(const char* s, size_t pos = 0) const
{
	const char* ptr = strstr(_str, s);
	if (ptr == nullptr)
	{
		return npos;
	}
	else
	{
		return ptr - _str;
	}

}

测试:

image-20220309161843483


9. 改

  • operator[] 访问每个字符

image-20220308144042928

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凛音Rinne

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值