• 【C/C++】string类的使用&&探索string底层原理


    在这里插入图片描述

    ​👻内容专栏: C/C++编程
    🐨本文概括:C++ string类的使用、探索string底层的原理、实现string类
    🐼本文作者: 阿四啊
    🐸发布时间:2023.9.24

    前提

    学习本章节的string,包括后面要学到的STL库中的知识,我们都需要渐渐的学会查找文档、查看文档进行学习。
    一般在cplusplus网站查找学习就足够了,上链接😜:https://legacy.cplusplus.com/ 建议保存url地址。
    有同学会感到非常沮丧,这是个英文网站,我看不懂英文怎么办,我显得很为难,能不能直接翻译成中文。博主不建议有这样想法的同学这么做,我们还是要去适应它,在工作中也是经常会查看英文文档的,所以学会英语阅读这个能力不可避免。

    一、为什么要学习string类?

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

    二、标准库中的string类

    string类

    string类在文档中的介绍如下:
    在这里插入图片描述
    说明

    1. string是表示字符串的字符串类
    2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
    3. string是在stl库出来之前设计的,所以接口函数繁多,这里我们讲解常用的一些接口。
    4. string在底层实际是:basic_string模板类的别名,
      typedef basic_stringstring;
    5. 不能操作多字节或者变长字符的序列。

    三、string类的常用接口使用

    1. string类对象的常见构造

    constructor)函数名称功能说明
    string()(重点)构造空的string类对象,即空字符串
    string(const char* s) (重点)用C-string来构造string类对象
    string(size_t n, char c)string类对象中包含n个字符c
    string(const string&s) (重点)拷贝构造函数
    void test_string1()
    {
    	string s1;
    	string s2("hello world");
    	string s3(s2);
    	
    	//构造+拷贝构造编译器优化为一次构造
    	string s4 = "hello string";//单参数的构造函数支持隐式类型转换
    	
    	cout << s1 << endl;
    	cout << s2 << endl;
    	cout << s3 << endl;
    	cout << s4 << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2.string类对象的容量操作

    函数名称(capacity)功能说明
    size返回字符串有效字符长度
    length返回字符串有效字符长度
    capacity返回空间总大小
    empty检测字符串释放为空串,是返回true,否则返回false
    clear清空有效字符
    reserve为字符串预留空间
    resize将有效字符串的个数改为n个,多出的空间由字符c填充
    void test_string2()
    {
    	string s1("hello world");
    	cout << s1 << endl;
    	cout << s1.size() << endl;
    	cout << s1.length() << endl;
    	cout << s1.capacity() << endl;
    
    	cout << "---------" << endl;
    
    	//将s1字符串中清空,注意只是将size的大小清0,而没有改变底层空间(capacity)的大小
    	s1.clear();
    	cout << s1.size() << endl;
    	cout << s1.capacity() << endl;
    
    	cout << "---------" << endl;
    	string s2("abc");
    
    	//将s2中有效字符增加到10个,多出位置会用'x'进行补充
    	//"abcxxxxxxx"
    	s2.resize(10,'x');
    	cout << s2 << endl;
    	cout << s2.size() << endl;
    	cout << s2.capacity() << endl;
    
    	//将s2中有效字符增加到20个,多出位置用缺省值'\0'补充
    	//"abcxxxxxxx\0\0\0\0\0\0\0\0\0\0"
    	s2.resize(20);
    	cout << s2 << endl;
    	cout << s2.size() << endl;
    	cout << s2.capacity() << endl;
    
    	//将s2中有效个数缩小到5个
    	s2.resize(5);
    	cout << s2 << endl;
    	cout << s2.size() << endl;
    	cout << s2.capacity() << 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

    注意⚠️:

    1. size()length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()
    2. clear()只是将string中有效字符清空,不改变底层空间大小。
    3. resize(size_t n)resize(size_t n, char c) 都是将字符串中有效字符个数改变到n个,不同的是当字
      符个数增多时:resize(n)0来填充多出的元素空间,resize(size_t n, char c) 用字符c来填充多出的
      元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
    void test_string3()
    {
    	string s;
    	// 测试reserve是否会改变string中有效元素个数
    	s.reserve(100);
    	cout << s.size() << endl;
    	cout << s.capacity() << endl;
    
    	// 测试reserve参数小于string的底层空间大小时,是否会将空间缩小
    	s.reserve(50);
    	cout << s.size() << endl;
    	cout << s.capacity() << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    // 利用reserve提高插入数据的效率,避免增容带来的开销
    void test_string4()
    {
    
    	//使用string时,如果提前已经知道string中大概要放多少个元素,
    	//可以提前用reverse()将string中空间设置好
    
    	string s;
    	s.reserve(100);
    	size_t sz = s.capacity();
    	cout << "initial capacity: " << sz << endl;
    	for (int i = 0; i < 100;++i)
    	{
    		s.push_back('c');
    		if (sz != s.capacity())
    		{
    			sz = s.capacity();
    			cout << "capacity changed: " << sz << endl;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于
    string的底层空间总大小时,reserve不会改变容量大小。
    reserve主要用途是如果提前想要开辟多大的数据空间,可以利用reserve设置好,而避免扩容带来的开销。

    3.string类的访问及遍历操作

    函数名称功能说明
    operator[](重点)返回pos位置的字符,const string类对象调用
    begin + endbegin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
    rebin + rend反向迭代器
    范围forC++11支持更简洁的范围for的新遍历方式
    void test_string5()
    {
    	string s("hello world");
    	//string中遍历的三种方式
    	//1.for + operator[] 
    	//注意我们不过可以遍历字符串中的每个字符,还可以在遍历时作修改
    	for (size_t i = 0; i < s.size(); i++)
    	{
    		cout << s[i] << " ";
    	}
    	cout << endl;
    	for (size_t i = 0; i < s.size(); i++)
    	{ 
    		cout << ++s[i] << " ";
    	}
    	cout << endl << "------------------------" << endl;
    
    	string str("hello string");
    
    	//2.迭代器
    	string::iterator it = str.begin();
    
    	while (it != str.end())
    	{
    		cout << *it << " ";
    		it++;
    	}
    	cout << endl;
    
    	//string::reverse_iterator rit = str.rbegin();
    	//C++11之后,支持使用auto关键字定义迭代器,可以让编译器去自动推断类型
    	auto rit = str.rbegin();
    	while (rit != str.rend())
    	{
    		cout << *rit << " ";
    		rit++;
    	}
    
    	cout << endl << "------------------------" << endl;
    
    	string s2("hello world");
    	//3.范围for
    	for (auto ch : s2)
    	{
    		cout << ch << " ";
    	}
    	cout << endl;
    	for (auto& ch : s2)
    	{
    		cout << ++ch << " ";
    	}
    }
    
    • 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

    在这里插入图片描述

    在这里插入图片描述

    注意⚠️:迭代器iterator还有const版本的,即cbegincendcrbegincrend四个由const修饰的成员函数,返回值为const_iterator或者const_reverse_iterator ,表示不能被修改,所以他们只支持读的功能,那么在一些特殊的情况下,比如函数传参,传入const修饰的string对象,那么,只能选择四个由const修饰的成员函数,否则就会编译报错。示例如下:

    void func(const string& str)
    {
    	string::const_iterator it = str.begin();
    
    	while (it != str.end())
    	{
    		//*it = 'a'; // 不能给常量赋值
    		cout << *it << " ";
    		it++;
    	}
    	cout << endl;
    
    	//string::const_reverse_iterator rit = str.rbegin();
    	auto rit = str.rbegin();
    	while (rit != str.rend())
    	{
    		cout << *rit << " ";
    		rit++;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    4.string类对象的处理操作

    函数名称功能说明
    push_back在字符串后尾插字符c
    append在字符串后追加一个字符串
    operator+=在字符串后追加字符串str
    c_str返回C格式字符串
    insert在字符串指定的位置插入一个字符串或者字符
    earse删除从pos位置开始,长度为len的字符串
    assign赋予新的值给字符串
    replace从pos位置开始,替换长度为n的一段字符串,或者字符
    swap交换两个字符串的内容
    void test_string6()
    {
    	string s1;
    	//在s1字符串后插入一个字符
    	s1.push_back('x');
    	//在s1字符串后追加一段字符
    	s1.append("hello");
    
    	s1 += 's';//在s1字符串后插入一个字符
    	s1 += "tring";//在s1字符串后追加一段字符
    
    	cout << s1 << endl;
    	cout << s1.c_str() << endl;//以C语言字符串的方式打印
    
    	//s1.insert(12, 1, '!');
    	s1.insert(s1.end(),'!');
    	cout << s1 << endl;
    
    	s1.erase(10,2);//从下标10的位置开始,删除两个字符
    	cout << s1 << endl;
    
    	s1.erase(2);//从下标2的位置开始,删除缺省值为npos个字符(一直删除到字符串末尾)
    	cout << s1 << endl;
    
    	string s2("hello world");
    
    	s1.assign(s2);//将s2的值赋给s1
    	cout << s1 << endl;
    
    	s2.replace(5, 1, "%%20");
    	cout << s2 << endl;
    
    	string s3("hello");
    	string s4("world");
    	s3.swap(s4);//交换两个字符串
    
    	cout << "s3:" << s3 << endl;
    	cout << "s4:" << s4 << 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

    注意⚠️:insert/erase/replace能不用就尽量不用 ,因为他们都涉及到挪动数据,效率不高。为什么库里面有swap函数,这里还另外需要设计一个swap成员函数呢?其实因为库里面是单纯交换的string对象,会涉及到三次拷贝,效率很低,于是祖师爷直接设计一个成员函数,将他们的成员变量交换即可。
    在这里插入图片描述

    函数名称功能说明
    find从字符串pos位置开始往后找字符c,返回该字符在字符串中第一次出现的位置
    rfind从字符串pos位置开始往前找字符c,返回该字符在字符串中第一次出现的位置
    substr在str中从pos位置开始,截取n个字符,然后将其返回
    void test_string7()
    {
    	//如何拿到一个文件的后缀
    	string s1("test.cpp");
    	size_t i = s1.find('.');//找到.位置,并返回下标
    	string ret1 = s1.substr(i);//从下标为i位置开始,截取到s1末尾
    	cout << ret1 << endl; //打印.cpp
    
    	//如果是test.cpp.exe.zip 该如何找?
    	string s2("test.cpp.exe.zip");
    	size_t j = s2.rfind('.');//找到.位置,并返回下标
    	string ret2 = s2.substr(j);//从下标为i位置开始,截取到s2末尾
    	cout << ret2 << endl;//打印.zip
    	
    	//https://legacy.cplusplus.com/reference/string/
    	//如何根据以上网站分割出协议、域名、资源名
    	string s3("https://legacy.cplusplus.com/reference/string/");
    	size_t x = s3.find(':');
    	string ret3 = s3.substr(0, x);
    
    	size_t y = s3.find('/', x + 3);
    	string ret4 = s3.substr(x + 3, y - (x + 3));
    
    	string ret5 = s3.substr(y);
    
    	cout << ret3 << endl;
    	cout << ret4 << endl;
    	cout << ret5 << 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

    5.sting类非成员函数

    函数功能说明
    operator>>输入运算符重载
    operator<<输出运算符重载
    getline获取一行字符串
    relational operators字符串比较大小

    以上为string相关接口函数的具体用法,讲到的都是比较常用的接口函数,这里就不全部讲述了,后面用到,可以自行查看文档。

    四、sting类的模拟实现

    上面已经对string类进行了简单的介绍,大家只要能够正常使用即可。在面试中,面试官总喜欢让学生自己来模拟实现string类。

    //为了和标准库中的string进行区分,我们自定义一个命名空间MyString,将自己实现的string类写到Mystring命名空间里面
    namespace MyString
    {
    	class string
    	{
    	public:
    
    	private:
    		char* _str;
    		size_t _size;
    		size_t _capacity;
    	};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    1.对string类对象的基本操作

    为了保持理解整体性,我将所有代码放在一块,注释中会穿插着说明。

    #pragma once
    #include
    #include
    
    //为了和标准库中的string进行区分,我们自定义一个命名空间MyString
    namespace MyString
    {
    	class string
    	{
    	public:
    		//迭代器
    		typedef char* iterator;
    		typedef const char* const_iterator;
    
    		iterator begin()
    		{
    			return _str;
    		}
    
    		const_iterator begin() const
    		{
    			return _str;
    		}
    
    		iterator end()
    		{
    			return _str + _size;
    		}
    		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(_size)
    		{
    			//多留一个字节的空间给'\0'
    			_str = new char[_capacity + 1];
    			strcpy(_str, str);
    
    		}
    		void swap(string& s)
    		{
    			std::swap(_str, s._str);
    			std::swap(_size, s._size);
    			std::swap(_capacity, s._capacity);
    		}
    		//现代写法
    		//s2(s1)
    		string(const string& s)
    			:_str(nullptr)
    			,_size(0)
    			,_capacity(0)
    			//要给s2的内容进行初始化,否则因为s2是随机值,与swap交换后,swap销毁时程序可能会崩溃
    		{
    			string tmp(s._str);
    			swap(tmp);
    		}
    
    		// s2 = s1
    		string& operator=(const string& s)
    		{
    			if (*this != s)
    			{
    				//调用析构函数
    				string tmp(s);
    
    				swap(tmp);//swap会顺便释放s2的空间
    			}
    			
    			return *this; 
    		}
    
    
    
    		//传统写法
    		拷贝构造函数
    		s2(s1);
    		//string(const string& s)
    		//{
    		//	_str = new char[s._capacity + 1];
    		//	strcpy(_str, s._str);
    		//	_capacity = s._capacity;
    		//	_size = s._size;
    		//}
    		赋值重载
    		s2 = s1
    		//string& operator=(const string& s)
    		//{
    		//	//不能直接将s1的数据拷贝给s2 不能确定s2的空间比s1小还是大
    		//	
    		//	//开辟一段新的空间 拷贝数据过来 然后释放s2原来的旧空间
    		//	if (*this != s)
    		//	{
    		//		char* tmp = new char[s._capacity + 1];
    		//		strcpy(tmp, s._str);
    		//		delete[] _str;
    
    		//		_str = tmp;
    		//		_size = s._size;
    		//		_capacity = s._capacity;
    		//	}
    		//	
    		//	return *this;
    		//}
    		//析构函数
    		~string()
    		{
    			delete[] _str;
    			_str = nullptr;
    			_size = _capacity = 0;
    		}
    		//清空字符串
    		void clear()
    		{
    			_str[0] = '\0';
    			_size = 0;
    		}
    
    		void reserve(size_t n)  
    		{
    			if (n >  _capacity)
    			{
    				//预留一个字节的位置给反斜杠0
    				char* tmp = new char[n + 1];
    				strcpy(tmp, _str);
    				delete[] _str;
    
    				_str = tmp;
    				_capacity = n;
    			}
    		}
    		//将有效字符串的个数改为n个
    		void resize(size_t n, char ch = '\0')
    		{
    			if (n <= _size)
    			{
    				_str[n] = '\0';
    				_size = n;
    			}
    			else
    			{
    				//判断是否需要扩容
    				reserve(n);
    
    				while (_size < n)
    				{
    					_str[_size] = ch;
    					_size++;
    				} 
    				_str[_size] = '\0';
    			}
    		}
    
    		//插入操作都会涉及到扩容,我们先写好reverse(),然后再复用
    		//对string的处理操作
    		//字符串后尾插字符
    		void push_back(char ch)
    		{
    			if (_size == _capacity)
    			{
    				reserve(_capacity == 0 ? 4 : _capacity * 2);
    			}
    			_str[_size] = ch;
    			_size++;
    			_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;
    		}
    
    		string& operator+=(char ch) 
    		{
    			push_back(ch);
    			return *this;
    		}
    		string& operator+=(const char* str)
    		{
    			append(str);
    			return *this;
    		}
    	 
    		//插入单个字符
    		void insert(size_t pos,char ch)
    		{
    			assert(pos <= _size);
    
    			if (_size == _capacity)
    			{
    				reserve(_capacity == 0 ? 4 : _capacity * 2);
    			}
    
    			//无符号类型不会小于零 可能会发生程序奔溃
    			/*size_t end = _size;
    			while (end >= pos) 
    			{
    				_str[end + 1] = _str[end];
    				end--;
    			}*/
    
    			//解决方案一: 强制类型转换
    			/*int end = _size;
    			while (end >= (int)pos)
    			{
    				_str[end + 1] = _str[end];
    				end--;
    			}*/
    
    			//解决方案二:修改区间
    			size_t end = _size + 1;
    			while (end > pos)
    			{
    				_str[end] = _str[end - 1];
    				end--;
    			}
    
    			_str[pos] = ch;
    			_size++;
    		}
    		//插入字符串
    		void 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)
    			{
    				_str[end] = _str[end - len];
    				end--;
    			}
    			strncpy(_str + pos, str, len);
    			_size += len;
    		}
    		//删除从pos位置开始,长度为len的字符
    		void erase(size_t pos, size_t len = npos)
    		{
    			assert(pos < _size);
    
    			if (len == npos || pos + len >= _size)
    			{
    				_str[pos] = '\0';
    				_size -= len;
    			}
    			else
    			{
    				size_t begin = pos + len;
    				while (begin <= _size)
    				{
    					_str[pos] = _str[begin];
    					begin++;
    					pos++;
    				}
    				_size -= len;
    			}
    
    		}
    		//查找 -- 返回找到字符或者字符串位置的下标
    		size_t find(char ch, size_t pos = 0)
    		{
    			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)
    		{
    			const char* p = strstr(_str + pos, str);
    			if (p)
    			{
    				return p - _str;
    			}
    			else
    			{
    				return npos;
    			}
    		}
    
    		//截取一段子串,返回字符串对象
    		string substr(size_t pos = 0, size_t len = npos)
    		{
    			string s;
    			if (len == npos || pos + len >= _size)
    			{
    				len = _size - pos;
    			}
    
    			for (size_t i = pos; i < pos + len; i++)
    			{
    				s += _str[i];
    			}
    
    			return s;
    			//需要要写拷贝构造函数与赋值重载,
    			//否则会引发浅拷贝问题(多个对象共享同一份资源,那么对已经释放的空间继续做访问操作,程序崩溃)
    		}
    
    		//关系运算符
    		//比较的是每一个字符串字符,和strcmp的原理比较相似
    		//返回值为布尔值
    
    		bool operator>(const string& s)
    		{
    			return strcmp(_str,s._str) > 0;
    		}
    		bool operator<(const string& s)
    		{
    			return strcmp(_str, s._str) < 0;
    		}
    		bool operator==(const string& s)
    		{
    			return strcmp(_str, s._str) == 0;
    		}
    		bool operator<=(const string& s)
    		{
    			return *this < s || *this == s;
    		}
    		bool operator>=(const string& s)
    		{
    			return *this > s || *this == s;
    		}
    		bool operator!=(const string& s)
    		{
    			return  !(*this == s);
    		}
     
    
    		//[]访问操作符
    		char operator[](size_t i) const
    		{
    			return _str[i];
    		}
    		char operator[](size_t i)
    		{
    			return _str[i];
    		}
    
    		//返回string大小
    		size_t size() const
    		{
    			return _size;
    		}
    		//返回内存空间大小
    		size_t capacity() const
    		{
    			return _capacity;
    		}
    		//返回C格式的字符串
    		const char* c_str() const
    		{
    			return _str;
    		}
    	private:
    		char* _str;
    		size_t _size;
    		size_t _capacity;
    
    		const static size_t npos;
    		//const static size_t npos = -1;//特例
    	};
    
    	//静态成员变量需要在类外初始化
    	const size_t string::npos = -1;
    
    	//string的流插入和流提取
    	ostream& operator<<(ostream& out, const string& s)
    	{
    		/*for (size_t i = 0; i < s.size(); i++)
    		{
    			out << s[i];
    		}*/
    		for (auto ch : s)
    		{
    			out << ch;
    		}
    
    		return out;
    	}
    	istream& operator>>(istream& in, string& s)
    	{
    		s.clear();
    		//方式一:对string本身扩容
    		//s.reserve(128);
    
    		//方式二:优化
    		//以免输入影响string的扩容,我们定义一个buffer数组,
    		//buffer的开销只是暂时的,除了作用域就会销毁,
    		//而不要因为输入去扩充string本身的容量
    		char buffer[129];
    		size_t i = 0;
    
    		char ch;
    		/*in是拿不到空格和换行的*/
    		//in >> ch;
    		
    		/*拿单个字符只能考虑get库函数,类似于getchar*/
    		ch = in.get();
    
    		while (ch != ' ' && ch != '\n')
    		{
    			buffer[i++] = ch;
    
    			if (i == 128)
    			{
    				buffer[i] = '\0';
    				s += buffer;
    				i = 0;
    			}
    			//in >> ch
    			ch = in.get();
    		}
    
    		if (i != 0)
    		{
    			buffer[i] = '\0';
    			s += buffer;
    		}
    
    		return in;
    	}
    	
    	//测试接口 需要在main函数指定类域+函数名使用
    	
    	void test_string1()
    	{
    		/*string str("hello world");
    		cout << str.c_str() << endl;*/
    
    		string s1;
    		cout << s1.c_str() << endl;
    
    		string s2("hello world");
    
    		for (size_t i = 0; i < s2.size(); i++)
    		{
    			cout << s2[i] << " ";
    		}
    		cout << endl;
    
    		string::iterator it = s2.begin();
    		while (it != s2.end())
    		{
    			cout << *it << " ";
    			it++;
    		}
    		cout << endl;
    		for (auto ch : s2)
    		{
    			cout << ch << " ";
    		}
    	}
    
    	void test_string2()
    	{
    		string s1;
    		s1.push_back('#');
    		cout << s1.c_str() << endl;
    		
    		string s2;
    		s2.append("hello world");
    
    		cout << s2.c_str() << endl;
    	}
    	void test_string3()
    	{
    		string s1("hello world");
    		s1.insert(0,'#');
    		cout << s1.c_str() << endl;
    		 
    		s1.insert(6, "%%20");
    		cout << s1.c_str() << endl;
    
    		s1.erase(0, 6);
    		cout << s1.c_str() << endl;
    
    		s1.erase(1);
    		cout << s1.c_str() << endl;
    
     
    	}
    	void test_string4()
    	{
    		string s1("hello world");
    		cout << s1 << endl;
    
    		s1.resize(5);
    		
    		cout << s1 << endl;
    
    		s1.resize(20, '#');
    		cout << s1 << endl;
    	}
    	void test_string5()
    	{
    		//如何拿到一个文件的后缀
    		string s1("test.cpp");
    		size_t i = s1.find('.');//找到.位置,并返回下标
    		string ret1 = s1.substr(i);//从下标为i位置开始,截取到s1末尾
    		cout << ret1 << endl; //打印.cpp
     
    
    		//https://legacy.cplusplus.com/reference/string/
    		//如何根据以上网站分割出协议、域名、资源名
    		string s3("https://legacy.cplusplus.com/reference/string/");
    		size_t x = s3.find(':');
    		string ret3 = s3.substr(0, x);
    
    		size_t y = s3.find('/', x + 3);
    		string ret4 = s3.substr(x + 3, y - (x + 3));
    
    		string ret5 = s3.substr(y);
    
    		cout << ret3 << endl;
    		cout << ret4 << endl;
    		cout << ret5 << endl;
    
    	}
    
    	void test_string6()
    	{
    		string s1("hello world");
    		string s2;
    		s2 = s1;
    		cout << s2 << endl;
    	}
    
    	void test_string7()
    	{
    		string s1;
    		cin >> s1;
    		cout << s1;
    	}
    }
     
    
    • 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
    • 471
    • 472
    • 473
    • 474
    • 475
    • 476
    • 477
    • 478
    • 479
    • 480
    • 481
    • 482
    • 483
    • 484
    • 485
    • 486
    • 487
    • 488
    • 489
    • 490
    • 491
    • 492
    • 493
    • 494
    • 495
    • 496
    • 497
    • 498
    • 499
    • 500
    • 501
    • 502
    • 503
    • 504
    • 505
    • 506
    • 507
    • 508
    • 509
    • 510
    • 511
    • 512
    • 513
    • 514
    • 515
    • 516
    • 517
    • 518
    • 519
    • 520
    • 521
    • 522
    • 523
    • 524
    • 525
    • 526
    • 527
    • 528
    • 529
    • 530
    • 531
    • 532
    • 533
    • 534
    • 535
    • 536
    • 537
    • 538
    • 539
    • 540
    • 541
    • 542
    • 543
    • 544
    • 545
    • 546
    • 547
    • 548
    • 549
    • 550
    • 551
    • 552
    • 553
    • 554
    • 555
    • 556
    • 557
    • 558
    • 559
    • 560
    • 561
    • 562
    • 563

    2.浅拷贝

    浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规

    就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,
    万一不想分享就你争我夺,玩具损坏。
    
    • 1
    • 2

    我们在模拟实现substr的接口时,返回s,然后是一个string类的对象接收,此时就会编译器调用拷贝构造函数,但是我们没有写拷贝构造函数,编译器会生成默认的拷贝构造函数,在类和对象章节我们说过,默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。但是substr中的返回值s出了作用域就销毁了,外面再拿string类对象接收然后访问,程序就会出现崩溃。

    所以,我们可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。父母给每个孩子都买一份玩具,各自玩各自的就不会有问题了。

    3.深拷贝

    如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。

    传统写法与现代写法

    在这里插入图片描述

    4.写时拷贝

    具体介绍

    👇以下一段话转自酷壳-CoolShell陈皓老师的一篇文章:C++ STL STRING的COPY-ON-WRITE技术


    Scott Meyers在《More Effective C++》中举了个例子,不知你是否还记得?在你还在上学的时候,你的父母要你不要看电视,而去复习功课,于是你把自己关在房间里,做出一副正在复习功课的样子,其实你在干着别的诸如给班上的某位女生写情书之类的事,而一旦你的父母出来在你房间要检查你是否在复习时,你才真正捡起课本看书。这就是“拖延战术”,直到你非要做的时候才去做

    当然,这种事情在现实生活中时往往会出事,但其在编程世界中摇身一变,就成为了最有用的技术,正如C++中的可以随处声明变量的特点一样,Scott Meyers推荐我们,在真正需要一个存储空间时才去声明变量(分配内存),这样会得到程序在运行时最小的内存花销。执行到那才会去做分配内存这种比较耗时的工作,这会给我们的程序在运行时有比较好的性能。必竟,20%的程序运行了80%的时间。

    当然,拖延战术还并不只是这样一种类型,这种技术被我们广泛地应用着,特别是在操作系统当中,当一个程序运行结束时,操作系统并不会急着把其清除出内存,原因是有可能程序还会马上再运行一次(从磁盘把程序装入到内存是个很慢的过程),而只有当内存不够用了,才会把这些还驻留内存的程序清出


    简而言之在,实际生活当中我们并不看好拖延行为,但是在C++语言中,这种拖延战术反而成为了一种具有优势的技术,写时拷贝技术(copy on write)就是一个很好的例子。

    我们先来分析一下使用浅拷贝的问题有哪些?

    1. 浅拷贝对同一块空间会释放两次(调用两次析构函数);
    2. 一个数据修改会影响到另一个数据

    所以对于浅拷贝,那么深拷贝就是最好的方案吗?其实不是。

    原理

    深拷贝在共享资源的基础上,进行拷贝多次,会导致空间资源上的浪费。避免一些空间浪费和减少拷贝操作的技术叫做写时拷贝技术。
    👇大致原理如图中所示:
    在这里插入图片描述
    大家理解原理即可,具体实现操作我们会在进阶智能指针章节讲到!

    🔗文章最后,分享一篇C++string很好的面试题,也是陈皓老师的。
    🔗点击跳转👉👉C++面试中STRING类的一种正确写法

  • 相关阅读:
    如何入门Python——学习Python的指南针
    word文档编辑受限制怎么解除?
    SAM论文翻译
    Python并发编程实战,用多线程、多进程、多协程加速程序运行
    实战干货——教你用Fiddler捕获HTTPS请求
    Bioinformatics2019 | FP2VEC+:基于新分子特征的分子性质预测
    Python画各种爱心
    关于语言大模型的八大论断
    千兆光模块存在哪些局限性
    SpringCloud微服务注册中心:Nacos介绍,微服务注册,Ribbon通信,Ribbon负载均衡,Nacos配置管理详细介绍
  • 原文地址:https://blog.csdn.net/qq_63320529/article/details/133156537