一、支持 数组类模板 存储的 自定义类




1、可拷贝和可打印的自定义类


在上一篇博客 中 , 定义了 可拷贝 与 可打印 的 自定义类 Student , 可以被存放到 数组类模板 中 ;

由于其 成员变量 char m_name[32] 是 数组类型 , 创建时就直接分配了内存空间 , 即使浅拷贝也可以完成对 该类型对象的 拷贝工作 ;

class Student
{
	friend ostream& operator<<(ostream& out, const Student& s);
public:
	Student(){
		m_age = 10;
		strcpy(m_name, "NULL");
	}
	Student(const char* name, int age) {
		strcpy(this->m_name, name);
		this->m_age = age;
	}
	void printT() {
		cout << "name : " << m_name << " , age : " << m_age << endl;
	}

private:
	char m_name[32];
	int m_age;
};

// 重载左移运算符实现
ostream& operator<<(ostream& out, const Student& s) {
	out << "name : " << s.m_name << " , age : " << s.m_age << " ; ";
	return out;
}

2、改进方向


本篇博客中 , 开始讨论 自定义类 中是 char* 类型指针的情况 , 这里涉及到了 堆内存分配 以及 深拷贝 问题 ;


如果将上述 Student 类中的 char m_name[32] 数组成员 , 改为 char* m_name 指针成员 ;


那么需要进行 堆内存管理 ,

  • 在 构造函数中 分配堆内存 ;
  • 在 析构函数中 释放堆内存 ;

为了避免 浅拷贝 问题出现 , 需要

  • 进行 等号 = 运算符重载 ;
  • 以及 重写 拷贝构造函数 ;

为了使用 cout 打印该 类对象 , 需要 进行 左移 << 运算符重载 ;


3、改进方向 - 构造函数


在类的 无参构造函数 和 有参构造函数中 ,

使用 new 关键字 , 自动在堆内存中分配内存 , 然后为 堆内存 中的空间赋值 ;

	Student(){
		m_age = 10;
		// 创建一个数组个数为 1 的数组, 存放 '\0' 值
		// 这是一个空字符串
		m_name = new char[1];
		strcpy(m_name, "");
	}

	Student(const char* name, int age) {
		// 计算字符串大小
		// 总的大小是 字符个数 + \0 字符, 因此多一个字节
		int len = strlen(name) + 1;
		// 根据字符串大小创建 字符数组
		m_name = new char[len];
		strcpy(this->m_name, name);

		this->m_age = age;
	}

4、改进方向 - 析构函数


在析构函数中 , 需要将 使用 new 关键字申请的 堆内存进行释放 , 这里必须使用 delete 进行释放 ;

使用 malloc 申请的堆内存 , 必须使用 free 进行释放 ;

使用 new 申请的堆内存 , 必须使用 delete 进行释放 ;

	~Student()
	{
		if (m_name != NULL){
			delete[] m_name;
			m_name = NULL;
		}
	}

5、改进方向 - 重载左移运算符


重载左移运算符 , 以便可以在 cout 中打印该类信息 ;

首先 , 在类内部声明 重载左移运算符 的友元函数 ;

class Student
{
	friend ostream& operator<<(ostream& out, const Student& s);
}

然后 , 在 类外部 的 全局函数 中 , 实现 重载左移运算符函数 ;

// 重载左移运算符实现
ostream& operator<<(ostream& out, const Student& s) {
	out << "name : " << s.m_name << " , age : " << s.m_age << " ; ";
	return out;
}

6、改进方向 - 重载拷贝构造函数 和 等号运算符


重载拷贝构造函数 和 等号运算符 , 方便类初始化 和 使用等号赋值 ;

	Student(const Student& s) {
		// 计算字符串大小
		// 总的大小是 字符个数 + \0 字符, 因此多一个字节
		int len = strlen(s.m_name) + 1;
		// 根据字符串大小创建 字符数组
		m_name = new char[len];
		strcpy(this->m_name, s.m_name);

		this->m_age = s.m_age;
	}

	// 重载等号操作符
	Student& operator=(const Student& obj) {
		if (m_name != NULL) {
			delete[] m_name;
			m_name = NULL;
		}

		// 计算字符个数
		int len = strlen(obj.m_name) + 1;
		// 根据字符串大小创建 字符数组
		m_name = new char[len];
		strcpy(this->m_name, obj.m_name);
		this->m_age = obj.m_age;

		return *this;
	}




二、代码示例




1、Array.h 头文件


#pragma once

#include "iostream"
using namespace std;

template <typename T>
class Array
{
	// 左移 << 操作符重载
	// 注意 声明时 , 需要在 函数名 和 参数列表之间 注明 泛型类型 <T>
	//		实现时 , 不能在 函数名 和 参数列表之间 注明 泛型类型 <T>
	friend ostream& operator<< <T> (ostream& out, const Array& a);

public:
	// 有参构造函数
	Array(int len = 0);

	// 拷贝构造函数
	Array(const Array& array);

	// 析构函数
	~Array();

public:
	// 数组下标 [] 操作符重载
	// 数组元素类型是 T 类型
	T& operator[](int i);

	// 等号 = 操作符重载
	Array& operator=(const Array& a);

private:
	// 数组长度
	int m_length;

	// 指向数组数据内存 的指针
	// 指针类型 是 泛型类型 T
	T* m_space;
};

2、Array.cpp 代码文件


#include "Array.h"

// 左移 << 操作符重载
// 注意 声明时 , 需要在 函数名 和 参数列表之间 注明 泛型类型 <T>
//		实现时 , 不能在 函数名 和 参数列表之间 注明 泛型类型 <T>
template <typename T>
ostream& operator<< (ostream& out, const Array<T>& a)
{
	for (int i = 0; i < a.m_length; i++)
	{
		// 在一行内输入数据, 使用空格隔开, 不换行
		out << a.m_space[i] << " ";
	}
	// 换行
	out << endl;
	return out;
}


// 有参构造函数
template <typename T>
Array<T>::Array(int len)
{
	// 设置数组长度
	m_length = len;

	// 为数组在堆内存中分配内存
	// 注意 元素类型为 T
	m_space = new T[m_length];

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

// 拷贝构造函数
// 这是一个深拷贝 拷贝构造函数
template <typename T>
Array<T>::Array(const Array<T>& array)
{
	// 设置数组长度
	m_length = array.m_length;

	// 创建数组
	// 注意 元素类型为 T
	m_space = new T[m_length];

	// 为数组赋值
	for (int i = 0; i < m_length; i++)
	{
		m_space[i] = array.m_space[i];
	}

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

// 析构函数
template <typename T>
Array<T>::~Array()
{
	if (m_space != NULL)
	{
		// 释放 new T[m_length] 分配的内存 
		delete[] m_space;
		m_space = NULL;
		m_length = 0;
	}

	cout << " 调用析构函数 " << endl;
}

// 数组下标 [] 操作符重载
template <typename T>
T& Array<T>::operator[](int i)
{
	return m_space[i];
}

// 等号 = 操作符重载
template <typename T>
Array<T>& Array<T>::operator=(const Array<T>& a)
{
	if (this->m_space != NULL)
	{
		// 释放 new int[m_length] 分配的内存 
		delete[] this->m_space;
		this->m_space = NULL;
	}

	// 设置数组长度
	this->m_length = a.m_length;

	// 创建数组
	this->m_space = new T[m_length];

	// 为数组赋值
	for (int i = 0; i < m_length; i++)
	{
		this->m_space[i] = a.m_space[i];
	}

	cout << " 调用 等号 = 操作符重载 函数" << endl;

	// 返回是引用类型
	// 返回引用就是返回本身
	// 将 this 指针解引用, 即可获取数组本身
	return *this;
}

3、Test.cpp 主函数代码文件


#define _CRT_SECURE_NO_WARNINGS
#include "iostream"
using namespace std; 

// 此处注意, 类模板 声明与实现 分开编写
// 由于有 二次编译 导致 导入 .h 头文件 类模板函数声明 无法找到 函数实现
// 必须 导入 cpp 文件
#include "Array.cpp"

class Student
{
	friend ostream& operator<<(ostream& out, const Student& s);
public:
	Student(){
		m_age = 10;
		// 创建一个数组个数为 1 的数组, 存放 '\0' 值
		// 这是一个空字符串
		m_name = new char[1];
		strcpy(m_name, "");
	}

	Student(const char* name, int age) {
		// 计算字符串大小
		// 总的大小是 字符个数 + \0 字符, 因此多一个字节
		int len = strlen(name) + 1;
		// 根据字符串大小创建 字符数组
		m_name = new char[len];
		strcpy(this->m_name, name);

		this->m_age = age;
	}

	Student(const Student& s) {
		// 计算字符串大小
		// 总的大小是 字符个数 + \0 字符, 因此多一个字节
		int len = strlen(s.m_name) + 1;
		// 根据字符串大小创建 字符数组
		m_name = new char[len];
		strcpy(this->m_name, s.m_name);

		this->m_age = s.m_age;
	}

	void printT() {
		cout << "name : " << m_name << " , age : " << m_age << endl;
	}

	~Student()
	{
		if (m_name != NULL){
			delete[] m_name;
			m_name = NULL;
		}
	}

	// 重载等号操作符
	Student& operator=(const Student& obj) {
		if (m_name != NULL) {
			delete[] m_name;
			m_name = NULL;
		}

		// 计算字符个数
		int len = strlen(obj.m_name) + 1;
		// 根据字符串大小创建 字符数组
		m_name = new char[len];
		strcpy(this->m_name, obj.m_name);
		this->m_age = obj.m_age;

		return *this;
	}

private:
	char* m_name;
	int m_age;
};

// 重载左移运算符实现
ostream& operator<<(ostream& out, const Student& s) {
	out << "name : " << s.m_name << " , age : " << s.m_age << " ; ";
	return out;
}

int main() {

	// 验证 有参构造函数
	Array<Student> array(3);

	Student s0("Tom", 18), s1("Jerry", 12), s2("Jack", 16);
	array[0] = s0;
	array[1] = s1;
	array[2] = s2;

	// 遍历数组 打印数组元素
	for (int i = 0; i < 3; i++) {
		array[i].printT();
	}

	cout << array << endl;

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

	return 0;
}

4、Test.cpp 主函数代码文件


执行结果 :

调用有参构造函数
name : Tom , age : 18
name : Jerry , age : 12
name : Jack , age : 16
name : Tom , age : 18 ; name : Jerry , age : 12 ; name : Jack , age : 16 ;

Press any key to continue . . .

【C++】泛型编程 ⑮ ( 类模板示例 - 数组类模板 | 自定义类中持有指针成员变量 )-LMLPHP

11-24 23:14