string类的模拟实现

前提:

1:须知知识点

strcpy的模拟实现-CSDN博客

strstr的模拟实现-CSDN博客

两个函数都会有使用,不会点开链接

2:实现的文件结构:

++①:全在一个.h文件里面进行实现++

++②:将①分离成一个.h 和 一个.cpp文件++

3:实现函数一览

本文只模拟实现string类的常用重点的函数

A:默认成员函数

①:构造函数(无参 和 字符串作为参数)

②:拷贝函数(传统写法和现代写法)

③:赋值运算符重载(传统写法和现代写法)

④:析构函数

B:功能性函数

①:swap()函数

②:reserve()函数

③:erase()函数

④:clear()函数

⑤:find()函数

⑥:c_str()函数

C:重要函数

①:push_back()函数

②:append()函数

③:+=运算符重载

④:[ ]运算符重载

⑤:insert()函数

⑥:substr()函数

⑦:<< 和 >> 运算符重载

一:默认成员函数的实现

初始状态:

cpp 复制代码
namespace key
{
	class string 
	{

	public:

	private:
		char* _str = nullptr;
		size_t size = 0;
		size_t capacity = 0;
		static const size_t npos;

	};
	const size_t string::npos = -1;

}

解释:

**1:**在库中的string类中的npos是一个静态成员变量,按照规则,声明初始化规则如图所示

**2:**为了避免和库中的string冲突 选择用一个key的域保护起来

①:构造函数

cpp 复制代码
//构造函数
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];

			strcpy(_str, str);
		}
		//缺省函数 
		//既能string s1;
		//也能string s1("hello");

解释:

**1:**缺省函数,既能string s1,也能string s1("hello")

**2:**string库中的_capacity 和 _size都是不计入'\0'的,模拟实现就要跟库统一,所以开空间的时候多开了一个放置'\0'

**3:**strlen的复杂度不低,建议只使用一次strlen,用得到的值再去进行成员变量的初始化

②:拷贝构造函数

a 传统写法:

cpp 复制代码
//拷贝函数(传统写法)
//string s2(s1) 或  string s2 = s1
		string(const string& s)
		{
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

解释:

++**1:**传统写法就是老老实实地去完成深拷贝。++

b 现代写法:

cpp 复制代码
//拷贝函数(现代写法)
//string s2(s1)或 string s2 = s1;
		string(const string& s)
		{
			//用s(s2的引用)的_str去构造一个tmp对象
			string tmp(s._str);
			//然后用swap函数把tmp对象 和 *this 进行交换
			swap(tmp);
		}

解释:

**1:**不再自己开辟空间,而是通过 string tmp(s._str); 这一步,复用构造函数,得到一个tmp对象,然后再自己和tmp对象交换

**2:**而且tmp对象的所有成员变量及_str指向的空间,出了函数都会被释放

**3:**总的来说,就是一种复用构造函数和swap函数去简化拷贝函数的方法

**4:**我们在成员变量初始化的时候,给了缺省值(_str = nullptr),在这里至关重要,有些编译器不会自动置空,此时交换后,tmp内的_str指向的是随机空间,tmp 出了作用域要调用析构函数,对随机值指向的空间进行释放,此时再去delete[ ] _str,就可能会引发崩溃。但是 delete / free 一个空,是不会报错的,所以成员变量_str在声明的时候给的nullptr至关重要

③:赋值运算符重载

a 传统写法:

cpp 复制代码
//赋值重载(传统写法)
//s1 = s3 
string& operator=(const string& s) {
	if (this != &s) {                              // 防止自己给自己赋值  
		char* tmp = new char[s._capacity + 1];     // Step1:先在tmp上开辟新的空间
		strcpy(tmp, s._str);                       // Step2:把s3的值赋给tmp
		delete[] _str;                             // Step3:释放原有的空间
		_str = tmp;                                // Step4:把tmp的值赋给s1
 
		// 把容量和大小赋过去
		_size = s._size;      
		_capacity = s._capacity;
	}
 
	return *this;   // 结果返回*this
}

**解释:**传统写法,就是自己开空间自己拷贝数据

b 现代写法:

cpp 复制代码
	//赋值函数(现代写法)
		//s1 = s3;
		string& operator=(string s)
		{
			//该函数参数s去接受s3的时候,s3拷贝生成了s
			//直接swap 交换s 和 *this(s1)
			swap(s);
			return *this;
		}

解释:

**1:**参数从 string& s 变成了 string s ,这意味着 s3 到 s 这一步,是拷贝构造,生成s这个对象,而我们赋值的意义就在于要一个和s3一样的东西,但是空间的地址又不一样,此时s3拷贝构造生成的s这个对象是经过我们自己写好的深拷贝生成的,所以符合要求

**2:**所以直接swap交换了 s1 和 s 即可

**3:**返回值是string& 的意义在于,可以连续的使用赋值操作符,这也是和库一致的

④:析构函数

cpp 复制代码
//析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = 0;
			_capacity = 0;
		}

**解释:**过于简单 不作赘述

二:功能性函数的实现

①:swap()函数

cpp 复制代码
	//swap()函数
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

**解释:**交换两个对象的一切东西,不生产中间拷贝

②:reserve()函数

cpp 复制代码
		//扩容函数
		//s1.reverse(10)
		void reserve(size_t n)
		{
			//vs的string类的reserve函数不支持缩容
			//所以我们为了保持一致,只对n>capacity进行扩容处理
			if (n > _capacity)
			{
				//开辟新的空间(n+1是因为多开一个给'/0')
				char* tmp = new char[n + 1];
				//把旧空间的_str strcpy 到 tmp空间中
				strcpy(tmp, _str);
				//释放旧空间
				delete[] _str;
				//_str指向tmp
				_str = tmp;
				//容量重置成n
				_capacity = n;

			}

		}

解释:

**1:**vs的string类的reserve函数不支持缩容,所以我们为了保持一致,只对n>capacity进行扩容处理

**2:**因为n是和_capcacity比较,_capcacity是不计入'\0'的,所以我们开空间的时候自然是要多开一个的

**3:**只是重置_capacity为n即可,因为_capacity就是代表容量,而_size则不用管,其实代表存储的有效字符的大小,扩容并没有影响存储的有效字符

③:erase()函数

cpp 复制代码
		//erase()函数
		//s1.erase(2,5);从下标为2开始,清理5个字符
		void erase(size_t pos, size_t len = npos)
		{
			//断言
			assert(pos < _size);
			//不传len值,代表从pos位置开始全部清理
			//len + pos >= _size 也是从pos位置开始全部清理
			if (len == npos || len + pos >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else//代表从下标pos位置开始清理一部分
			{
				//将_str + pos + len往后的内容 strcpy到 _str + pos往后
				strcpy(_str + pos, _str + pos + len);
				//置_size-=len
				_size -= len;
			}

		}

解释:

**1:**断言是必要的,因为下标是_size代表,是'\0'的位置,从这里及以后的位置开始清理,无任何的意义

**2:**参数len给了缺省值,这是与库一致,不给len即代表len等于npos,npos在前文已经说过,一个size_t的类型,值为-1,即整形的最大值 ,所以len不给,代表从下标为pos位置开始全部清理

**3:**经过第2点,可以len == npos 代表一种从下标为pos位置开始全清理,初次之外,当len + pos >= _size 的时候,也是从下标为pos位置开始全清理

**4:**2和3的判断条件必须 len == npos在前,len + pos >= _size在后,因为如果len等于npos,你是先判断len + pos >= _size时,会整形溢出,会出各种各样的bug,所以len == npos必须在前

**5:**也不能仅仅要len + pos >= _size这个条件,因为理由还是可能会整形溢出

④:clear()函数

cpp 复制代码
//clear()函数
		//s1.clear();
		void clear()
		{
			//_size置0 且 在第一个位置放入'\0'
			_size = 0;
			_str[0] = '\0';
		}

解释:

**1:**此函数一般不直接使用,但在operator>> 的实现中有着重要作用

**2:**不仅要_size = 0 还要 _str[0] = '\0',因为有一些遍历不看size的大小,而是看什么时候遇到'\0',它才会停止

⑤:find()函数

find()找字符

cpp 复制代码
//find()函数 找字符
		//s1.('h',2)从下标为2开始查找 'h'字符
		size_t find(char c, size_t pos = 0)const
		{
			//assert(pos < _size);
			//遍历查找
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == c)
					return i;
			}
			//来到这里 代表找不到 返回npos(与库一致)
			return npos;
		}

解释:

**1:**find()函数找字符串,即遍历查找即可,找到返回下标,找不到返回npos(与库一致)

2: pos不给,则为0(与库的参数用形式一致)

**3:**const修饰的是*this,因为find函数不会对对象的内容改变,所以写成const,这样正常对象和const对象都能够使用

find()找字符串

cpp 复制代码
//find()函数 找字符串
		//s1.("hello",2)从下标为2开始查找 "hello"字符串
		size_t find(const char* str, size_t pos = 0)
		{
			//assert(pos < _size);
			//用strstr函数来从_str+pos位置开始查找字符串
			const char* ptr = strstr(_str + pos, str);
			//ptr为空 代表没找到 返回npos(于库一致)
			if (ptr == nullptr)
			{
				return npos;
			}
			else
			{
				//指针相减得到下标
				return ptr - _str;
			}
		}

解释:

**1:**pos不给,则为0(与库的参数用形式一致)

**2:**strstr函数来进行查找字符串的功能,其次需要注意的是,在被查找的字符串中_str + pos指定查找的起始位置,pos为0则从头查找

**3:**strstr函数返回的如果是空,则代表没找到,此时返回npos即可(与库一致)

**4:**找到了返回的是一个被查找的字符串首次出现位置(指针),那我们要得到的是下标,所以ptr - _str 即可,指针相减得到的是下标

Q:为什么find函数都没有对pos进行断言

A:因为pos不管为什么值,都有对应的返回值,所以不需要断言

⑥:c_str()函数

cpp 复制代码
//c_str()函数
		const char* c_str() const//后面加const 对于是非const的对象都能调用
		{
			return _str;
		}

解释:

**1:**c_str() 返回的是当前字符串的首字符地址,是可读不写的,所以返回值类型是const char *

**2:**直接 return _str 即可实现。

⑦:迭代器

cpp 复制代码
typedef char* iterator;
		//只可读不可写的迭代器
		typedef const char* const_iterator;

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

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

		}

解释:

**1:**与库一致,针对const对象和非const对象都有对应的迭代器

2:

string的底层是连续地物理空间,给原生指针++解引用能正好贴合迭代器的行为,就能做到遍历。

但是对于链表和树型结构来说,迭代器的实现就没有这么简单了。

但是,强大的迭代器通过统一的封装,无论是树、链表还是数组......

它都能用统一的方式遍历,这就是迭代器的优势,也是它的强大之处

三:string类重要的函数的实现

①:push_back()函数

cpp 复制代码
//尾插
		void push_back(char c)
		{
			//if判满
			if (_size == _capacity)
			{
				//两种满的给不同容量newcapacity
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				//根据newcapacity扩容
				reserve(newcapacity);
			}
			//原先的_str[_size]是'\0',现在成为新尾插的字符
			_str[_size] = c;
			//_size++
			_size++;
			//'\0'往后移动一个
			_str[_size] = '\0';
		}

解释:

**1:**push_back意为尾插,既然是插入吗,肯定要先判断容量,在针对不同的容量为满的类型进行扩容

2:_str[_size] = c; 原本的'\0'的位置,插入了尾插的字符c,然后_size++,再补'\0'

②:append()函数

cpp 复制代码
		//append
		//s1.append("hello")
		void append(const char* str)
		{
			//计算出要追加的字符串的长度
			size_t len = strlen(str);
			//已有的字符串长度(_size)+len 超过容量即扩容
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			//把追加的str strcpy 到 _str + _size 往后
			strcpy(_str + _size, str);
			//置_size+=len
			_size += len;
			//reserve里面已经置capacity

		}

解释:

**1:**照样是判断容量,和push_back不同的是,是 _size + len 去判断,满了也是扩_size + len这么大容量的容

**2:**尾插字符串,则用strcpy函数即可,起始位置为_str + _size,即原本的'\0'的位置开始,然后更新_size即可,_capacity在扩容函数中就已重置了,所以不用再置

总结:push_back 和 append 都不常用,因为有更好的 +=运算符重载,其内部复用了push_back 和 append

③:+=运算符重载

cpp 复制代码
//+=运算符重载函数 
		//s1+='w' 给s1+=一个字符
		string& operator+=(char c)
		{
			//复用push_back()
			push_back(c);
			return *this;
		}
cpp 复制代码
//+=运算符重载函数
		//s1+="hello" 给s1+=一个字符串
		string& operator+=(const char* str)
		{
			//复用append()
			append(str);
			return *this;

		}

解释:

**1:**返回值是string& 是因为可以减少拷贝 ,二者分别复用了push_back 和 append

④:[ ]运算符重载

cpp 复制代码
//[]运算符重载(只可读)
		const char& operator[](size_t pos)const
		{
			//'\0'也能读
			assert(pos <= _size);
			return _str[pos];

		}
cpp 复制代码
//[]运算符重载(可读可写)
		char& operator[](size_t pos)
		{
			assert(pos <= _size);

			return _str[pos];
		}

解释:

**1:**和库一致,[ ]针对 const对象和正常对象都写了一个

**2:**返回值是char& 引用的意义是可以对其直接++

⑤:insert()函数

insert插入字符

cpp 复制代码
		//insert()函数 在下标为pos处插入字符
		//s1.insert(2,'w')在下标为2的位置,插入一个字符
		void insert(size_t pos, char c)
		{
			//断言 最多能在'\0'的位置插入数据
			assert(pos <= _size);
			//判满 与push_back类似
			if (_size == _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}

			//把下标从pos开始的所有字符向后移动一个
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				end--;
			}
			//腾出来的下标为pos的位置 给上要插入的字符
			_str[pos] = c;
			//_size++
			_size++;

		}

解释:

**1:**断言 最多能在'\0'的位置插入数据

**2:**判满,与push_back一致

**3:**把下标从pos开始的所有字符向后移动一个,统一往后移动一个,至于为什么end 初始化为_szie+1,后面会讲

**4:**腾出来的下标为pos的位置 给上要插入的字符

insert插入字符串

cpp 复制代码
//insert()函数 在下标为pos处插入字符串
		//s1.insert(2,"xxxx") 在下标为2处插入字符串"xxxx"
		void insert(size_t pos, const char* str)
		{
			//断言 最多能在'\0'的位置插入数据
			assert(pos <= _size);

			//判满 与append类似
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}

			//把下标从pos开始的所有字符向后移动len个
			size_t end = _size + len;
			while (end > pos)
			{
				_str[end] = _str[end - len];
				end--;
			}

			//把str的全部字符 复制到 s1的_str+pos处及往后的位置
			//strncpy 不计入'\0'
			strncpy(_str + pos, str, len);
			//置_size+=len
			_size += len;

		}

解释:

**1:**大部分都类似,移动的时候,变成了移动len个

**2:**用了strncpy函数,因为strcpy会默认把被复制的字符串的'\0'也复制过去,因为这里是局部插入,所以用strcpy的话,则会有两个'\0',必然是错的

3: strncpy函数 可以对被复制的字符串进行指定字节个数的复制,所以可以避免复制到'\0',_str + pos即起始位置,len即我们算出来的str的大小,strlen函数本身就是不计算'\0'的,所以这里刚好可以

**4:**重置_size

⑥:substr()函数

cpp 复制代码
//substr()函数 截取_str的从下标为pos位置开始的len个字符去创建一个新的对象
		//s1.substr(2,5)
		string substr(size_t pos = 0, size_t len = npos)
		{
			//断言 不能从'\0'及以后的位置进行截取
			assert(pos < _size);

			//截取字符串末尾下标为 end
			size_t end = len + pos;

			//不传len值,代表从pos位置开始全部清理
			//len + pos >= _size 也是从pos位置开始全部清理
			if (len == npos || len + pos >= _size)
			{
				//end就变成了最后的位置('\0'的位置)
				end = _size;
			}

			//end-pos 代表 从pos位置开始到最后一个字符的长度(不是'\0')
			//reserve 会为'\0'多开一个空间

			//建立一个新的对象
			string s2;
			//新对象的空间为end-pos 大小不包含'\0'但是reserve里面会多开辟一个给'\0'
			s2.reserve(end - pos);

			//把截取的字符串  让新对象str不断地+=字符
			for (size_t i = pos; i < end; i++)
			{
				s2 += _str[i];
			}
			return s2;
		}
		 

解释:

**1:**断言,'\0'位置及以后进行截取无意义

**2:**pos不给为0,代表从头开始截取,len不给,代表从下标为pos的位置开始全部截取

**3:**对len和len+pos的判断与erase一致,在判断之前就size_t end = len + pos;这样判断不通过也会维持原样,判断通过了才会重置

4: 得到正确的end后,end-pos得到新对象的_capacity的大小,然后再对该部分的字符串进行循环

得放入到新对象的_str(遍历从下标pos开始,到end-1)

5:+=的时候,就会自动置_size,所以不要再置

⑦:<< 和 >> 运算符重载

cpp 复制代码
// cout << s1  →  operator<<(cout, s1)
ostream& operator<<(ostream& out, const string& s) {
	//for (auto ch : s) {
	//	out << ch;
	//}
 
	for (size_t i = 0; i < s.size(); i++) {
		out << s[i];
	}
 
	return out;
}
cpp 复制代码
// cin >>
istream& operator<<(istream& in, string& s) {
	char ch = in.get();
	while (ch != ' ' && ch != '\n')
    {
		s += ch;
		ch = in.get();
	}
 
	return in;
}

**解释:**都很简单,需要注意的是istream类的对象对我们输入的字符串进行读的时候,遇到空格和换行不会进行读取,而是默认跳过,所以我们需要istream的成员函数get()才能读取到空格和换行

四:代码的测试

cpp 复制代码
void test()
	{
		string s1;
		string s2("world");
		cout << "构造出的空s1:" << s1;
		cout << "构造出的s2:" << s2;
		cout << "使用+=,连续给s1+=字符" << endl;
		s1 += 'h';
		cout << s1;
		s1 += 'e';
		cout << s1;
		cout << "使用+=,给s1+=一个字符串llo " << endl;
		s1 += "llo";
		cout << "最终的的s1:" << s1;
		cout << endl;
		cout << endl;


		string s3 = s1;
		cout << "由s1拷贝得到的s3:" << s3;
		string s4 = s2;
		cout << "由s2拷贝得到的s4:" << s4;
		cout << endl;


		string s5;
		s5 = s1;
		string s6;
		s6 = s2;
		cout << "由s1赋值得到的s5:" << s5;
		cout << "由s2赋值得到的s6:" << s6;
		cout << endl;


		string s7("hello");
		cout << "打印正常非const对象s7:" << s7;
		const string s8("world");
		cout << "打印const对象s8:" << s8;
		cout << endl;


		//迭代器验证

		cout << "正常对象s7使用迭代器进行遍历打印:";
		string::iterator it = s7.begin();
		while (it != s7.end())
		{
			cout << *it;
			++it;
		}

		cout << endl;

		cout << "正常对象s7使用迭代器进行更改再打印:";
		string::iterator it2 = s7.begin();
		while (it2 != s7.end())
		{
			*it2 = 'x';
			cout << *it2;
			++it2;
		}
		cout << endl;
		cout << endl;


		cout << "const对象s8使用const迭代器进行遍历打印:";
		string::const_iterator it3 = s8.begin();
		while (it3 != s8.end())
		{
			cout << *it3;
			++it3;
		}

		cout << endl;

		//const对象s8无法进行写 
		/*cout << "const对象s8使用const迭代器进行遍历打印:";
		string::const_iterator it4 = s8.begin();
		while (it4 != s8.end())
		{
			*it4 = 'x';
			cout << *it;
			++it;
		}*/

		s1.swap(s2);
		cout << "用swap()函数交换s1和s2" << endl;
		cout << "交换后的s1:" << s1;
		cout << "交换后的s2:" << s2;
		cout << endl;

		cout << "在s1中找字符串rld" << endl;
		int pos = s1.find("rld");//不给pos 即从头开始找
		cout << "将下标给substr得到一个新的对象s9" << endl;

		string s9 = s1.substr(pos);//不传len 即从pos开始全部截取
		cout << "s9:" << s9;

		cout << "inset()函数在s9头插字符串wo:" << endl;
		s9.insert(0, "wo");
		cout << "s9:" << s9;

	}

测试结果如下:

解释: 对多个函数进行了测试,准确无误

五:一些细节

①:构造/拷贝/赋值函数

解释:

**1:**string类的成员变量都可以直接在构造函数的体内进行初始化,那就不用也不要去初始化列表进行初始化了,因为和成员变量声明的顺序不一致可以会导致出错

**2:**string类的成员变量不是全部都是内置类型,其中一个变量指向了一块空间,所以构造函数 拷贝函数 赋值函数我们都是要进行深拷贝的,不管是传统写法还是现代写法,本质都是进行深拷贝

②:insert()函数

**1:**为什么end 初始化为_szie+1?因为这样当pos为0的时候么也就是头插的时候,end最小也只会小到0,不会为-1,为-1会怎么样?size_t为-1代表整形的最大值,所以根本无法跳出循环,如下图所示:

cpp 复制代码
			//把下标从pos开始的所有字符向后移动一个
			size_t end = _size;
			while (end >= pos)
			{
				_str[end+1] = _str[end ];
				end--;
			}

**解释:**当我们end等于_size时,循环条件必须是end>=pos,才能把该向后移动的字符全部进行移动,此时就会出现死循环的情况

**2:**除开 end 初始化为_szie+1 然后循环条件end>pos,还有强转的方法:

cpp 复制代码
        int end = _size;
		while (end >= (int)pos)
		{
			_str[end + 1] = _str[end];
			--end;
		}

**解释:**end和pos都是int了,就不会出错,切记不要仅仅把end写作int,因为这样循环判断里面的end>=pos,pos是size_t,二者比较会发生隐式类型转换(int向size_t转换),此时连监视窗口看不出来错误(end依旧是-1,pos依旧是0),这是万万不可的,如图:

cpp 复制代码
//错误!!! 
int end = _size;
		while (end >= pos)
		{
			_str[end + 1] = _str[end];
			--end;
		}

六:源码

①:全写在一个.h里面的源码

cpp 复制代码
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

namespace key
{
	class string
	{
	public:
		//构造函数
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];

			strcpy(_str, str);
		}
		//缺省函数 
		//既能string s1;
		//也能string s1("hello");



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

		//拷贝函数(现代写法)
		//string s1(s2);
		string(const string& s)
		{
			//用s(s2的引用)的_str去构造一个tmp对象
			string tmp(s._str);
			//然后用swap函数把tmp对象 和 *this 进行交换
			swap(tmp);
		}

		拷贝函数(传统写法)
		//string(const string& s)
		//{
		//	_str = new char[s._capacity + 1];
		//	strcpy(_str, s._str);
		//	_size = s._size;
		//	_capacity = s._capacity;
		//}


		//赋值函数(现代写法)
		//s1 = s2;
		string& operator=(string s)
		{
			//该函数参数s去接受s2的时候,s2拷贝生成了s
			//直接swap 交换s 和 *this(s1)
			swap(s);
			return *this;
		}
		赋值函数(传统写法)
		//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()
		{
			delete[] _str;
			_str = nullptr;
			_size = 0;
			_capacity = 0;
		}

		//以上是默认成员函数
		//---------------------------------------------------------

			//迭代器
			//可读可写的迭代器
		typedef char* iterator;
		//只可读不可写的迭代器
		typedef const char* const_iterator;

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

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

		}


		//扩容函数
		//s1.reverse(10)
		void reserve(size_t n)
		{
			//vs的string类的reserve函数不支持缩容
			//所以我们为了保持一致,只对n>capacity进行扩容处理
			if (n > _capacity)
			{
				//开辟新的空间(n+1是因为多开一个给'/0')
				char* tmp = new char[n + 1];
				//把旧空间的_str strcpy 到 tmp空间中
				strcpy(tmp, _str);
				//释放旧空间
				delete[] _str;
				//_str指向tmp
				_str = tmp;
				//容量重置成n
				_capacity = n;

			}

		}

		//c_str()函数
		const char* c_str() const//后面加const 对于是非const的对象都能调用
		{
			return _str;
		}

		//size函数()
		size_t size() const
		{
			//返回成员变量_size的值
			return _size;
		}

	

		//[]运算符重载(只可读)
		const char& operator[](size_t pos)const
		{
			//'\0'也能读
			assert(pos <= _size);
			return _str[pos];

		}
		//对const类型的对象 返回值也必须是const 这叫权限一致




		//[]运算符重载(可读可写)
		char& operator[](size_t pos)
		{
			assert(pos <= _size);

			return _str[pos];
		}


		//find()函数 找字符
		//s1.('h',2)从下标为2开始查找 'h'字符
		size_t find(char c, size_t pos = 0)const
		{
			//assert(pos < _size);
			//遍历查找
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == c)
					return i;
			}
			//来到这里 代表找不到 返回npos(与库一致)
			return npos;
		}

		//find()函数 找字符串
		//s1.("hello",2)从下标为2开始查找 "hello"字符串
		size_t find(const char* str, size_t pos = 0)
		{
			//assert(pos < _size);
			//用strstr函数来从_str+pos位置开始查找字符串
			const char* ptr = strstr(_str + pos, str);
			//ptr为空 代表没找到 返回npos(于库一致)
			if (ptr == nullptr)
			{
				return npos;
			}
			else
			{
				//指针相减得到下标
				return ptr - _str;
			}
		}

		//erase()函数
		//s1.erase(2,5);从下标为2开始,清理5个字符
		void erase(size_t pos, size_t len = npos)
		{
			//断言
			assert(pos < _size);
			//不传len值,代表从pos位置开始全部清理
			//len + pos >= _size 也是从pos位置开始全部清理
			if (len == npos || len + pos >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else//代表从下标pos位置开始清理一部分
			{
				//将_str + pos + len往后的内容 strcpy到 _str + pos往后
				strcpy(_str + pos, _str + pos + len);
				//置_size-=len
				_size -= len;
			}

		}

		//clear()函数
		//s1.clear();
		void clear()
		{
			//_size置0 且 在第一个位置放入'\0'
			_size = 0;
			_str[0] = '\0';
		}

		//以上是一些工具函数
		//-----------------------------------------------------------------

		//尾插
		void push_back(char c)
		{
			//if判满
			if (_size == _capacity)
			{
				//两种满的给不同容量newcapacity
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				//根据newcapacity扩容
				reserve(newcapacity);
			}
			//原先的_str[_size]是'\0',现在成为新尾插的字符
			_str[_size] = c;
			//_size++
			_size++;
			//'\0'往后移动一个
			_str[_size] = '\0';
		}

		//append
		//s1.append("hello")
		void append(const char* str)
		{
			//计算出要追加的字符串的长度
			size_t len = strlen(str);
			//已有的字符串长度(_size)+len 超过容量即扩容
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			//把追加的str strcpy 到 _str + _size 往后
			strcpy(_str + _size, str);
			//置_size+=len
			_size += len;
			//reserve里面已经置capacity

		}

		//+=运算符重载函数 
		//s1+='w' 给s1+=一个字符
		string& operator+=(char c)
		{
			//复用push_back()
			push_back(c);
			return *this;
		}

		//+=运算符重载函数
		//s1+="hello" 给s1+=一个字符串
		string& operator+=(const char* str)
		{
			//复用append()
			append(str);
			return *this;

		}



		//insert()函数 在下标为pos处插入字符
		//s1.insert(2,'w')在下标为2的位置,插入一个字符
		void insert(size_t pos, char c)
		{
			//断言 最多能在'\0'的位置插入数据
			assert(pos <= _size);
			//判满 与push_back类似
			if (_size == _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}

			//把下标从pos开始的所有字符向后移动一个
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				end--;
			}
			//腾出来的下标为pos的位置 给上要插入的字符
			_str[pos] = c;
			//_size++
			_size++;

		}


		//insert()函数 在下标为pos处插入字符串
		//s1.insert(2,"xxxx") 在下标为2处插入字符串"xxxx"
		void insert(size_t pos, const char* str)
		{
			//断言 最多能在'\0'的位置插入数据
			assert(pos <= _size);

			//判满 与append类似
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}

			//把下标从pos开始的所有字符向后移动len个
			size_t end = _size + len;
			while (end > pos)
			{
				_str[end] = _str[end - len];
				end--;
			}

			//把str的全部字符 复制到 s1的_str+pos处及往后的位置
			//strncpy 不计入'\0'
			strncpy(_str + pos, str, len);
			//置_size+=len
			_size += len;

		}


		//substr()函数 截取_str的从下标为pos位置开始的len个字符作为一个新的对象
		//s1.substr(2,5)
		string substr(size_t pos = 0, size_t len = npos)
		{

			assert(pos < _size);
			size_t end = len + pos;

			if (len == npos || len + pos >= _size)
			{
				end = _size;
			}

			string s2;
			s2.reserve(end - pos);

			for (size_t i = pos; i < end; i++)
			{
				s2 += _str[i];
			}
			return s2;
		}
		//以上是string的重要函数
		//--------------------------------------		

	private:

		size_t _size = 0;
		size_t _capacity = 0;
		char* _str = nullptr;
		static const size_t npos;

	};
	const size_t string::npos = -1;

	//<<运算符重载
	ostream& operator<<(ostream& out, const string& s)
	{
		for (size_t i = 0; i < s.size(); i++)
		{
			out << s[i];
		}
		cout << '\n';
		return out;

	}

	//>>运算符重载
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char buff[128];
		char ch = in.get();
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}

			ch = in.get();
			if (i > 0)
			{
				buff[i] = '\0';
				s += buff;
			}
		}
		return in;

	}
	//流插入 流提取的运算符重载
	//-----------------------------------------------

	void test()
	{
		string s1;
		string s2("world");
		cout << "构造出的空s1:" << s1;
		cout << "构造出的s2:" << s2;
		cout << "使用+=,连续给s1+=字符" << endl;
		s1 += 'h';
		cout << s1;
		s1 += 'e';
		cout << s1;
		cout << "使用+=,给s1+=一个字符串llo " << endl;
		s1 += "llo";
		cout << "最终的的s1:" << s1;
		cout << endl;
		cout << endl;


		string s3 = s1;
		cout << "由s1拷贝得到的s3:" << s3;
		string s4 = s2;
		cout << "由s2拷贝得到的s4:" << s4;
		cout << endl;


		string s5;
		s5 = s1;
		string s6;
		s6 = s2;
		cout << "由s1赋值得到的s5:" << s5;
		cout << "由s2赋值得到的s6:" << s6;
		cout << endl;


		string s7("hello");
		cout << "打印正常非const对象s7:" << s7;
		const string s8("world");
		cout << "打印const对象s8:" << s8;
		cout << endl;


		//迭代器验证

		cout << "正常对象s7使用迭代器进行遍历打印:";
		string::iterator it = s7.begin();
		while (it != s7.end())
		{
			cout << *it;
			++it;
		}

		cout << endl;

		cout << "正常对象s7使用迭代器进行更改再打印:";
		string::iterator it2 = s7.begin();
		while (it2 != s7.end())
		{
			*it2 = 'x';
			cout << *it2;
			++it2;
		}
		cout << endl;
		cout << endl;


		cout << "const对象s8使用const迭代器进行遍历打印:";
		string::const_iterator it3 = s8.begin();
		while (it3 != s8.end())
		{
			cout << *it3;
			++it3;
		}

		cout << endl;

		//const对象s8无法进行写 
		/*cout << "const对象s8使用const迭代器进行遍历打印:";
		string::const_iterator it4 = s8.begin();
		while (it4 != s8.end())
		{
			*it4 = 'x';
			cout << *it;
			++it;
		}*/

		s1.swap(s2);
		cout << "用swap()函数交换s1和s2" << endl;
		cout << "交换后的s1:" << s1;
		cout << "交换后的s2:" << s2;
		cout << endl;

		cout << "在s1中找字符串rld" << endl;
		int pos = s1.find("rld");//不给pos 即从头开始找
		cout << "将下标给substr得到一个新的对象s9" << endl;

		string s9 = s1.substr(pos);//不传len 即从pos开始全部截取
		cout << "s9:" << s9;

		cout << "inset()函数在s9头插字符串wo:" << endl;
		s9.insert(0, "wo");
		cout << "s9:" << s9;

	}
}

②:分离开的.h和.cpp

.h

cpp 复制代码
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<assert.h>
#include<iostream>

using namespace std;

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

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

		size_t size() const
		{
			return _size;
		}

		string(const char* str = "");
		// ִд
		string(const string& s);
		string& operator=(string s);
		~string();

		const char& operator[](size_t pos) const;
		char& operator[](size_t pos);
		void reserve(size_t n);
		void push_back(char ch);

		void append(const char* str);
		string& operator+=(char ch);
		string& operator+=(const char* str);

		void insert(size_t pos, char ch);
		void insert(size_t pos, const char* str);
		void erase(size_t pos, size_t len = npos);
		void swap(string& s);
		size_t find(char ch, size_t pos = 0);
		//21:10
		size_t find(const char* str, size_t pos = 0);
		string substr(size_t pos = 0, size_t len = npos);
		void clear();

	private:
		size_t _capacity = 0;
		size_t _size = 0;
		char* _str = nullptr;

		const static size_t npos = -1;
	};

	istream& operator>>(istream& in, string& s);
	ostream& operator<<(ostream& out, const string& s);
}

.cpp

cpp 复制代码
#include"string.h"


namespace bit
{
	string::string(const char* str)
	{
		_size = strlen(str);
		_capacity = _size;
		_str = new char[_capacity + 1];

		strcpy(_str, str);
	}

	// ִд
	string::string(const string& s)
	{
		string tmp(s._str);
		swap(tmp);
	}

	string& string::operator=(string s)
	{
		swap(s);

		return *this;
	}

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

	const char& string::operator[](size_t pos) const
	{
		assert(pos <= _size);

		return _str[pos];
	}

	char& string::operator[](size_t pos)
	{
		assert(pos <= _size);

		return _str[pos];
	}

	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}

	void string::push_back(char ch)
	{
		if (_size == _capacity)
		{
			size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
			reserve(newCapacity);
		}

		_str[_size] = ch;
		_size++;
		_str[_size] = '\0';
	}

	void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			reserve(_size + len);
		}

		strcpy(_str + _size, str);
		_size += len;
	}

	string& string::operator+=(char ch)
	{
		push_back(ch);

		return *this;
	}

	string& string::operator+=(const char* str)
	{
		append(str);

		return *this;
	}

	void string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);

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

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

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

		_str[pos] = ch;
		_size++;
	}

	void 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);
		_size += len;
	}

	void string::erase(size_t pos, size_t len)
	{
		assert(pos < _size);

		if (len == npos || pos + len >= _size)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			strcpy(_str + pos, _str + pos + len);
			_size -= len;
		}
	}

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

	size_t string::find(char ch, size_t pos)
	{
		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
			{
				return i;
			}
		}

		return npos;
	}

	
	size_t string::find(const char* str, size_t pos)
	{
		const char* ptr = strstr(_str + pos, str);
		if (ptr == nullptr)
		{
			return npos;
		}
		else
		{
			return ptr - _str;
		}
	}

	string string::substr(size_t pos, size_t len)
	{
		assert(pos < _size);
		size_t end = pos + len;
		if (len == npos || pos + len >= _size)
		{
			end = _size;
		}

		string str;
		str.reserve(end - pos);
		for (size_t i = pos; i < end; i++)
		{
			str += _str[i];
		}

		return str;
	}

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


	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}

		return out;
	}

	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char buff[128];
		char ch = in.get();
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}

			ch = in.get();
		}

		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}

		return in;
	}
}
相关推荐
要加油哦~1 分钟前
字节青训营 | 数字分组求偶数和
开发语言·javascript·ecmascript
safe0303 分钟前
SPA和SSR
开发语言·前端·javascript·汇编·vue.js·react.js·前端框架
姓刘的哦22 分钟前
C语言中的位操作
c语言·开发语言
·云扬·23 分钟前
WeakHashMap详解
java·开发语言·学习·1024程序员节
翔云API1 小时前
PC端增值税发票识别API-翔云智能财务发票识别OCR接口
android·c语言·开发语言·php
x66ccff2 小时前
【python】flash-attn安装
开发语言·python
小魏冬琅2 小时前
《掌握 Java:从基础到高级概念的综合指南》(12/15)
java·开发语言
即兴小索奇3 小时前
Bash、sh 和 Shell都弄混了?
linux·开发语言·bash
人生の三重奏4 小时前
numpy——数学运算
开发语言·python·numpy
水w4 小时前
docker怎么实现资源隔离的?
运维·开发语言·后端·docker·容器