文章目录

  • 一、STL 容器的 值 ( Value ) 语意
  • 1、STL 容器存储任意类型元素原理
  • 2、STL 容器元素可拷贝原理
  • 3、STL 容器元素类型需要满足的要求
  • 4、STL 容器迭代器遍历
  • 二、代码示例 - 自定义可存放入 STL 容器的元素类
  • 1、代码示例
  • 2、执行结果







一、STL 容器的 值 ( Value ) 语意



1、STL 容器存储任意类型元素原理



C++ 语言中的 STL 容器 , 可以存储任何类型的元素 , 是因为 STL 容器 使用了 C++ 模板技术进行实现 ;

C++ 模板技术 是 基于 2 次编译实现的 ;

  • 第一次编译 , 扫描模板 , 收集有关模板实例化的信息
  • 第二次编译 , 根据实际调用的类型 , 生成包含真实类型的实例化的代码 ;


2、STL 容器元素可拷贝原理



STL 容器 定义时 , 所有的 STL 容器 的相关操作 , 如 插入 / 删除 / 排序 / 修改 , 都是 基于 值 Value 语意 的 , 不是 基于 引用 Reference 语意的 ;

  • 比如 : 向 STL 容器中 插入元素时 , 插入的都是实际的 值 Value 语意 , 不是 引用 Reference 语意 ;

如果 基于 引用 或者 指针 操作 , 假如 在外部 该 指针 / 引用 指向的对象被回收 , 那么容器操作就会出现问题 ;

STL 容器 中 , 存储的元素 , 必须是可拷贝的 , 也就是 元素类 必须提供 拷贝构造函数 ;



3、STL 容器元素类型需要满足的要求



STL 容器元素类型需要满足的要求 :

  • 提供 无参 / 有参 构造函数 : 保证可以创建元素对象
  • 提供 拷贝构造函数 : STL 容器的元素是可拷贝的
  • 提供 重载 = 操作符函数 : STL 容器的元素可以被赋值 ;


4、STL 容器迭代器遍历



除了 queue 队列容器 与 stack 堆栈容器 之外 , 每个 STL 容器都可以使用 迭代器 进行遍历 ;

  • 调用 begin() 函数 , 获取 指向 首元素 的迭代器 ;
  • 调用 end() 函数 , 获取 末尾迭代器 , 该迭代器 指向 最后一个元素的后面位置 ;

除了 queue 与 stack 容器外 , 都可以使用如下代码进行遍历 ;

//容器的遍历
	cout << "遍历容器 :" << endl;
	for (auto it = container.begin(); it != container.end(); it++)
	{
		// 遍历当前元素 , 打印 / 判断 等操作
	}
	cout << "遍历结束" << endl;






二、代码示例 - 自定义可存放入 STL 容器的元素类



1、代码示例



STL 容器元素类型需要满足的要求 :

  • 提供 无参 / 有参 构造函数 : 保证可以创建元素对象 , 并存放到容器中 ;
  • 提供 拷贝构造函数 : STL 容器的元素是可拷贝的 , 这是容器操作的基础 ;
  • 提供 重载 = 操作符函数 : STL 容器的元素可以被赋值 ;


这里自定义 Student 类 , 需要满足上述要求 , 在 Student 类中 , 定义两个成员 , char* 类型指针 和 int 类型成员 ;

其中 char* 类型指针涉及到 堆内存 的 申请 和 释放 ;



在 有参构造 函数中 , 主要作用是 创建新对象

/// <summary>
	/// 创建普通构造函数
	/// </summary>
	/// <param name="name">传入的常量字符串</param>
	/// <param name="age">传入的年龄</param>
	Student(char* name, int age)
	{
		// 为 m_name 指针分配内存
		// 内存大小是传入字符串大小 + 1
		// 最后 + 1 是为了设置 \0 字符串结尾用的
		// 在析构函数中还要将该内存析构
		m_name = new char[strlen(name) + 1];
		// 将实际的值拷贝到
		// 拷贝字符串数据
		// 需添加 #define _CRT_SECURE_NO_WARNINGS 声明
		strcpy(m_name, name);
		m_age = age;
	}

在 拷贝构造函数 中 , 主要作用是 使用 现有 Student 对象 初始化新对象

/// <summary>
	/// 拷贝构造函数
	/// 在 Student s = s2 情况下调用
	/// </summary>
	/// <param name="obj">使用该 obj 对象初始化新的 Student 对象</param>
	Student(const Student& obj)
	{
		// 为 m_name 指针分配内存
		m_name = new char[strlen(obj.m_name) + 1];
		// 拷贝字符串数据
		// 需添加 #define _CRT_SECURE_NO_WARNINGS 声明
		strcpy(m_name, obj.m_name);
		// 设置年龄
		m_age = obj.m_age;
	}

在 重载等号 = 操作符函数 中 , 主要作用是 使用 现有的 Student 对象 B 为一个 已存在的 Student 对象 A 进行赋值 , 先将 A 对象的 char* 指针释放 , 然后重新申请内存 , 最后再赋值 , int 类型的成员直接赋值 ;

/// <summary>
	/// 重载 等号 = 操作符 函数
	/// </summary>
	/// <param name="obj">等号右边的值</param>
	/// <returns>调用者本身</returns>
	Student& operator=(const Student& obj)
	{
		//先释放 调用者 本身的 m_name 指针指向的内存
		if (m_name != NULL)
		{
			// 使用 new 分配的内存需要使用 delete 释放
			delete[] m_name;
			// 释放内存后指针置空避免野指针
			m_name = NULL;
			// 年龄也设置为默认值
			m_age = 0;
		}

		// 重新分配新的 字符串 内存
		m_name = new char[strlen(obj.m_name) + 1];
		// 拷贝字符串数据
		// 需添加 #define _CRT_SECURE_NO_WARNINGS 声明
		strcpy(m_name, obj.m_name);
		// 拷贝年龄
		m_age = obj.m_age;
		// 返回调用者本身, 以便进行链式调用
		return *this;
	}

此外 , 还有析构函数 , 在析构函数中 , 释放申请的 char* 内存 , 然后置空 ;

~Student()
	{
		if (m_name != NULL)
		{
			// 释放使用 new 关键字分配的内存
			delete[] m_name;
			// 释放内存后的指针置空 避免野指针
			m_name = NULL;
			// 将年龄字段设置为默认值
			m_age = 0;
		}
	}



代码示例 :

// 调用 strcpy 函数需要添加该声明, 否则编译报错
#define _CRT_SECURE_NO_WARNINGS
#include "iostream"
using namespace std;
#include "vector"

class Student
{
public:
	/// <summary>
	/// 创建普通构造函数
	/// </summary>
	/// <param name="name">传入的常量字符串</param>
	/// <param name="age">传入的年龄</param>
	Student(char* name, int age)
	{
		// 为 m_name 指针分配内存
		// 内存大小是传入字符串大小 + 1
		// 最后 + 1 是为了设置 \0 字符串结尾用的
		// 在析构函数中还要将该内存析构
		m_name = new char[strlen(name) + 1];
		// 将实际的值拷贝到
		// 拷贝字符串数据
		// 需添加 #define _CRT_SECURE_NO_WARNINGS 声明
		strcpy(m_name, name);
		m_age = age;
	}
	~Student()
	{
		if (m_name != NULL)
		{
			// 释放使用 new 关键字分配的内存
			delete[] m_name;
			// 释放内存后的指针置空 避免野指针
			m_name = NULL;
			// 将年龄字段设置为默认值
			m_age = 0;
		}
	}

	/// <summary>
	/// 拷贝构造函数
	/// 在 Student s = s2 情况下调用
	/// </summary>
	/// <param name="obj">使用该 obj 对象初始化新的 Student 对象</param>
	Student(const Student& obj)
	{
		// 为 m_name 指针分配内存
		m_name = new char[strlen(obj.m_name) + 1];
		// 拷贝字符串数据
		// 需添加 #define _CRT_SECURE_NO_WARNINGS 声明
		strcpy(m_name, obj.m_name);
		// 设置年龄
		m_age = obj.m_age;
	}
	
	/// <summary>
	/// 重载 等号 = 操作符 函数
	/// </summary>
	/// <param name="obj">等号右边的值</param>
	/// <returns>调用者本身</returns>
	Student& operator=(const Student& obj)
	{
		//先释放 调用者 本身的 m_name 指针指向的内存
		if (m_name != NULL)
		{
			// 使用 new 分配的内存需要使用 delete 释放
			delete[] m_name;
			// 释放内存后指针置空避免野指针
			m_name = NULL;
			// 年龄也设置为默认值
			m_age = 0;
		}

		// 重新分配新的 字符串 内存
		m_name = new char[strlen(obj.m_name) + 1];
		// 拷贝字符串数据
		// 需添加 #define _CRT_SECURE_NO_WARNINGS 声明
		strcpy(m_name, obj.m_name);
		// 拷贝年龄
		m_age = obj.m_age;
		// 返回调用者本身, 以便进行链式调用
		return *this;
	}

public:
	/// <summary>
	/// 打印类的成员变量
	/// </summary>
	void print()
	{
		cout << "姓名 : " << m_name << " , 年龄 : " << m_age << endl;
	}
protected:
private:
	// 姓名
	char* m_name;

	// 年龄
	int	m_age;
};

int main() {

	Student s((char*)"Tom", 18);
	s.print();

	// 将 s 对象加入到 vec 动态数组中
	vector<Student> vec;
	vec.push_back(s);


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

	return 0;
};



2、执行结果



执行结果:

姓名 : Tom , 年龄 : 18
Press any key to continue . . .

【C++】STL 容器 - STL 容器的值语意 ( 容器存储任意类型元素原理 | STL 容器元素可拷贝原理 | STL 容器元素类型需要满足的要求 | 自定义可存放入 STL 容器的元素类 )_c++