一、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 容器外 , 都可以使用如下代码进行遍历 ;
代码语言:javascript复制 //容器的遍历
cout << "遍历容器 :" << endl;
for (auto it = container.begin(); it != container.end(); it )
{
// 遍历当前元素 , 打印 / 判断 等操作
}
cout << "遍历结束" << endl;
二、代码示例 - 自定义可存放入 STL 容器的元素类
1、代码示例
STL 容器元素类型需要满足的要求 :
- 提供 无参 / 有参 构造函数 : 保证可以创建元素对象 , 并存放到容器中 ;
- 提供 拷贝构造函数 : STL 容器的元素是可拷贝的 , 这是容器操作的基础 ;
- 提供 重载 = 操作符函数 : STL 容器的元素可以被赋值 ;
这里自定义 Student 类 , 需要满足上述要求 , 在 Student 类中 , 定义两个成员 , char* 类型指针 和 int 类型成员 ;
其中 char* 类型指针涉及到 堆内存 的 申请 和 释放 ;
在 有参构造 函数中 , 主要作用是 创建新对象 , 这里 直接 申请内存 , 并使用参数中的值 进行赋值 ;
代码语言:javascript复制 /// <summary>
/// 创建普通构造函数
/// </summary>
/// <param name="name">传入的常量字符串</param>
/// <param name="age">传入的年龄</param>
Student(char* name, int age)
{
// 为 m_name 指针分配内存
// 内存大小是传入字符串大小 1
// 最后 1 是为了设置 字符串结尾用的
// 在析构函数中还要将该内存析构
m_name = new char[strlen(name) 1];
// 将实际的值拷贝到
// 拷贝字符串数据
// 需添加 #define _CRT_SECURE_NO_WARNINGS 声明
strcpy(m_name, name);
m_age = age;
}
在 拷贝构造函数 中 , 主要作用是 使用 现有 Student 对象 初始化新对象 , 直接申请内存 , 并将 被拷贝的对象 的值 赋值给新创建的 Student 对象 ;
代码语言:javascript复制 /// <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 类型的成员直接赋值 ;
代码语言:javascript复制 /// <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* 内存 , 然后置空 ;
代码语言:javascript复制 ~Student()
{
if (m_name != NULL)
{
// 释放使用 new 关键字分配的内存
delete[] m_name;
// 释放内存后的指针置空 避免野指针
m_name = NULL;
// 将年龄字段设置为默认值
m_age = 0;
}
}
代码示例 :
代码语言:javascript复制// 调用 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 是为了设置 字符串结尾用的
// 在析构函数中还要将该内存析构
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 . . .