通过自定义string类型来理解运算符重载

理解系统自带string类型

在C语言中我们一般使用字符数组来保存字符串。
存在诸多不方便的地方:比如数组大小、数组长度、数组扩容等等。
为了更好地使用字符串,C++提供了string类型。
使用它需要引入头文件:#include <string>

示例代码

我们可以通过如下代码,来简单展示系统提供string类型的功能:

#include <iostream>
#include <string>
using namespace std;

int main()
{
	String str1;
	String str2 = "aaa"; 
	String str3 = "bbb";
	String str4 = str2 + str3;
	String str5 = str2 + "ccc";
	String str6 = "ddd" + str2;

	cout << "str6:" << str6 << endl;
	if (str5 > str6)
	{
		cout << str5 << " > " << str6 << endl;
	}
	else
	{
		cout << str5 << " < " << str6 << endl;
	}

	int len = str6.length();
	for (int i = 0; i < len; i++)
	{
		cout << str6[i] << " ";
	}
	cout << endl;

	char buff[1024] = { 0 };
	strcpy(buff, str6.c_str());
	cout << "buff:" << buff << endl;

	return 0;
}

功能展示

运行上述代码,得到结果如下:
在这里插入图片描述
我们可以看到:系统提供的string类型支持以下操作:

  1. 支持两个对象的相加;
  2. 支持对象和常量字符串的相加;
  3. 支持对象之间的比较操作(通过字典比较大小);
  4. 支持输出运算符的重载;
  5. 支持下标运算符的重载。

注意:
这里列出的操作仅是代表本示例代码体现出的功能,并不代表完整的功能。

自定义string类型

为了和系统提供的string类型做出区分,我们自定义的类型取名为:String(大写了首字母)。
由于我们使用到了其他地方的资源(一般是指堆上),故系统缺省的拷贝构造函数(浅拷贝)会出问题,所以我们需要自定义拷贝构造函数,同理,赋值函数也需要自定义。
构造、析构、拷贝构造、赋值函数自定义如下:

// 自己实现一个字符串对象
class String
{
public:
	String(const char* p = nullptr)
	{
		if (p != nullptr)
		{
			_pstr = new char[strlen(p) + 1];
			strcpy(_pstr, p);
		}
		else
		{
			_pstr = new char[1];
			*_pstr = '\0';
		}
	}
	~String()
	{
		delete[] _pstr;
		_pstr = nullptr;
	}
	String(const String& str)
	{
		_pstr = new char[strlen(str._pstr) + 1];
		strcpy(_pstr, str._pstr);
	}
	String& operator=(const String& src)
	{
		if (this == &src)
			return *this;

		delete[]_pstr;

		_pstr = new char[strlen(src._pstr) + 1];
		strcpy(_pstr, src._pstr);
		return *this;
	}
private:
	char* _pstr;
};

比较运算符(>、<、==)重载

通过系统提供的string类型实现的功能我们可以看到,比较运算符(>、<、==)是返回一个bool值,根据两个字符串的字典顺序比较大小。
具体实现如下:

bool operator>(const String& str)const
{
	return strcmp(_pstr, str._pstr) > 0;
}
bool operator<(const String& str)const
{
	return strcmp(_pstr, str._pstr) < 0;
}
bool operator==(const String& str)const
{
	return strcmp(_pstr, str._pstr) == 0;
}

细节:
因为只涉及读操作,故我们均把它实现为常方法(const),并且传递进来的参数也只涉及读操作,并且设计为引用

输出运算符(<<)重载

一般而言,对于自定义的类型,系统编译器不知道输出运算符需要输出什么信息,需要提供运算符(<<)的重载。
并且由于该运算符的调用不依赖于特定的对象,故我们设计为全局函数,这么一来,就无法访问到类型的私有数据(private),针对此问题,我们可以将该重载方法定义为该类型的友元函数
具体实现如下:

class
{
public:
private:
	friend ostream& operator<<(ostream& out, const String& str);
};
ostream& operator<<(ostream& out, const String& str)
{
	out << str._pstr;
	return out;
}

加法运算符(+)重载

对于string类型来说,+意味着将两个字符串进行合并
可能我们首先会想到strcat()函数,将两个字符串连接起来。
这里存在的一个问题就是:合并后的字符串是否有足够的空间容纳,我们会发现,前面的相关构造函数都是通过strlen()方法计算实参的大小,并以此开辟空间大小,换句话说:有多大开辟多大,一点不剩。所以,将后一个字符串连接到前一个字符串的时候,就会出现空间不够的问题。
如果意识到这个问题,那么实现就很容易了。
具体实现如下:

String operator+(const String& lhs, const String& rhs)
{
	char* _ptmp = new char[strlen(lhs._pstr) + strlen(rhs._pstr) + 1];
	strcpy(_ptmp, lhs._pstr);
	strcat(_ptmp, rhs._pstr);
	String tmp(_ptmp);
	delete[]_ptmp;
	return tmp;
}

注意:
这里的实现依然实现在全局函数中,故,为了能够访问私有数据,依然要定义为友元函数

下标运算符([])重载

通过下标运算符,可以模拟实现数组下标的相关操作。
具体实现如下:

char& operator[](int index) { return _pstr[index]; }

完整代码

#include <iostream>
#include <string>
#pragma warning(disable:4996)
using namespace std;
// char arr[]="jkasdas";

// 自己实现一个字符串对象
class String
{
public:
	String(const char* p = nullptr)
	{
		if (p != nullptr)
		{
			_pstr = new char[strlen(p) + 1];
			strcpy(_pstr, p);
		}
		else
		{
			_pstr = new char[1];
			*_pstr = '\0';
		}
	}
	~String()
	{
		delete[] _pstr;
		_pstr = nullptr;
	}
	String(const String& str)
	{
		_pstr = new char[strlen(str._pstr) + 1];
		strcpy(_pstr, str._pstr);
	}
	String& operator=(const String& src)
	{
		if (this == &src)
			return *this;

		delete[]_pstr;

		_pstr = new char[strlen(src._pstr) + 1];
		strcpy(_pstr, src._pstr);
		return *this;
	}
	bool operator>(const String& str)const
	{
		return strcmp(_pstr, str._pstr) > 0;
	}
	bool operator<(const String& str)const
	{
		return strcmp(_pstr, str._pstr) < 0;
	}
	bool operator==(const String& str)const
	{
		return strcmp(_pstr, str._pstr) == 0;
	}
	int length()const { return strlen(_pstr); }
	char& operator[](int index) { return _pstr[index]; }
	const char& operator[](int index)const { return _pstr[index]; }
	const char* c_str()const { return _pstr; }
private:
	char* _pstr;
	friend String operator+(const String& lhs, const String& rhs);
	friend ostream& operator<<(ostream& out, const String& str);
};

ostream& operator<<(ostream& out, const String& str)
{
	out << str._pstr;
	return out;
}

String operator+(const String& lhs, const String& rhs)
{
	char* _ptmp = new char[strlen(lhs._pstr) + strlen(rhs._pstr) + 1];
	strcpy(_ptmp, lhs._pstr);
	strcat(_ptmp, rhs._pstr);
	String tmp(_ptmp);
	delete[]_ptmp;
	return tmp;
}

功能对比

我们此时将示例代码中的所有string转换成String,来对比看看结果:
在这里插入图片描述
可以看到,一模一样

存在问题

但是我们自定义的代码并不是完美的,它存在这样的问题:
加法运算符重载函数中:
我们的代码如下:

String operator+(const String& lhs, const String& rhs)
{
	char* _ptmp = new char[strlen(lhs._pstr) + strlen(rhs._pstr) + 1];
	strcpy(_ptmp, lhs._pstr);
	strcat(_ptmp, rhs._pstr);
	String tmp(_ptmp);
	delete[]_ptmp;
	return tmp;
}

乍一看好像没有什么问题,但是我们仔细研究,就会发现,
为了将局部新开辟的内存空间_ptmp资源释放,我们后面又构造了一个临时对象String tmp(_ptmp),之后将局部变量资源释放delete[]_ptmp,但是这样一来的效率非常低!
那么如何能够实现不泄露内存,并且提高效率呢?
未完待续。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值