【C++】string类扩展知识

文章目录

  • 1.string类的模拟实现
    • [1.1 浅拷贝](#1.1 浅拷贝)
    • [1.2 深拷贝](#1.2 深拷贝)
    • [1.3 传统版写法的String类](#1.3 传统版写法的String类)
    • [1.4 现代版写法的String类](#1.4 现代版写法的String类)
    • [1.4 写时拷贝(了解)](#1.4 写时拷贝(了解))
    • [1.5 string类的模拟实现](#1.5 string类的模拟实现)
    • [1.6 vs和g++下string结构的说明](#1.6 vs和g++下string结构的说明)
    • [1.7 补充的接口](#1.7 补充的接口)
  • 2.扩展阅读

1.string类的模拟实现

说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。

1.1 浅拷贝

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

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

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

1.2 深拷贝

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

1.3 传统版写法的String类

cpp 复制代码
//传统写法
string::string(const string& s)
{
  _str = new char[s._capacity + 1];
	//strcpy不能拷贝中间有'\0'的字符串
	//strcpy(_str, s._str);
	memcpy(_str, s._str, s._size + 1);
	_size = s._size;
	_capacity = s._capacity;
}

//传统写法
//s1=s3
string& string::operator=(const string& s)
{
	//&s是取地址操作
	//如果不是自己给自己赋值就开空间 
	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;
}		

1.4 现代版写法的String类

cpp 复制代码
//现代写法:本质效率没有提升,只是写法不同
string::string(const string& s)
{
	//this指针没初始化,可能有风险,所以可以在声明变量时可以设置默认参数
	//private:
	//	char* _str=nullptr;
	//	size_t _size=0;
	//	size_t _capacity=0;
	string tmp(s._str);
	swap(tmp);
}
//现代写法
string& string::operator=(const string& s)
{
	if (this != &s)
	{
		string tmp(s._str);
		swap(tmp);
	}

	return *this;
}
//现代写法更简洁版
string& string::operator=(string tmp)
{
	swap(tmp);
	return *this;
}

1.4 写时拷贝(了解)

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。

引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源

写时拷贝
写时拷贝在读取时的缺陷

1.5 string类的模拟实现

构造函数和析构函数不能有返回值

string模拟实现参考

cpp 复制代码
//string.h
#pragma once
#include<iostream>
//strlen需要包含的
#include<string.h>
#include<assert.h>
#include<algorithm>
using namespace std;
//string与库里有命名冲突
namespace bit
{
	class string
	{
	public:
		//1)[]下标访问
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
		//2)范围for底层是迭代器
		typedef char* iterator;
		typedef const char* const_iterator;//内容不能修改
		//开始位置的迭代器
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + _size;
		}

		size_t size() const
		{
			return _size;
		}
		const char* c_str() const
		{
			return _str;
		}

		//常量字符串默认结尾有'\0'
		string(const char* str = "");
		~string();
		string(const string& s);
		string& operator=(const string& s);

		void reserve(size_t n);
		void push_back(char ch);
		void append(const char* str);
		//清除数据
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}

		void resize(size_t n, char ch = '\0');
		void insert(size_t pos, char ch);
		void insert(size_t pos, const char* str);
		void erase(size_t pos=0, size_t len=npos);
		size_t find(char ch, size_t pos = 0);
		size_t find(const char* str, size_t pos = 0);
		string substr(size_t pos = 0, size_t len = npos);
		void swap(string& s);

		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;
		size_t _size;
		size_t _capacity;
	public:
		//在.cpp中定义,但是const static整型可以这么用,特殊处理
		const static size_t npos=-1;
		//double不支持
		//const static double npos = 1.0;
	};
	std::ostream& operator<<(std::ostream& out, const string& s);
	std::istream& operator>>(std::istream& in, string& s);
	std::istream& getline(std::istream& in, string& str, char delim = '\n');
}
cpp 复制代码
//string.cpp
#include"string.h"

namespace bit
{
	//_str要预留一个字节存'\0'
	//string()
	//	:_str(new char[1] {'\0'} )
	//		, _size(0)
	//		, _capacity(0)
	//{}
	string::string(const char* str)
		:_size(strlen(str))
	{
		//不能用sizeof,只能计算数组大小
		_str = new char[_size + 1];
		_capacity = _size;
		strcpy(_str, str);
	}
	//析构未完成
	string::~string()
	{
		delete[] _str;
		_str = nullptr;
		_size = 0;
		_capacity = 0;
	}
	void string::reserve(size_t n)
	{
		//永远多开一个空间
		//capacity和size是有效数字个数
		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)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		_str[_size] = ch;
		_size++;
		_str[_size] = '\0';
	}
	//添加字符
	void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			reserve(std::max(_size + len, _capacity * 2));
		}
		//strcpy(_str + _size, str);
		memcpy(_str + _size, str, len + 1);
		_size += len;
	}
	void string::insert(size_t pos, char ch)
	{
		//可以在'\0'(下标为_size)位置直接插
		assert(pos <= _size);
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		//挪动数据
		size_t end = _size + 1;
		while (end > pos)
		{
			_str[end] = _str[end - 1];
			--end;
		}
		_str[pos] = ch;
		++_size;

	}
	void string::insert(size_t pos, const char* str)
	{
		assert(pos <= _size);
		size_t len = strlen(str);
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		//挪动数据,第一种方法
		//(int)pos 将 pos 强制转换为 int
		//是为了避免 end(有符号 int)与无符号 pos(通常 size_t)比较时可能出现的无符号整数回绕问题
		//例如当 pos=0 时,如果 end 是无符号且递减到 0,再减 1 会变成极大值,导致无限循环
		//这里 end 是 int,pos 转成 int,比较在 int 域中进行,安全。
		/*int end = _size;
		while (end > (int)pos)
		{
			_str[end+len] = _str[end];
			--end;
		}
		strncpy(_str + pos, str, len);
		_size += len;*/

		size_t end = _size + len;
		while (end > pos + len - 1)
		{
			_str[end] = _str[end - len];
			--end;
		}
		//strncpy(_str + pos, str, len);为了统一写成下面这种
		memcpy(_str + pos, str, len);
		_size += len;
	}
	void string::erase(size_t pos, size_t len)
	{
		//删除有效字符个数,不是assert(pos <= _size);
		assert(pos < _size);
		//说明删除后面所有
		//npos是整型的最大值,len==npos就是删完所有的意思
		if (len == npos || len >= _size - pos)
		{
			//直接覆盖
			_size = pos;
			_str[_size] = '\0';
		}
		else
		{
			//删除部分
			//把后面往前覆盖
			//strcpy(_str + pos , _str + pos + len);
			//需要移动的长度[str+pos+len,_size],左闭右闭,左减右+1
			memcpy(_str + pos, _str + pos + len, _size - (pos + len) + 1);
			_size -= len;
		}
	}

	//传统写法
	//string::string(const string& s)
	//{
	//	_str = new char[s._capacity + 1];
	//	//strcpy不能拷贝中间有'\0'的字符串
	//	//strcpy(_str, s._str);
	//	memcpy(_str, s._str, s._size + 1);
	//	_size = s._size;
	//	_capacity = s._capacity;
	//}
	
	//现代写法:本质效率没有提升,只是写法不同
	string::string(const string& s)
	{
		//this指针没初始化,可能有风险,所以可以在声明变量时可以设置默认参数
		//private:
		//	char* _str=nullptr;
		//	size_t _size=0;
		//	size_t _capacity=0;
		string tmp(s._str);
		swap(tmp);
	}
	
	//传统写法
	//s1=s3
	//string& string::operator=(const string& s)
	//{
	//	//&s是取地址操作
	//	//如果不是自己给自己赋值就开空间 
	//	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& string::operator=(const string& s)
	{
		if (this != &s)
		{
			string tmp(s._str);
			swap(tmp);
		}

		return *this;
	}
	//现代写法更简洁版
	//string& string::operator=(string tmp)
	//{
	//	swap(tmp);
	//	return *this;
	//}


	std::ostream& operator<<(std::ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			cout << ch;
		}
		return out;
	}
	//改变容器的大小,多余空间赋值ch
	void string::resize(size_t n, char ch)
	{
		if (n <= _size)
		{
			//删除,保留前n个
			_size = n;
			_str[_size] = '\0';
		}
		else
		{
			reserve(n);
			for (size_t i = _size; i < n; i++)
			{
				_str[i] = ch;
			}
			_size = n;
			_str[_size] = '\0';
		}
	}
	size_t string::find(char ch, size_t pos)
	{
		assert(pos < _size);
		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 char* ptr = strstr(_str + pos, str);
		//如果找到让它返回下标
		if (ptr)
			return ptr - str;
		else
			return npos;
	}
	//从字符串pos位置提取n个字符拷贝
	string string::substr(size_t pos, size_t len)
	{
		assert(pos < _size);
		if (len == npos || len >= _size - pos)
		{
			len = _size - pos;
		}
		string sub;
		reserve(len);
		for (size_t i = 0; i < len; i++)
		{
			sub += _str[pos + i];
		}
		return sub;
	}
	std::istream& operator>>(std::istream& in, string& s)
	{
		s.clear();

		char buff[256];
		int i = 0;

		char ch;
		//这样写读取不到' '
		//in >> ch;
		ch = in.get();
		while (ch != '\n' && ch != ' ')
		{
			buff[i++] = ch;
			//长的字符串
			if (i == 255)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get(); 
		}
		//短的字符串
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
	bool string::operator<(const string& s) const
	{
		return strcmp(_str, s._str) < 0;
	}
	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
	{
		return strcmp(_str, s._str) == 0;
	}
	bool string::operator!=(const string& s) const
	{
		return !(*this == s);
	}
	std::istream& getline(std::istream& in, string& s, char delim)
	{
		s.clear();

		char buff[256];
		int i = 0;

		char ch;
		//这样写读取不到' '
		//in >> ch;
		ch = in.get();
		while (ch != delim )
		{
			buff[i++] = ch;
			//长的字符串
			if (i == 255)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}
		//短的字符串
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
	//库里的swap函数,会拷贝三次,代价太大,下面的更高效
	void string::swap(string& s)
	{
		std::swap(_str, s._str);
		std::swap(_size, s._size);
		std::swap(_capacity, s._capacity);
	}
}
cpp 复制代码
//test.cpp
#include"string.h"

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

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

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

		const string s3 = "hello world";//隐式类型转换,构造+拷贝构造->优化为构造
		for (size_t i = 0; i < s3.size(); i++)
		{
			cout << s3[i] << '-';
		}
		cout << endl;

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

		string s4 = "hello world";
		string::iterator it4 = s4.begin();
		while (it4 != s4.end())
		{
			*it4 += 1;
			//类似指针
			cout << *it4 << "  ";
			++it4;
		}

		string::const_iterator it3 = s3.begin();
		while (it3 != s3.end())
		{
			//类似指针
			cout << *it3 << "  ";
			++it3;
		}
	}
	void test_string2()
	{
		bit::string s1;
		cout << s1.c_str() << endl;

		string s2("hello world");
		cout << s2.c_str() << endl;
		s2.push_back('x');
		s2.push_back('y');
		s2.push_back('z');
		cout << s2.c_str() << endl;

		string s3("hello");
		s3.append("xxxxxxxxxxxxxxxxxx");
		cout << s3.c_str() << endl;
		string s4("world");
		s4.append("xx");
		cout << s4.c_str() << endl;

		s4+= '*';
		s4 += "hello bit";
		cout << s4.c_str() << endl;
	}
	void test_string3()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;
		s1.insert(5, 'x');
		cout << s1.c_str() << endl;

		string s2("hello world");
		cout << s2.c_str() << endl;
		s2.insert(5, "yyy");
		cout << s2.c_str() << endl;
	}
	void test_string4()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;
		s1.erase(5, 2);
		cout << s1.c_str() << endl;

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

		string s3("hello world");
		cout << s3.c_str() << endl;
		s3.erase(5,100);
		cout << s3.c_str() << endl;
	}
	void test_string5()
	{
		//默认生成的构造是浅拷贝,会导致s1和s2指向同一空间,析构两次
		string s1("hello world");
		string s2(s1);
		s1[0] = 'x';
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;

		//赋值重载
		string s3("hello worldxxxxxx");
		s1 = s3;
		cout << s1.c_str() << endl;
		cout << s3.c_str() << endl;
	}
	void test_string6()
	{
		//字符串中间可以有'\0'这个有效字符
		string s1("hello world");
		s1 += 'x';
		s1 += '\0';
		s1 += "yyy";
		cout << s1 << endl;
		cout << s1.c_str() << endl;
		  
		//对中间有'\0'的字符拷贝
		string s2(s1);
		cout << s1 << endl;
		cout << s2 << endl;
	}
	void test_string7()
	{
		string s1;
		s1.resize(100, '*');
		cout << s1 << endl;

		s1.resize(10);
		cout << s1 << endl;
		s1.resize(20, '#');
		cout << s1 << endl;
	}
	void test_string8()
	{
		string ur1 = "https://cplusplus.com/reference/string/string/rfind/";
		//分离网络协议
		size_t i1 = ur1.find(':');
		if (i1 != string::npos)
		{
			//protocol协议
			string protocol = ur1.substr(0, i1);
			cout << protocol << endl;
			//分离域名
			size_t i2 = ur1.find('/', i1 + 3);
			if (i2 != string::npos)
			{
				string domain = ur1.substr(i1 + 3, i2 - (i1 + 3));
				cout << domain << endl;
				//分离最后
				string uri = ur1.substr(i2 + 1);
				cout << uri << endl;
			}
		}
	}
	void test_string9()
	{
		string s1,s2("xxxxx");
		cin >> s1>>s2;
		cout << s1 << endl;
		cout << s2 << endl;

		getline(cin,s1);
		cout << s1 << endl;
	}
	void test_string10()
	{
		string s1("hello world"), s2("xxxxx");
		s1.swap(s2);
		cout << s1 << endl;
		cout << s2 << endl;
	}
}
int main()
{
	//bit::test_string1();
	//bit::test_string2();
	//bit::test_string3();
	//bit::test_string4();
	//bit::test_string5();
	//bit::test_string6();
	//bit::test_string7();
	//bit::test_string8();
	//bit::test_string9();
	bit::test_string10();
	return 0;
}

1.6 vs和g++下string结构的说明

注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。

  • vs下string的结构

string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:

  1. 当字符串长度小于16时,使用内部固定的字符数组来存放
  2. 当字符串长度大于等于16时,从堆上开辟空间
cpp 复制代码
union _Bxty
{ 
	// storage for small buffer or pointer to larger one
	 value_type _Buf[_BUF_SIZE];
	 pointer _Ptr;
	 char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;

这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。

其次:还有一个size_t字段保存字符串长度 ,一个size_t字段保存从堆上开辟空间总的容量

最后:还有一个指针做一些其他事情

故总共占16+4+4+4=28个字节。

  • g++下string的结构

G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:

  • 空间总大小
  • 字符串有效长度
  • 引用计数
cpp 复制代码
struct _Rep_base
{
 size_type               _M_length;
 size_type               _M_capacity;
 _Atomic_word            _M_refcount;
};
  • 指向堆空间的指针,用来存储字符串。

1.7 补充的接口

2.扩展阅读

面试中string的一种正确写法
STL中的string类怎么了?

相关推荐
add45a2 小时前
C++中的智能指针详解
开发语言·c++·算法
闻缺陷则喜何志丹2 小时前
【分治法 前缀和】P8572 [JRKSJ R6] Eltaw|普及+
c++·算法·前缀和·洛谷·分治法
ulias2122 小时前
函数栈帧的创建和销毁
开发语言·数据结构·c++·windows·算法
迷海2 小时前
力扣原题《分发糖果》,采用二分原则,纯手搓,待验证
c++·算法·leetcode
Trouvaille ~2 小时前
【项目篇】从零手写高并发服务器(七):定时器TimerWheel与线程池
运维·服务器·网络·c++·reactor·高并发·muduo库
j_xxx404_2 小时前
蓝桥杯基础--模拟
数据结构·c++·算法·蓝桥杯·排序算法
sqyno1sky2 小时前
零成本抽象在C++中的应用
开发语言·c++·算法
cm6543202 小时前
C++中的职责链模式
开发语言·c++·算法
小菜鸡桃蛋狗3 小时前
C++——类和对象(中)
开发语言·c++