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

文章目录

  • 一、支持 数组类模板 存储的 自定义类
    • 1、可拷贝和可打印的自定义类
    • 2、改进方向
    • 3、改进方向 - 构造函数
    • 4、改进方向 - 析构函数
    • 5、改进方向 - 重载左移运算符
    • 6、改进方向 - 重载拷贝构造函数 和 等号运算符
  • 二、代码示例
    • 1、Array.h 头文件
    • 2、Array.cpp 代码文件
    • 3、Test.cpp 主函数代码文件
    • 4、Test.cpp 主函数代码文件






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




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 . . .

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/185994.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

io.lettuce.core.RedisCommandExecutionException

io.lettuce.core.RedisCommandExecutionException: ERR invalid password ERR invalid password-CSDN博客 io.lettuce.core.RedisCommandExecutionException /** Copyright 2011-2022 the original author or authors.** Licensed under the Apache License, Version 2.0 (the…

RabbitMQ基础教程

1.什么是消息队列 消息队列&#xff08;Message Queue&#xff09;&#xff0c;我们一般简称为MQ。消息队列中间件是分布式系统中重要的组件&#xff0c;具有异步性、松耦合、分布式、可靠性等特点。用于实现高性能、高可用、可伸缩和最终一致性架构。是大型分布式系统不可缺少…

JVM类加载的过程和JVM垃圾回收机制

文章目录 一、JVM类加载的过程1.1类加载的基本流程1.1.1加载1.1.2验证1.1.3准备1.1.4解析1.1.5初始化 1.2双亲委派模型 二、JVM垃圾回收机制2.1找到垃圾2.1.1引用计数(比如Python&#xff0c;PHP中用到)2.1.2可达性分析(比如Java中用到) 2.2释放垃圾2.2.1标记清除2.2.2复制算法…

RAM模型从数据准备到pretrain、finetune与推理全过程详细说明

提示&#xff1a;RAM模型&#xff1a;环境安装、数据准备与说明、模型推理、模型finetune、模型pretrain等 文章目录 前言一、环境安装二、数据准备与解读1.数据下载2.数据标签内容解读3.标签map内容解读 三、finetune训练1.微调训练命令2.load载入参数问题3.权重载入4.数据加载…

大数据技术之数据安全与网络安全——CMS靶场实训

大数据技术之数据安全与网络安全——CMS靶场实训 在当今数字化时代&#xff0c;大数据技术的迅猛发展带来了前所未有的数据增长&#xff0c;同时也催生了对数据安全和网络安全的更为迫切的需求。本篇博客将聚焦于大数据技术背景下的数据安全与网络安全&#xff0c;并通过CMS&a…

4.操作系统常见面试题(2)

3.4 虚拟内存 直接使⽤物理内存会产⽣⼀些问题 1. 内存空间利⽤率的问题&#xff1a;各个进程对内存的使⽤会导致内存碎⽚化&#xff0c;当要⽤ malloc 分配⼀块很⼤的内存空间时&#xff0c;可能会出现虽然有⾜够多的空闲物理内存&#xff0c;却没有⾜够⼤的连续空闲内存这种…

点大商城V2.5.3分包小程序端+小程序上传提示限制分包制作教程

这几天很多播播资源会员反馈点大商城V2.5.3小程序端上传时提示大小超限&#xff0c;官方默认单个包都不能超过2M&#xff0c;总分包不能超20M。如下图提示超了93KB&#xff0c;如果出现超的不多情况下可采用手动删除一些images目录下不使用的图片&#xff0c;只要删除超过100KB…

82基于matlab GUI的图像处理

基于matlab GUI的图像处理&#xff0c;功能包括图像一般处理&#xff08;灰度图像、二值图&#xff09;&#xff1b;图像几何变换&#xff08;旋转可输入旋转角度、平移、镜像&#xff09;、图像边缘检测&#xff08;拉普拉斯算子、sobel算子、wallis算子、roberts算子&#xf…

unordered_map 与 unordered_set 的模拟实现

unordered_map 与 unordred_set 的模拟实现与 map 与 set 的模拟实现差不多。map 与 set 的模拟实现中&#xff0c;底层的数据结构是红黑树。unordered_map 与 unordered_set 的底层数据结构是哈希表。因此&#xff0c;在模拟实现 unordered_map 与 unordred_set 之前你必须确保…

nodejs微信小程序+python+PHP-青云商场管理系统的设计与实现-安卓-计算机毕业设计

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder

密码&#xff0c;加密&#xff0c;解密 spring-security-crypto-5.7.3.jar /** Copyright 2002-2011 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with t…

HTML新特性【缩放图像、图像切片、平移、旋转、缩放、变形、裁切路径、时钟、运动的小球】(二)-全面详解(学习总结---从入门到深化)

目录 绘制图像_缩放图像 绘制图像_图像切片 Canvas状态的保存和恢复 图形变形_平移 图形变形_旋转 图形变形_缩放 图形变形_变形 裁切路径 动画_时钟 动画_运动的小球 引入外部SVG 绘制图像_缩放图像 ctx.drawImage(img, x, y, width, height) img &#xf…

开源与闭源

我的观点&#xff1a; 开源与闭源软件都有各自的优势和劣势&#xff0c;没有绝对的对错之分。.. 一、开源和闭源的优劣势比较 开源的好处与劣处 优势&#xff1a; 创新与合作&#xff1a;开源软件能够吸引更多的开发者参与到项目中来&#xff0c;促进创新和合作。开放的源代码…

【网易云商】构建高效 SaaS 系统的技术要点与最佳实践

SaaS 是什么 定义 相信大家都对云服务中的 IaaS、PaaS、SaaS 早就有所耳闻&#xff0c;现在更是衍生出了 aPaaS、iPaaS、DaaS 等等的类似概念。对于 SaaS 也有各种各样的定义&#xff0c;本文给出的定义是&#xff1a; SaaS 是一种基于互联网提供服务和软件的交付模式&#xf…

一文彻底看懂Python切片,Python切片理解与操作

1.什么是切片 切片是Python中一种用于操作序列类型(如列表、字符串和元组)的方法。它通过指定起始索引和结束索引来截取出序列的一部分,形成一个新的序列。切片是访问特定范围内的元素,就是一个Area。 说个笑话:切片不是切片,而是切片,但是又是切片。大家理解下呢(末…

80C51单片机----数据传送类指令

目录 一.一般传送指令&#xff0c;即mov指令 1.16位传送&#xff08;仅1条&#xff09; 2.8位传送 &#xff08;1&#xff09;目的字节为A&#xff08;累加器&#xff09; &#xff08;2&#xff09;目的字节为Rn(工作寄存器) &#xff08;3&#xff09;目的字节为direct…

java中的String.format()方法详解

介绍 String.format() 是 Java 中的一个字符串格式化方法&#xff0c;它用于生成指定格式的字符串。这个方法可以接受一个或多个参数&#xff0c;并将它们按照指定的格式插入到字符串中。它使用了类似于 C 语言中的 printf 函数的语法。 String.format() 方法的使用格式如下&…

Tars框架 Tars-Go 学习

Tars 框架安装 网上安装教程比较多&#xff0c;官方可以参数这个 TARS官方文档 (tarsyun.com) 本文主要介绍部署应用。 安装完成后Tars 界面 增加应用amc 部署申请 amc.GoTestServer.GoTestObj 名称不知道的可以参考自己创建的app config 点击刷新可以看到自己部署的应用 服…

微机原理_3

一、单项选择题(本大题共15小题,每小题3分,共45分。在每小题给出的四个备选项中,选出一个正确的答案,请将选定的答案填涂在答题纸的相应位置上。) 在 8086 微机系统中&#xff0c;完成对指令译码操作功能的部件是&#xff08;)。 A. EU B. BIU C. SRAM D. DRAM 使计算机执行某…

【Rust日报】2023-11-22 Floneum -- 基于 Rust 的一款用于 AI 工作流程的图形编辑器

Floneum -- 基于 Rust 的一款用于 AI 工作流程的图形编辑器 Floneum 是一款用于 AI 工作流程的图形编辑器&#xff0c;专注于社区制作的插件、本地 AI 和安全性。 Floneum 有哪些特性&#xff1a; 可视化界面&#xff1a;您无需任何编程知识即可使用Floneum。可视化图形编辑器可…