【C++】泛型编程 ⑭ ( 类模板示例 - 数组类模板 | 容器思想 | 自定义类可拷贝 - 深拷贝与浅拷贝 | 自定义类可打印 - 左移运算符重载 )

news2024/9/23 5:21:39

文章目录

  • 一、容器思想
    • 1、自定义类可拷贝 - 深拷贝与浅拷贝
    • 2、自定义类可拷贝 - 代码示例
    • 3、自定义类可打印 - 左移运算符重载
  • 二、代码示例
    • 1、Array.h 头文件
    • 2、Array.cpp 代码文件
    • 3、Test.cpp 主函数代码文件
    • 4、执行结果






一、容器思想




1、自定义类可拷贝 - 深拷贝与浅拷贝


上一篇博客 【C++】泛型编程 ⑬ ( 类模板示例 - 数组类模板 | 构造函数和析构函数 的 声明与实现 | 普通成员函数 的 声明与实现 | 外部友元函数 的 声明与实现 ) 中 , 实现了一个 数组 类模板 , 数组 中的 数据元素 是 泛型类型 , 可以是任意类型 ;

也就是说 , 该数组可以存储 任意类型 的数据 , 包括 自定义类对象 ;

该数组 就是一个 数据的容器 ;

数组中 每个元素 插入数据时 , 其本质是一个 拷贝操作 , 数组 的 内存空间 在 声明实际类型 以及 创建 时 , 就已经确定了 , 向数组中插入元素 , 就是将 已有的 数据 拷贝到 已经分配好的内存中 ;

向 数据容器 ( 数组 ) 中插入的数据 , 必须可以被 拷贝 , 如果 不能被拷贝 , 就会出现插入数据失败的问题 ;


容器 中的 类型 可拷贝 , 就是要求 容器中的 数据类型 都是 值语义 , 不是 引用语义 ,

向 容器 中插入元素 , 就是拷贝 数据内容 到容器中 , 要将真实的值拷贝进去 , 不是将 引用地址 拷贝进去 ,

就是 深拷贝 和 浅拷贝 的问题 ;


下面的示例中 , 自定义类中的成员变量 char m_name[32] 是 在定义时 , 直接分配好的 ,

如果 自定义类 中有 指针类型的成员变量 , 如 char* m_name , 涉及到 动态分配内存 , 如果没有定义 拷贝构造函数 , 默认的 拷贝构造函数 是 浅拷贝 函数 , 直接将 指针地址 简单拷贝 , 这就是 不可被拷贝的情况 ;

那么多个 数组元素 就会共享 相同的 堆内存 数据 , 此时就会出现问题 ;


如果遇到了上述问题 , 定义了 char* m_name 成员变量 , 涉及到 动态分配内存 , 那么 该自定义类 必须自己实现 深拷贝 的 拷贝构造函数 ;


编写的类 , 可以存储到 数组类模板 容器 中 , 那么 该类 必须 支持 拷贝工作 , 具体一些就是 深拷贝 工作 ;


2、自定义类可拷贝 - 代码示例


下面简单实现一个类 , 该类中维护了 2 个成员变量 , char m_name[32] 数组变量 和 int m_age 变量 , 这两个 成员 都是在 创建时 就会分配内存空间 , 不存在 深拷贝问题 ;

如果 char m_name[32] 数组变量 改为 char* m_name 指针变量 , 就需要考虑 深拷贝问题了 ;

class Student
{
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;
};

3、自定义类可打印 - 左移运算符重载


数组类模板 中 , 实现了 左移运算符 打印日志 , 如果 数组中 存储 自定义类对象 想要通过 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;
}




二、代码示例




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;
		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;
}

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、执行结果


执行结果 :

 调用有参构造函数
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 . . .

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

企业软件定制开发的优势|app小程序网站搭建

企业软件定制开发的优势|app小程序网站搭建 企业软件定制开发是一种根据企业特定需求开发定制化软件的服务。相比于购买现成的软件产品&#xff0c;企业软件定制开发具有许多优势。 1.企业软件定制开发可以满足企业独特需求。每个企业都有自己独特的业务流程和需求&#xff0c;…

为什么vue中数组和对象的props默认值要写成函数形式?

多个组件数据不相互干涉 假如在一个地方引用了同一个组件&#xff0c;并给他们都绑定了单独的值。如果只声明为一个对象或数组&#xff0c;可能会导致在某一个实例中修改数据&#xff0c;影响到其他实例中的数据&#xff0c;因为数组和对象是引用类型的数据。为了在多次引用组件…

揭秘:如何精准定位性能瓶颈,优化系统性能?

你好&#xff0c;我是静姐&#xff0c;目前在一家准一线互联网大厂做测试开发工程师。对于一般公司普通测试工程师来说&#xff0c;可能性能测试做的并不是很复杂&#xff0c;可能只是编写下脚本&#xff0c;做个压测&#xff0c;然后输出报告结果&#xff0c;瓶颈分析和调优的…

ubuntu安装cuda驱动报错及解决,屡试不爽

机器重启输入nvidia-smi提示如下错误,字面意思就是驱动和库不匹配 Failed to initialize NVML: Driver/library version mismatch 查看一下nvidia相关库 sudo dpkg --list | grep nvidia-* 将所有已安装库卸载 sudo apt purge nvidia-* 重新安装驱动 sudo ./NVIDIA-Linux-…

Java实现王者荣耀小游戏

主要功能 键盘W,A,S,D键&#xff1a;控制玩家上下左右移动。按钮一&#xff1a;控制英雄发射一个矩形攻击红方小兵。按钮控制英雄发射魅惑技能&#xff0c;伤害小兵并让小兵停止移动。技能三&#xff1a;攻击多个敌人并让小兵停止移动。普攻&#xff1a;对小兵造成基础伤害。小…

redis的集群,主从复制,哨兵

redis的高可用 在Redis中&#xff0c;实现高可用的技术主要包括持久化、主从复制、哨兵和集群&#xff0c;下面分别说明它们的作用&#xff0c;以及解决了什么样的问题。 持久化&#xff1a; 持久化是最简单的高可用方法&#xff08;有时甚至不被归为高可用的手段&#xff09;…

京东大数据分析:2023年10月手机行业销量同比增长249%

今年双11&#xff0c;手机仍是竞争极为激烈的产品品类&#xff0c;各大手机厂商均在双11之前做足了准备&#xff0c;10月下旬&#xff0c;各电商平台双十一预售正式开启。而在双11大促节的参与下&#xff0c;10月份手机市场的整体销售也呈现增长趋势。 根据鲸参谋平台的数据显示…

mybatis 基本操作 删除 插入 更新 查询

根据主键删除数据 插入数据 -- 插入 insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) values (tom,塔姆,tom,1 , 1.png ,now(),1,now(),now() Options(keyProperty "id",useGeneratedKeys true) Insert(&quo…

[译]JavaScript中Base64编码字符串的细节

本文作者为 360 奇舞团前端开发工程师 本文为翻译 原文标题&#xff1a;The nuances of base64 encoding strings in JavaScript 原文作者&#xff1a;Matt Joseph 原文链接&#xff1a;https://web.dev/articles/base64-encoding Base64编码和解码是一种常见的将二进制内容转…

将对象转成URL参数

背景 有的时候前端跳转到其他平台的页面需要携带额外的参数&#xff0c;需要将对象转成用 & 连接的字符串拼接在路径后面。 实现方法

SquareCTF-2023 Web Writeups

官方wp&#xff1a;CTFtime.org / Square CTF 2023 tasks and writeups sandbox Description&#xff1a; I “made” “a” “python” “sandbox” “”“” nc 184.72.87.9 8008 先nc连上看看&#xff0c;只允许一个单词&#xff0c;空格之后的直接无效了。 flag就在当…

河北专升本(微机原理)

目录 第一章&#xff1a;计算机基础与数制转化 1. 进制运算基础 2. 常用编码形式 3. 计算机系统的组成及其工作原理 4. 微机系统主要技术指标 第二章&#xff1a;8086微处理器及其系统 1. 8086微处理器&#xff08;CPU&#xff09; 2. 8086的存储器及I/O组织 3. 8086系…

TikTok历史探秘:短视频中的时间之旅

在数字时代的浪潮中&#xff0c;TikTok崭露头角&#xff0c;成为社交媒体领域的一颗耀眼新星。这款短视频应用以其独特的创意、时尚和娱乐性质&#xff0c;吸引了全球数以亿计的用户。 然而&#xff0c;TikTok并非一夜之间的奇迹&#xff0c;它背后蕴藏着丰富而有趣的历史故事…

Course1-Week2-多输入变量的回归问题

Course1-Week2-多输入变量的回归问题 文章目录 Course1-Week2-多输入变量的回归问题1. 向量化和多元线性回归1.1 多维特征1.2 向量化1.3 用于多元线性回归的梯度下降法 2. 使梯度下降法更快收敛的技巧2.1 特征缩放2.2 判断梯度下降是否收敛2.3 如何设置学习率 3. 特征工程3.1 选…

管理时间的四象限法则

在管理工作和生活中&#xff0c;我们经常面临着各种各样的任务和事务。为了更好地处理这些任务&#xff0c;可以借鉴“重要紧急”、“重要不紧急”、“不重要紧急”以及“不重要不紧急”这四个象限的概念。 重要紧急&#xff1a;这类任务需要立刻行动&#xff0c;因为它们对目…

PPT思维导图怎么做?这2个思维导图工具墙裂推荐!

在日常学习和工作中&#xff0c;我们常常会面临需要处理大量信息的情况&#xff0c;这时候&#xff0c;一种叫做思维导图的工具可能会成为你的救星。 不同于传统的线性记录方式&#xff0c;思维导图以其独特的视觉表现力和结构化的信息处理方式&#xff0c;使得人们能够更加有…

实时截留抖音询价的用户:10个合规方法,让你的业务迅速增长!

先来看实操成果&#xff0c;↑↑需要的同学可看我名字↖↖↖↖↖&#xff0c;或评论888无偿分享 一、引言 随着抖音的普及度越来越高&#xff0c;越来越多的商家开始关注抖音询价用户。这些潜在客户对于企业的发展至关重要&#xff0c;如何实时截留这些用户成为商家关注的重点…

Python 基础【四】--数据类型-字符串【2023.11.23】

1 .定义 字符串是 Python 的一种数据类型&#xff0c;它可以通过单引号 ‘、双引号 "、三引号 ‘’’ 或 “”"来定义。 aabcd bacsdcd c"""accsfv""" print(a) print(b) print(c)2 .基本操作 访问单个字符 注意&#xff1a;从0开始…

detectron2安装

目录 macos安装windows安装提前准备下载detetron2文件激活你要安装的环境运行安装代码 windows下可能出现的问题 macos安装 建议直接照着官方文档&#xff0c;我当时记得是一步安装成功 网站 windows安装 略微麻烦&#xff0c;听我娓娓到来 提前准备 安装git 下载detetro…

C语言--三目运算符

一.介绍⭐ <表达式1>&#xff1f;<表达式2>&#xff1a;<表达式3> 它的含义是&#xff1a;如果表达式1的值为真&#xff08;非零&#xff09;&#xff0c;则整个表达式的值为表达式2的值&#xff1b;否则&#xff0c;整个表达式的值为表达式3的值。 三目运算…