实现一个动态顺序表(C++)
** 引言:基于C++语法的学习,同时加强对于数据结构的理解,后续选择用C++实现基础的数据结构。欲实现的目标:对C++的类和模板的特性进一步理解,同时思考对应stl容器基础实现。 **
简单实现数据结构–顺序表
一、 什么是顺序表?
顺序表是线性表的一种存储结构。它的核心思想是:用一段地址连续的存储单元(通常是数组),依次存储线性表中的数据元素。你可以把它想象成一排连续的房间(存储单元),每个房间的门牌号(地址)是连续的。每个房间里住着一个数据元素,并且这些元素是按照顺序(第一个、第二个…第n个)依次入住这些房间的。
** 核心特征:逻辑上相邻的元素,在物理存储位置上也相邻。**
- 逻辑结构:线性关系。(a₁, a₂, a₃, …, aₙ)
- 物理结构:存储在一片连续的内存空间中。
二、 顺序表的基本实现
顺序表通常由两部分组成:
存储数据的数组 (data[]):用于存放实际的数据元素。
记录当前长度的变量 (length或size):用于记录当前顺序表中实际有多少个元素。
下面为具体的代码实现:
SeqList.h
#pragma once
#include<iostream>
#include<stdexcept>template <typename T>
class SeqList
{
public://构造函数 -->> 初始化类对象SeqList(int N = 10) :_size(0), _capacity(N) {//检查if (N < 0){throw std::invalid_argument("初始容量不能为负数");}_a = (N > 0) ? new T[N] : nullptr;}//拷贝构造函数 -->>1.只有单个形参// 2.参数为类类型的const引用,不能使用值传递,会引发无限调用// 3.已存在的类类型对象创建新对象时由编译器自动调用SeqList(const SeqList& other) :_size(other._size), _capacity(other._capacity) {_a = new T[_capacity];for (int i = 0; i < _size; i++){_a[i] = other._a[i];}}//赋值运算符重载 -->>1.通常接受同类对象的引用(常为const引用),但也可以接受其他类型(如转换构造函数的参数类型)//2.返回值类型:必须返回当前对象的引用(ClassName& ),以支持链式赋值(如a = b = c)//3.检测是否自己给自己赋值//4.返回 * this :要复合连续赋值的含义SeqList& operator=(const SeqList& other) {if (this != &other) {delete[] _a;_size = other._size;_capacity = other._capacity;_a = new T[_capacity];for (int i = 0; i < _size; ++i) {_a[i] = other._a[i];}}return *this;}//析构函数~SeqList() {delete[] _a;_a = nullptr;_size = 0;_capacity = 0;}//顺序表的基本操作--增删查改//1.在指定位置插入元素void insert(int pos, const T& x) {if (pos < 0 || pos>_size) {throw std::out_of_range("插入位置越界");}if (_size == _capacity) {resize();}//移动元素,腾出位置for (int i = _size; i > pos; --i) {_a[i] = _a[i - 1];}_a[pos] = x;_size++;}// 在末尾添加元素void push_back(const T& x) {if (_size == _capacity) {resize();}_a[_size++] = x;}// 在头部添加元素void push_front(const T& x) {insert(0, x);}//2.在指定位置删除元素void erase(int pos) {if (pos < 0 || pos>_size) {throw std::out_of_range("插入位置越界");}for (int i = pos; i < _size - 1; ++i) {_a[i] = _a[i +1];}_size--;}// 在末尾删除元素void pop_back() {if (_size == 0) {throw std::runtime_error("顺序表为空,无法删除元素");}_size--;}// 在头部删除元素void pop_front() {erase(0);}//3. 查找元素位置int find(const T& x) const {for (int i = 0; i < _size; ++i) {if (_a[i] == x) {return i;}}return -1;}// 获取指定位置元素T get(int pos) const {if (pos < 0 || pos >= _size) {throw std::out_of_range("访问位置越界");}return _a[pos];}// 修改指定位置元素void set(int pos, const T& x) {if (pos < 0 || pos >= _size) {throw std::out_of_range("修改位置越界");}_a[pos] = x;}// 判断是否包含元素bool contains(const T& x) const {return find(x) != -1;}// 清空顺序表void clear() {_size = 0;}// 获取当前元素数量int size() const {return _size;}// 获取容量int getCapacity() const {return _capacity;}// 判断是否为空bool isEmpty() const {return _size == 0;}// 打印顺序表内容void print() const {std::cout << "[";for (int i = 0; i < _size; ++i) {std::cout <<_a[i];if (i != _size - 1) {std::cout << ", ";}}std::cout << "]" << std::endl;}private:T* _a; //存储动态数据的数组int _size;//当前元素数量int _capacity;//容量//扩容函数void resize() {int new_capacity = (_capacity == 0)?1:_capacity * 2;T* new_a = new T[new_capacity];//复制原有函数for (int i = 0; i < _size; i++) {new_a[i] = _a[i];}//释放旧内存,并更新新指针delete[] _a;_a = new_a;_capacity = new_capacity;}
};
test .h
#include"SeqList.h"#include <iostream>
#include <string>// 测试构造函数和基本属性
void testConstructorAndProperties() {std::cout << "=== 测试构造函数和基本属性 ===" << std::endl;// 测试默认构造函数SeqList<int> list1;std::cout << "默认构造函数 - 初始大小: " << list1.size() << " (预期: 0)" << std::endl;std::cout << "默认构造函数 - 初始容量: " << list1.getCapacity() << " (预期: 10)" << std::endl;std::cout << "默认构造函数 - 是否为空: " << (list1.isEmpty() ? "是" : "否") << " (预期: 是)" << std::endl;// 测试指定容量的构造函数SeqList<std::string> list2(5);std::cout << "\n指定容量构造函数 - 初始容量: " << list2.getCapacity() << " (预期: 5)" << std::endl;// 测试异常构造(负容量)try {SeqList<double> list3(-3);std::cout << "负容量构造未抛出异常(错误)" << std::endl;}catch (const std::invalid_argument& e) {std::cout << "负容量构造捕获预期异常: " << e.what() << " (正确)" << std::endl;}std::cout << "=== 构造函数测试结束 ===\n" << std::endl;
}// 测试插入功能
void testInsertOperations() {std::cout << "=== 测试插入功能 ===" << std::endl;SeqList<int> list;// 测试尾插list.push_back(10);list.push_back(20);list.push_back(30);std::cout << "尾插后: ";list.print(); // 预期: [10, 20, 30]// 测试头插list.push_front(5);list.push_front(0);std::cout << "头插后: ";list.print(); // 预期: [0, 5, 10, 20, 30]// 测试指定位置插入list.insert(3, 15);std::cout << "指定位置插入后: ";list.print(); // 预期: [0, 5, 10, 15, 20, 30]// 测试扩容(超过初始容量10)for (int i = 40; i <= 100; i += 10) {list.push_back(i);}std::cout << "扩容后: ";list.print(); // 预期包含11个元素std::cout << "扩容后容量: " << list.getCapacity() << " (预期: 20)" << std::endl;// 测试插入异常(越界)try {list.insert(-1, 99);std::cout << "负索引插入未抛出异常(错误)" << std::endl;}catch (const std::out_of_range& e) {std::cout << "负索引插入捕获预期异常: " << e.what() << " (正确)" << std::endl;}try {list.insert(20, 99); // 当前大小为11,插入索引20越界std::cout << "超范围插入未抛出异常(错误)" << std::endl;}catch (const std::out_of_range& e) {std::cout << "超范围插入捕获预期异常: " << e.what() << " (正确)" << std::endl;}std::cout << "=== 插入测试结束 ===\n" << std::endl;
}// 测试删除功能
void testDeleteOperations() {std::cout << "=== 测试删除功能 ===" << std::endl;SeqList<int> list;for (int i = 1; i <= 5; ++i) {list.push_back(i * 10);}std::cout << "初始列表: ";list.print(); // 预期: [10, 20, 30, 40, 50]// 测试尾删list.pop_back();std::cout << "尾删后: ";list.print(); // 预期: [10, 20, 30, 40]// 测试头删list.pop_front();std::cout << "头删后: ";list.print(); // 预期: [20, 30, 40]// 测试指定位置删除list.erase(2);list.print(); // 预期: [20, 40]// 测试空表删除异常SeqList<int> emptyList;try {emptyList.pop_back();std::cout << "空表尾删未抛出异常(错误)" << std::endl;}catch (const std::runtime_error& e) {std::cout << "空表尾删捕获预期异常: " << e.what() << " (正确)" << std::endl;}try {emptyList.erase(0);std::cout << "空表指定删除未抛出异常(错误)" << std::endl;}catch (const std::out_of_range& e) {std::cout << "空表指定删除捕获预期异常: " << e.what() << " (正确)" << std::endl;}std::cout << "=== 删除测试结束 ===\n" << std::endl;
}// 测试查找和修改功能
void testFindAndModify() {std::cout << "=== 测试查找和修改功能 ===" << std::endl;SeqList<std::string> list;list.push_back("Apple");list.push_back("Banana");list.push_back("Cherry");list.push_back("Date");std::cout << "初始列表: ";list.print(); // 预期: [Apple, Banana, Cherry, Date]// 测试查找功能int index = list.find("Cherry");std::cout << "Cherry的索引: " << index << " (预期: 2)" << std::endl;index = list.find("Grape");std::cout << "Grape的索引: " << index << " (预期: -1)" << std::endl;// 测试包含功能bool hasBanana = list.contains("Banana");std::cout << "是否包含Banana: " << (hasBanana ? "是" : "否") << " (预期: 是)" << std::endl;bool hasGrape = list.contains("Grape");std::cout << "是否包含Grape: " << (hasGrape ? "是" : "否") << " (预期: 否)" << std::endl;// 测试修改功能list.set(1, "Blueberry");std::cout << "修改后列表: ";list.print(); // 预期: [Apple, Blueberry, Cherry, Date]// 测试修改异常(越界)try {list.set(10, "Mango");std::cout << "越界修改未抛出异常(错误)" << std::endl;}catch (const std::out_of_range& e) {std::cout << "越界修改捕获预期异常: " << e.what() << " (正确)" << std::endl;}std::cout << "=== 查找和修改测试结束 ===\n" << std::endl;
}// 测试拷贝构造和赋值运算
void testCopyAndAssignment() {std::cout << "=== 测试拷贝构造和赋值运算 ===" << std::endl;// 准备原始列表SeqList<int> original;for (int i = 1; i <= 3; ++i) {original.push_back(i * 100);}std::cout << "原始列表: ";original.print(); // 预期: [100, 200, 300]// 测试拷贝构造SeqList<int> copyConstructed(original);std::cout << "拷贝构造的列表: ";copyConstructed.print(); // 预期: [100, 200, 300]// 修改拷贝的列表,验证原始列表不受影响copyConstructed.set(0, 999);std::cout << "修改后拷贝列表: ";copyConstructed.print(); // 预期: [999, 200, 300]std::cout << "原始列表是否变化: ";original.print(); // 预期: [100, 200, 300] (不变)// 测试赋值运算SeqList<int> assigned;assigned = original;std::cout << "赋值后的列表: ";assigned.print(); // 预期: [100, 200, 300]// 修改赋值的列表,验证原始列表不受影响assigned.push_back(400);std::cout << "修改后赋值列表: ";assigned.print(); // 预期: [100, 200, 300, 400]std::cout << "原始列表是否变化: ";original.print(); // 预期: [100, 200, 300] (不变)std::cout << "=== 拷贝和赋值测试结束 ===\n" << std::endl;
}// 测试清空和其他功能
void testClearAndOthers() {std::cout << "=== 测试清空和其他功能 ===" << std::endl;SeqList<double> list;list.push_back(3.14);list.push_back(2.718);list.push_back(1.618);std::cout << "初始列表: ";list.print(); // 预期: [3.14, 2.718, 1.618]// 测试获取元素double val = list.get(1);std::cout << "索引1的元素: " << val << " (预期: 2.718)" << std::endl;// 测试清空list.clear();std::cout << "清空后列表: ";list.print(); // 预期: []std::cout << "清空后大小: " << list.size() << " (预期: 0)" << std::endl;std::cout << "清空后是否为空: " << (list.isEmpty() ? "是" : "否") << " (预期: 是)" << std::endl;// 测试获取元素异常(越界)try {list.get(0);std::cout << "空表获取元素未抛出异常(错误)" << std::endl;}catch (const std::out_of_range& e) {std::cout << "空表获取元素捕获预期异常: " << e.what() << " (正确)" << std::endl;}std::cout << "=== 清空和其他测试结束 ===\n" << std::endl;
}int main() {// 执行所有测试testConstructorAndProperties();testInsertOperations();testDeleteOperations();testFindAndModify();testCopyAndAssignment();testClearAndOthers();std::cout << "所有测试执行完毕!" << std::endl;return 0;
}
这个顺序表类具有以下特点:
- 模板设计:支持任意数据类型(如 int、float、string 等)
*动态扩容:当元素数量达到容量上限时,自动将容量翻倍
*完整操作集:- 插入操作:头部插入、尾部插入、指定位置插入
- 删除操作:头部删除、尾部删除、指定位置删除
- 查找操作:按值查找位置、判断是否包含某元素
- 修改操作:修改指定位置元素
- 辅助操作:清空、获取大小、判断是否为空等
- 安全机制:
- 对越界访问等非法操作抛出异常
- 实现拷贝构造函数和赋值运算符,避免浅拷贝问题
- 析构函数正确释放动态分配的内存
提供了完整的测试用例,验证了整数和字符串两种类型的使用(也可以自行测试)