STL的string功能以及模拟实现

什么是string类

string类是是表示字符串的字符串类

该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator> string;
不能操作多字节或者变长字符的序列。

学习使用string(常用的接口)

我们在使用的时候我们包一下他的头文件#include<string>

1、string构造函数

在这里插入图片描述

我们定义对象构造函数有很多种形式。

在这里插入图片描述

int main()
{
	string s1;
	cout << "s1="<<s1 << endl;
	string s2("hello world");
	cout << "s2=" << s2 << endl;

	string s3 = "hello world";
	cout << "s3=" << s3 << endl;

	//表示从s3,取第6个位置一共三个位置进行初始化。也就是wor
	string s4 (s3, 6, 3);
	cout << "s4=" << s4 << endl;

	//字符串太短,就直接取到尾
	string s5 (s3, 6, 13);
	cout << "s5=" << s5 << endl;

	string s6(s3, 6);
	cout << "s6=" << s6 << endl;

	string s7("hello world", 5);
	cout << "s7=" << s7 << endl;

	string s8(10, '*');
	cout << "s8=" << s8 << endl;

	return 0;
}

在这里插入图片描述

2、 string类对象的容量操作

在这里插入图片描述

sizelength两个功能相同,都是计算字符串的长度。
我们用size就行

max_size就是能存放最大的量。但是实际存不了。
capacity,就是容量。但是计算的时不包括\0的位置。

int main()
{
	string s1("hello world");
	cout << s1.size() << endl;
	cout << s1.length() << endl;
	cout << s1.max_size() << endl;
	out << s1.capacity() << endl;


}

在这里插入图片描述

reserve是提前开空间,如果我们知道需要多少空间,提前开空间按,减少扩容,提高效率。
并且reserve,只会改变capacity并不会改变size
resize:不仅可以开空间,而且还可以将其初始化,它会改变capacity也会改变size。将size变小,就可以删除数据
在这里插入图片描述

int main()
{
	string s1;
	s1.reserve(100);
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;

	string s2;
	//用缺省参数,将其初始化为\0
	//s2.resize(100);
	//将其前100个初始化为x
	s2.resize(100, 'x');
	cout << s2.size() << endl;
	cout << s2.capacity() << endl;
}

3、string类对象的访问及遍历操作

在这里插入图片描述

int main()
{
	string s1("hello");
	s1.push_back(' ');
	s1.push_back('!');
	cout << s1 << endl;
	s1.append("world");
	cout << s1 << endl;

}
//两种操作都一样
int main()
{
	//用运算符重载+=什么都可以用。
	//既可以添加字符,也可以添加字符串
	string s1("hello");
	s1 += ' ';
	s1 += '!';
	cout << s1 << endl;
	s1 += "world";
	cout << s1 << endl;
}

4、迭代器

我们可以想像迭代器就是像指针一样的东西。
可以模仿快排的双指针
begin()表示字符的第一个指针。
end()表示字符最后一个指针的下一个指针。
相当于一个左闭右开的区间。

正向迭代器

int main()
{
	string s1("hello world");
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}

反向迭代器

既然我们支持正向遍历数组,那么我们也支持反向遍历数组。
就是所谓的反向迭代器.
反向迭代器reverde_iterator

int main()
{
	string s1("hello world");
	//rbegin指向\0,rend指向第一个字符的前一个
	string::reverse_iterator it = s1.rbegin();
	while (it != s1.rend())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}

正向const迭代器

int main()
{
	const string s1("hello world");
	//只允许遍历和读容器的数据,不能写
	//类比指针,const修饰的是*it,但是it可以修改。
	string::const_iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}

反向const迭代器

int main()
{
	const string s1("hello world");
	//rbegin指向\0,rend指向第一个字符的前一个
	string::const_reverse_iterator it = s1.rbegin();
	while (it != s1.rend())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}

我们发现反向const迭代器怎么这么长,每回写都让我们很烦,这时我们的auto就可以上场了。可以自动识别类型,但是对可读性造成一定的影响。所以我们必须要在足够了解的情况下使用。

int main()
{
	const string s1("hello world");
	//rbegin指向\0,rend指向第一个字符的前一个
	//string::const_reverse_iterator it = s1.rbegin();
	auto it = s1.rbegin();
	while (it != s1.rend())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}

5、访问string的三种方式

第一种:用下标的方式访问字符。
第二种:范围for,其实他的本质就是迭代器
第三种:迭代器

6、insert和erase

在指定位置插入或删除。
在这里插入图片描述
在这里插入图片描述

7、operate []at

两个功能其实是一样的

8. replacefindrfind

replace将指定位置替换为自己想要的
find找到自己想要找到的位置,找到返回下标,没找到返回npos,也就是-1
rfind从后往前找到自己想要找到的位置,找到返回下标,没找到返回npos,也就是-1

面试题:将这个字符串的空格替换为%%20.
`“hello world I love ereryday!”

int main()
{
	string s1 = "hello world I love you!";
	//我们提前算好我们的空格,然后提前开空间
	size_t num = 0;
	for (auto ch : s1)
	{
		if (ch == ' ')
		{
			++num;
		}
	}
	//提前开空间,避免replace时扩容
	s1.reserve(s1.size() + 3 * num);
	//第二个参数是缺省值=0,就是从开始的地方找
	size_t pos = s1.find(' ');
	while (string::npos != pos)
	{
		s1.replace(pos, 1, "%%20");
		//在pos之后第四个位置开始找
		pos = s1.find(' ',pos+4);
	}
	cout << s1 << endl;
}

`

//第二种方法以空间换时间
int main()
{
	string s1 = "hello world I love you!";
	//我们提前算好我们的空格,然后提前开空间
	string tmp;
	for (auto ch : s1)
	{
		if (ch != ' ')
		{
			tmp += ch;
		}
		if (ch == ' ')
		{
			tmp += "%% 20";
		}
	}
	s1 = tmp;
	cout << s1 << endl;
}

9 、string中的swap

在我们的string中我们也有一个交换函数。库里面也有一个swap。
我们使用string中swap效率更高。

因为在我们的string中可以直接交换指针,指向不同的字符串。
而库中的swap,我们还要用一个临时变量去交换。

在这里插入图片描述

在这里插入图片描述

int main()
{
	string s1("abcd");
	string s2("xxxx");
	
	//用string中的交换
	s1.swap(s2);
	cout << s1 << endl << s2 << endl;

	//用库中的交换
	swap(s1, s2);
	cout << s1 << endl << s2 << endl;
}

10、c_str

c_str返回字符串的地址

int main()
{
	string s1("hello world");

	cout << s1 << endl;
	cout << s1.c_str() << endl;
}

第一个我们是用的进行运算符重载后的流插入,按照我们的size打印,size多大就打印多少。
第二个是用的库里的流插入。s1.c_str()相当于一个字符串指针。
我们看似没有差别,其实差别很大。
看下面这个例子

int main()
{
	string s1("hello world");
	s1 += '\0';
	s1 += '\0';
	s1 += "xxxxx";

	cout << s1 << endl;
	cout << s1.c_str() << endl;
}

在这里插入图片描述
用c_str的例子

int main()
{
	string fliename("test.cpp");
	FILE* fout = fopen(fliename.c_str(), "r");

	char ch = fgetc(fout);
	while (ch != EOF)
	{
		cout << ch;
		ch = fgetc(fout);
	}
	return 0;
}

11、substr

显示子串在这里插入图片描述

int main()
{
	string file("string.cpp");
	size_t pos = file.find('.');
	//取一个子串
	//后面那个参数直接用缺省值,直接就可以取到结尾
	string suffix = file.substr(pos);
	cout << suffix;
}

12、find_first_of

在字符串中搜索与其参数中指定的任何字符匹配的每个字符。

例题
"please,look me"中的abe替换字符为x.

int main()
{
	string s1("please ,look me");
	size_t found = s1.find_first_of("abe");
	while (found != string::npos)
	{
		s1[found] = 'x';
		found = s1.find_first_of("abe");
	}
	cout << s1;
}

13、getline

我们的cin输入,遇到空格换行就结束了。
我们如果像输入一句话,对于cin只能输入一个单词。

但是我们getline遇到换行才会结束。
string s1; getline(cin,s1);

例题字符串最后一个单词的长度

模拟实现string

首先,我们模拟实现string,包括三个成员变量(字符指针,大小,容量)
为了不和库里的string冲突,我们需要进行一步操作。
将我们模拟实现的string封装起来。具体怎么做看下面代码

//外面这个类将string包装起来,防止冲突
namespace tongtong
{
	//模拟实现的string
	class string
	{
	public:

	private:
		char* _str;
		size_t size;
		size_t capacity;
	};
};

一、string的构造函数

①无参的构造函数

string()
	:_str(nullptr)
	, _size(0)
	,_capacity(0)
{}

其实对于我们的无参构造函数看着没什么问题,其实有点问题。那我举一个例子大家看看什么问题。
我们上面了解了c_str,我们如果要实现这个要求我们然后这个字符串的地址。

const char* c_str()
{
	return _str;
}

我们如果想打印我们的字符串,就会发现直接报错,那么出现了什么问题呢?
>在这里插入图片描述

大家先思考一下
我们s1进行无参的构造函数,并且在初始化列表中初始化了空指针。我们先通过s1找到了c_str的成员函数,这时候是没有崩溃的,并且将空指针返回也是没有问题的。问题就出在我们通过这个空指针打印。这时就涉及到了空指针的解引用。就会出现问题。

②代参的构造函数

string(const char* str)
	:_str(str)
	,_size(strlen(str))
	,_capacity(strlen(str))
{}

其实他也是有问题的
我们设置成带参数,str的类型是const char*,但是我们成员变量_str是char*类型,我们在初始化列表中传参数的时候就涉及到权限的放大。
所以我们的解决办法就是将我们的成员参数也设置为const。
这样的操作无疑是局限的,那么我们后续的操作就都不可以改变我们的_str。

③修改构造函数

我们先改我们带参数的构造函数,我们的直接将我们的常量字符串给他,肯定不可以,那么我们就自己开一块空间,将字符串的内容拷贝过来。这样我们自己开辟的空间就可以自己随意修改,而不是像我们上面将成员变量修改为const。

我们开辟空间,就没有必要在初始化列表开空间,就直接在函数中开就可以。具体的细节我们开代码注释。

string(const char* str)
		:_size(strlen(str))
	{
		//为什们将capacity的初始化也放到函数中?
		//在初始化列表,初始化的顺序跟定义的顺序有关,
		//如果我们先定义的capacity,我们size还没定义就是随机值。
		_capacity = _size;
		//因为capacity不包括字符的结束标准 \0 
		//所以在开辟空间的时候,我们多开一位。
		_str = new char [_capacity + 1];
		//将我们字符串的内容拷贝过来。
		strcpy(_str, str);
	}

修改无参数的构造函数,我们防止空指针的解引用,我们可以也给他开一个字节的空间,放\0,也就是字符串的结束标志,表示空字符串。

		string()
			//我们初始化为什们要用new[]呢?
			//为了跟我们带参构造函数保持一致,
			//在析构的时候用一样的类型。delete[]new;
			:_str(new char[1]{'\0'})
			, _size(0)
			,_capacity(0)
		{

		}

我们在类和对象中,我们知道,无参的构造函数可以用缺省参数代替,所以我们将带参数的构造函数用缺省参数,就可以将这两个合二为一。

//下面两种形式都可以,我们""里面就有\0。
//string(const char* str = "\0")
string(const char* str = "")
	:_size(strlen(str))
{
	_capacity = _size == 0 ? 3 : _size;
	_str = new char [_capacity + 1];
	strcpy(_str, str);
}

二、析构函数

很简单不想解释了,直接看代码

~string()
{
//注意的点就是,delete一定要和new保持一致
	delete[]_str;
	_str = nullptr;
	_size = _capacity = 0;
}

三、拷贝构造

编译器会自动生成我们的拷贝构造,但是我们在类和队对象的章节学过,它完成的是浅拷贝。
对于我们的字符串,我们_str是一个指针,指向一块数组,当我们完成浅拷贝的时候,我们拷贝出来的指针只是将_str的四个字节拷贝了过去,所以两块空间用指针指向同一块空间。

当我们进行析构函数的时候,我们同一块空间析构了两次,这显然是由问题的,所以会直接报错,我们不能直接用默认拷贝构造,我们要自己写。

//拷贝构造
string(const string& s)
	: _size(s._size)
	, _capacity( s._capacity)
{
	//防止new失败,我们先创建一个临时变量开辟空间
	//拷贝完后,在给_str
	char* tmp = new char[s._capacity + 1];
	strcpy(tmp, s._str);
	_str = tmp;
}

四、赋值重载

这个也是默认成员函数,编译器会自动生成,跟拷贝构造一样进行浅拷贝,和我们上面出现的情况一样,所以我们要进行深拷贝,自己写一个赋值重载。

//赋值重载
string& operator=(string& s)
{
	//防止自己和自己赋值
	if (this != &s)
	{
		// 防止new失败,我们先创建一个临时变量开辟空间
		//拷贝完后,在给_str
		char* tmp = new char[s._capacity + 1];
		strcpy(tmp, s._str);
		delete[]_str;
		_str = tmp;
		_size = s._size;
		_capacity = s._capacity;
	}
	
	return *this;
	
}

五、返回sizecapacity

就是将我们的size直接返回,需要注意的一点就是我们返回size,一般都是不可修改的,不可修改一般就设置成const
这样的话,就跟加安全。

size_t size() const
{
	return _size;
}
size_t capacity() const
{
	return _size;
}

六、[]的运算符重载

这个也是比较容易,就是返回字符串的位置,我们可以通过引用返回改变我们字符串的值。
但是当我们想要打印字符串的时候,我们并不想修改字符串,我们就可以用const修饰,让其构成运算符重载。让我们的程序更高效

//可修改的
char& operator[](size_t pos)
{
	assert(pos < _size);
	return _str[pos];
}
//不可修改的
const char& operator[](size_t pos) const
{
	assert(pos < _size);
	return _str[pos];
}

七、迭代器

我们暂时将其考虑为指针,就可以。
像用指针一样,用迭代器。我们先写一个正向迭代器。

① 正向迭代器。

//迭代器,//我们先知道怎么用就可以
typedef char* itterator;
itterator begin()
{
	return _str;
}
itterator end()
{
	return _str + _size;
}

用迭代器遍历字符串。

//测试迭代器
void test_string4()
{
	string s1("hello world");
	string::itterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	//范围for的底层就是迭代器
	for (auto ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
}

② 正向const迭代器

我们再来一个正向const迭代器

//正向const迭代器
typedef const char* const_itterator;
const_itterator begin() const
{
	return _str;
}
const_itterator end() const
{
	return _str + _size;
}

八、string比较大小(运算符重载)

这个唯一注意的点就是
我们如果不修改就加上const

//比较大小
bool operator>(const string& s) const
{
	return strcmp(_str, s._str) > 0;
}
bool operator==(const string& s)const
{
	return strcmp(_str, s._str) == 0;
}
bool operator>=(const string& s)const
{
	return *this > s ||  s == * this ;
}
bool operator<(const string& s)const
{
	return !(*this >= s);
}
bool operator<=(const string& s)const
{
	return !(*this > s);
}
bool operator!=(const string& s)const
{
	return !(*this == s);
}

八、扩容

扩容我们知道有两种方式,
一种是reserve,改变capacity不变size
另一种是resize,即变capacity,还变size,还能初始化

reserve扩容

void reserve(size_t n)
{
	//reserve 不支持缩容
	//所以我们自己加个条件
	if (n > _capacity)
	{
		//给\0开一个空间
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[]_str;
		_str = tmp;
		_capacity = n;

	}
}

resize扩容

//扩容
//resize是扩容+初始化,用缺省参数就是将其初始化
void resize(size_t n, char ch = '\0')
{
	//也不支持缩容但是会改sz的大小
	if (n < _size)
	{
		//删除数据,保留前n个
		_size = n;
		_str[_size] = '\0';
	}
	else if(n > _size)
	{
		if (n < _capacity)
		{
			memset(_str+_size, ch, n - _size );
			_size = n;
			_str[_size] = '\0';
		}
		else
		{
			reserve(n);
			memset(_str+_size, ch, n - _size );
			_size = n;
			_str[_size] = '\0';
		}
	}

}

九、尾插数据

增加数据有两种方式,
一种是push_back,增加一个字符
另一种是append增加一个字符串

push_back

//增加数据
//增加一个字符
void push_back(char ch)
{
	if (_size + 1 > _capacity)
	{
		//二倍扩容
		reverse(_capacity * 2);
	}
	_str[_size] = ch;
	++_size;
	//最后加上\0
	_str[_size] = '\0';
}
		

append

//增加一个字符串
void append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		//如果原数组太小,二倍之后还可能放不下
		reverse(_size + len);
	}
	strcpy(_str + _size, str);
	_size += len;
	//最后加上\0
	_str[_size] = '\0';

十、+=的运算符重载(替换尾插数据的函数)

我们直接用+=既简单又好看。
就是利用上面的两个函数,构成运算符重载

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

十一、在固定为插入,删除

这时我们应该用insert
在我们的插入也分为插入一个字符,和插入一个字符串。

①插入一个字符

//插入数据
//在某个位置插入一个字符
void insert(size_t pos, char ch)
{
	assert(pos <= _size);
	if (_size + 1 > _capacity)
	{
		reserve(_capacity * 2);
	}
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		--end;
	}
	_str[pos] = ch;
	++_size;
}

② 插入一个字符串

//在某个位置插入一个字符串
void insert(size_t pos, const char* str)
{
	assert(pos <= _size);
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}
	size_t end = _size + len;
	while (end > pos + len - 1 )
	{
		_str[end] = _str[end - len];
		end--;
	}
	strncpy(_str + pos, str, len);
	_size += len;
}

③ 删除

//删除数据
void erase(size_t pos, size_t len = npos)
{
	if (len == npos || len >= _size - pos)
	{
		_str[pos] = '\0';
	}
	else
	{
		strcpy(_str + pos, _str + pos + len);
		_size -= pos;
	}
}

十二、交换

将我们两个指针进行交换。
所以比我们库里的交换函数快,少了一次拷贝构造,和两次赋值重载。

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

十三、find

就是在指定位置找。但是查找有两种方式
一种是查找字符
第二种是查找子串

① 找字符

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

②找子串

//查找一个子串
size_t find(const char* str, size_t pos = 0)
{
	assert(pos < _size);

	char * p = strstr(_str + pos, str);
	if (p == nullptr)
	{
		return npos;
	}
	else
	{
		//指针相减就是中间的个数
		return p - _str;
	}
}

十四、流插入,流提取

流插入

我们的流插入不一定就是友元函数,我们也可以用迭代器,和[]来找到他的数据

//流插入
ostream& operator<<(ostream& out, const string s)
{
	for (int i = 0; i < s.size(); i++)
	{
		out << s[i];
	}
	return out;
}

流提取

我们看下面这个流提取,并分析他为什么是错的

istream& operator>>(istream& in, string s)
{
	s.clear();
	char ch;
	in >> ch;
	while (ch != ' ' && ch != '\n')
	{
		s += ch;
		in >> ch;
	}
	return in;
}

cin的缓冲区,输入多个数据,两个数据分割就是空格或者换行,编译器认为我们是要多个字符,所以我们的空格换行我们就拿不到,它一直在读数据。所以cinscanf识别不了' ''\n'

所以我们可以提取数据需要不区分空格的操作符。
正好C++种get就不会区分,每一个字符我们都可以拿到。

istream& operator>>(istream& in, string s)
{
	//每次提取都将我们的空间为空
	s.clear();
	char ch = in.get();
	//防止输入的字符串太长,一直扩容
	char buff[128];
	size_t i = 0;
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;

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

模拟实现的全部代码

string.h

#pragma once
#include<assert.h>
//外面这个类将string包装起来,防止冲突
namespace tongtong
{

	//模拟实现的string
	class string
	{
	public:


		//string()
		//	//我们初始化为什们要用new[]呢?
		//	//为了跟我们带参构造函数保持一致,
		//	//在析构的时候用一样的类型。delete[]new;
		//	:_str(new char[1]{'\0'})
		//	, _size(0)
		//	,_capacity(0)
		//{

		//}
		//string(const char* str)
		//	:_size(strlen(str))
		//{
		//	//为什们将capacity的初始化也放到函数中?
		//	//在初始化列表,初始化的顺序跟定义的顺序有关,
		//	//如果我们先定义的capacity,我们size还没定义就是随机值。
		//	_capacity = _size;
		//	//因为capacity不包括字符的结束标准 \0 
		//	//所以在开辟空间的时候,我们多开一位。
		//	_str = new char [_capacity + 1];
		//	//将我们字符串的内容拷贝过来。
		//	strcpy(_str, str);
		//}

		//下面两种形式都可以,我们""里面就有\0。
		//string(const char* str = "\0")
		string(const char* str = "")
			:_size(strlen(str))
		{
			_capacity = _size == 0 ? 3 : _size;
			_str = new char [_capacity + 1];
			strcpy(_str, str);
		}
		//拷贝构造
		string(const string& s)
			: _size(s._size)
			, _capacity( s._capacity)
		{
			//防止new失败,我们先创建一个临时变量开辟空间
			//拷贝完后,在给_str
			char* tmp = new char[s._capacity + 1];
			strcpy(tmp, s._str);
			_str = tmp;

		}
		//赋值重载
		string& operator=(string& s)
		{
			//防止自己和自己赋值
			if (this != &s)
			{
				// 防止new失败,我们先创建一个临时变量开辟空间
				//拷贝完后,在给_str
				char* tmp = new char[s._capacity + 1];
				strcpy(tmp, s._str);
				delete[]_str;
				_str = tmp;
				_size = s._size;
				_capacity = s._capacity;
			}
			
			return *this;
			
		}
		size_t size() const
		{
			return _size;
		}
		size_t capacity() const
		{
			return _capacity;
		}
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		//正向迭代器
		typedef char* itterator;

		itterator begin()
		{
			return _str;
		}
		itterator end()
		{
			return _str + _size;
		}
		
		//正向const迭代器
		typedef const char* const_itterator;
		const_itterator begin() const
		{
			return _str;
		}
		const_itterator end() const
		{
			return _str + _size;
		}
		//比较大小
		bool operator>(const string& s) const
		{
			return strcmp(_str, s._str) > 0;
		}
		bool operator==(const string& s)const
		{
			return strcmp(_str, s._str) == 0;
		}
		bool operator>=(const string& s)const
		{
			return *this > s ||  s == * this ;
		}
		bool operator<(const string& s)const
		{
			return !(*this >= s);
		}
		bool operator<=(const string& s)const
		{
			return !(*this > s);
		}
		bool operator!=(const string& s)const
		{
			return !(*this == s);
		}
		//扩容
		void reserve(size_t n)
		{
			//reserve 不支持缩容
			//所以我们自己加个条件
			if (n > _capacity)
			{
				//给\0开一个空间
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[]_str;
				_str = tmp;
				_capacity = n;
			}
		}
		//扩容
		//resize是扩容+初始化,用缺省参数就是将其初始化
		void resize(size_t n, char ch = '\0')
		{
			//也不支持缩容但是会改sz的大小
			if (n < _size)
			{
				//删除数据,保留前n个
				_size = n;
				_str[_size] = '\0';
			}
			else if(n > _size)
			{
				if (n < _capacity)
				{
					memset(_str+_size, ch, n - _size );
					_size = n;
					_str[_size] = '\0';
				}
				else
				{
					reserve(n);
					memset(_str+_size, ch, n - _size );
					_size = n;
					_str[_size] = '\0';
				}
			}

		}

		//增加数据
		//增加一个字符
		void push_back(char ch)
		{
			if (_size + 1 > _capacity)
			{
				//二倍扩容
				reserve(_capacity * 2);
			}
			_str[_size] = ch;
			++_size;
			//最后放上\0
			_str[_size] = '\0';

		}
		//增加一个字符串
		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				//如果原数组太小,二倍之后还可能放不下
				reserve(_size + len);
			}
			strcpy(_str + _size, str);
			_size += len;
			//最后加上\0
			_str[_size] = '\0';
		}
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

		//插入数据
		//在某个位置插入一个字符
		string& insert(size_t pos, char ch)
		{
			assert(pos <= _size);
			if (_size + 1 > _capacity)
			{
				reserve(_capacity * 2);
			}
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}
			_str[pos] = ch;
			++_size;
			return *this;
		}
		//在某个位置插入一个字符串
		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			size_t end = _size + len;
			while (end > pos + len - 1 )
			{
				_str[end] = _str[end - len];
				end--;
			}
			strncpy(_str + pos, str, len);
			_size += len;
			return *this;
		}
		//删除数据
		string& erase(size_t pos, size_t len = npos)
		{
			if (len == npos || len >= _size - pos)
			{
				_str[pos] = '\0';
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= pos;
			}
			return *this;
		}
		//交换
		void swap(string& s)
		{

			std::swap(_str, s._str);
			std::swap(_capacity, s._capacity);
			std::swap(_size, s._size);
		}
		//查找一个字符
		size_t find(char ch, size_t pos = 0)
		{
			assert(pos < _size);
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}
			return npos;
		}
		//查找一个子串
		size_t find(const char* str, size_t pos = 0)
		{
			assert(pos < _size);

			char * p = strstr(_str + pos, str);
			if (p == nullptr)
			{
				return npos;
			}
			else
			{
				//指针相减就是中间的个数
				return p - _str;
			}
		}
		//滞空字符串
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
		const char* c_str()
		{
			return _str;
		}
		~string()
		{
			delete[]_str;
			_str = nullptr;
			_size = _capacity = 0;
		}



	private:
		char* _str;
		size_t _size;
		size_t _capacity;
		//定义一个无符号的静态成员变量,当我们的默认参数
		static const size_t npos;

	};

	size_t const string::npos = -1;

	//流插入
	ostream& operator<<(ostream& out, const string s)
	{
		for (int i = 0; i < s.size(); i++)
		{
			out << s[i];
		}
		return out;
	}
	//istream& operator>>(istream& in, string s)
	//{
	//	char ch;
	//	in >> ch;
	//	while (ch != ' ' && ch != '\n')
	//	{
	//		s += ch;
	//		in >> ch;
	//	}
	//	return in;
	//}
	istream& operator>>(istream& in, string s)
	{
		//每次提取都将我们的空间为空
		s.clear();
		char ch = in.get();
		//防止输入的字符串太长,一直扩容
		char buff[128];
		size_t i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;

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

	void test_string1()
	{
		string s1;
		string s2("hello world");
		string::itterator it = s1.begin();
		while (it != s1.end())
		{
			cout << *it << endl;
			//it++;
		}
		cout << endl;
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;
		s2[0]++;
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;
	}
	void test_string2()
	{
		string s1;
		string s2("hello world");
		string s3(s2);
		string s4 = s2;

		cout << s3.c_str() << endl;
		cout << s4.c_str() << endl;
		s1 = s3;
		cout << s1.c_str() << endl;
		s1 = s1;
		cout << s1.c_str() << endl;

	}
	void Print(const string& s) 
	{
		for (size_t i = 0; i < s.size(); i++)
		{
			cout << s[i] << " ";
		}
		string::const_itterator it = s.begin();
		while (it != s.end())
		{
			cout << *it << " ";
			it++;
		}
	}
	void test_string3()
	{
		string s1("hello world");
		for (size_t i = 0; i < s1.size(); i++)
		{
			s1[i]++;
		}
		Print(s1);
		cout << endl;
	}
	void test_string4()
	{
		string s1("hello world");
		string::itterator it = s1.begin();
		while (it != s1.end())
		{
			(*it)--;
			cout << *it << " ";
			it++;
		}
		cout << endl;
		//范围for的底层就是迭代器
		for (auto ch : s1)
		{
			cout << ch << " ";
		}
		cout << endl;
	}
	void test_string5()
	{
		string s1("hello world");
		string s2("hello world");
		cout << (s1 < s2) << endl;
		cout << (s1 >= s2) << endl;
		cout << (s1 == s2) << endl;
		
		//s1.push_back(' ');
		//s1.append("hello");
		s1 += ' ';
		s1 += "hello";
		cout << s1.c_str() << endl;
	}
	void test_string6()
	{
		string s1("abcdef");
		//s1.resize(20, 'x');
		//cout << s1.c_str() << endl;
		//s1.resize(30, 'y');
		//cout << s1.c_str() << endl;

		//s1.resize(10);
		//cout << s1.c_str() << endl;

		s1.insert(0, 'x');
		cout << s1.c_str() << endl;
		s1.insert(0, "222222");
		cout << s1.c_str() << endl;
	}

	void test_string7()
	{
		string s1("abcdef");
		//s1 += '\0';
		s1 += "xxxx";
		cout << s1 << endl;
		cout << s1.c_str() << endl;
	/*	string s2("xxxxxx");*/
		//s1.swap(s2);
		//cout << s1.c_str() << endl;
		//cout << s2.c_str() << endl;

		cin >> s1;
	}
};


test.cpp

#define _CRT_SECURE_NO_WARNINGS 1 

#include<iostream>
using namespace std;
#include"string.h"

int main()
{
	tongtong::test_string1();
	return 0;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

桐桐超努力

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

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

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

打赏作者

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

抵扣说明:

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

余额充值