自己实现一个string类

自己实现一个string类

1.要求

自己实现一个string类,包含构造函数拷贝构造函数析构函数赋值运算符重载函数左移运算符重载函数

class CMyString
{
public:
	CMyString(const char* pData = nullptr); // 构造函数
	CMyString(const CMyString& str);        // 拷贝构造函数
	~CMyString();                           // 析构函数

	CMyString& operator =(const CMyString& str); // 赋值运算符重载函数
	friend ostream& operator<<(ostream& out, CMyString& str); // 左移运算符重载

private:
	char* m_pData;
};

CMyString的成员变量是char*指针。

构造函数:参数是const类型(保证字符串常量也能够初始化),默认参数是空指针nullptr。代码实现如下,先判断是否为空指针,如果是,则分配大小为1的char数组空间,用来保存结束符\0;如果不是,则分配大小为strlen(pData) + 1的char数组空间,然后将pData中的数据复制到m_pData

CMyString::CMyString(const char* pData)
{
	if (pData == nullptr) {
		m_pData = new char[1];
		*m_pData = '\0';
	}
	else {
		m_pData = new char[strlen(pData) + 1];
		strcpy(m_pData, pData);
	}
}

拷贝构造函数:函数的参数是const的CMyString类型,并且传入的是引用。注意这里必须传递引用,因为是值传递的话,就会拷贝一份实参str,又会调用拷贝构造函数,此时又是值传递,又会调用拷贝构造函数,从而进入死循环,故必须传入引用。函数的实现如下,和构造函数类似。

CMyString::CMyString(const CMyString & str)
{
	m_pData = new char[strlen(str.m_pData) + 1];
	strcpy(m_pData, str.m_pData);
}

析构函数:释放char类型的指针m_pData对应的内存空间,从而防止内存泄漏。函数实现如下:

CMyString::~CMyString()
{
	delete[]m_pData;
}

赋值运算符重载函数:函数的参数是const的CMyString类型,并且传入的是引用,这样能够提高效率,因为值传递会调用一次拷贝构造函数,同时我们在赋值运算符重载函数中不会改变传入实例的状态,因此给传入的引用参数加上const关键字。函数的返回值是CMyString &,并在函数结束前返回自身的引用*this,返回引用的目的是为了能够实现连续的赋值,比如str1=str2=str3

在实现函数的过程中要注意以下两点:

  • 要先释放当前对象的内存,然后再分配新的内存空间,进行复制。如果没有释放,就会造成内存泄漏。
  • 要判断是否是自拷贝,如果是传入的参数就是当前的实例*this,就不进行赋值,直接返回。如果是自拷贝,先释放了当前实例的内存,即传入进来的参数也被释放,这样就再也找不到传进来的参数了,就无法进行复制了。
CMyString & CMyString::operator=(const CMyString & str)
{
	// 如果是自己赋值给自己,直接返回
	if (this == &str) return *this;

	// 释放当前对象的内存
	if (m_pData != nullptr) delete[]m_pData;

	// 赋值
	m_pData = new char[strlen(str.m_pData)+1];
	strcpy(m_pData, str.m_pData);
	return *this; //返回当前的实例
}

本来觉得写成这样已经觉得可以了,最近看了剑指offer,发现还可以进行优化。因为这样也会有问题,当释放完当前对象的内存后,如果此时的内存空间不足会导致new char会抛出异常,而此时实例已经被释放了,m_pData是一个空指针,这样容易导致程序崩溃。考虑异常安全,我们可以先创建一个临时对象,再交换临时对象和原来的值。代码实现如下:

CMyString & CMyString::operator=(const CMyString & str)
{
	// 如果是自己赋值给自己,直接返回
	if (this != &str) {
		CMyString tmp(str); // 临时对象

		char* ptr = tmp.m_pData;
		tmp.m_pData = m_pData;
		m_pData = ptr;
	}
	return *this; //返回当前的实例
}

在函数中,先创建了一个临时对象tmp,然后交换tmp.m_pData和实例自身的m_pDatatmp是一个临时变量当程序执行到if的外面,则离开了tmp变量的作用域,那么就会自动调用tmp的析构函数,把tmp.m_pData所指向的内存释放掉,由于tmp.m_pData指向的内存就是实例之前的m_pData所指的内存,这就相当于借用临时变量tmp的析构函数来释放当前的内存空间。

在这里插入图片描述

在考虑异常安全的代码中,我们在CMyString的构造函数中用new分配内存,如果内存不足抛出异常如bad_alloc等,这时我们并没有修改原来实例的状态,从而保证了异常安全。

左移运算符重载:需要声明友元函数来实现,函数参数是输出流对象ostream & outCMyString & str,返回值为ostream &,这里需要返回引用,才能实现连续打印,比如cout<<str1<<str2

ostream & operator<<(ostream & out, CMyString & str)
{
	out << str.m_pData;
	return out;
}

2.代码实现

编译环境:Visual Studio 2017

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

class CMyString
{
public:
	CMyString(const char* pData = nullptr); // 构造函数
	CMyString(const CMyString& str);        // 拷贝构造函数
	~CMyString();                           // 析构函数

	CMyString& operator =(const CMyString& str); // 赋值运算符重载函数
	friend ostream& operator<<(ostream& out, CMyString& str); // 左移运算符重载

private:
	char* m_pData;
};

CMyString::CMyString(const char* pData)
{
	if (pData == nullptr) {
		m_pData = new char[1];
		*m_pData = '\0';
	}
	else {
		m_pData = new char[strlen(pData) + 1];
		strcpy(m_pData, pData);
	}
}

CMyString::CMyString(const CMyString & str)
{
	m_pData = new char[strlen(str.m_pData) + 1];
	strcpy(m_pData, str.m_pData);
}

CMyString::~CMyString()
{
	delete[]m_pData;
}
#if 0
CMyString & CMyString::operator=(const CMyString & str)
{
	// 如果是自己赋值给自己,直接返回
	if (this == &str) return *this;

	// 释放当前对象的内存
	if (m_pData != nullptr) delete[]m_pData;

	// 赋值
	m_pData = new char[strlen(str.m_pData)+1];
	strcpy(m_pData, str.m_pData);
	return *this; //返回当前的实例
}
#endif

// 进一步优化:考虑异常的安全性,如果释放内存过后,新开辟内存时,空间不足
CMyString & CMyString::operator=(const CMyString & str)
{
	// 如果是自己赋值给自己,直接返回
	if (this != &str) {
		CMyString tmp(str); // 临时对象

		char* ptr = tmp.m_pData;
		tmp.m_pData = m_pData;
		m_pData = ptr;
	}
	return *this; //返回当前的实例
}

ostream & operator<<(ostream & out, CMyString & str)
{
	out << str.m_pData;
	return out;
}

int main() {
	CMyString str0; //测试默认构造函数
	CMyString str1("I am str1"); // 测试有参构造函数
	CMyString str2("I am str2");
	CMyString str3(str2); // 测试拷贝构造函数
	cout << "str0: " << str0 << endl;
	cout << "str1: " << str1 << endl;
	cout << "str2: " << str2 << endl;
	cout << "str3: " << str3 << endl;

	
	str0 = str1; // 测试实例a赋值给实例b
	str2 = str2; // 测试自拷贝
	cout << "str0: " << str0 << endl;

	str0 = str2 = str1; // 测试连续赋值
	cout << "str0: " << str0 << endl;

	system("pause");
	return 0;
}

执行结果:

str0:
str1: I am str1
str2: I am str2
str3: I am str2
str0: I am str1
str0: I am str1
请按任意键继续. . .

3.参考

《剑指offer》

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值