刚入门 C++ 数据结构的你,是不是也遇到过这些头疼的问题?想实现一个能灵活存数据的顺序表,却卡在类结构设计上,不知道该用哪些成员变量;好不容易写了插入、删除接口,却总出现数组越界、数据覆盖的 bug;甚至还没搞懂动态扩容的原理,程序就因为内存泄漏崩溃了……
其实不用慌,顺序表作为 C++ 中最基础的线性结构,本质就是== “可动态调整容量的数组”==,它不仅是后续学习链表、栈、队列的基石,更是面试中高频考察的知识点 —— 比如面试官常会问 “顺序表头插为什么效率低”“扩容时怎么避免内存浪费”,这些问题都需要你深入理解顺序表的实现逻辑才能答好。
今天这篇文章,我就带大家从0开始用 C++ 封装顺序表:从类的结构设计(明确 _data、_size、_capacity 的作用),到动态扩容的底层实现,再到10个核心接口(尾插 / 头插 / 指定位置插入、尾删 / 头删 / 指定位置删除、查找 / 修改等)的逐行代码解析,每一步都附详细注释和原理说明,最后还会给出完整可运行的测试代码。哪怕你是刚接触数据结构的新手,跟着步骤走也能轻松掌握!
顺序表需要存储三个关键信息:
_size)_capacity)即当前可存储的最大元素数我们定义一个SeqList类,结构如下:
// 定义顺序表存储的数据类型,后续修改类型方便
typedef int DataType;
class SeqList
{
public:
private:
DataType* _data;
size_t _size;
size_t _capacity;
};然后我们思考剩下的接口功能该如何实现
// 构造函数实现
SeqList::SeqList()
:_data(nullptr)
,_size(0)
,_capacity(0)
{}
// 带容量的构造
explicit SeqList::SeqList(size_t init_capacity)
:_size(0)
,_capacity(init_capacity)
{
if (init_capacity > 0)
{
_data = new DataType[init_capacity];
}
else
{
_data = nullptr;
}
}我们实现一份默认构造和一份带容量的构造
默认构造可以满足数据量未知的情况
而带容量构造可以数据量已知的情况,可以提前开辟所需的内存空间,容易控制容量
// 拷贝构造
SeqList::SeqList(const SeqList& slt)
: _size(slt._size)
, _capacity(slt._capacity)
{
// _size不能超过_capacity
if (slt._size > slt._capacity)
{
throw std::invalid_argument("源对象状态无效:元素个数超过容量,无法拷贝");
}
if (_capacity > 0)
{
try
{
// 普通new失败会抛bad_alloc异常,无需检查nullptr
_data = new DataType[_capacity];
// 拷贝数据:循环次数受限于_size(已确保_size ≤ _capacity)
for (size_t i = 0; i < _size; ++i)
{
_data[i] = slt._data[i];
}
}
catch (const std::bad_alloc& e)
{
// 内存分配失败时,确保当前对象处于有效空状态
_data = nullptr;
_size = 0;
_capacity = 0;
throw std::runtime_error("拷贝构造失败:内存分配错误 - " + std::string(e.what()));
}
}
else
{
_data = nullptr;
}
}拷贝构造函数我们选择深拷贝(开辟新的内存空间,将源数据逐一拷贝),而对于浅拷贝(又称值拷贝),它的行为是只拷贝指针的指向,对于内部的数据不做处理,画一幅图理解一下:

SeqList& SeqList::operator=(const SeqList& slt)
{
if (this != &slt)
{
// 先释放旧内存
delete[] _data;
_data = nullptr; // 释放后先置空,避免野指针
// 拷贝大小和容量
_size = slt._size;
_capacity = slt._capacity;
// _size不能超过_capacity
if (slt._size > slt._capacity)
{
throw std::invalid_argument("源对象状态无效:元素个数超过容量,无法拷贝");
}
if (_capacity > 0)
{
try
{
_data = new DataType[_capacity]; // 分配失败会抛bad_alloc
for (size_t i = 0; i < _size; ++i)
{
_data[i] = slt._data[i];
}
}
catch (const std::bad_alloc& e)
{
// 分配失败时,当前对象应保持有效状态(空表)
_size = 0;
_capacity = 0;
throw std::runtime_error("赋值失败:内存分配错误 - " + std::string(e.what()));
}
}
else
{
_data = nullptr;
}
}
return *this; // 始终返回当前对象的引用
}同理,赋值重载也采用深拷贝的形式,这里注意两个点:
if (this != &slt)条件判断,防止自己给自己赋值,白白消耗SeqList类// 析构
SeqList::~SeqList()
{
delete[] _data;
_data = nullptr;
_size = _capacity = 0;
}在插入操作之前,我们先要检查容量是否充足,如果_size == _capacity就要先扩容
// 私有成员函数实现
void SeqList::reverse()
{
// 先判断需不需要扩容
if (_size == _capacity)
{
// 采用两倍扩容的方法
size_t new_capacity = _capacity == 0 ? 4 : 2 * _capacity;
// 扩容操作
try
{
DataType* tmp_data = new DataType[new_capacity];
// 复制数据
for (size_t i = 0; i < _size; i++)
{
tmp_data[i] = _data[i];
}
// 释放旧内存 更新指针
delete[] _data;
_data = tmp_data;
_capacity = new_capacity;
}
catch (const bad_alloc& e)
{
cerr << "扩容失败,错误信息:" << e.what() << endl;
return;
}
}
}尾插是在顺序表的尾部插入一个元素,然后_size有效数据个数加一,分为以下几种情况:

void SeqList::push_back(DataType val)
{
// 先考虑容量问题
SeqList::reverse();
_data[_size++] = val;
}尾插的时间复杂度是
,不用挪动数据
头插是在顺序表的头部插入一个元素,然后_size有效数据个数加一,分为以下几种情况:

void SeqList::push_front(DataType val)
{
// 先考虑容量问题
SeqList::reverse();
// 头插
// 可以不用这个逻辑 将_data[0] = val放到最后,如果顺序表为空,则不会进入循环
//if (_data == nullptr)
//{
// _data[0] = val;
//}
//else
//{
// for (size_t i = _size; i > 0; i++)
// {
// _data[i] = _data[i - 1];
// }
//}
// (0,_size] 索引为0的元素被覆盖
for (size_t i = _size; i > 0; i--)
{
_data[i] = _data[i - 1];
}
_data[0] = val;
++_size;
}注释掉的部分可以优化,按照一般思路来说,先考虑空表,然后考虑非空表,但是写到最后发现代码完全可以合并优化,时间复杂度:
pos处插入元素
void SeqList::insert(size_t pos, DataType val)
{
// 确保pos合法性
assert(pos >= 0 & _size);
// 检查容量
SeqList::reverse();
// (pos,_size] pos位置的元素被覆盖
for (size_t i = _size; i > pos; i--)
{
_data[i] = _data[i - 1];
}
_data[pos] = val;
++_size;
}时间复杂度:
尾删操作是删除顺序表最后一个元素,空表不能执行删除操作,非空表只需_size--即可,这样原先位置的元素就被丢弃了

void SeqList::pop_back()
{
// 空表不能执行删除操作
assert(_data);
_size--;
}头删操作是删除顺序表第一个元素,空表不能执行删除操作,非空表从索引为1的元素开始,依次往前挪动一位数据

void SeqList::pop_front()
{
// 空表不能执行删除操作
assert(_data);
// [1,_size]
for (size_t i = 1; i <= _size; i++)
{
_data[i - 1] = _data[i];
}
--_size;
}时间复杂度:
删除索引为pos的元素,空表不能执行删除操作,还要确保pos的合法性,非空表从索引为pos+ 1位置开始,依次向前挪动一位数据,直到尾部结束

void SeqList::earse(size_t pos)
{
// 空表不能执行删除操作
assert(_data);
// 确保pos合法性
assert(pos >= 0 && pos <= _size);
// [pos,_size - 1]
for (size_t i = pos;i < _size - 1; i++ )
{
_data[i] = _data[i + 1];
}
--_size;
}时间复杂度:
// 查找与修改
// O(n)
DataType SeqList::find(DataType val)const
{
for (size_t i = 0; i < _size; i++)
{
if (_data[i] == val) return _data[i];
}
// 没找到
return -1;
}
void SeqList::modify(size_t pos, DataType val)
{
// 确保pos合法性
assert(pos >= 0 && pos <= _size);
_data[pos] = val;
}void SeqList::clear()
{
_size = 0;
}bool SeqList::empty() const
{
return _size == 0;
}*检查顺序表的_size和_capacity
size_t SeqList::size() const
{
return _size;
}
size_t SeqList::capacity() const
{
return _capacity;
}void SeqList::Print() const
{
for (size_t i = 0; i < _size; i++)
{
cout << _data[i] << " ";
}
cout << endl;
}// 测试默认成员函数
void test_defalut_member()
{
SeqList slt1;
SeqList slt2(10);
SeqList slt3 = slt1;
slt1 = slt2;
}
// 测试插入操作
void test_insert()
{
SeqList slt1(10);
slt1.push_back(1);
slt1.push_back(2);
slt1.push_back(3);
slt1.push_back(4);
slt1.Print();
slt1.push_front(10);
slt1.push_front(20);
slt1.push_front(30);
slt1.Print();
slt1.insert(4, 800);
slt1.Print();
}
// 测试删除操作
void test_earse()
{
SeqList slt1(10);
slt1.push_back(1);
slt1.push_back(2);
slt1.push_back(3);
slt1.push_back(4);
slt1.push_back(5);
slt1.push_back(6);
slt1.Print();
slt1.pop_back();
slt1.pop_back();
slt1.Print();// 1 2 3 4
slt1.pop_front();
slt1.pop_front();
slt1.Print();// 3 4
slt1.push_back(5);
slt1.push_back(6);
slt1.push_back(7);
slt1.push_back(8);
slt1.Print();// 3 4 5 6 7 8
slt1.earse(2);
slt1.Print();
}
// 测试查找与修改操作
void test_find_modify()
{
SeqList slt1(10);
slt1.push_back(1);
slt1.push_back(2);
slt1.push_back(3);
slt1.push_back(4);
slt1.push_back(5);
slt1.push_back(6);
slt1.Print();
DataType ret1 = slt1.find(3);
DataType ret2 = slt1.find(10);
cout << ret1 << " " << ret2 << endl;
slt1.modify(2,2000);
slt1.Print();
slt1.modify(3, 2000);
slt1.Print();
}// SeqList.h
#include <iostream>
#include <cassert>
using namespace std;
// 定义顺序表存储的数据类型,后续修改类型方便
typedef int DataType;
class SeqList
{
public:
// 构造
SeqList();
// 带容量的构造
explicit SeqList(size_t init_capacity);
// 拷贝构造
SeqList(const SeqList& slt);
// 赋值重载
SeqList& operator=(const SeqList& slt);
// 析构
~SeqList();
// 插入操作
void push_back(DataType val);
void push_front(DataType val);
void insert(size_t pos,DataType val);
// 删除操作
void pop_back();
void pop_front();
void earse(size_t pos);
// 查找与修改
DataType find(DataType val)const;
void modify(size_t pos, DataType val);
// 其他操作
void clear();
bool empty() const;
size_t size() const;
size_t capacity() const;
void Print() const;
private:
DataType* _data;
size_t _size;
size_t _capacity;
// 扩容操作作为私有方法,仅供内部使用
void reverse();
};
// SeqList.cpp
#include "SeqList.h"
// 构造函数实现
SeqList::SeqList()
:_data(nullptr)
,_size(0)
,_capacity(0)
{
cout << "SeqList::SeqList()" << endl;
}
// 带容量的构造
SeqList::SeqList(size_t init_capacity)
:_size(0)
,_capacity(init_capacity)
{
if (init_capacity > 0)
{
_data = new DataType[init_capacity];
}
else
{
_data = nullptr;
}
}
// 拷贝构造
SeqList::SeqList(const SeqList& slt)
: _size(slt._size)
, _capacity(slt._capacity)
{
// _size不能超过_capacity
if (slt._size > slt._capacity)
{
throw std::invalid_argument("源对象状态无效:元素个数超过容量,无法拷贝");
}
if (_capacity > 0)
{
try
{
// 普通new失败会抛bad_alloc异常,无需检查nullptr
_data = new DataType[_capacity];
// 拷贝数据:循环次数受限于_size(已确保_size ≤ _capacity)
for (size_t i = 0; i < _size; ++i)
{
_data[i] = slt._data[i];
}
}
catch (const std::bad_alloc& e)
{
// 内存分配失败时,确保当前对象处于有效空状态
_data = nullptr;
_size = 0;
_capacity = 0;
throw std::runtime_error("拷贝构造失败:内存分配错误 - " + std::string(e.what()));
}
}
else
{
_data = nullptr;
}
}
SeqList& SeqList::operator=(const SeqList& slt)
{
if (this != &slt)
{
// 先释放旧内存
delete[] _data;
_data = nullptr; // 释放后先置空,避免野指针
// 拷贝大小和容量
_size = slt._size;
_capacity = slt._capacity;
// _size不能超过_capacity
if (slt._size > slt._capacity)
{
throw std::invalid_argument("源对象状态无效:元素个数超过容量,无法拷贝");
}
if (_capacity > 0)
{
try
{
_data = new DataType[_capacity]; // 分配失败会抛bad_alloc
for (size_t i = 0; i < _size; ++i)
{
_data[i] = slt._data[i];
}
}
catch (const std::bad_alloc& e)
{
// 分配失败时,当前对象应保持有效状态(空表)
_size = 0;
_capacity = 0;
throw std::runtime_error("赋值失败:内存分配错误 - " + std::string(e.what()));
}
}
else
{
_data = nullptr;
}
}
return *this; // 始终返回当前对象的引用
}
// 析构
SeqList::~SeqList()
{
delete[] _data;
_data = nullptr;
_size = _capacity = 0;
}
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
// 私有成员函数实现
void SeqList::reverse()
{
// 先判断需不需要扩容
if (_size == _capacity)
{
// 采用两倍扩容的方法
size_t new_capacity = _capacity == 0 ? 4 : 2 * _capacity;
// 扩容操作
try
{
DataType* tmp_data = new DataType[new_capacity];
// 复制数据
for (size_t i = 0; i < _size; i++)
{
tmp_data[i] = _data[i];
}
// 释放旧内存 更新指针
delete[] _data;
_data = tmp_data;
_capacity = new_capacity;
}
catch (const bad_alloc& e)
{
cerr << "扩容失败,错误信息:" << e.what() << endl;
return;
}
}
}
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
// 插入操作
void SeqList::push_back(DataType val)
{
// 先考虑容量问题
SeqList::reverse();
// 尾插
_data[_size++] = val;
}
void SeqList::push_front(DataType val)
{
// 先考虑容量问题
SeqList::reverse();
// 头插
// 可以不用这个逻辑 将_data[0] = val放到最后,如果顺序表为空,则不会进入循环
//if (_data == nullptr)
//{
// _data[0] = val;
//}
//else
//{
// for (size_t i = _size; i > 0; i++)
// {
// _data[i] = _data[i - 1];
// }
//}
// (0,_size] 索引为0的元素被覆盖
for (size_t i = _size; i > 0; i--)
{
_data[i] = _data[i - 1];
}
_data[0] = val;
++_size;
}
void SeqList::insert(size_t pos, DataType val)
{
// 确保pos合法性
assert(pos >= 0 & _size);
// 检查容量
SeqList::reverse();
// (pos,_size] pos位置的元素被覆盖
for (size_t i = _size; i > pos; i--)
{
_data[i] = _data[i - 1];
}
_data[pos] = val;
++_size;
}
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
// 删除操作
void SeqList::pop_back()
{
// 空表不能执行删除操作
assert(_data);
_size--;
}
void SeqList::pop_front()
{
// 空表不能执行删除操作
assert(_data);
// [1,_size]
for (size_t i = 1; i <= _size; i++)
{
_data[i - 1] = _data[i];
}
--_size;
}
void SeqList::earse(size_t pos)
{
// 空表不能执行删除操作
assert(_data);
// 确保pos合法性
assert(pos >= 0 && pos <= _size);
// [pos,_size - 1]
for (size_t i = pos;i < _size - 1; i++ )
{
_data[i] = _data[i + 1];
}
--_size;
}
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
// 查找与修改
DataType SeqList::find(DataType val)const
{
for (size_t i = 0; i < _size; i++)
{
if (_data[i] == val) return _data[i];
}
// 没找到
return -1;
}
void SeqList::modify(size_t pos, DataType val)
{
// 确保pos合法性
assert(pos >= 0 && pos <= _size);
_data[pos] = val;
}
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
// 其他操作
void SeqList::clear()
{
_size = 0;
}
bool SeqList::empty() const
{
return _size == 0;
}
size_t SeqList::size() const
{
return _size;
}
size_t SeqList::capacity() const
{
return _capacity;
}
void SeqList::Print() const
{
for (size_t i = 0; i < _size; i++)
{
cout << _data[i] << " ";
}
cout << endl;
}
// Test.cpp
#include "SeqList.h"
// 测试默认成员函数
void test_defalut_member()
{
SeqList slt1;
SeqList slt2(10);
SeqList slt3 = slt1;
slt1 = slt2;
}
// 测试插入操作
void test_insert()
{
SeqList slt1(10);
slt1.push_back(1);
slt1.push_back(2);
slt1.push_back(3);
slt1.push_back(4);
slt1.Print();
slt1.push_front(10);
slt1.push_front(20);
slt1.push_front(30);
slt1.Print();
slt1.insert(4, 800);
slt1.Print();
}
// 测试删除操作
void test_earse()
{
SeqList slt1(10);
slt1.push_back(1);
slt1.push_back(2);
slt1.push_back(3);
slt1.push_back(4);
slt1.push_back(5);
slt1.push_back(6);
slt1.Print();
slt1.pop_back();
slt1.pop_back();
slt1.Print();// 1 2 3 4
slt1.pop_front();
slt1.pop_front();
slt1.Print();// 3 4
slt1.push_back(5);
slt1.push_back(6);
slt1.push_back(7);
slt1.push_back(8);
slt1.Print();// 3 4 5 6 7 8
slt1.earse(2);
slt1.Print();
}
// 测试查找与修改操作
void test_find_modify()
{
SeqList slt1(10);
slt1.push_back(1);
slt1.push_back(2);
slt1.push_back(3);
slt1.push_back(4);
slt1.push_back(5);
slt1.push_back(6);
slt1.Print();
DataType ret1 = slt1.find(3);
DataType ret2 = slt1.find(10);
cout << ret1 << " " << ret2 << endl;
slt1.modify(2,2000);
slt1.Print();
slt1.modify(3, 2000);
slt1.Print();
}
int main()
{
test_defalut_member();
test_insert();
test_earse();
test_find_modify();
return 0;
}顺序表存储在连续的内存空间中,且元素类型相同
时间内访问任意元素
但存在以下缺陷: