程序员成长之旅——string(深浅拷贝、写时拷贝)

本文深入探讨了C++中string类的深拷贝、浅拷贝及写时拷贝的概念与实现。介绍了string类的初始化、比较、查找等基本操作,详细分析了深拷贝和浅拷贝的区别,以及写时拷贝的实现机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

string简介

string的初始化

首先,string要使用的话,必须包含其头文件。

#incldue<string>

还要知道的是string类是一个模范类,位于名字空间std中,因此还要有

using namespace std;

那么如何声明一个字符串变量呢?

string str;
string的比较等操作

string中可以使用 ==、>、<、>=、<=、和!=比较字符串,也可以使用+或者+=操作符来连接两个字符串,并且可以使用[]获取特定的字符。

string的一些特性

可以用一些函数来说明string的特性

int capacity()const; //返回当前容量(即string中不必增加内存即可存放的元素个数)
int max_size()const; //返回string对象中可存放最大字符串的长度
int size()const;     //返回当前字符串的大小
int length()const;   //返回当前字符串的长度
bool empty()const;   //当前字符串是否为空
void resize(int len,char c) //把字符串当前大小置为len,多去少补,多出的字符c填充不足的部分
string的查找

由于查找是使用最为频繁的功能之一,string提供了非常丰富的查找函数:(注:string::npos)

size_type find(const basic_string &str, size_type index);//返回str在字符串中第一次出现的位置(从index开始查找),如果没找到则返回string::npos
size_type find( const char *str, size_type index );  // 同上
size_type find( const char *str, size_type index, size_type length );  //返回str在字符串中第一次出现的位置(从index开始查找,长度为length),如果没找到就返回string::npos
size_type find( char ch, size_type index );  // 返回字符ch在字符串中第一次出现的位置(从index开始查找),如果没找到就返回string::npos

**注意:**查找字符串a是否包含子串b,不是用 strA.find(strB) > 0 而是 strA.find(strB) != string:npos (初学者比较容易犯的一个错误)
参考1
参考2

string其它常用的函数
string &insert(int p,const string &s);//在p位置插入字符串s
string &replace(int p, int n,const char *s); //删除从p开始的n个字符,然后在p处插入串s
string &erase(int p, int n);  //删除p开始的n个字符,返回修改后的字符串
string substr(int pos = 0,int n = npos) const;  //返回pos开始的n个字符组成的字符串
void swap(string &s2);    //交换当前字符串与s2的值
string &append(const char *s);   //把字符串s连接到当前字符串结尾
void push_back(char c)   //当前字符串尾部加一个字符c

浅拷贝

我们先来看一段代码

#include<iostream>
#include<string.h>
#include<assert.h>
using namespace std;
class String
{
public:
	String(const char* str = "")
	{
		//构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言下
		if (nullptr == str)
		{
			assert(false);
			return;
		}

		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};
void TestString()
{
	String s1("hello world!!!");
	String s2(s1);
}
int main()
{
	TestString();
	return 0;
}

在这里插入图片描述
根据调试我们会发现:由于没有写拷贝构造函数,编译器会默认生成一个拷贝构造函数,最终导致的问题是,它两会同时用一块内存空间,析构的时候会析构多次,从而会导致程序崩溃,我们把这种情况叫做浅拷贝。赋值运算符重载同上。

深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。
为了解决这个问题,我们引入了深拷贝,而深拷贝有两种写的方式。
传统版

namespace lpf
{
	class string
	{
	public:
		string(const char* str = "")
		{
			if (nullptr == str)
				str = "";

			_str = new char[strlen(str) + 1];
			strcpy(_str, str);
		}
		//深拷贝
		string(const string& s)
			:_str(new char[strlen(s._str)+1])
		{
			strcpy(_str, s._str);
		}
		//深拷贝
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* pStr = new char[strlen(s._str) + 1];
				strcpy(pStr, s._str);
				delete[] _str;
				_str = pStr;
			}
			return *this;
		}
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
			}
		}
	private:
		char* _str;
	};
}

现代版

namespace lpf
{
	class string
	{
	public:
		string(const char* str = "")
		{

			if (nullptr == str)
				str = "";

			_str = new char[strlen(str) + 1];
			strcpy(_str, str);
		}
		
		string(const string& s)
			:_str(nullptr)//防止崩溃 this指向的有可能是一个无效空间,因此要将其初始化
		{
			string strTemp(s._str);
			swap(_str, strTemp._str);
		}
		string& operator=(string s)
		{
			swap(_str, s._str);
			return *this;
		}

		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
			}
		}
	private:
		char* _str;
	};
}

写时拷贝

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

namespace lpf
{
	class string
	{
	public:
		string(const char* str = "")
		{
			if (nullptr == str)
			{
				str = "";
			}
			_str = new char[strlen(str) + 1];
			strcpy(_str, str);
			_pCount = new int(1);
		}
		string(const string& s)
			:_str(s._str)
			,_pCount(s._pCount)
		{
			++(*_pCount);
		}
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				Release();

				_str = s._str;
				_pCount = s._pCount;
				++(*_pCount);
			}
		}

		char& operator[](size_t index)
		{
			if (*_pCount > 1)
			{
				string strTemp(_str);
				Swap(strTemp);
			}
			return _str[index];
		}

		const char& operator[](size_t index)const
		{
			return _str[index];
		}

		~string()
		{
			Release();
		}
		void Swap(string& s)
		{
			swap(_str, s._str);
			swap(_pCount, s._pCount);
		}
	private:
		void Release()
		{
			if (_str && 0 == --(*_pCount))
			{
				delete[] _str;
				delete _pCount;
				_str = nullptr;
				_pCount = nullptr;
			}
		}
	private:
		char* _str;
		int* _pCount;
	};
}
void TestString()
{
	lpf::string s1("hello");
	lpf::string s2(s1);
	lpf::string s3("world");
	lpf::string s4(s3);

	s1 = s3;//s1 s3 s4共用一份空间  s2共用一份

	s2 = s4;//s1 s2 s3 s4共用同一份

	s1[0] = 'H';
}
int main()
{
	TestString();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

从零出发——

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

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

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

打赏作者

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

抵扣说明:

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

余额充值