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

Posted Redamanc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通过自定义string类型来理解运算符重载相关的知识,希望对你有一定的参考价值。

自定义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,但是这样一来的效率非常低!
那么如何能够实现不泄露内存,并且提高效率呢?
未完待续。

以上是关于通过自定义string类型来理解运算符重载的主要内容,如果未能解决你的问题,请参考以下文章

从自定义string类型理解右值引用

从自定义string类型理解右值引用

C++ 将自定义类型的转换运算符重载为 std::string

学习:类和对象——运算符重载

了解下C# 运算符重载

重载运算符语法讲解