c++简单实现-string

学习了c++, 其中STL是当之无愧的佼佼者。简单实现一些STL容器-string,只写了部分功能。

/*
	模拟实现一个简单的stirng容器
*/

#pragma once 
#define _CRT_SECURE_NO_WARNINGS 1
#include<string.h>
#include<iostream>
#include<assert.h>

using namespace std;

class String {
public:
	
	//string中迭代器很简单, 不用实现它都可以完成++、--、*、->操作。
	typedef char* iterator;					//定义迭代器
	typedef const char* const_iterator;
	
	//声明一个cout友元函数
	friend ostream& operator<<(ostream& _cout, const String& str);
	
	//构造函数
	String(const char* str = "") {
		_size = strlen(str);				
		_capacity = _size;
		//size只是表示有效元素的个数, 但必须需要存放一个'\0', 所以多开一个。
		_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)
	//{
	//	//现代写法, 创建一个临时对象,然后交换, 最后临时对象被析构。
	//	//但是请注意现在写法,交换后,临时对象最后会被析构,所以交换以前_str要存放nullptr。 
	//	String tmp(s._str);
	//	Swap(tmp);
	//}

	//类与类成员交换函数 - 浅拷贝。
	void Swap(String& str) {
		swap(_str, str._str);
		swap(_size, str._size);
		swap(_capacity, str._capacity);
	}
	
	//赋值运算符重载
	String& operator=(const String& s) {
		if (this != &s) {
			//先释放原有的, 然后在开辟新的,把s类值中拷贝过来。
			delete[] _str;
			
			_size = s._size;
			_capacity = s._capacity;
			_str = new char[_capacity + 1];
			strcpy(_str, s._str);
		}
		return *this;
	}
	
	//赋值运算符重载 - 现代写法
	//String& operator=(String str) {
	//	//现在写法-赋值运算符重载
	//	Swap(str);
	//	return *this;
	//}

	//析构函数
	~String() {
		if (_str) {
			delete[] _str;
		}
	}  

	//访问
	char& operator[](size_t pos) {
		//可读可写
		assert(pos < _size);
		return _str[pos];
	}
	
	//与上面operator[]函数构成重载,上面可读可写, 这个只读不能写。 
	//主要是搭配const String型的对象。 
	const char& operator[](size_t pos) const {
		//只能读不能写。
		//匿名的对象具有常性。
		assert(pos < _size);
		return _str[pos]; 
	}
	
	iterator begin() {
		return _str;
	}

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

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

	const iterator cbegin() {
		return _str;
	}

	const iterator cend() {
		return _str + _size;
	}

	//修改
	void push_back(char c) {
		//插入之前是需要判断的。
		if (_size == _capacity) {
			//容量满了, 需要扩容
			size_t newC = _capacity == 0 ? 15 : 2 * _capacity;
			reserve(newC);	//调用了增容。
		}
		_str[_size] = c;
		++_size;
		_str[_size] = '\0';
	}

	void append(const char* str) {
		//插入一个字符串
		int len = strlen(str);
		//如果_size+len<_capacity,我们是不需要扩容的, 否则则需要扩容。
		if (_size + len >= _capacity) {
			reserve(_size + len);
		}
		
		strcpy(_str + _size, str);
		_size += len;
		_str[_size] = 0;
	}

	String& operator+=(char c) {
		//只能加字符
		push_back(c);
		return *this;
	}

	String& operator+=(const char* str) {
		//加字符串
		Append(str);
		return *this;
	}
	
	String& operator+=(const String& str) {
		//加一个类
		Append(str._str);
		return *this;
	}
	
	//insert插入一个字符
	void insert(size_t pos, char c) {
		if (_size == _capacity) {
			size_t newC = _capacity == 0 ? 15 : 2 * _capacity;
			reserve(newC);
		}
		//先后移再插入 - 建议画图理解!
		size_t end = _size + 1;

		for (end; end > pos; --end) {
			_str[end] = _str[end - 1];
		}
		
		_str[pos] = c;
		_size++;
	}
	
	//insert插入一个字符串
	void insert(size_t pos, const char* str) {
		int len = strlen(str);
		if (_size + len >= _capacity) {
			reserve(_size + len);
		}

		size_t end = _size + len;
		size_t end1 = _size;
		
		for (end; end > pos; --end) {
			_str[end] = _str[end1];
			--end1;
		}

		int i = 0;
		
		while (str[i] != '\0') {
			_str[pos + i] = str[i];
			++i;
		}
		
		_size += len;
	}
	
	//earse删除函数
	void erase(size_t pos, int len) {
		size_t begin = pos;
		size_t begin1 = pos + len;

		for (begin; begin < _size; ++begin) {
			_str[begin] = _str[begin1];
			++begin1;
		}

		--_size;
	}
	
	//删除迭代器对应的值
	void erase(iterator it)
	{
		assert(it < end() && it >= begin());
		while (it != end())
		{
			*it = *(it + 1);
			++it;
		}
		--_size;
	}
	//查找一个字符串, 找到后返回下标。 没有返回npos
	size_t find(const char* str, size_t pos = 0){
		char* start = strstr(_str + pos, str);
		if (start == nullptr)
			return npos;
		else
			return start - _str;
	}
	
	//查找一个字符, 找到后返回下标。 没有返回npos
	size_t find(char ch, size_t pos = 0){
		for (int i = pos; i < _size; ++i){
			if (ch == _str[i])
				return i;
		}
		return npos;
	}

	//容量	
	//reserve接口, 只增不减!
	void reserve(size_t n) {
		if (n > _capacity) {
			char* buffer = new char[n + 1];
			strcpy(buffer, _str);
			delete[] _str;
			_str = buffer;
			_capacity = n;
		}
	}

	//resize重新设置
	void resize(size_t n, char c = '\0') {
		//重新设置capacity容量的大小
		//分为3种情况:
		//				1、 n > _capacity -- 增容
		//				2、 _size < n < _capacity -- 赋值
		//				3、 sz < _size == --改变_size

		//下面的代码小巧精炼, 值得参考!
		if (n > _capacity) {
			//如果n>容量, 则需要扩容
			reserve(n);
		}
		if (n > _size) {
			//不管是否扩容过, 只要n > _size, 我们就必须要赋初始值
			memset(_str + _size, c, n - _size);
		}

		//最后修改_size
		_size = n;
		_str[_size] = '\0';
	}
	
	//字符串的大小
	size_t size() const{
		return _size;
	}
	
	//当前动态开辟的容量大小
	size_t capacity() const{
		return _capacity;
	}
	
	//提供一个char*的指针。 只读不写
	const char* c_str() const {
		return _str;   
	}

private:
	static const size_t npos;
	char* _str;
	int _size;
	int _capacity;
};

//对静态成员赋值。 
const size_t String::npos = -1;

//友元函数实现
ostream& operator<<(ostream& _cout, const String& str) {
	for (const auto& ch : str) {
		_cout << ch;
	}
	return _cout;
}


代码中我尽可能多地表达每段函数的信息。 一些简单的我就不做详细的解读。
在写源码时, 需要掌握几点:
1、熟悉并知道容器中每个接口的功能
2、知道每个容器的优缺点

分析:
1、我们可以看出string容器是线性存储的, 因此这种容器就像线性表一样会有一个缺点–不易于任意位置插入删除(效率不高),更偏向于遍历。
2、当然也有优点: 那就是对内存使用率是比较好的。遍历效率高!
3、还有一点增容这块代价还是很大的!

发布了25 篇原创文章 · 获赞 16 · 访问量 924

猜你喜欢

转载自blog.csdn.net/weixin_44024891/article/details/103905892