string的使用和模拟实现

发布于:2025-05-26 ⋅ 阅读:(20) ⋅ 点赞:(0)

string的使用

string可以理解为:管理char字符数组的顺序表
使用string之前需要加头文件#include < string >

可以理解string的底层是这样的

class string
{
private:
	char* _str;
	size_t _size;
	size_t _capacity;
};

Member functions(成员函数)

(constructor)构造

1. string();
2. string(const string& str);
3. string(const string& str, size_t pos, size_t len = npos);
4. string(const char* s);
5. string(const char* s, size_t n);
6. string(size_t n, char c);
7. template <class InputIterator>
   string  (InputIterator first, InputIterator last);

其中1、2、4是最常用的构造。
对npos的定义,npos属于string容器的成员常量

//size_t相当于unsigned int,npos给值-1,就是npos是一个非常大的数
static const size_t npos = -1;

构造的使用

#include <iostream>
#include <string>

using namespace std;

int main()
{
	//1
	string s1;
	cout << s1 << endl << endl;
	//4
	string s2("hello world");
	cout << s2 << endl << endl;
	//2 拷贝构造
	string s3(s2);
	cout << s3 << endl << endl;
	//3 从下标pos位置开始拷贝len个字符,如果len的大小大于pos位置及其之后字符
	//的个数或len是缺省值npos,那么就把pos位置及其之后的字符全部拷贝过来
	string s4(s2, 6, 3);
	cout << s4 << endl;
	string s5(s2, 6, 50);
	cout << s5 << endl;
	string s6(s2, 6);
	cout << s6 << endl << endl;
	//5  从str指向的字符串中拷贝前n个字符
	const char* str = "hello world";
	string s7(str, 5);
	cout << s7 << endl << endl;
	//6 拷贝n个c字符
	string s8(10, '!');
	cout << s8 << endl;
	//7 等后面了解迭代器之后再使用
	
	return 0;
}

在这里插入图片描述

(destructor)析构

~string();

operator=

1. string& operator=(const string& str);
2. string& operator=(const char* s);
3. string& operator=(char c);

其中1、2是最常用的赋值。
operator=的使用

#include <iostream>
#include <string>

using namespace std;

int main()
{
	//1
	string s1;
	string str("hello world");
	const char* s = "world hello";
	s1 = str;
	cout << s1 << endl << endl;
	//2
	s1 = s;
	cout << s1 << endl << endl;
	//3
	s1 = 'x';
	cout << s1 << endl;

	return 0;
}

在这里插入图片描述

遍历+修改

涉及:operator[]、operator<<、size、length、begin、end
string的遍历+修改有三种方式

#include <iostream>
#include <string>
#include <vector> //顺序表
#include <list> //链表
#include <algorithm> //算法

using namespace std;

int main()
{
	string s1("hello world");
	const string s2("hello world");
	//遍历+修改
	//1. 下标+[] operator[] (小众)
	s1[0]++; //s1.operator[](0)++;
	//s2[0]++; //s2被const修饰,不能改变
	cout << s1 << endl;

	//对s1的每个字符都++
	//s1.size()和s1.length()是一样的
	//原本只有length但是为了让STL容器的风格保持一致又添加了个size
	for(size_t i = 0; i < s1.size(); ++i)
	{
		s1[i]++;
	}
	cout << s1 << endl;

	//2.迭代器iterator (所有容器的主流遍历+修改方式)
	//迭代器具有迁移性,掌握了一个容器的迭代器使用,
	//其他的容器的迭代器的使用就更容易上手
	//迭代器可以理解为一个像指针一样的东西
	//没s1的每个字符都--
	string::iterator it1 = s1.begin();
	//[begin(), end()) 左闭右开
	//s1.begin()返回开始位置的迭代器
	//s1.end()返回最后一个数据位置的下一个位置的迭代器
	//等于说s1,end()是返回'\0'这个位置的迭代器
	while(it1 != s1.end())
	{
		(*it1)--;
		it1++;
	}
	cout << s1 << endl;

	//这里是对迭代器去魅
	//现阶段就把迭代器理解为一个像指针一样的东西就好
	vector<int> v;
	//尾部插入
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);

	vector<int>::iterator it2 = v.begin();
	while(it2 != v.end())
	{
		cout << *it2 << endl;
		it2++;
	}
	cout << endl;
	
	list<int> lt;
	lt.push_back(10);
	lt.push_back(20);
	lt.push_back(30);

	list<int>::iterator it3 = lt.begin();
	while (it3 != lt.end())
	{
		cout << *it3 << " ";
		it3++;
	}
	cout << endl;

	//逆置string、vector、list
	reverse(s1.begin(), s1.end());
	reverse(v.begin(), v.end());
	reverse(lt.begin(), lt.end());
	//这就是迭代器的强大之处,算法通过迭代器去操作这些容器
	//并脱离了具体的底层结构和底层结构解耦
	//解耦:降低耦合,就是降低算法和容器的关联关系,让算法实用于所有的STL容器
	
	//3. 范围for 也叫语法糖(意思是范围for用着很爽) C++11添加的
	//所有容器都支持范围for
	//在使用范围for之前先说以下auto
	//auto可以自动推导变量的类型
	auto x = 10;
	auto y = 10.1;
	cout << x << endl;
	cout << y << endl;

	int& z = x;
	auto m = z; //m不是z的引用,它的类型是int
	m++; //m++不改变z和x
	auto& n = z; //n是z的引用

	//下面两个auto推导出来的类型都是int*
	auto p1 = &x;
	auto* p2 = &x; //这种写法只能是指针
	//auto和auto*的区别
	auto p3 = x; //auto推导出来的类型是int
	//auto* p4 = x; //编译报错,不能这样写
	
	//自动取容器数据
	//自动判断结束
	//自动迭代
	for(auto e : s1)
	{
		cout << e << " ";
	}
	cout << endl;
	
	for(auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	for(auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	//修改
	//范围for的底层就是迭代器,auto e = *迭代器
	//所以e是*迭代器的拷贝,要修改容器数据就要加引用
	//这里的auto也可以写具体的类型,但习惯上都写的auto
	//for(char& e : s1)
	for(auto& e : s1)
	{
		e--;
		cout << e << " ";
	}
	cout << endl;

	//以前数组的遍历方式
	int arr[] = { 1, 2 ,3, 4, 5 };
	for(int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
	//但是现在数组可以用范围for遍历
	//可以认为它在语法上特殊处理过
	for(auto e : arr)
	{
		cout << e << " ";
	}
	cout << endl;
	 
	
	return 0;
}

在这里插入图片描述

迭代器

涉及:begin/end、rbegin/rend、cbegin/cend、crbegin/crend
迭代器分为:普通迭代器和const迭代器,普通迭代器修饰普通对象,const迭代器修饰const对象
常用:begin/end、rbegin/rend

int main()
{
	string s1("123456");
	const string s2("hello world");
	//begin/end 正向迭代器 正向遍历
	string::iterator it1 = s1.begin();
	while(it1 != s1.end())
	{
		(*it1)--;
		it1++;
	}
	cout << s1 << endl;

	string::const_iterator it2 = s2.begin();
	while(it2 != s2.end())
	{
		//(*it2)--;//const_iterator迭代器指向的数据不能修改
		it2++;
	}
	cout << s2 << endl << endl;

	//rbegin/rend 反向迭代器 反向遍历
	string::reverse_iterator it3 = s1.rbegin();
	while(it3 != s1.rend())
	{
		(*it3)++;
		cout << *it3;
		it3++;
	} 
	cout << endl;

	string::const_reverse_iterator it4 = s2.rbegin();
	while(it4 != s2.rend())
	{
		//(*it4)--; //不能修改
		cout << *it4;
		it4++;
	}
	cout << endl << endl;
	
	//cbegin/cend、crbegin/crend是确定要返回const版本
	//c就是const
}

在这里插入图片描述

//构造第7点
7. template <class InputIterator>
   string  (InputIterator first, InputIterator last);
   		  //起始位置的迭代器,末尾数据位置的下一个位置的迭代器
int main()
{
	string s1("hello world");
	string s2(s1.begin(), s1.end());
	cout << s2 << endl;
}

在这里插入图片描述

max_size、capacity、push_back、clear、empty、shrink_to_fit、pop_back

常用:push_back、clear、empty、pop_back

int main()
{
	string s1("123456");
	//max_size就是这个string字符串最长能有多长
	//但是其实这个接口没有什么意义,实际到不了那么长
	//X64和X86环境下max_size的值不同,不同平台也不同
	cout << s1.max_size() << endl;
	cout << endl;

	//capacity 返回当前string字符串的容量,不包含\0
	//返回的值加1才是实际的容量
	//size和capacity均不包含\0
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	
	string s2;
	size_t old = s2.capacity();
	cout << "capacity:" << old << endl;
	for(size_t i = 0; i < 100; ++i)
	{
		//push_back 尾部插入
		s2.push_back('x');
		if(s2.capacity() != old)
		{
			old = s2.capacity();
			cout << "capacity:" << old << endl;
		}
	}
	//VS2022第一次扩容2倍,后面都是1.5倍扩容,不同平台底层实现不同
	cout << endl;

	//clear 清空数据
	//empty 判空
	cout << s1 << endl;
	s1.clear();
	if(s1.empty())
		cout << "yes" << endl;
	cout << endl;

	//shrink_to_fit 缩容
	cout << "size:" << s2.size() << endl;
	cout << "capacity:" << s2.capacity() << endl;
	//s2.clear(); //clear只会改变size不会改变容量
	for(size_t i = 0; i < 50; ++i)
	{
		//pop_back 尾部删除
		//尾删只会改变size不会改变容量
		s2.pop_back();
	}
	s2.shrink_to_fit();
	cout << "size:" << s2.size() << endl;
	cout << "capacity:" << s2.capacity() << endl;
	//为什么这些接口都不缩容,这不是浪费空间吗?
	//1. 缩容的代价很大,因为在堆上申请的空间不支持先释放一部分
	//2. 缩容是新开一块小空间,然后把旧数据拷贝过来
	//3. 缩容就是一种以时间换空间的做法

	//shrink_to_fit会让capacity减小到>=size,,为什么不直接减小到size?
	//因为它这个容量底层也是有对齐规则的,不同平台底层对齐规则不同
}

在这里插入图片描述

reserve、resize

这两个都不常用

int main()
{
	//reserve 扩容 跟shrink_to_fit类似
	string s1("123456");
	cout << "size:" << s1.size() << endl;
	cout << "capacity:" << s1.capacity() << endl;
	s1.reserve(100);
	cout << "size:" << s1.size() << endl;
	cout << "capacity:" << s1.capacity() << endl;
	//reserve会不会缩容是不确定的,但如果缩容的话,肯定不会影响原始数据内容
	s1.reserve(3);
	cout << "size:" << s1.size() << endl;
	cout << "capacity:" << s1.capacity() << endl;
	cout << endl;

	//reserve使用场景:知道容量要多少,直接开好,避免扩容,提高效率
	string s2;
	size_t old = s2.capacity();
	cout << "capacity:" << old << endl;
	s2.reserve(100);
	for(size_t i = 0; i < 100; ++i)
	{
		s2.push_back('x');
		if(s2.capacity() != old)
		{
			old = s2.capacity();
			cout << "capacity:" << old << endl;
		}
	}
	cout << endl;

	//resize 改变size的值
	//void resize(size_t n);
	//void resize(size_t n, char c);
	//如果n < size,保留前n个字符,删除之后的字符
	//如果n >= size,传字符c就用c填满多出来的空间,不传就用\0填满
	string s3("123456");
	s3.resize(3);
	cout << "size:" << s3.size() << endl;
	cout << s3 << endl;
	s3.resize(9);
	cout << "size:" << s3.size() << endl;
	cout << s3 << endl;
	s3.resize(13, 'x');
	//这里打印出来是123xxxx,直接忽略了\0,实际是123\0\0\0\0\0\0xxxx,监视窗口可以查看
	//std::string的特性:std::string类内部维护了字符串的长度信息,通过size()成员函数获取。
	//它并不依赖\0来标识字符串结束。当使用cout输出std::string对象时,cout会根据
	//std::string内部记录的长度去输出字符序列,而不是像处理C风格字符串那样遇到\0就停止。
	cout << "size:" << s3.size() << endl;
	cout << s3 << endl;
	
	return 0;
}

在这里插入图片描述

operator[]、at、back、front

常用:operator[]

void test_string()
{
	//operator[]对越界是断言报错
	//但断言在Release版本下是不起作用的
	string s1("hello world");
	s1[0]++;
	cout << s1 << endl;
	//s1[15]; //越界直接断言报错
	//它是在operator[]函数中,assert(pos < _size)断言检查的

	//at的功能和operator[]是一样的
	//只是at对越界是抛异常,它是可以捕获的
	s1.at(0)--;
	cout << s1 << endl;
	//s1.at(15); //越界抛异常
	
	//普通数组是没有越界检查的,它是抽查的
	int a[10] = { 0 };
	//a[16] = 1;//不同平台情况不同

	//back/front 返回结束/开始位置的字符
	cout << s1.back() << endl;
	cout << s1.front() << endl;	
}

int main()
{
	//捕获异常
	try
	{
		test_string();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	
	return 0;
}

断言报错
在这里插入图片描述
抛异常,不捕获异常
在这里插入图片描述
抛异常,捕获异常
在这里插入图片描述
在这里插入图片描述

operator+=、append、assign、insert、erase、replace、find

常用:operator+=、find

int main()
{
	//append追加 就是尾部插入
	string s1("hello world");
	s1.push_back(' ');
	s1.append("hello bit");
	s1.append(10, 'x');
	cout << s1 << endl;
	//operator+= 完全可以替代append
	string s2("hello world");
	s2 += " ";
	s2 += "hello world";
	s2 += "xxxxxxxxxx";
	cout << s2 << endl << endl;

	//assign 赋值
	//operator= 也可以替代assign
	s1.assign(10, 'y');
	cout << s1 << endl << endl;

	//insert 在一个位置之前插入
	//insert谨慎使用,底层涉及数据挪动,效率低下,O(N)
	string s3("hello world");
	s3.insert(0, "yyy");
	cout << s3 << endl;
	s3.insert(0, 1, '!');
	cout << s3 << endl;
	s3.insert(s3.begin(), '!');
	cout << s3 << endl << endl;

	//erase 删除
	//erase谨慎使用,底层涉及数据挪动,效率低下,O(N)
	string s4("hello world");
	s4.erase(5, 1);
	cout << s4 << endl;
	s4.erase(5);
	cout << s4 << endl << endl;

	//replace 替换
	//replace谨慎使用,底层涉及数据挪动,效率低下,O(N)
	string s5("hello world");
	s5.replace(5, 1, "###");
	cout << s5 << endl;

	//find 找到第一个匹配的字符或字符串
	//找到返回该字符位置或该字符串首字符位置
	//找不到返回string::npos
	//把所有的空格替换成%%
	string s6("hello world hello bit");
	//效率低下
	size_t pos = s6.find(' ');
	while(pos != string::npos)
	{
		s6.replace(pos, 1, "%%");
		pos = s6.find(' ', pos + 2);
	} 	
	cout << s6 << endl;
	
	//空间换时间
	string s7;
	s7.reserve(s6.size());
	for(auto e : s6)
	{
		if(e == ' ')
			s7 += "%%";
		else
			s7 += e;
	}
	cout << s7 << endl;

	return 0;
}

在这里插入图片描述

c_str、data、get_allocator

int main()
{
	//c_str 返回指向字符数组的指针
	//就是将string类型的字符串转换成const char*类型并返回
	//c_str就是为了更好的兼容C语言
	string filename("test.cpp");
				//这里需要const char*类型
	FILE* fout = fopen(filename.name.c_str(), "r");
	if(fout == nullptr)
	{
		cout << "fopen error" << endl;
		return 1;
	}
	
	char ch = fgetc(fout);
	while(ch != EOF)
	{
		cout << ch;
		ch = getc(fout);
	}
	//data跟c_str类似,可以将filename.name.c_str()
	//改成filename.name.data()测试一下

	//get_allocator用于获取容器当前使用的内存分配器
	//get_allocator基本不会用到
	
	return 0;
}

copy、rfind、substr

常用:rfind、substr

//找后缀
string findsubffix(string& filename)
{
	//从前往后找,找到的可能不是后缀,比如filename4
	//size_t i = filename.find('.');
	//if (i != string::npos)
	//{
	//	return filename.substr(i);
	//}
	string empty;
	return empty;
	return string();
	//return "";

	//rfind 从后往前找
	size_t i = filename.rfind('.');
	if (i != string::npos)
	{
		return filename.substr(i);
	}
	//string empty;
	//return empty;
	//return string();
	return "";
}

//切分url
void split_url(string& url)
{
	size_t i1 = url.find(':');
	if(i1 != string::npos)
	{
		cout << url.substr(0, i1) << endl;
	}

	size_t i2 = i1 + 3;
	size_t i3 = url.find('/');
	if(i3 != string::npos)
	{
		cout << url.substr(i2, i3 - i2) << endl;
		cout << url.substr(i3 + 1) << endl;
	}
}

int main()
{
	//copy将string类型的一个子串拷贝到一个C的字符数组中
	//copy不常用,一般会用substr
	//substr 拷贝当前字符数组的一个子串,并返回该子串
	//string substr (size_t pos = 0, size_t len = npos) const;
	//substr是从pos位置开始,拷贝len个字符
	string filename("test.cpp");
	string s1 = filename.substr(4);
	cout << s1 << endl;

	//找后缀
	string filename1("test.cpp");
	string filename2("test.c");
	string filename3("test");
	string filename4("test.cpp.tar.zip");
	cout << findsubffix(filename1) << endl;
	cout << findsubffix(filename2) << endl;
	cout << findsubffix(filename3) << endl;
	cout << findsubffix(filename4) << endl;
	
	string url1 = "https://legacy.cplusplus.com/reference/string/string/?kw=string";
	string url2 = "https://www.doubao.com/chat/5772109691013378";
	split_url(url1);
	split_url(url2);
	
	return 0;
}

从前往后找
在这里插入图片描述
从后往前找
在这里插入图片描述
在这里插入图片描述

find_first_of、find_last_of、find_first_not_of、find_last_not_of

不常用

find_first_of 在当前字符串中找传参的字符串中任意一个字符并返回其位置
find_last_of 从后往前找
find_first_not_of 在当前字符串中找不是传参的字符串中任意一个字符并返回其位置
find_last_not_of 从后往前找

compare

comepare 字符串比较,不常用,因为string重载了字符串大小比较relational operators (string)

Non-member function overloads(非成员函数重载)

operator+、relational operators、getline

它们都常用

int main()
{
	string s1("hshd");
	string s2("hshderjfk");
	const char* str = "ysdh";
	//字符串大小比较relational operators (string)
	s1 == s2;
	s1 == str;
	str == s1;

	//operator+ (string)
	s1 + s2;
	s1 + str;
	str + s1;

	//getline 得到一行,跟cin相似,但是getline是遇到\n才结束
	//getline也可以自己设置终止字符,但不会读入终止字符
	getline(cin, s1);
	cout << s1 << endl;
	getline(cin, s1, '!');
	cout << s1 << endl;
	
	return 0;
}

在这里插入图片描述

operator>>、swap

都常用

int main()
{
	//>>流提取运算符
	string s1("hello world");
	cout << s1 << endl;
	//遇到空格或换行结束
	cin >> s1;
	cout  << s1 << endl;

	//swap等string模拟实现的时候再讲
	
	return 0;
}

在这里插入图片描述

string的模拟实现

准备string.h、string.cpp、test.cpp三个文件
只实现了一些常用的功能

string.h

#pragma once
#include <cstring>
#include <cassert>
#include <iostream>

using namespace std;

//避免模拟实现的string跟库里的string重复
namespace bs
{
	class string
	{
	public:
		//迭代器类型
		typedef char* iterator;
		//const迭代器类型
		typedef const char* const_iterator;
		//我们在底层实现string的迭代器时直接把它typedef为指针了,但编译器
		//底层不是这样的,其他的很多容器的迭代器是不能typedef为指针的
		
		//迭代器[begin(), end())
		iterator begin();
		iterator end();

		//const迭代器[begin(), end())
		const_iterator begin() const;
		const_iterator end() const;
		
		//无参构造,可以直接用有参加缺省值代替
		//string();
		//构造
		string(const char* str = "");
		//拷贝构造
		string(const string& str);
		//string内部实现的对string类型进行交换的swap
		void swap(string& str);
		//传统string赋值写法
		//string& operator=(const string& str);
		//现代string赋值写法
		string& operator=(string tmp);
		//析构
		~string();

		//返回当前字符个数,不包含\0
		size_t size() const;

		//const的operator[],值不可改变
		const char& operator[](size_t i) const;
		//普通的operator[],值可以改变
		char& operator[](size_t i);

		//C++风格的string类型字符串转C风格的const char*类型字符串
		const char* c_str() const;
		//扩容
		void reserve(size_t n);
		//尾部插入
		void push_back(char ch);
		//追加
		void append(const char* str);

		//operator+=
		string& operator+=(char ch);
		string& operator+=(const char* str);
		
		//在pos位置之前插入
		string& insert(size_t pos, char ch);
		string& insert(size_t pos, const char* str);
		//删除从pos位置开始,len长度的字符
		string& erase(size_t pos = 0, size_t len = npos);
		//尾部删除
		void pop_back();
		//从前往后查找第一个匹配的字符或字符串,返回其位置
		size_t find(char ch, size_t pos = 0) const;
		size_t find(const char* str, size_t pos = 0) const;
		//从pos位置截取len长度的子串并返回
		string substr(size_t pos = 0, size_t len = npos) const;
		//清空字符串
		void clear();

		//关系运算符
		bool operator<(const string& s) const;
		bool operator<=(const string& s) const;
		bool operator>(const string& s) const;
		bool operator>=(const string& s) const;
		bool operator==(const string& s) const;
		bool operator!=(const string& s) const;

	private:
		//成员变量
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;

		//VS2022在底层实现string时用了个buff字符数组,避免空间浪费
		//_size < 16 串存在buff数组上
		//_size >= 16 串存在_str指向的空间上
		//char buff[16];
	public:
		//成员常量
		static const size_t npos;
	};
	
	//非成员函数重载
	
	//流插入和流提取
	ostream& operator<<(ostream& out, const string& s);
	istream& operator>>(istream& in, string& s);
	
	//getline
	istream& getline(istream& in, string& s, char delim = '\n');
	
	//交换
	void swap(string& x, string& y);

}

string.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "string.h"

//命名空间名字相同,在不同文件,是同一个命名空间
namespace bs
{
	//string::string()
	//	:_str(new char[1]{'\0'})
	//	,_size(0)
	//	,_capacity(0)
	//{}
	
	string::string(const char* str)
	//strlen的时间复杂度O(N),所以才只在初始化列表中初始化_size
	//再在函数体中用_size给_str和_capacity赋值
		:_size(strlen(str))
	{
		//cout << "string::string(const char* str)" << endl;
		//多申请一个空间给\0
		_str = new char[_size + 1];
		_capacity = _size;
		//strcpy和memcpy的区别
		//strcpy拷贝到\0就结束了,memcpy会把给定的长度拷贝完
		//所以用memcpy更保险
		//strcpy(_str, str);
		memcpy(_str, str, _size + 1);
	}
	
	//传统写法和现代写法的区别
	//传统写法是自己造轮子,现代写法是直接用别人写好的

	//传统写法
	//string::string(const string& str)
	//{
	//	//cout << "string::string(const string& str)" << endl;
	//	_str = new char[str._capacity + 1];
	//	memcpy(_str, str._str, str._size + 1);
	//	_size = str._size;
	//	_capacity = str._capacity;
	//}

	//string内部实现的swap
	void string::swap(string& str)
	{
		//内置类型交换,直接用库里swap的交换
		std::swap(_str, str._str);
		std::swap(_size, str._size);
		std::swap(_capacity, str._capacity);

	}

	//现代写法
	string::string(const string& str)
	{
		//cout << "string::string(const string& str)" << endl;
		//利用直接构造实例化一个tmp,再让tmp跟*this交换
		string tmp(str._str);
		swap(tmp);
	}

	//传统写法
	//string& string::operator=(const string& str)
	//{
	//	if (this != &str)//防止自己赋值自己
	//	{
	//		char* tmp = new char[str._capacity + 1];
	//		memcpy(tmp, str._str, str._size + 1);
	//		delete[] _str;
	//		_str = tmp;
	//		_size = str._size;
	//		_capacity = str._capacity;
	//	}
	//
	//	return *this;
	//}

	//现代写法
	//string& string::operator=(const string& str)
	//{
	//	if (this != &str)
	//	{
	//		string tmp(str);
	//		swap(tmp);
	//	}
    //
	//	return *this;
	//}

	//现代写法-更简洁
	string& string::operator=(string tmp)//传值传参调用构造
	{
		//cout << "string& string::operator=(string tmp)" << endl;
		swap(tmp);
		return *this;
	}

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

	string::iterator string::begin()
	{
		return _str;
	}

	string::iterator string::end()
	{
		return _str + _size;
	}

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

	size_t string::size() const
	{
		return _size;
	}
	
	const char& string::operator[](size_t i) const
	{
		assert(i < _size);
		return _str[i];
	}
	
	char& string::operator[](size_t i)
	{
		assert(i < _size);
		return _str[i];
	}

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

	void string::reserve(size_t n)
	{
		//cout << "reserve" << n << endl;
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];
			//strcpy(tmp, _str);
			memcpy(tmp, _str, _size + 1);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}
	
	void string::push_back(char ch)
	{
		if (_size >= _capacity)
		{
			//扩容
			size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
			reserve(newcapacity);
		}
		_str[_size] = ch;
		++_size;
		_str[_size] = '\0';
	}
	
	void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			//扩容
			size_t newcapacity = _size + len < 2 * _capacity ? 2 * _capacity : _size + len;
			reserve(newcapacity);
		}
		//strcpy(_str + _size, str);
		memcpy(_str + _size, str, len + 1);
		_size += len;
	}

	string& string::operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}
	
	string& string::operator+=(const char* str)
	{
		append(str);
		return *this;
	}
	
	string& string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);
	
		//实际_size 不会大于_capacity
		if (_size >= _capacity)
		{
			//扩容
			size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
			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;

		return *this;
	}

	string& string::insert(size_t pos, const char* str)
	{
		assert(pos <= _size);

		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			//扩容
			size_t newcapacity = _size + len < 2 * _capacity ? 2 * _capacity : _size + len;
			reserve(newcapacity);
		}

		//挪动数据 两种方法
		//int end = _size;
		//while (end >= (int)pos)
		//{
		//	_str[end + len] = _str[end];
		//	--end;
		//}

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

		//memcpy(_str + pos, str, len);
		for (size_t i = 0; i < len; ++i)
		{
			_str[pos + i] = str[i];
		}
		_size += len;

		return *this;
	}

	string& string::erase(size_t pos, size_t len)
	{
		//要删的数据长度大于pos位置及其后面的数据长度
		if (len == npos || len >= _size - pos)
		{
			_size = pos;
			_str[_size] = '\0';
		}
		else
		{
			size_t i = pos + len;
			memmove(_str + pos, _str + i, _size - i + 1);
			_size -= len;
		}

		return *this;
	}

	void string::pop_back()
	{
		assert(_size > 0);
		--_size;
		_str[_size] = '\0';
	}

	size_t string::find(char ch, size_t pos) const
	{
		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
	{
		//strstr时间复杂度O(N^2)
		const char* p = strstr(_str + pos, str);
		if (p != nullptr) return p - _str;
		return npos;
	}

	string string::substr(size_t pos, size_t len) const
	{
		assert(pos < _size);

		if (len == npos || len >= _size - pos)
			len = _size - pos;
		
		string ret;
		ret.reserve(len);
		for (size_t i = 0; i < len; ++i)
		{
			ret += _str[pos + i];
		}
		//cout << &ret << endl;
		return ret;
	}

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

	//"hello"  "hello"  false
	//"hellowww" "hello" false
	//"hello"  "hellowww" true
	bool string::operator<(const string& s) const
	{
		size_t i1 = 0, i2 = 0;
		while (i1 < _size && i2 < s._size)
		{
			if (_str[i1] < s._str[i2])
			{
				return true;
			}
			else if (_str[i1] > s._str[i2])
			{
				return false;
			}
			else
			{
				i1++;
				i2++;
			}
		}

		return i2 < s._size;
	}
	
	bool string::operator<=(const string& s) const
	{
		return (*this < s || *this == s);
	}

	bool string::operator>(const string& s) const
	{
		return !(*this <= s);
	}

	bool string::operator>=(const string& s) const
	{
		return !(*this < s);
	}

	bool string::operator==(const string& s) const
	{
		size_t i1 = 0, i2 = 0;
		while (i1 < _size && i2 < s._size)
		{
			if (_str[i1] != s._str[i2])
			{
				return false;
			}
			else
			{
				i1++;
				i2++;
			}
		}

		return i1 == _size && i2 == s._size;
	}

	bool string::operator!=(const string& s) const
	{
		return !(*this == s);
	}

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

	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch = in.get();//一个字符一个字符地读取
		char buff[128];//空间换时间
		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;
	}

	istream& getline(istream& in, string& s, char delim)
	{
		s.clear();
		char ch = in.get();
		char buff[128];
		int i = 0;
		while (ch != delim)
		{
			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 swap(string& x, string& y)
	{
		x.swap(y);//直接调用string内部实现地swap
	}

	const size_t string::npos = -1;
	//定义静态变量时,static仅在声明处需要,用于指定内部链接;定义处无需重复,避免重复声明内部链接属性。
}

test.cpp

#include "string.h"

namespace bs
{
	void test_string1()
	{
		string s1;
		cout << s1.c_str() << endl;

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

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

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

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

		const string s3("hello world");
		string::const_iterator it3 = s3.begin();
		while (it3 != s3.end())
		{
			cout << *it3 << " ";
			it3++;
		}
		cout << endl;
	}

	void test_string2()
	{
		string s1("hello world");
		s1.push_back('x');
		cout << s1.c_str() << endl;
		s1.append("hello bit");
		cout << s1.c_str() << endl;
		s1 += 'y';
		s1 += "zzz";
		cout << s1.c_str() << endl << endl;

		string s2("hello world");
		cout << s2 << endl;
		s2 += '\0';
		s2 += '\0';
		s2 += '!';
		cout << s2 << endl;
		cout << s2.c_str() << endl;
		s2 += "yyyyyyyyyyyyyyyyyyyy";
		cout << s2 << endl;
		cout << s2.c_str() << endl;
	}

	void test_string3()
	{
		string s1("hello world");
		s1.insert(6, 'x');
		cout << s1 << endl;
		//s1.insert(60, 'x');
		s1.insert(0, 'x');
		cout << s1 << endl << endl;

		string s2("hello world");
		s2.insert(6, "xxx");
		cout << s2 << endl;
		s2.insert(0, "xxx");
		cout << s2 << endl << endl;

		string s3("hello world");
		s3.erase(7, 3);
		cout << s3 << endl;

		string s4("hello world");
		s4.erase(7, 40);
		cout << s4 << endl;

		string s5("hello world");
		s5.erase(7);
		cout << s5 << endl << endl;

		string s6("hello world");
		while (s6.size())
		{
			s6.pop_back();
			cout << s6 << endl;
		}
		//s6.pop_back();
		cout << endl;

	}

	void split_url(string& url)
	{
		size_t i1 = url.find(':');
		if (i1 != string::npos)
		{
			//string s = url.substr(0, i1);
			//cout << &s << endl;
			//cout << s << endl;
			cout << url.substr(0, i1) << endl;
		}

		size_t i2 = i1 + 3;
		size_t i3 = url.find('/', i2);
		if (i3 != string::npos)
		{
			cout << url.substr(i2, i3 - i2) << endl;
			cout << url.substr(i3 + 1) << endl;
		}
		cout << endl;
	}

	void test_string4()
	{
		string url1 = "https://legacy.cplusplus.com/reference/string/string/?kw=string";
		string url2 = "https://www.doubao.com/chat/5772109691013378";
		split_url(url1);
		split_url(url2);
	}

	void test_string5()
	{
		string s1("hello"), s2("hello");
		string s3("helloxxx"), s4("hello");
		string s5("hello"), s6("helloxxx");

		cout << (s1 < s2) << endl;
		cout << (s3 < s4) << endl;
		cout << (s5 < s6) << endl << endl;

		cout << (s1 <= s2) << endl;
		cout << (s3 <= s4) << endl;
		cout << (s5 <= s6) << endl << endl;

		cout << (s1 > s2) << endl;
		cout << (s3 > s4) << endl;
		cout << (s5 > s6) << endl << endl;

		cout << (s1 >= s2) << endl;
		cout << (s3 >= s4) << endl;
		cout << (s5 >= s6) << endl << endl;

		cout << (s1 == s2) << endl;
		cout << (s3 == s4) << endl;
		cout << (s5 == s6) << endl << endl;

		cout << (s1 != s2) << endl;
		cout << (s3 != s4) << endl;
		cout << (s5 != s6) << endl << endl;

	}

	void test_string6()
	{
		//string s1("hello"), s2("hello");
		//cout << s1 << " " << s2 << endl;
		//cin >> s1 >> s2;
		//cout << s1 << " " << s2 << endl;

		//string line;
		//getline(cin, line);
		//cout << line << endl;
		//getline(cin, line, '!');
		//cout << line << endl;

		string s3;
		cin >> s3;
		cout << s3.size() << endl;
		cout << s3 << endl;
	}

	void test_string7()
	{
		string s1("hello");
		cout << s1 << endl;
		string s2(s1);
		cout << s2 << endl;
		s2[0] = 'x';
		cout << s1 << endl;
		cout << s2 << endl;

		string s3;
		s3 = s1;
		cout << s3 << endl;

	}

	void test_string8()
	{
		//为什么string要设计一个成员函数swap和一个全局函数swap?
		string s1("hello"), s2("world");
		swap(s1, s2); //如果我们不实现全局的swap,这里会调用算法库
		//的模板的swap,那样将会有3次深拷贝,效率低下
		s1.swap(s2);//调用成员函数swap效率高,不会有拷贝问题,
		//string的设计者怕使用者不知道string中有成员函数swap,
		//而去调用算法库的,所以又写了一个全局的,这样它会优先调全局的
	}

}

int main()
{
	bs::test_string8();
	//typeid返回类型或对象的真实信息
	//cout << typeid(bs::string::iterator).name() << endl;
	//cout << typeid(std::string::iterator).name() << endl;

}

//int main()
//{
//	string s1("11111111");
//	string s2("111111111111111111111111111111111111111");
//	cout << sizeof(s1) << endl;//28
//	cout << sizeof(s2) << endl;//28
//	//库里底层实现多了个buff字符数组,以空间换时间
//}

请添加图片描述


网站公告

今日签到

点亮在社区的每一天
去签到