• C++【string类】


    先给大家推荐两个网站用于学习C++
    cplusplus
    C++官网


    一、为什么学习string类

    1、C语言中的字符串

    C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问

    2、两道面试题

    字符串转整型数字
    字符串相加

    OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数


    二、标准库中的string类

    string类文档介绍

    string就是一个管理字符串的数组
    string的出现早于STL,C++早期的库就只有string,所以在功能操作方面把string归到STL中,但是历史方面string不属于STL

    1. 字符串是表示字符序列的类
    1. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
    1. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)。
    1. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
    1. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个
      类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作

    总结:

    1. string是表示字符串的字符串类
    2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作
    3. string在底层实际是:basic_string模板类的别名,typedef basic_string string;
    4. 不能操作多字节或者变长字符的序列

    使用string类时,必须包含#include头文件以及using namespace std;

    1、string类对象的常见构造

    函数名称功能说明
    string() (重点)构造空的string类对象,即空字符串
    string(const char* s) (重点)用C-string来构造string类对象
    string(size_t n, char c)string类对象中包含n个字符c
    string(const string&s) (重点)拷贝构造函数

    我们先来看看这些,还有许多没有出现在表格的知识,我们下面会补充的

    我们知道string其实是一个模板,那为什么要把string设置成为一个模板呢?
    这就要涉及到编码了,比如我们常见的ASCLL码

    我们知道计算机是美国人发明的,美国人就要在计算机上面展现他们的文字,也就是26个大小写字母,10个阿拉伯数字,加上一些标点符号以及特殊符号,就够了。所以,美国人把他们的文字编完以后取了个名字就叫ASCLL编码

    编码的本质就是:计算机存的值与我们的文字符号要建立一种映射关系

    也就是说,我们想要存入abcd等内容,在计算机内部是转换成一个值来表示的,这个计算机的值与我们存入的内容是一种映射关系

    举例:
    在这里插入图片描述

    但是,这个ascll码表仅仅只针对中国文字,计算机是在世界上流行的,所以只能表示一种语言肯定是不够的。计算机需要表示世界上大多数国家的语言

    这个时候unicode出现了,也就是我们常说的万国码/统一码,用于解决各个国家的文字

    所以unicode给出了好几种解决方案:UTF-8 , UTF-16 , UTF-32

    UTF-8 : UTF-8要兼容ascll码表,所以被用的最多。起步用一个字符来表示,比如说用一个字符来表示ascll,用两个字符来表示中文,一些生僻字或者较难的中文可以用三个甚至是四个字符来表示。

    UTF-16 :UTF-16不兼容ascll码表,并且起步用两个字符来表示

    UTF-32 : 同理,UTF-32不兼容ascll码表,并且起步用四个字符来表示

    当然,国外的人做的终究不够深入,所以中国人专门设置了一个编码——GBK

    案例:
    在这里插入图片描述

    这就是为什么把string类弄成模板的原因——你的实现方法可能是UTF-8,也有可能是UTF-16,还有可能是UTF-32。也就是说编码有可能是一个字节一个字节来编的,也有可能是两个两个字节来编的,还有可能是四个四个字节来编的

    在这里插入图片描述

    我们来看看string的大致模板:

    // 动态增长字符数组
    template<class T>
    class basic_string
    {
    private:
    	T* _str;
    	size_t _size;
    	size_t _capacity;
    };
    typedef basic_string<char> string;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这里不能直接typedef basic_string string;因为普通类的类名是类型,但是类模板的类名不是类型,要带上模板参数 < char >

    接下来回归主题,看看string类的构造函数:
    在这里插入图片描述
    可以看到string类构造函数有7个,但是我们不需要全部掌握,重点会列举出来:

    void test_test1()
    {
    	string s1;//等价于 basic_string s1;
    	string s2("西安市雁塔区");
    	string s3 = "西安市雁塔区";//没有explicit修饰,可以支持隐式类型转换
    	string s4(10, '*');
    	cout << s1 << endl;
    	cout << s2 << endl;
    	cout << s3 << endl;
    	cout << s4 << endl;
    	s2 += "莱安中心";//底层就是扩容
    	cout << s2 << endl;
    	string s5(s2);//拷贝构造
    	string s6 = s2;//拷贝构造
    	cout << s5 << s6 << endl;
    
    	string s7("hello world", 5);//用前5个字符数据来初始化
    	cout << s7 << endl;
    	string s8(s7, 2, 3);//从第2个下标位置开始,后面的3个字符数据来初始化,如果3位置上给的值过大,那么就有多少取多少,直到结束
    	cout << s8 << endl;
    	string s9(s7, 2, 30);
    	cout << s9 << endl;
    	string s10(s7, 1);
    	cout << s10 << endl;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    2、string类数据的遍历

    for循环

    //char& operator[](size_t i)
    //{
    //	assert(i<_size);
    //	return _str[i];
    //}
    	string s1("1234");
    	// 遍历
    	// 1、下标 []
    	for (size_t i = 0; i < s1.size(); ++i)//size不计算斜杠0
    	{
    		s1[i]++;//operator[]
    	}
    	cout << s1 << endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    operator[]作用:取第i个位置的字符,并且返回引用

    范围for

    	string s1("1234");
    	// 遍历
    	// 2、范围for
    	for (auto& ch : s1)//这里要用引用,不然修改的是拷贝的临时变量
    	{
    		ch--;
    	}
    	cout << s1 << endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    反转数据

    	size_t begin = 0, end = s1.size() - 1;//下标
    	while (begin < end)
    	{
    		swap(s1[begin++], s1[end--]);
    	}
    	cout << s1 << endl;
    	reverse(s1.begin(), s1.end());//后面算法会学到
    	cout << s1 << endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    迭代器

    迭代器iterator是一个行为上像指针一样的东西,但并不是完全就是指针

    在这里插入图片描述
    非const版本正向迭代器(begin与end)

    void test()
    {
    	string s1 = "123456";
    	string::iterator is1 = s1.begin();
    	while (is1 != s1.end())
    	{
    		is1 += 1;
    		++is1;
    	}
    	is1 = s1.begin();
    	while (is1 != s1.end())
    	{
    		cout << *is1 <<" ";
    		++is1;
    	}
    	cout << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

    非const版本反向迭代器(rbegin与rend)

    void test1()
    {
    	string s1 = "123456";
    	string::iterator is1 = s1.begin();
    	while (is1 != s1.end())
    	{
    		cout << *is1 << " ";
    		++is1;
    	}
    	cout << endl;
    	//string::reverse_iterator ris1 = s1.rbegin();
    	auto ris1 = s1.rbegin();//这里auto就体现出作用了
    	while (ris1 != s1.rend())//这里不能一个使用正向一个使用反向,会报错
    	{
    		cout << *ris1 << " ";
    		++ris1;//反向也是使用++,因为反向就是反过来了,++表示从右向左走
    	}
    	cout << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    const版本正向迭代器

    这里的cosnt不是保护迭代器本身,而是保护迭代器指向的内容

    void Print(const string& s)//使用引用,防止拷贝构造
    {
    	string::const_iterator is = s.begin();//const修饰is指向的内容(解引用)不能修改,is是可以进行修改的
    	//const string::_iterator is = s.begin();//这样写const修饰的是is了,is不能被修改,不能够进行++is操作
    
    	while (is != s.end())
    	{
    		is += 1;
    		++is;
    	}
    	is = s.begin();
    	while (is != s.end())
    	{
    		cout << *is << " ";
    		++is;
    	}
    	cout << endl;
    }
    int main()
    {
    	string s1 = "123456";
    	Print(s1);
    	//test1();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述

    const版本反向迭代器
    在这里插入图片描述

    既然下标访问就可以完成遍历了,为什么还有有迭代器呢?
    这是因为我们后面的数据结构中链表,树等等的知识就不能使用下标访问了,迭代器才是最通用的

    总结:

    1、只读功能函数——const版本
    2、只写功能函数——非const版本
    3、读写功能函数——const + 非const版本

    3、string类对象的容量操作

    功能说明
    size(重点)返回字符串有效字符长度
    length返回字符串有效字符长度
    capacity返回空间总大小
    empty (重点)检测字符串释放为空串,是返回true,否则返回false
    clear (重点)清空有效字符
    reserve (重点)为字符串预留空间
    resize (重点)将有效字符的个数该成n个,多出的空间用字符c填充

    string容量相关方法使用代码演示

    void test4()
    {
    	string s("hello world");
    	cout << s.size() << endl;
    	cout << s.length() << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述
    可以看到size和length得出结果是一样的,那么为什么会出现两个作用一样,名字不一样的接口呢?

    前面说过了,C++中的string出现早于STL,早期的库只有string。本来C++刚开始是使用length的,但是其他容器接口,比如链表、树、和哈希表等使用length()意义就不明确了,所以为了保持接口一致就引入了size()

    样例:
    在这里插入图片描述
    在这里插入图片描述

    扩容:
    在这里插入图片描述
    在这里插入图片描述
    reverse——修改capacity容量

    在这里插入图片描述
    vs中开辟空间大一点是为了内存的对齐,我们后面会学到

    在这里插入图片描述

    resize——修改有效数据个数(特殊情况会修改capacity)

    前提条件: size=11 , capacity=15

    情况一:size<=11——删除数据
    情况二:11 情况三:size>15——扩容+插入数据
    在这里插入图片描述

    总结

    1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
    1. clear()只是将string中有效字符清空,不改变底层空间大小。
    1. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
    1. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

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

    函数名称功能说明
    operator[] (重点)返回pos位置的字符,const string类对象调用
    begin+ endbegin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
    rbegin + rendbegin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
    范围forC++11支持更简洁的范围for的新遍历方式

    string中元素访问及遍历代码

    5、string类对象的修改操作

    函数名称功能说明
    push_back在字符串后尾插字符c
    append在字符串后追加一个字符串
    operator+= (重点)在字符串后追加字符串str
    c_str(重点)返回C格式字符串
    find + npos(重点)从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
    rfind从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
    substr在str中从pos位置开始,截取n个字符,然后将其返回
    insert在str中从pos位置插入数据
    erase在str中从pos位置开始删除数据
    assign清空之后再赋值
    replace替换为指定数据

    string中插入和查找等使用代码
    在这里插入图片描述

    insert和erase:

    void test_string8()
    {
    	string s("hello world");
    	s.insert(0, "bit");//在下标为0的位置插入bit
    	cout << s << endl;
    	s.insert(9, "bit");//在下标为9的位置插入bit
    	cout << s << endl;
    	s.erase(9, 3);//从第9个位置开始,连续删除3个数据
    	cout << s << endl;
    	s.erase(0, 3);//从第0个位置开始,连续删除3个数据
    	cout << s << endl;
    	s.erase(5, 30);
    	cout << s << endl;//如果len不给值,或者值过大,那么从pos位置开始,直到删除掉后面所有数据截止
    	s.erase(5);
    	cout << s << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    void test_string9()
    {
    	string s1("hello world hello world");
    	string s2("hello world hello world");
    	string s3(s2);
    	string s4(s3);
    	s1.assign("hello bit", 5);//字符串前面5个字符赋值给s1
    	cout << s1 << endl;
    	s2.replace(6, 5, "bit");//将s2下标为6的位置,到下标6后面的5个位置的数据替换为bit
    	cout << s2 << endl;
    	// 将' '替换成空格
    	size_t pos = s3.find(' ');//find寻找空格
    	while (pos != string::npos)
    	{
    		s3.replace(pos, 1, "20%");
    		pos = s3.find(' ', pos + 3);
    	}
    	cout << s3 << endl;
    	string ret;//空串
    	for (auto ch : s4)
    	{
    		if (ch != ' ')
    		{
    			ret += ch;
    		}
    		else
    		{
    			ret += "%20";
    		}
    	}
    	cout << ret << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    在这里插入图片描述

    void test_string10()
    {
    	string file1("test.cpp");
    	string file2("String.h");
    	FILE* fout1 = fopen(file1.c_str(), "r");
    	FILE* fout2 = fopen(file2.c_str(), "r");
    	assert(fout1);
    	assert(fout2);
    	char ch1 = fgetc(fout1);
    	while (ch1 != EOF)
    	{
    		cout << ch1;
    		ch1 = fgetc(fout1);
    	}
    	char ch2 = fgetc(fout2);
    	while (ch2 != EOF)
    	{
    		cout << ch2;
    		ch2 = fgetc(fout2);
    	}
    	fclose(fout1);
    	fclose(fout2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述
    find和rfind:

    void test_string11()
    {
    	// "Test.cpp"
    	// "Test.cpp.tar.zip"
    	string file;
    	cin >> file;
    	// 要求取后缀
    	//size_t pos = file.find('.');
    	size_t pos = file.rfind('.');//从后往前找
    	if (pos != string::npos)
    	{
    		//string suffix = file.substr(pos, file.size() - pos);
    		string suffix = file.substr(pos);//截取
    		cout << suffix << endl;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    string中插入和查找等代码

    总结:

    1. 在string尾部追加字符时,s.push_back ( c ) / s.append(1, c) / s += 'c’三种的实现方式差不多,一般 情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
    2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

    6、string类非成员函数

    函数功能说明
    aperator+尽量少用,因为传值返回,导致深拷贝效率低
    relational operators比较大小
    aperator<<输出运算符重载
    aperator>>输入运算符重载
    getline获取一行字符串

    三、string类的模拟实现

    #define _CRT_SECURE_NO_WARNINGS
    #pragma once
    #include
    #include
    using namespace std;
    
    namespace bzh
    {
    	class string
    	{
    	public:
    		typedef char* iterator;
    		iterator begin()
    		{
    			return _str;
    		}
    		iterator end()
    		{
    			return _str + _size;
    		}
    		/*string()
    		{
    			_str = new char[1];
    			_str[0] = '\0';
    			_capacity = _size = 0;
    		}*/
    		string(const char* str = "") //'\0'->0  "\0" ->\0\0  ""->\0
    		{
    			_size = strlen(str);
    			_capacity = _size;//这里就让容量等于数据个数,开空间时再多开辟一个空间来存放\0
    			_str = new char[_capacity + 1];
    			strcpy(_str, str);
    		}
    
    		void swap(string& s)
    		{
    			std::swap(_str, s._str);//因为库函数里面有swap函数,现在我们类里面也定义了一个swap函数
    			std::swap(_size, s._size);//所以调用swap函数先在局部找swap,找不到然后再去全局找swap
    			std::swap(_capacity, s._capacity);
    			//这里不加std,3个swap就都是调用类里面的swap函数,参数不符合(类函数有this指针,但是我们不能传值给它)
    			//所以会报错,我们要加上std+域作用限定符来表面我们调用的swap是库里面的函数
    		}
    
    		//s1(s3)
    		string(const string& s)//拷贝构造优化
    			//要先把this指针指向的对象初始化,不然等下交换之后tmp内的_str就指向一个随机值也就是没有初始化野指针
    			:_str(nullptr)
    			, _size(0)
    			, _capacity(0)
    		{
    			string tmp(s._str);
    			swap(tmp);//等价于this->swap(tmp);
    		}
    		//string(const string& s)//拷贝构造
    		//{
    		//	_str = new char[s._capacity + 1];
    		//	_capacity = s._capacity;
    		//	_size = s._size;
    		//	strcpy(_str, s._str);
    		//}
    
    		//s1 = s3
    		string& operator=(string s)//----------------------------------赋值拷贝进一步优化
    		{//这里直接复用拷贝构造函数,s就被赋值为了s3
    			swap(s);//s赋值为了s3,所以直接交换
    			return *this;
    		}
    		//string& operator=(const string& s)//--------------------------------赋值拷贝优化
    		//{
    		//	if (this != &s)
    		//	{
    		//		string tmp(s._str);//也可以写成string tmp(s);拷贝构造
    		//		swap(tmp);
    		//	}
    		//	return *this;
    		//}
    		//string& operator=(const string& s)//--------------------------------赋值拷贝
    		//{
    		//	if (this != &s)
    		//	{
    		//		char* p = new char[s._capacity + 1];
    		//		strcpy(p, s._str);
    		//		delete[]_str;
    		//		_str = p;
    		//		_size = s._size;
    		//		_capacity = s._capacity;
    		//	}
    		//	return *this;
    		//}
    
    		~string()
    		{
    			delete[] _str;
    			_str = nullptr;
    			_capacity = _size = 0;
    		}
    		size_t size() const
    		{
    			return _size;
    		}
    		size_t capacity() const
    		{
    			return _capacity;
    		}
    		char& operator[](int pos)//可读可写版本
    		{
    			assert(pos < _size);
    			return _str[pos];
    		}
    		char& operator[](int pos) const//只读版本
    		{
    			assert(pos < _size);
    			return _str[pos];
    		}
    		void resize(size_t n, char ch = '\0')
    		{
    			if (n > _size)
    			{
    				reserve(n);
    				for (size_t i = _size; i < n; ++i)
    				{
    					_str[i] = ch;
    				}
    				_size = n;
    				_str[_size] = '\0';
    			}
    			else
    			{
    				_str[n] = '\0';
    				_size = n;
    			}
    		}
    		void reserve(size_t n)
    		{
    			if (n > _capacity)//只扩容,不缩容
    			{
    				char* p = new char[n + 1];//多一个\0
    				strcpy(p, _str);
    				delete[] _str;
    				_str = p;
    				_capacity = n;
    			}
    		}
    		void push_back(char ch)
    		{
    			if (_size == _capacity)
    				//如果上面构造的时候不采用_capacity = _size,这里就不能这样判断;如果是开空间_capacity+1就可以
    			{
    				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;//解决空string问题
    				reserve(newcapacity);
    				//reserve(_capacity * 2);//扩容两倍
    			}
    			_str[_size] = ch;
    			++_size;
    			_str[_size] = '\0';
    		}
    		void append(const char* str)
    		{
    			size_t len = strlen(str);
    			if (len+_size > _capacity)
    			{
    				reserve(len + _size);
    			}
    			strcpy(_str + _size, str);
    			_size += len;
    		}
    		string& operator+=(char ch)
    		{
    			push_back(ch);
    			return *this;
    		}
    		string& operator+=(const char* str)
    		{
    			append(str);
    			return *this;
    		}
    		const char* c_str() const
    		{
    			return _str;
    		}
    		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)//这里pos要强转为int类型,不然int类型的end会发生隐式类型提升
    			//{
    			//	_str[end + 1] = _str[end];
    			//	--end;
    			//}
    			size_t end = _size + 1;//这里+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 (len + _size > _capacity)
    			{
    				reserve(len + _size);
    			}
    			/*int end = _size;
    			while (end >= (int)pos)
    			{
    				_str[end + len] = _str[end];
    				--end;
    			}*/
    			size_t end = _size + len;
    			while (end > pos + len - 1)
    			{
    				_str[end] = _str[end - len];//当end小于len时就越界了
    				--end;
    			}
    			strncpy(_str + pos, str, len);//不能用strcpy,会拷贝\0,c_str遇到\0停止
    			_size += len;
    			return *this;
    		}
    		string& erase(size_t pos, size_t len = npos)
    		{
    			assert(pos < _size);
    			if (len == npos || pos + len >= _size)
    			{
    				_str[pos] = '\0';
    				_size = pos;
    			}
    			else
    			{
    				strcpy(_str + pos, _str + pos + len);
    				_size -= len;
    			}
    			return *this;
    		}
    		size_t find(const char ch, size_t pos = 0) const
    		{
    			assert(pos < _size);
    			while (pos < _size)
    			{
    				if (_str[pos] == ch)
    				{
    					return pos;
    				}
    				++pos;
    			}
    			return npos;
    		}
    		size_t find(const char* str, size_t pos = 0) const
    		{
    			assert(pos < _size);
    			const char* p = strstr(_str + pos, str);
    			if (p == nullptr)
    			{
    				return npos;
    			}
    			return p - _str;
    		}
    		void clear()
    		{
    			_size = 0;
    			_str[0] = '\0';
    		}
    	private:
    		char* _str;  
    		size_t _size;
    		size_t _capacity;
    		const static size_t npos = -1;
    	};
    	ostream& operator<<(ostream& out, const string& s)//这里s用const修饰,那么上面的size函数要用const修饰
    	{
    		for (size_t i = 0; i < s.size(); ++i)
    		{
    			out << s[i];
    		}
    		return out;
    	}
    	istream& operator>>(istream& in, string& s)//s不能用const修饰
    	{
    		s.clear();
    		//char ch = in.get();//输入数据过大要一直扩容,不好
    		//while (ch != ' ' && ch != '\n')
    		//{
    		//	s += ch;
    		//	//in >> ch;
    		//	ch = in.get();
    		//}
    
    		char cnt[128] = { '\0' };
    		size_t i = 0;
    		char ch = in.get();
    		while (ch != ' ' && ch != '\n')
    		{
    			if (i == 127)
    			{
    				s += cnt;
    				i = 0;
    			}
    			cnt[i++] = ch;
    			ch = in.get();
    		}
    		if (i > 0)
    		{
    			cnt[i] = '\0';
    			s += cnt;
    		}
    		return in;
    	}
    	istream& getline(istream& is, string& s)
    	{
    		char ch = is.get();
    		while (ch != '\n')
    		{
    			s += ch;
    			ch = is.get();
    		}
    		return is;
    	}
    	/*void func(const string& s)——————>const版本
    	{
    		for (size_t i = 0; i < s.size(); ++i)
    		{
    			cout << s[i] << " ";
    		}
    		cout << s.c_str() << endl;
    		string::iterator it1 = s.begin();
    		while (it1 != s.end())
    		{
    			(*it1)--;
    			++it1;
    		}
    		cout << s.c_str() << endl;
    		for (auto ch : s)
    		{
    			cout << ch << " ";
    		}
    		cout << endl;
    	}*/
    	void test1()
    	{
    		string s1("hello world 123");
    		cout << s1.c_str() << endl;
    
    		for (size_t i = 0; i < s1.size(); ++i)
    		{
    			s1[i]++;
    		}
    		cout << s1.c_str() << endl;
    
    		string::iterator it1 = s1.begin();
    		while (it1 != s1.end())
    		{
    			(*it1)--;
    			it1++;
    		}
    		cout << s1.c_str() << endl;
    
    		for (auto& e : s1)
    		{
    			e += 1;
    		}
    		cout << s1.c_str() << endl;
    	}
    
    	void test2()
    	{
    		string s2("hello");
    		s2 += ' ';
    		s2 += 'a';
    		s2 += "world hello world";
    		cout << s2.c_str() << endl;
    
    		string s;
    		s += 'q';
    		cout << s.c_str() << endl;
    	}
    
    	void test3()
    	{
    		string s1("helloworld");
    		s1.insert(5, ' ');
    		cout << s1.c_str() << endl;
    		s1.insert(0, 'a');
    		s1.insert(0, 'b');
    		s1.insert(0, 'c');
    		cout << s1.c_str() << endl;
    
    		string s2("helloworld");
    		cout << s2.c_str() << endl;
    		s2.insert(5, " + ");
    		cout << s2.c_str() << endl;
    		s2.insert(0, "hello world ");
    		cout << s2.c_str() << endl;
    	}
    
    	void test4()
    	{
    		string s1("hello world hello world");
    		s1.erase(5, 1);
    		cout << s1.c_str() << endl;
    		s1.erase(5, 11);
    		cout << s1.c_str() << endl;
    	}
    	void test5()
    	{
    		string s1("hello world");
    		s1.resize(5);
    		cout << s1.size() << endl;
    		cout << s1.capacity() << endl;
    		cout << s1.c_str() << endl << endl;
    
    		string s2("hello world");
    		//s2.resize(15);
    		s2.resize(15, 'x');
    		cout << s2.size() << endl;
    		cout << s2.capacity() << endl;
    		cout << s2.c_str() << endl << endl;
    
    		string s3("hello world");
    		s3.resize(20, 'x');
    		cout << s3.size() << endl;
    		cout << s3.capacity() << endl;
    		cout << s3.c_str() << endl << endl;
    	}
    
    	void test6()
    	{
    		//string s1("hello world");
    		std::string s1("hello world");
    		cout << s1 << endl;
    		cout << s1.c_str() << endl;
    
    		const char* ptr = 0;//0可以初始化指针,不会报错;'\0'初始化指针编译的时候会报错
    		//s1.insert(5, '\0');//用库函数的时候,有三个参数,一个pos位置,一个n插入个数,一个ch插入字符
    		s1.insert(5,1, '\0');
    		cout << s1.size() << endl;
    		cout << s1.capacity() << endl;
    		cout << s1 << endl;
    		cout << s1.c_str() << endl;
    
    		string s2;
    		cin >> s2;
    		cout << s2 << endl;
    
    		/*string s3;
    		getline(cin, s3);
    		cout << s3 << endl;*/
    	}
    	void test7()
    	{
    		string s1("hello world");
    		string s2(s1);
    
    		cout << s1 << endl;
    		cout << s2 << endl;
    
    		string s3("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
    		string s4 = s3;
    		cout << s4 << endl;
    		cout << s3 << endl;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
    • 444
    • 445
    • 446
    • 447
    • 448
    • 449
    • 450
    • 451
    • 452
    • 453
    • 454
    • 455
    • 456
    • 457
    • 458
    • 459
    • 460
    • 461
    • 462
    • 463
    • 464
    • 465
    • 466
    • 467
    • 468
    • 469
    • 470

    以上只是string类的核心功能实现,大家可以继续补充接口的实现功能

    string类的补充知识

    大家先看一段代码:

    int main()
    {
    	string s1("1111");
    	string s2("111111111111111111111111111111111111");
    	cout << sizeof(s1) << endl;
    	cout << sizeof(s2) << endl;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    结果会出大家意料之外的
    在这里插入图片描述
    为什么会这样呢?
    这是因为,string类在vs下面储存的时候,有4个主要数据,一个是buff,一个是ptr,一个是size,一个是capacity,如下图
    在这里插入图片描述
    如果string对象小于16,那么就存储在buff中,否者就通过ptr指向存储字符串的空间,这也就是为什么每一个string对象的字节是28了(64位平台字节40)
    在这里插入图片描述

    写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
    引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给
    计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该
    对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

    在这里插入图片描述

    总结

    本期内容就到这里了,把常用的string接口用好就行,不常用的以后遇到我们再查看学习使用就行
    不需要掌握全部接口

  • 相关阅读:
    掌握了不一定能拿到大厂 Offer,但不掌握一定进不去大厂的算法
    WebRTC与CSS滤镜(CSS filter)
    [Games101] Lecture 02 Review of Linear Algebra
    项目管理之立项TG0
    02.MySQL函数及约束、多表笔记
    Ubuntu 22.04 下 CURL(C++) 实现分块上传/下载文件源码
    FutureTask的测试使用和方法执行分析
    传染病sir模型matlab案例代小村亚姆
    Qt中的事件处理
    测试技能提升篇——k8s的网络核心概念
  • 原文地址:https://blog.csdn.net/kdjjdjdjjejje128/article/details/127546671