string类的简单实现

#pragma once
#include <iostream>
#include <assert.h>

 namespace bit
{
	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(const  char* str  = "")
			:_str(new char[strlen(str) + 1])
			, _size(strlen(str))
			, _capacity(strlen(str))
		{
			strcpy(_str, str);
		}*/
//不建议这样使用构造函数,的确这样可以减少两次strlen的复杂度,但是此处的初始化的顺序要和下面声明的顺序要一致,不利于维护与修改,否则就会出现随机值的问题,所以最终建议是将构造的内容一并写入函数体内
		/*string(const  char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
			,_str(new char[_capacity + 1])	
		{
			strcpy(_str, str);
		}*/
		string(const  char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		string()
			:_str(new char[1])//这里不能用空指针来初始化,空对象可能会解引用空指针导致崩溃,这里开一个是因为如果是空字符串也有一个\0来标识结束
			, _size(0)
			, _capacity(0)
		{
			_str[0] = '\0';
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}
		const char* c_str() const
		{
			return _str;
		}
		size_t size() const
		{
			return  _size;
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
		//传统写法
		/*string(const string& s)
			:_str(new char[s._capacity+1])
			,_size(s._size)
			,_capacity(s._capacity)
		{
			strcpy(_str, s._str);
		}

		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(const string& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			string tmp(s._str);
			std::swap(_str, tmp._str);
			std::swap(_size, tmp._size);
			std::swap(_capacity, tmp._capacity);
		}
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				string tmp(s);
				std::swap(_str, tmp._str);
				std::swap(_size, tmp._size);
				std::swap(_capacity, tmp._capacity);
			}
			return *this;
		}

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[]_str;
				_str = tmp;
				_capacity = n;
			}
		}
		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}
		string& operator +=(char ch)
		{
			push_back(ch);
			return *this;
		}

		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(len + _size);
			}
			strcpy(_str + len, str);
			_size += len;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

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

	};
}

一,构造函数

这里我们将讨论几种构造函数的实现方法的可行性,并给出理由。

首先我们这里采用最简单的char* ,size和capacity的总体框架来设计整个类。

第一种:构造函数主要用于c语言风格的string类的构造,先传入一个c语言的char类型指针,再构造一个c++类并使用初始化列表进行初始化,注意这里new的时候要多加一个\0的位子,这种方法的缺陷就是使用了三次o(n)的strlen,复杂度比较高。

大概的使用场景如下:

const char* cStyleString = "Hello, World!"; // A C-style string
string myString(cStyleString); // Creating a custom string object with the content of the C-style string

第二种:为了减少使用strlen的次数,我们进行了顺序调换,避免了重复的使用。但是不建议这样使用构造函数,的确这样可以减少两次strlen的复杂度,但是此处的初始化的顺序要和下面声明的顺序要一致,不利于维护与修改,否则就会出现随机值的问题,所以最终建议是将构造的内容一并写入函数体内,也就是第三种方法。

无参的默认构造函数:注意这里不能用空指针来初始化,空对象可能会解引用空指针导致崩溃,这里开一个是因为如果是空字符串也有一个\0来标识结束。

二,析构函数

这里的析构函数比较简单,只是要注意这里的delete后面要加上[],和前面的new[]对应上即可

三,函数接口

1.c_str()

这个函数非常常用,尤其在需要与C风格字符串兼容的代码或库进行交互时很有用。一些C/C++库和函数(尤其是老旧的库)可能要求以C风格字符串作为输入或者返回C风格字符串,这时候你可以使用这个函数来获取类对象中存储的C风格字符串。

2.size()

返回该string的_size,注意这里的c_str和size都要用const来修饰,防止修改

3.[]的符号重载

首先判断传入参数是否超过_size的大小,然后返回指定位置的字母。注意这里要单独写两种,一种是const修饰的一种是不带const修饰的,普通对象调用不带const,const对象就要调用带const,主要原因就是权限只能缩小而不能放大。

三,迭代器

这里由于是string类,所以直接用指针来代替,但不是所有的类的迭代器都是指针,只有类似于连续数组的对象才能用指针代替。定义一个begin和end就行,不过同样的我们需要定义一个带const的迭代器。

四,拷贝构造函数

 

这里没有写拷贝构造,编译器会自动生成,对于内置类型是完成值拷贝或者浅拷贝,而这里所有的成员变量都是内置类型。所以在浅拷贝的时候,s2也会指向字符串。当出了函数作用域时调用析构函数就会析构两次,从而会发生崩溃。

所以为了解决浅拷贝的问题,应该定义自己的拷贝构造函数使用深拷贝,自己开辟一块空间,然后再把值拷贝下来就不会发生指向同一块地址的问题。

这里我们的传统写法先判断是不是给自己赋值,如果是就直接return,如果不是我们直接开辟新空间,然后拷贝数据,最后释放原空间,并把变量赋值一遍。 

而现代写法为了方便起见,我们先定义一个tmp用来存放赋值内容,然后将tmp与需要赋值的对象和tmp进行交换就完成了拷贝构造。同理=的符号重载也可以用相似的方法实现。

 

 

五,一些函数接口

尾插reserve函数用来开辟一个新的tmp对象,尾插函数在_size的地方替换要插入的字符,最后处理一下_size和\0

+=的符号重载直接复用push_back就行。

append函数首先判断字符串是否能够装下,如果装不下就调用reserve函数,然后再把值拷贝过去就行,+=直接调用append就行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值