C++string类的实现

本文详细介绍了C++标准库中的字符串类(string),包括其构造函数、拷贝构造函数、拷贝赋值函数等核心成员函数的实现原理。通过示例代码讲解了如何正确管理资源,避免浅拷贝带来的问题。

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

C++中提供了一种新的数据类型——字符串类型(string)。实际上string并不是C++的基本类型,它是在C++标准库中声明的一个字符串类,用这种数据类型可以定义对象,每一个字符串变量都是string类的一个对象。标准库类型string表示可变长的字符序列,使用string类型必须首先包含它的头文件。
作为标准库的一部分,string定义在命名空间std中。
【例】

#include<string>//注意这里没有.h 
using namespace std;

string类的意义有两个:第一个是为了处理char类型的数组,并封装了标准C中的一些字符串处理的函数。而当string类进入了C++标准后,它的第二个意义就是一个容器。

string类有106个成员接口函数。C++ 的一个常见面试题是让你实现一个 String 类,限于时间,不可能要求具备 std::string 的功能,但至少要求能正确管理资源。具体来说:
1)能像 int 类型那样定义变量,并且支持赋值、复制。
2)能用作函数的参数类型及返回类型。
3)能用作标准库容器的元素类型,即 vector/list/deque 的 value_type。(用作 std::map 的 key_type 是更进一步的要求,本文从略)。

下面是模拟实现string类的几个重要函数:
1、构造函数
【例】

#define _CRT_SECURE_NO_WARNINGS
#include<string>

using namespace std;

class String
{
public:
	String(char *str = "")
	{
		if (str == NULL)
		{
			_str = new char[1];//为了与delete[]配合使用
			*_str = '\0';
			size = 0;
		}
		else
		{
			int length = strlen(str);
			_str = new char[length + 1];
			strcpy(_str, str);
			size = length;
		}
	}
	~String()
	{
		if (NULL != _str)
		{
			delete[] _str;//delete[]释放的空间必须是动态分配的
		}
	}
private:
	char *_str;//指向字符串的指针
	size_t size;//保存当前字符串长度
};

void Test2()
{
	String s1("hello");
	String s2(new char[3]);
}

int main()
{
	Test2();
	system("pause");
	return 0;
}

释:在构造函数中,_str被初始化为空字符串(只有‘\0’)而不是NULL。因为C++中的任何字符串的长度至少为1(即至少包含一个结束符‘\0’)。空字符串也是有效的字符串,它的长度为1,因此他代表一块合法的内存单元而不是NULL。

2、拷贝构造函数
1)浅拷贝
【例】

String::String(const String& s)//浅拷贝
	:_str(s._str)
{}

void Test2()
{
	String s1("hello");
	String s2(s1);
}

以上代码会使系统崩溃。因为s1只是将_str的地址传给s2中的_str,即浅拷贝(位拷贝),如下图所示:
这里写图片描述
由图可知:s1和s2指向同一块内存,析构时先对s2释放“hello”这块空间,当要再析构s1时,系统将崩溃。

释:浅拷贝又称为位拷贝,编译器只是将指针的内容拷贝过来,导致多个对象共用一块内存空间,当其中任意对象将这块空间释放之后,当再次访问时将出错。

解决方法:(深拷贝)给要拷贝构造的对象重新分配空间。
【例】

String::String(const String& s)//深拷贝
	:_str(new char[strlen(s._str) + 1])
{
	strcpy(_str, s._str);
	size = strlen(s._str);
}

void Test2()
{
	String s1("hello");
	String s2(s1);
}

其运行的状态如图所示:
这里写图片描述
由图可知:拷贝的对象s2中_str的值(字符串的地址)和s1对象中的_str的值不同,“hello”保存在地址不同的两个空间里,说明了系统为对象重新开辟了空间——深拷贝。

其工作原理如图所示:
这里写图片描述

3、拷贝赋值函数
【例】

	//方法一:
	String& String::operator=(const String& s)
	{
		if (this != &s)//检查自赋值
		{
			delete[]_str;
			_str = new char[strlen(s._str) + 1];
			strcpy(_str, s._str);
	      size = strlen(s._str);

		}
		return *this;//为了支持链式访问
	}

	//方法二:
	String& String:: operator=(const String& s)
	{
		if (this != &s)//1)检查自赋值
		{//2)创建临时变量获得分配的内存空间,并复制原来的内容
			char *tmp = new char[strlen(s._str) + 1];
			strcpy(tmp, s._str);
			delete[]_str;//3)释放原有的内存
			_str = tmp;
			size = strlen(s._str);
		}
		return *this;//4)返回本对象引用
	}

其运行状态如图所示:
这里写图片描述

分析:一般情况下,上面的两种写法都可以,但是相对而言,第二种更优一点。
方法一,先释放原有的空间,但是如果下面用new开辟新空间时失败了,而这时将s2赋值给s3,不仅不能成功赋值(空间开辟失败),还破坏了原有的s3对象。
方法二,先开辟新空间,将新空间的地址赋给一个临时变量,就算这时空间开辟失败,也不会影响原本s3对象。
综上所述:第二种方法更优一点。

注意:最后的返回值是为了支持链式访问。
例如:s3 = s2 = s1;

4、拷贝构造函数的现代写法:
【例】

String::String(const String& s)
	:_str(NULL)//一定要对_str初始化
{
	String tmp(s._str);
	std::swap(tmp._str, _str);
}

释:如果没有初始化,_str的值很可能是一个随机值,其指向的内存空间是不合法的。当tmp._str和_str交换后析构tmp就会出错。

5、赋值运算符重载函数的两种现代写法
【例】

//第一种:
String& String::operator=(String s)
{
	std::swap(_str, s._str);
	return *this;
}

//第二种:
String& String::operator=(const String& s)
{
	if (this->_str != s._str)
	{
		String tmp(s);
		std::swap(tmp._str, _str);
	}
	return *this;
}

在面试时,一般写出上面四个string类的成员函数即可,除非面试官特别要求。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值