【C++】string的实现

文章目录

  • 1.前言
  • [2. 框架了解](#2. 框架了解)
  • 3.具体实现
    • [1. 基本结构](#1. 基本结构)
    • [2. 构造函数](#2. 构造函数)
    • [3. 析构函数](#3. 析构函数)
    • [4. 拷贝构造函数](#4. 拷贝构造函数)
    • [5. size()以及capacity()](#5. size()以及capacity())
    • [6. c_str](#6. c_str)
    • [7. iterator迭代器实现](#7. iterator迭代器实现)
    • [8. operator[]](#8. operator[])
    • [9. reserve](#9. reserve)
    • [10. push_back、append和insert及operator+=](#10. push_back、append和insert及operator+=)
    • [11. erase](#11. erase)
    • [12. find](#12. find)
    • [13. substr](#13. substr)
    • [14. 赋值运算符重载](#14. 赋值运算符重载)
    • [15. 运算符重载](#15. 运算符重载)
    • [16. << 和 >> 重载](#16. << 和 >> 重载)
  • [4. 源代码](#4. 源代码)
    • [1. string.h](#1. string.h)
    • [2. string.cpp](#2. string.cpp)
    • [3. test.cpp](#3. test.cpp)

1.前言

前面我们说了string的使用,接下来我们就来自己实现一下string,让我们对string的理解更加深刻--->>点击查看《string的使用》

2. 框架了解

我们知道,stl容器都是使用模板来实现的,但是我们这里先不使用模板来实现,模板主要是涉及编码的问题,我们这里先不涉及那么多编码的问题,所以我们还是定义成了string.h和string.cpp以及测试test.cpp三个文件,声明和定义分离,如果是模板的话就不能声明和定义分离了

3.具体实现

1. 基本结构

字符串对我们来说就好像字符数组,所以我们底层其实是用一个字符类型的顺序表来实现的(这里为了跟标准库内的string区分开,我们使用自己的命名空间)

cpp 复制代码
namespace William
{
	class string
	{
	public:
	
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	public:
		static size_t npos;
	};
  1. 这里其实就跟我们顺序表的架构是差不多的,只不过这里的数据类型是char类型
  2. 这里的容量和大小因为都不可能为负数,所以类型使用的是size_t
  3. 这里的npos我们后续会继续说

2. 构造函数

cpp 复制代码
string()
	:_str(new char[1]{'\0'})
	,_size(0)
	,_capacity(0)
{}

string(const char* str)
{
	_size = strlen(str);
	//_capacity不包含\0
	_capacity = _size;
	_str = new char[_capacity + 1];
	strcpy(_str, str);
}
  1. 在这里的无参构造函数中,我们注意到_str初始化时进行了new,这里是为了适配c_str,防止解引用空指针,用nullptr是会崩溃的
  2. 我们要知道这里的_capacity是不包含\0的,但是我们是需要把\0存起来的,所以我们new的时候空间是要加一的
  3. 而且这里也要注意我们初始化列表的顺序,这需要结合我们之前的类和对象知识,初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的先后顺序无关。所以这里带参的构造函数也可以不走初始化列表初始化
    下面我们可以把这两个构造函数合并成一个带缺省参数的构造函数
cpp 复制代码
string(const char* str = "")
{
	_size = strlen(str);
	_capacity = _size;
	_str = new char[_capacity + 1];
	strcpy(_str, str);
}

3. 析构函数

cpp 复制代码
~string()
{
	if (_str)
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}
}
  1. 前面我们使用的是new[],所以我们这里的delete就要使用delete[]
  2. 析构之前要看看字符串是否为空,如果为空就不需要析构
  3. delete之后要把_str指向空指针,容量和大小要清空

4. 拷贝构造函数

cpp 复制代码
string(const string& s)
{
	_str = new char[s._capacity + 1];
	strcpy(_str, s._str);
	_size = s._size;
	_capacity = s._capacity;
}
  1. 我们之前在类和对象里面知道,只要一个对象有显式实现的析构函数,那么就要实现深拷贝
  2. 这里的容量加一还是为了存放\0
  3. 注意我们这里的拷贝构造和后面的赋值运算符重载都是有现代写法的,我们说的都是传统写法,现代写法可以去最下面我们的源代码那里查看

5. size()以及capacity()

cpp 复制代码
size_t size() const
{
	return _size;
}

size_t capacity() const
{
	return _capacity;
}
  1. 这里就跟构造函数那里说的一样,容量和大小是不可能为负数的,所以我们使用了size_t类型
  2. 我们这两个接口是肯定不允许用户修改的,所以我们使用了const进行了修饰

6. c_str

cpp 复制代码
const char* c_str()
{
	return _str;
}

const char* c_str() const
{
	return _str;
}
  1. 这个接口可以返回C语言形式的字符串
  2. 这里实现了两个,区别就是一个是对于普通对象的一个是针对const对象的,而无论是哪种对象,我们都是不希望用户修改我们的原始字符串的,使用我们返回是也使用了const

测试

cpp 复制代码
void test_string1()
{
	string s1;
	string s2("hello world");
	cout << s1.c_str() << endl;
	cout << s2.c_str() << endl;
}
int main()
{
	William::test_string1();
	return 0;
}

关于这里的测试,还有一些知识需要大家回顾,就是关于声明和定义分离的相关知识,我们前面在框架那里说过,我们是创建了三个文件的,我们这里的测试是在test.cpp中实现的,但是我们要在string.h中声明,我们的string.cpp和test.cpp文件都会包含string.h这个头文件,如果我们在string.h中实现测试函数,那么在string.cpp和test.cpp中都会有一份这个测试函数,在最后链接的时候就会出现冲突

7. iterator迭代器实现

cpp 复制代码
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;
}
  1. 这里我们可以看到,string的迭代器底层就是字符指针
  2. 这里我们分别实现了普通对象的迭代器和const对象的迭代器
  3. 我们要知道,迭代器都是左闭右开的,所以我们这里的end其实指向的是最后一个元素的下一个位置
  4. 编译器会根据对象的类型,自动选择最合适的迭代器,这个不需要我们担心

测试

cpp 复制代码
void test_string1()
{
	string s1;
	string s2("hello world");
	cout << s1.c_str() << endl;
	cout << s2.c_str() << endl;

	string::iterator it = s2.begin();
	while (it != s2.end())
	{
		*it += 2;
		cout << *it << " ";
		++it;
	}
	cout << endl;

	for (auto ch : s2)
	{
		cout << ch << " ";
	}
	cout << endl;
}

int main()
{
	William::test_string1();
	return 0;
}

这里我们可以看到,实现迭代器之后就可以支持范围for了

8. operator[]

这里因为[]运算符在string里面太重要了,所以我们这里单独拉出来说一下

cpp 复制代码
char& operator[](size_t pos)
{
	assert(pos < _size);
	return _str[pos];
}

const char& operator[](size_t pos) const
{
	assert(pos < _size);
	return _str[pos];
}
  1. 我们重载了[]之后,string就可以像数组一样实现下标访问了,这是极其方便的
  2. 这里我们也是重载了普通对象的和const对象的

测试

cpp 复制代码
void test_string1()
{
	string s1;
	string s2("hello world");
	cout << s1.c_str() << endl;
	cout << s2.c_str() << endl;

	for (size_t i = 0; i < s2.size(); i++)
	{
		s2[i] += 2;
	}
	cout << s2.c_str() << endl;
}

int main()
{
	William::test_string1();
	return 0;
}

9. reserve

cpp 复制代码
void string::reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}
  1. 这个函数是我们扩容或者指定开辟空间大小的函数
  2. 我们默认采取只扩容不缩容的策略
  3. C++中的扩容不能像C语言中realloc那样,只能像这样先再开辟一块新空间,然后把原始字符串的内容给这个新空间拷贝过去,再让原始字符串的指针指向这个新空间
    4.注意,在这里及下面的接口中,只要是指明类域的就是我们在string.cpp文件中实现的函数,这里体现出我们一开始说的声明和定义分离

10. push_back、append和insert及operator+=

这四个接口都是实现了string中增加元素的功能

cpp 复制代码
void string::push_back(char ch)
{
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 :_capacity * 2);
	}
	_str[_size] = ch;
	++_size;
	_str[_size] = '\0';
}

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

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

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

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

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

	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 (len == 0) return;

	if (_size + len > _capacity)
	{
		reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
	}

	size_t end = _size + len;
	while (end > pos + len - 1)//注意这里
	{
		_str[end] = _str[end - len];
		--end;
	}

	for (int i = 0; i < len; i++)
	{
		_str[pos + i] = str[i];
	}

	_size += len;
}
  1. 首先我们要知道push_back接口是只能添加单个字符的,而其他接口是既可以添加字符也可以添加字符串
  2. 在push_back中一定要注意最后要加上\0
  3. 在这些接口中,我们都需要先看看容量是否足够,不够需要扩容,这里是通过reserve扩容的
  4. 这些接口中,最需要注意的就是insert接口,因为在insert接口中它是需要挪动数据的,但是这个循环的判断条件极其容易出错,最核心的原因是我们定义的类型(size_t)的问题,我们的end是size_t类型,当它减到0时本应该跳出循环,但由于它的无符号类型,当它为0再减1时本应是-1,但是-1在内存中的补码全为1,对于无符号类型来说就直接变成最大值了,就会一直循环下去导致死循环,我们这里的end很多人都会写成直接等于_size,就会出现上述的错误,这里一定要记得end是等于_size+ 1的

测试

cpp 复制代码
void test_string2()
{
	string s1("hello world");
	s1 += '+';
	s1 += '*';
	cout << s1.c_str() << endl;

	s1 += "love";
	cout << s1.c_str() << endl;

	s1.insert(5, '&');
	cout << s1.c_str() << endl;

	string s2("hello world");
	s2.insert(5, "&&&");
	cout << s2.c_str() << endl;
}

int main()
{
	
	William::test_string2();
	return 0;
}

我们注意到,我们的operator+=的实现是通过push_back和append的复用来实现的,所以我们这里直接测试+=和insert就行

11. erase

前面我们说了string中如何增加数据,现在我们来看看怎么减少数据
这是我们在头文件中的声明:

cpp 复制代码
void erase(size_t pos, size_t len = npos);

为什么要单独把这个说一下呢,因为我们可以看看这个声明中有一个明显不同的缺省参数npos,这就是我们在基本结构那里说的那个npos,这里我们就来说一下它的左右,在基本结构那里我们可以看到它是定义成了一个全局的变量,所以我们不能在头文件中把它初始化了,只能声明它,初始化是在string.cpp中实现的,如果我们在头文件中初始化就会发生链接错误,这个npos我们认为它是size_t类型的,我们把它赋值为-1,对于无符号来说这就是整型的最大值了,即我们这里的缺省参数的意义是默认删除到字符串结束

注:这里是需要结合我们的类和对象下中的static成员部分的内容来理解的,详情请看------->>>点击查看《类和对象下》
其实这里直接在头文件中初始化npos也是可以的,算是C++对于这个东西单独开了一个绿灯,所以大家可能会在某些地方看到直接在头文件中初始化,对于这个npos来说确实可以编译通过,但是不建议大家这样写

cpp 复制代码
void string::erase(size_t pos, size_t len)
{
	assert(pos < _size);

	if (len >= _size - pos)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		for (int i = pos + len; i <= _size; i++)
		{
			_str[i - len] = _str[i];
		}
		_size -= len;
	}
}
  1. erase成员函数实现的是对pos位置处开始向后len个字符的删除
  2. 我们通过上面对npos的讲解知道,如果没有传len的值,erase是默认删除到字符串结束的,而且我们要注意到没有在函数头这里再写一遍缺省参数,这是我们关于缺省参数的一个知识点>>>点击查看详情<<<
  3. 这里的删除和我们的顺序表中的删除很类似,是不需要真的去把数据清空的,只是修改string对象的大小

测试

cpp 复制代码
void test_string3()
{
	string s1("hello world");
	s1.erase(6, 100);
	cout << s1.c_str() << endl;

	string s2("hello world");
	s2.erase(6);
	cout << s2.c_str() << endl;

	string s3("hello world");
	s3.erase(6, 3);
	cout << s3.c_str() << endl;
}

int main()
{
	William::test_string3();
	return 0;
}

12. find

这是我们在头文件中的声明

cpp 复制代码
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);
cpp 复制代码
size_t string::find(char ch, size_t pos)
{
	assert(pos < _size);
	for (int i = pos; i < _size; i++)
	{
		if (_str[i] == ch)
		{
			return i;
		}
	}
	return npos;
}

size_t string::find(const char* str, size_t pos)
{
	assert(pos < _size);
	const char* ptr = strstr(_str + pos, str);
	if (ptr == nullptr)
	{
		return npos;
	}
	else
	{
		return ptr - _str;
	}
}
  1. find接口就是从pos位置开始查找指定字符或者字符串,如果没有指定pos,则从0位置开始查找,第一个就是查找字符,第二个是查找字符串
  2. 这里我们采用的都是暴力匹配,像KMP算法之类的本人还没有那么高的水平,字符串匹配用的是C语言中的函数,这里也体现出我们当时适配C语言的重要性

测试

我们这里把find测试和接下来的substr以及赋值运算符放在一起展示了,这样更清晰一点

13. substr

这是我们在头文件中的声明

cpp 复制代码
string substr(size_t pos = 0, size_t len = npos);
cpp 复制代码
string string::substr(size_t pos, size_t len)
{
	assert(pos < _size);
	if (len > _size - pos)
	{
		len = _size - pos;
	}

	string sub;
	sub.reserve(len);
	for (int i = 0; i < len; i++)
	{
		sub += _str[pos + i];
	}
	return sub;
}
  1. 我们这里的substr功能是从pos位置开始,获取len个字符长度的字串,这里我们从声明中可以知道pos和len都是有缺省值的,所以什么参数都不传的话,该接口会默认从字符串的起始位置开始一直到字符串结束,也就是整个字符串
  2. 注意这里是需要理解我们拷贝构造的知识的,如果这里没有拷贝构造的话,程序是会崩溃的>>>点击查看拷贝构造相关知识<<<,这里我们就简单说一下,这里我们的传值返回是需要调用拷贝构造的,sub在出了函数之后就会销毁产生一个临时变量,我们需要用这个临时变量来拷贝构造我们的子串,如果我们没有显式实现拷贝构造没有实现深拷贝的话,随着sub的销毁子串也就让子串指向一个野指针了,而且不能引用返回,也是因为sub出了作用域就会销毁

测试

这里如上面find那里说的一样

14. 赋值运算符重载

通过上面substr中的注意事项中我们看到了拷贝构造的重要性,接下来我们就会实现一个跟拷贝构造容易搞混的默认成员函数------赋值运算符重载>>>点击查看赋值运算符重载详情<<<

cpp 复制代码
string& operator=(const string& s)
{
	if (this != &s)
	{
		delete[] _str;
		_str = new char[s._capacity + 1];
		strcpy(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;
	}
	return *this;
}
  1. 这里的this指针就是要被赋值的对象,我们需要先把它给释放掉,再让它指向一块新空间,并把赋值的对象拷贝给要被赋值的对象
  2. 需要注意的是,在语法层面上是允许自己给自己赋值的,所以我们一定要判断一下是不是自己给自己赋值,如果是且没有做判断的话,上来就把自己释放就出错了

测试

cpp 复制代码
void test_string4()
{
	string s("test.cpp.zip");
	size_t pos = s.find('.');
	string sub = s.substr(pos);
	cout << sub.c_str() << endl;

	string copy(s);
	cout << copy.c_str() << endl;

	s = sub;
	cout << sub.c_str() << endl;
	cout << s.c_str() << endl;

	s = s;
	cout << s.c_str() << endl;
}

int main()
{
	William::test_string4();
	return 0;
}

15. 运算符重载

这是我们在头文件中的声明

cpp 复制代码
bool operator<(const string& s1, const string& s2);
bool operator<=(const string& s1, const string& s2);
bool operator>(const string& s1, const string& s2);
bool operator>=(const string& s1, const string& s2);
bool operator==(const string& s1, const string& s2);
bool operator!=(const string& s1, const string& s2);

这里我们并没有重载<<和>>,我们在下面会单独说这两个

cpp 复制代码
bool operator<(const string& s1, const string& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) < 0;
}

bool operator<=(const string& s1, const string& s2)
{
	return s1 < s2 || s1 == s2;
}

bool operator>(const string& s1, const string& s2)
{
	return !(s1 <= s2);
}

bool operator>=(const string& s1, const string& s2)
{
	return !(s1 < s2);
}

bool operator==(const string& s1, const string& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) == 0;
}

bool operator!=(const string& s1, const string& s2)
{
	return !(s1 == s2);
}
  1. 我们这里是直接写成全局函数了,目的是跟库中一样可以支持字符串和字符串比以及字符和字符串比等,但是我们这里只实现了字符串与字符串比较,给大家先简单说明一下
  2. 这里的比较我们只需要实现两个,剩下的就可以通过这两个复用来实现了

测试

cpp 复制代码
void test_string5()
{
	string s1("hello world");
	string s2("hello world");

	cout << (s1 < s2) << endl;
	cout << (s1 == s2) << endl;
	cout << ("hello world" < s2) << endl;//隐式类型转换
	cout << (s1 == "hello world") << endl;//隐式类型转换

	//这里没有隐式类型转换,运算符重载必须用一个类类型的参数
	cout << ("hello world" == "hello world") << endl;
}

int main()
{
	William::test_string5();
	return 0;
}

16. << 和 >> 重载

这是我们在头文件中的声明

cpp 复制代码
ostream& operator<<(ostream& out, const string& s);
istream& operator>>(istream& in, string& s);//这里的istream是可以加const的

重载完<<和>>之后我们在cout的时候就不用再调用c_str了

cpp 复制代码
ostream& operator<<(ostream& out, const string& s)
{
	for (auto ch : s)
	{
		out << ch;
	}
	return out;
}

istream& operator>>(istream& in, string& s)//这里的istream是可以加const的
{
	s.clear();

	//优化,防止频繁扩容
	const int N = 256;
	char buff[N];
	int i = 0;

	//这里由于istream直接流提取的特性,会直接忽略空格和换行,所以不能这样写
	/*char ch;
	in >> ch;
	while (ch != ' ' && ch != '\n')
	{
		s += ch;
		in >> ch;
	}
	return in;*/

	char ch;
	ch = in.get();
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == N - 1)
		{
			buff[i] = '\0';
			s += buff;

			i = 0;
		}

		//s += ch;
		ch = in.get();
	}

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

	return in;
}
  1. << 和 >> 是必须要写成全局函数的,因为重载为成员函数this指针默认抢占了第一个形参位置,第一个形参位置是左侧运算对象,调用时就变成了对象<<cout------>>>点击查看<<和>>重载详情
  2. 正如我们在代码中的注释所说的一样,我们在实现>>重载的时候一定要注意使用get函数,否则会直接忽略空格和换行

测试

cpp 复制代码
void test_string6()
{
	string s("hello world");
	cout << s << endl;

	string str;
	cin >> str;
	cout << str << endl;
}

int main()
{
	William::test_string6();
	return 0;
}

4. 源代码

1. string.h

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

namespace William
{
	class string
	{//短小频繁调用的函数,可以直接定义到类里面,默认是inline
	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()
		//	:_str(new char[1]{'\0'})
		//	,_size(0)
		//	,_capacity(0)
		//{}

		//string(const char* str)
		//{
		//	_size = strlen(str);
		//	//_capacity不包含\0
		//	_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(const string& s)
		{
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				delete[] _str;
				_str = new char[s._capacity + 1];
				strcpy(_str, s._str);
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}*/

		//现代写法
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		string(const string& s)
		{
			string tmp(s._str);
			swap(tmp);//直接用std中的swap会有3次深拷贝
		}
		//string& operator=(const string& s)
		//{
		//	if (this != &s)
		//	{
		//		//string tmp(s._str);//调用构造
		//		string tmp(s);//调用拷贝构造
		//		swap(tmp);
		//	}
		//	return *this;
		//}
		string& operator=(string tmp)
		{
			swap(tmp);
			return *this;
		}

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

		const char* c_str()
		{
			return _str;
		}

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

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

		size_t size() const
		{
			return _size;
		}

		size_t capacity() const
		{
			return _capacity;
		}

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

		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[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);

		size_t find(char ch, size_t pos = 0);
		size_t find(const char* str, size_t pos = 0);

		string substr(size_t pos = 0, size_t len = npos);

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

		static const size_t npos;
	};

	bool operator<(const string& s1, const string& s2);
	bool operator<=(const string& s1, const string& s2);
	bool operator>(const string& s1, const string& s2);
	bool operator>=(const string& s1, const string& s2);
	bool operator==(const string& s1, const string& s2);
	bool operator!=(const string& s1, const string& s2);

	ostream& operator<<(ostream& out, const string& s);
	istream& operator>>(istream& in, string& s);//这里的istream是可以加const的
}

2. string.cpp

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

namespace William
{
	const size_t string::npos = -1;

	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)
		{
			reserve(_capacity == 0 ? 4 :_capacity * 2);
		}
		_str[_size] = ch;
		++_size;
		_str[_size] = '\0';
	}

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

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

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

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

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

		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 (len == 0) return;

		if (_size + len > _capacity)
		{
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}

		size_t end = _size + len;
		while (end > pos + len - 1)//注意这里
		{
			_str[end] = _str[end - len];
			--end;
		}

		for (int i = 0; i < len; i++)
		{
			_str[pos + i] = str[i];
		}

		_size += len;
	}

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

		if (len >= _size - pos)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			for (int i = pos + len; i <= _size; i++)
			{
				_str[i - len] = _str[i];
			}
			_size -= len;
		}
	}

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

	size_t string::find(const char* str, size_t pos)
	{
		assert(pos < _size);
		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);
		if (len > _size - pos)
		{
			len = _size - pos;
		}

		string sub;
		sub.reserve(len);
		for (int i = 0; i < len; i++)
		{
			sub += _str[pos + i];
		}
		return sub;
	}

	bool operator<(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) < 0;
	}

	bool operator<=(const string& s1, const string& s2)
	{
		return s1 < s2 || s1 == s2;
	}

	bool operator>(const string& s1, const string& s2)
	{
		return !(s1 <= s2);
	}

	bool operator>=(const string& s1, const string& s2)
	{
		return !(s1 < s2);
	}

	bool operator==(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) == 0;
	}

	bool operator!=(const string& s1, const string& s2)
	{
		return !(s1 == s2);
	}

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

	istream& operator>>(istream& in, string& s)//这里的istream是可以加const的
	{
		s.clear();

		//优化,防止频繁扩容
		const int N = 256;
		char buff[N];
		int i = 0;

		//这里由于istream直接流提取的特性,会直接忽略空格和换行,所以不能这样写
		/*char ch;
		in >> ch;
		while (ch != ' ' && ch != '\n')
		{
			s += ch;
			in >> ch;
		}
		return in;*/

		char ch;
		ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == N - 1)
			{
				buff[i] = '\0';
				s += buff;

				i = 0;
			}

			//s += ch;
			ch = in.get();
		}

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

		return in;
	}
}

3. test.cpp

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

namespace William
{
	void test_string1()
	{
		string s1;
		string s2("hello world");
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;

		for (size_t i = 0; i < s2.size(); i++)
		{
			s2[i] += 2;
		}
		cout << s2.c_str() << endl;

		string::iterator it = s2.begin();
		while (it != s2.end())
		{
			*it += 2;
			cout << *it << " ";
			++it;
		}
		cout << endl;

		for (auto ch : s2)
		{
			cout << ch << " ";
		}
		cout << endl;
	}

	void test_string2()
	{
		string s1("hello world");
		s1 += '+';
		s1 += '*';
		cout << s1.c_str() << endl;

		s1 += "love";
		cout << s1.c_str() << endl;

		s1.insert(5, '&');
		cout << s1.c_str() << endl;

		string s2("hello world");
		s2.insert(5, "&&&");
		cout << s2.c_str() << endl;
	}

	void test_string3()
	{
		string s1("hello world");
		s1.erase(6, 100);
		cout << s1.c_str() << endl;

		string s2("hello world");
		s2.erase(6);
		cout << s2.c_str() << endl;

		string s3("hello world");
		s3.erase(6, 3);
		cout << s3.c_str() << endl;
	}

	void test_string4()
	{
		string s("test.cpp.zip");
		size_t pos = s.find('.');
		string sub = s.substr(pos);
		cout << sub.c_str() << endl;

		string copy(s);
		cout << copy.c_str() << endl;

		s = sub;
		cout << sub.c_str() << endl;
		cout << s.c_str() << endl;

		s = s;
		cout << s.c_str() << endl;
	}

	void test_string5()
	{
		string s1("hello world");
		string s2("hello world");

		cout << (s1 < s2) << endl;
		cout << (s1 == s2) << endl;
		cout << ("hello world" < s2) << endl;//隐式类型转换
		cout << (s1 == "hello world") << endl;//隐式类型转换

		//这里没有隐式类型转换,运算符重载必须用一个类类型的参数
		cout << ("hello world" == "hello world") << endl;
	}

	void test_string6()
	{
		string s("hello world");
		cout << s << endl;

		string str;
		cin >> str;
		cout << str << endl;
	}
}

int main()
{
	//William::test_string1();
	//William::test_string2();
	//William::test_string3();
	//William::test_string4();
	//William::test_string5();
	//William::test_string6();
	return 0;
}
相关推荐
fff9811182 小时前
C++与Qt图形开发
开发语言·c++·算法
计算机安禾2 小时前
【数据结构与算法】第3篇:C语言核心机制回顾(二):动态内存管理与typedef
c语言·开发语言·数据结构·c++·算法·链表·visual studio
不想写代码的星星2 小时前
C++模板特化:别把“特例”写成“特坑”——从全特化到变量模板
c++
njidf3 小时前
C++中的访问者模式
开发语言·c++·算法
C_Si沉思3 小时前
C++中的工厂模式变体
开发语言·c++·算法
m0_569881473 小时前
C++中的适配器模式变体
开发语言·c++·算法
汉克老师4 小时前
GESP2026年3月认证C++五级( 第三部分编程题(2)找数)
c++·排序·双指针·二分算法·gesp5级·gesp五级
长安第一美人4 小时前
AI辅助下的嵌入式UI系统设计与实践(二)[代码阅读理解]
c++·嵌入式硬件·ui·显示屏·工业应用
比昨天多敲两行4 小时前
C++ 多态
开发语言·c++