【C++】运算符重载案例 - 字符串类 ③ ( 重载 左移 << 运算符 | 自定义类使用技巧 | 直接访问类的私有指针成员 | 为指针分配指定大小内存并初始化 0 )

2023-10-15 16:03:06 浏览数 (1)

一、重载 左移 << 运算符

1、左移 << 运算符作用

左移运算符重载 , 可参考 【C 】运算符重载 ⑧ ( 左移运算符重载 | 友元函数 / 成员函数 实现运算符重载 | 类对象 使用 左移运算符 ) 博客 ;

左移运算符 用于 cout << s1 << endl 语句中 , 将 s1 对象输出到 cout 标准输出流中 , 这是

2、重载 左移 << 运算符

使用 全局函数 实现 左移运算符 << 重载 :

  • 首先 , 写出函数名 , cout << s1 左移操作符重载 , 函数名规则为 " operate " 后面跟上要重载的运算符 , 函数名是 operate<< ;
代码语言:javascript复制
operate<<
  • 然后 , 根据操作数 写出函数参数 , 参数一般都是 对象的引用 ;
    • cout << s1 左操作数是 ostream cout 标准输出流 , 参数中是引用类型 ;
    • cout << s1 右操作数是 String s 类对象 , 参数中是引用类型 ;
代码语言:javascript复制
operator<<(ostream& out, String& s)
  • 再后 , 根据业务完善返回值 , 返回值可以是 引用 / 指针 / 元素 ; 此处返回 void 即可 ; 返回 ostream& 引用类型 , 是为了支持链式调用 cout << s1 << endl;
代码语言:javascript复制
ostream& operator<<(ostream& out, String& s)
  • 最后 , 实现函数体 , 编写具体的运算符操作业务逻辑 ;
代码语言:javascript复制
// 全局函数 中实现 String 左移运算符重载
// 返回 ostream& 引用类型 , 是为了支持链式调用 cout << s1 << endl;
ostream& operator<<(ostream& out, String& s)
{
	cout << "调用重载 左移 << 操作符函数 ostream& operator<<(ostream& out, String& s)" << endl;

	// 在函数体中将 String 对象的 m_p 指针指向的数据输出到 out 输出流中
	out << s.m_p  << endl;

	// 该返回值还需要当左值使用
	return out;
}

同时 , 还要在 String 类中 , 将上述函数声明为 String 类的 友元函数 ;

代码语言:javascript复制
class String
{
	// 使用 全局函数 实现 左移运算符 << 重载
	// 将全局函数 声明为 String 的友元函数
	friend ostream& operator<<(ostream& out, String& s);
}

二、自定义类使用技巧


1、直接访问类的私有指针成员

在开发中 , 自定义了一个 class 类 , 其中定义了 指针 类型的 成员变量 ;

一般情况下 , 成员变量 都要 声明为 私有 private 的 ;

如果要 直接是使用 私有的指针变量 ,

可以通过 public 函数获取 私有成员 ;

代码语言:javascript复制
class String
{
public:
	// 获取私有成员 char* m_p
	char* str()
	{
		return this->m_p;
	}

	// 获取私有成员 int m_len
	int len()
	{
		return this->m_len;
	}

private:
	// 字符串长度 , 不包括 ''
	// 内存占用空间大小 = 字符串长度   1
	int m_len;

	// 字符串指针, 指向堆内存中的字符串
	char* m_p;
};

2、为指针分配指定大小内存并初始化 0

在 有参构造函数 中 , 接收 int 类型的参数 , 该参数表示字符串大小 ;

如果 参数 为 0 , 则创建一个空字符串 , 指针指向的内存空间大小为 1 , 只存放一个 ‘’ 字符 , 表示字符串的结尾 ;

如果 参数 大于 0 , 为 字符串指针 分配 该大小 1 的内存空间 , 然后将这块内存空间赋值 0 ;

代码示例 :

代码语言:javascript复制
// 有参构造函数 , 接收 int 类型值 , 表示字符串大小
String::String(int len)
{
	if (len == 0)
	{
		// 默认构造一个空字符串 , 字符串长度为 0 
		// 但是 , 字符串指针 指向的内存空间大小是 1 , 内容是 ''
		this->m_len = 0;

		// 使用 new 关键字为 char* m_p; 指针分配内存
		// 对于基础数据类型 new 等同于 malloc
		this->m_p = new char[this->m_len   1];

		// 拷贝空字符串到 m_p 指向的内存中
		strcpy(this->m_p, "");
	}
	else
	{
		// 获取传入字符串的长度
		// 但是 , 字符串指针 指向的内存空间大小需要  1 , 内容是 ''
		this->m_len = len;

		// 使用 new 关键字为 char* m_p; 指针分配内存
		// 对于基础数据类型 new 等同于 malloc
		this->m_p = new char[this->m_len   1];

		// 将内存空间设置为 0 内容
		memset(this->m_p, 0, this->m_len);
	}
};

三、完整代码示例


1、String.h 类头文件

代码语言:javascript复制
#pragma once

#include "iostream"
using namespace std;

class String
{
public:
	// 默认的无参构造函数
	String();

	// 有参构造函数 , 接收一个 char* 类型字符串指针
	String(const char* p);

	// 有参构造函数 , 接收 int 类型值 , 表示字符串大小
	String(int len);

	// 拷贝构造函数 , 使用 String 对象初始化 对象值
	String(const String& s);

	// 析构函数
	~String();

public:
	// 重载等号 = 操作符 , 右操作数是 String 对象的情况
	String& operator=(const String& s);

	// 重载等号 = 操作符 , 右操作数是 字符串常量值 的情况
	String& operator=(const char* p);

	// 重载 数组下标 [] 操作符
	char& operator[](int i);

	// 使用 全局函数 实现 左移运算符 << 重载
	// 将全局函数 声明为 String 的友元函数
	friend ostream& operator<<(ostream& out, String& s);

public:
	// 获取私有成员 char* m_p
	char* str();

	// 获取私有成员 int m_len
	int len();

private:
	// 字符串长度 , 不包括 ''
	// 内存占用空间大小 = 字符串长度   1
	int m_len;

	// 字符串指针, 指向堆内存中的字符串
	char* m_p;
};

2、String.cpp 类实现

代码语言:javascript复制
// 使用 strcpy 函数报错
// error C4996: 'strcpy': This function or variable may be unsafe. 
// Consider using strcpy_s instead. 
// To disable deprecation, use _CRT_SECURE_NO_WARNINGS. 
// See online help for details.
#define _CRT_SECURE_NO_WARNINGS

#include "String.h"

// 默认的无参构造函数
String::String()
{
	// 默认构造一个空字符串 , 字符串长度为 0 
	// 但是 , 字符串指针 指向的内存空间大小是 1 , 内容是 ''
	m_len = 0;

	// 使用 new 关键字为 char* m_p; 指针分配内存
	// 对于基础数据类型 new 等同于 malloc
	m_p = new char[m_len   1];

	// 拷贝空字符串到 m_p 指向的内存中
	strcpy(m_p, "");

	cout << "调用无参构造函数" << endl;
}

// 有参构造函数 , 接收一个 char* 类型字符串指针
String::String(const char* p)
{
	if (p == NULL)
	{
		// 默认构造一个空字符串 , 字符串长度为 0 
		// 但是 , 字符串指针 指向的内存空间大小是 1 , 内容是 ''
		this->m_len = 0;

		// 使用 new 关键字为 char* m_p; 指针分配内存
		// 对于基础数据类型 new 等同于 malloc
		this->m_p = new char[this->m_len   1];

		// 拷贝空字符串到 m_p 指向的内存中
		strcpy(this->m_p, "");
	}
	else
	{
		// 获取传入字符串的长度
		// 但是 , 字符串指针 指向的内存空间大小需要  1 , 内容是 ''
		this->m_len = strlen(p);

		// 使用 new 关键字为 char* m_p; 指针分配内存
		// 对于基础数据类型 new 等同于 malloc
		this->m_p = new char[this->m_len   1];

		// 拷贝字符串到 m_p 指向的内存中
		strcpy(this->m_p, p);
	}
	cout << "调用有参构造函数" << endl;
}

// 有参构造函数 , 接收 int 类型值 , 表示字符串大小
String::String(int len)
{
	if (len == 0)
	{
		// 默认构造一个空字符串 , 字符串长度为 0 
		// 但是 , 字符串指针 指向的内存空间大小是 1 , 内容是 ''
		this->m_len = 0;

		// 使用 new 关键字为 char* m_p; 指针分配内存
		// 对于基础数据类型 new 等同于 malloc
		this->m_p = new char[this->m_len   1];

		// 拷贝空字符串到 m_p 指向的内存中
		strcpy(this->m_p, "");
	}
	else
	{
		// 获取传入字符串的长度
		// 但是 , 字符串指针 指向的内存空间大小需要  1 , 内容是 ''
		this->m_len = len;

		// 使用 new 关键字为 char* m_p; 指针分配内存
		// 对于基础数据类型 new 等同于 malloc
		this->m_p = new char[this->m_len   1];

		// 将内存空间设置为 0 内容
		memset(this->m_p, 0, this->m_len);
	}
};

// 拷贝构造函数 , 使用 String 对象初始化 对象值
String::String(const String& s)
{
	// 拷贝字符串长度
	// 注意 : 字符串指针 指向的内存空间大小需要  1 , 内容是 ''
	this->m_len = s.m_len;

	// 使用 new 关键字为 char* m_p; 指针分配内存
	// 对于基础数据类型 new 等同于 malloc
	this->m_p = new char[this->m_len   1];

	// 拷贝字符串到 m_p 指向的内存中
	strcpy(this->m_p, s.m_p);

	cout << "调用拷贝构造函数" << endl;
}

// 析构函数
String::~String()
{
	if (this->m_p != NULL)
	{
		// 之前使用 new 分配的内存
		// 释放内存就需要使用 delete 
		// 使用 malloc 分配的内存需要使用 free 释放
		delete[] this->m_p;

		// 设置指针指为空 , 避免出现野指针
		this->m_p = NULL;

		// 设置字符串长度为 0
		this->m_len = 0;
	}
}

// 重载等号 = 操作符 , 右操作数是 String 对象的情况
String& String::operator=(const String& s)
{
	// 先处理本对象已分配的内存
	if (this->m_p != NULL)
	{
		// 之前使用 new 分配的内存
		// 释放内存就需要使用 delete 
		// 使用 malloc 分配的内存需要使用 free 释放
		delete[] this->m_p;

		// 设置指针指为空 , 避免出现野指针
		this->m_p = NULL;

		// 设置字符串长度为 0
		this->m_len = 0;
	}

	// 拷贝字符串长度
	// 注意 : 字符串指针 指向的内存空间大小需要  1 , 内容是 ''
	this->m_len = s.m_len;

	// 使用 new 关键字为 char* m_p; 指针分配内存
	// 对于基础数据类型 new 等同于 malloc
	this->m_p = new char[this->m_len   1];

	// 拷贝字符串到 m_p 指向的内存中
	strcpy(this->m_p, s.m_p);

	cout << "调用重载 等号 = 操作符函数 String& String::operator=(const String& s)" << endl;

	return *this;
}

// 重载等号 = 操作符 , 右操作数是 字符串常量值 的情况
String& String::operator=(const char* p)
{
	// 先处理本对象已分配的内存
	if (this->m_p != NULL)
	{
		// 之前使用 new 分配的内存
		// 释放内存就需要使用 delete 
		// 使用 malloc 分配的内存需要使用 free 释放
		delete[] this->m_p;

		// 设置指针指为空 , 避免出现野指针
		this->m_p = NULL;

		// 设置字符串长度为 0
		this->m_len = 0;
	}

	// 拷贝字符串长度
	// 注意 : 字符串指针 指向的内存空间大小需要  1 , 内容是 ''
	this->m_len = strlen(p);

	// 使用 new 关键字为 char* m_p; 指针分配内存
	// 对于基础数据类型 new 等同于 malloc
	this->m_p = new char[this->m_len   1];

	// 拷贝字符串到 m_p 指向的内存中
	strcpy(this->m_p, p);

	cout << "调用重载 等号 = 操作符函数 String& String::operator=(const char* p)" << endl;

	return *this;
}

// 重载 数组下标 [] 操作符
char& String::operator[](int i)
{
	cout << "调用重载 下标 [] 操作符函数 char& String::operator[](int i)" << endl;

	// 直接返回对应 i 索引字符
	return this->m_p[i];
}

// 获取私有成员 char* m_p
char* String::str()
{
	return this->m_p;
}

// 获取私有成员 int m_len
int String::len()
{
	return this->m_len;
}

// 全局函数 中实现 String 左移运算符重载
// 返回 ostream& 引用类型 , 是为了支持链式调用 cout << s1 << endl;
ostream& operator<<(ostream& out, String& s)
{
	cout << "调用重载 左移 << 操作符函数 ostream& operator<<(ostream& out, String& s)" << endl;

	// 在函数体中将 String 对象的 m_p 指针指向的数据输出到 out 输出流中
	out << s.m_p  << endl;

	// 该返回值还需要当左值使用
	return out;
}

3、Test.cpp 测试类

代码语言:javascript复制
#include "iostream"
using namespace std;

// 导入自定义的 String 类
#include "String.h"

int main() {

	// 调用无参构造函数
	String s1;

	// 调用有参构造函数
	String s2("Tom");

	// 调用拷贝构造函数
	String s3 = s2;

	// 调用重载的等号运算符函数, 右操作数是 String 对象
	s1 = s2;

	// 调用重载的等号运算符函数, 右操作数是 字符串常量值 , char* 指针类型
	s3 = "Jerry";

	// 调用重载的下标运算符函数
	char c = s3[3];

	// 调用 重载的 左移运算符 函数
	cout << s3 << endl;

	// 控制台暂停 , 按任意键继续向后执行
	system("pause");

	return 0;
}

0 人点赞