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

亦凉 2024-01-30 03:36 221阅读 0赞

文章目录

  • 一、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 容器外 , 都可以使用如下代码进行遍历 ;

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

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


1、代码示例

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

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

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

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

在 有参构造 函数中 , 主要作用是 创建新对象 , 这里 直接 申请内存 , 并使用参数中的值 进行赋值 ;

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

在 拷贝构造函数 中 , 主要作用是 使用 现有 Student 对象 初始化新对象 , 直接申请内存 , 并将 被拷贝的对象 的值 赋值给新创建的 Student 对象 ;

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

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

  1. /// <summary>
  2. /// 重载 等号 = 操作符 函数
  3. /// </summary>
  4. /// <param name="obj">等号右边的值</param>
  5. /// <returns>调用者本身</returns>
  6. Student& operator=(const Student& obj)
  7. {
  8. //先释放 调用者 本身的 m_name 指针指向的内存
  9. if (m_name != NULL)
  10. {
  11. // 使用 new 分配的内存需要使用 delete 释放
  12. delete[] m_name;
  13. // 释放内存后指针置空避免野指针
  14. m_name = NULL;
  15. // 年龄也设置为默认值
  16. m_age = 0;
  17. }
  18. // 重新分配新的 字符串 内存
  19. m_name = new char[strlen(obj.m_name) + 1];
  20. // 拷贝字符串数据
  21. // 需添加 #define _CRT_SECURE_NO_WARNINGS 声明
  22. strcpy(m_name, obj.m_name);
  23. // 拷贝年龄
  24. m_age = obj.m_age;
  25. // 返回调用者本身, 以便进行链式调用
  26. return *this;
  27. }

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

  1. ~Student()
  2. {
  3. if (m_name != NULL)
  4. {
  5. // 释放使用 new 关键字分配的内存
  6. delete[] m_name;
  7. // 释放内存后的指针置空 避免野指针
  8. m_name = NULL;
  9. // 将年龄字段设置为默认值
  10. m_age = 0;
  11. }
  12. }

代码示例 :

  1. // 调用 strcpy 函数需要添加该声明, 否则编译报错
  2. #define _CRT_SECURE_NO_WARNINGS
  3. #include "iostream"
  4. using namespace std;
  5. #include "vector"
  6. class Student
  7. {
  8. public:
  9. /// <summary>
  10. /// 创建普通构造函数
  11. /// </summary>
  12. /// <param name="name">传入的常量字符串</param>
  13. /// <param name="age">传入的年龄</param>
  14. Student(char* name, int age)
  15. {
  16. // 为 m_name 指针分配内存
  17. // 内存大小是传入字符串大小 + 1
  18. // 最后 + 1 是为了设置 \0 字符串结尾用的
  19. // 在析构函数中还要将该内存析构
  20. m_name = new char[strlen(name) + 1];
  21. // 将实际的值拷贝到
  22. // 拷贝字符串数据
  23. // 需添加 #define _CRT_SECURE_NO_WARNINGS 声明
  24. strcpy(m_name, name);
  25. m_age = age;
  26. }
  27. ~Student()
  28. {
  29. if (m_name != NULL)
  30. {
  31. // 释放使用 new 关键字分配的内存
  32. delete[] m_name;
  33. // 释放内存后的指针置空 避免野指针
  34. m_name = NULL;
  35. // 将年龄字段设置为默认值
  36. m_age = 0;
  37. }
  38. }
  39. /// <summary>
  40. /// 拷贝构造函数
  41. /// 在 Student s = s2 情况下调用
  42. /// </summary>
  43. /// <param name="obj">使用该 obj 对象初始化新的 Student 对象</param>
  44. Student(const Student& obj)
  45. {
  46. // 为 m_name 指针分配内存
  47. m_name = new char[strlen(obj.m_name) + 1];
  48. // 拷贝字符串数据
  49. // 需添加 #define _CRT_SECURE_NO_WARNINGS 声明
  50. strcpy(m_name, obj.m_name);
  51. // 设置年龄
  52. m_age = obj.m_age;
  53. }
  54. /// <summary>
  55. /// 重载 等号 = 操作符 函数
  56. /// </summary>
  57. /// <param name="obj">等号右边的值</param>
  58. /// <returns>调用者本身</returns>
  59. Student& operator=(const Student& obj)
  60. {
  61. //先释放 调用者 本身的 m_name 指针指向的内存
  62. if (m_name != NULL)
  63. {
  64. // 使用 new 分配的内存需要使用 delete 释放
  65. delete[] m_name;
  66. // 释放内存后指针置空避免野指针
  67. m_name = NULL;
  68. // 年龄也设置为默认值
  69. m_age = 0;
  70. }
  71. // 重新分配新的 字符串 内存
  72. m_name = new char[strlen(obj.m_name) + 1];
  73. // 拷贝字符串数据
  74. // 需添加 #define _CRT_SECURE_NO_WARNINGS 声明
  75. strcpy(m_name, obj.m_name);
  76. // 拷贝年龄
  77. m_age = obj.m_age;
  78. // 返回调用者本身, 以便进行链式调用
  79. return *this;
  80. }
  81. public:
  82. /// <summary>
  83. /// 打印类的成员变量
  84. /// </summary>
  85. void print()
  86. {
  87. cout << "姓名 : " << m_name << " , 年龄 : " << m_age << endl;
  88. }
  89. protected:
  90. private:
  91. // 姓名
  92. char* m_name;
  93. // 年龄
  94. int m_age;
  95. };
  96. int main() {
  97. Student s((char*)"Tom", 18);
  98. s.print();
  99. // 将 s 对象加入到 vec 动态数组中
  100. vector<Student> vec;
  101. vec.push_back(s);
  102. // 控制台暂停 , 按任意键继续向后执行
  103. system("pause");
  104. return 0;
  105. };

2、执行结果

执行结果:

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

在这里插入图片描述

发表评论

表情:
评论列表 (有 0 条评论,221人围观)

还没有评论,来说两句吧...

相关阅读