作为数据结构入门的第一个线性结构,顺序表(基于静态数组实现)是理解“线性存储”“元素操作”的基础。最近从零实现了顺序表的完整功能,过程中踩了几个典型新手坑,今天整理成一篇复盘博客,适合和我一样刚入门数据结构的小伙伴参考,附上完整可运行代码和问题解析,新手也能直接上手。

一、顺序表核心概念

顺序表是用一段连续的内存空间存储数据元素的线性结构,简单说就是“用数组存数据,再用一个变量记录有效元素个数”。它的核心特点是:

  • 随机访问快:通过下标能直接定位元素,时间复杂度O(1)

  • 插入/删除慢:需要移动元素腾出位置或填补空缺,时间复杂度O(n)

  • 静态顺序表:用固定大小的数组实现(本文采用这种,适合入门理解)

本文实现的顺序表,覆盖所有基础操作:初始化、创建、销毁、判空、获取长度、遍历、取值、查找、插入、删除,一站式搞定新手入门需求。

二、完整可运行代码

//2026 03 17

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
#define ElemType ll
#define MaxSize 50

struct SqList{
    ElemType data[MaxSize];
    int length;
};
void InitList(SqList*&L){
    L=(SqList*)malloc(sizeof(SqList));
    L->length=0;
}
void CreateList(SqList*&L,ElemType a[],int n){
    int i=0;
    InitList(L);
    while(i<n){
        L->data[i]=a[i];
        i++;
    }
    L->length=i;
}
void DestroyList(SqList*&L){
    free(L);
}
bool Listempty(SqList*L){
    return(L->length==0);
}
int Listsize(SqList*L){
    return(L->length);
}
void DispList(SqList*L){
    for(int i=0;i<L->length;i++){
        cout<<L->data[i];
        if(i<L->length-1)cout<<' ';
    }
    cout<<'\n';
}
bool GetElem(SqList*L,int i,ElemType&e){
    if(i<1||i>L->length)return false;
    e=L->data[i-1];
    return true;
}
int LocateElem(SqList*L,ElemType e){
    int i=0;
    while(i<L->length&&L->data[i]!=e) i++;
    if(i>=L->length)return 0;else return i+1;
}
bool ListInsert(SqList*&L,int i,ElemType e){
    int j;
    if(i<1||i>L->length+1||L->length==MaxSize)return false;
    i--;
    for(j=L->length;j>i;j--)L->data[j]=L->data[j-1];
    L->data[i]=e;
    L->length++;
    return true;
}
bool ListDelete(SqList*&L,int i,ElemType&e){
    int j;
    if(i<1||i>L->length)return false;
    i--;
    e=L->data[i];
    for(j=i;j<L->length-1;j++)L->data[j]=L->data[j+1];
    L->length--;
    return true;
}
int main(){
    SqList*L;
    ElemType a[]={13,56,89,78,45,89};
    CreateList(L,a,6);
    DispList(L);
    ListInsert(L,3,8);
    DispList(L);
    ElemType get1;
    GetElem(L,3,get1);
    cout<<get1<<'\n';
    cout<<LocateElem(L,78);
}

注释版本:

#include<bits/stdc++.h>
using namespace std;
using ll=long long;

// 宏定义:统一数据类型和顺序表最大容量,便于后续扩展
#define ElemType ll   // 数据元素类型,可按需修改(如int、double)
#define MaxSize 50    // 顺序表最大容量

// 顺序表结构体定义
struct SqList{
    ElemType data[MaxSize];  // 存储元素的静态数组
    int length;              // 顺序表当前有效元素个数
};

// 1. 初始化顺序表
// 核心:分配内存 + 初始化length为0(新手易漏)
void InitList(SqList*&L){
    // 分配内存(C风格malloc,新手可先掌握,后续可替换为C++的new)
    L=(SqList*)malloc(sizeof(SqList));
    L->length=0;  // 关键步骤:避免length为随机值导致逻辑错误
}

// 2. 根据数组创建顺序表(快速初始化)
void CreateList(SqList*&L,ElemType a[],int n){
    int i=0;
    InitList(L);  // 先初始化顺序表
    while(i<n){
        L->data[i]=a[i];  // 将数组元素赋值到顺序表
        i++;
    }
    L->length=i;  // 更新有效元素个数
}

// 3. 销毁顺序表(释放内存,避免泄漏)
void DestroyList(SqList*&L){
    free(L);      // 释放分配的内存
    L = nullptr;  // 关键:销毁后将指针置空,避免野指针
}

// 4. 判断顺序表是否为空
bool Listempty(SqList*L){
    return(L->length==0);  // 空表:有效元素个数为0
}

// 5. 获取顺序表有效长度
int Listsize(SqList*L){
    return(L->length);
}

// 6. 遍历输出顺序表
void DispList(SqList*L){
    // 优化:空表时给出提示,避免输出空行
    if(Listempty(L)){
        cout << "顺序表为空!" << '\n';
        return;
    }
    for(int i=0;i<L->length;i++){
        cout<<L->data[i];
        if(i<L->length-1)cout<<' ';  // 元素间加空格,最后一个元素不空格
    }
    cout<<'\n';
}

// 7. 获取指定位置的元素(位序从1开始,新手易混淆下标)
bool GetElem(SqList*L,int i,ElemType&e){
    // 边界检查:位置i必须在[1, length]之间
    if(i<1||i>L->length)return false;
    e = L->data[i-1];  // 位序转数组下标(数组从0开始)
    return true;
}

// 8. 查找元素的位序(返回从1开始的位序,未找到返回0)
int LocateElem(SqList*L,ElemType e){
    int i=0;
    // 遍历查找,找到则返回位序,未找到返回0
    while(i<L->length&&L->data[i]!=e) i++;
    if(i>=L->length)return 0;
    else return i+1;  // 关键:数组下标转位序(新手易漏+1)
}

// 9. 插入元素到指定位置(位序从1开始)
bool ListInsert(SqList*&L,int i,ElemType e){
    int j;
    // 边界检查:插入位置合法 + 顺序表未满
    if(i<1||i>L->length+1||L->length==MaxSize)return false;
    i--;  // 位序转数组下标
    // 元素后移,腾出插入位置(从最后一个元素开始移)
    for(j=L->length;j>i;j--)L->data[j]=L->data[j-1];
    L->data[i]=e;  // 插入元素
    L->length++;   // 更新有效元素个数
    return true;
}

// 10. 删除指定位置的元素(位序从1开始)
bool ListDelete(SqList*&L,int i,ElemType&e){
    int j;
    // 边界检查:删除位置合法
    if(i<1||i>L->length)return false;
    i--;  // 位序转数组下标
    e = L->data[i];  // 保存被删除的元素
    // 元素前移,填补删除空缺(从删除位置开始移)
    for(j=i;j<L->length-1;j++)L->data[j]=L->data[j+1];
    L->length--;  // 更新有效元素个数
    return true;
}

// 主函数:测试所有功能
int main(){
    SqList*L;
    ElemType a[]={13,56,89,78,45,89};  // 测试数组
    CreateList(L,a,6);                  // 创建顺序表
    
    cout << "1. 初始顺序表:";
    DispList(L);                        // 遍历输出
    
    ListInsert(L,3,8);                  // 第3位插入元素8
    cout << "2. 插入元素8后:";
    DispList(L);
    
    ElemType get1;
    GetElem(L,3,get1);                  // 获取第3位元素
    cout << "3. 第3位元素:" << get1 << '\n';
    
    cout << "4. 元素78的位序:" << LocateElem(L,78) << '\n';  // 查找元素
    
    DestroyList(L);                     // 销毁顺序表,避免内存泄漏
    return 0;
}

三、新手必踩的3个坑

写代码的过程中,3个典型新手坑,也是很多入门者容易忽略的点,整理出来帮大家避坑:

坑1:初始化时未给length赋值(最致命)

最初版本的InitList函数,只做了malloc分配内存,没有写L->length=0;。

错误问题:malloc分配的内存是随机值,length可能是一个很大的随机数,后续插入、遍历会出现逻辑错误(比如明明没元素,却认为有很多元素)。

解决:初始化时必须显式将length置0,这是顺序表操作的基础。

坑2:LocateElem函数返回值错误(最易混淆)

最初写LocateElem时,找到元素后返回i++(等价于返回i),但顺序表的“位序”是从1开始的,而数组下标是从0开始的。

错误问题:比如查找78,数组下标是4,返回4就错了,应该返回位序5。

解决:找到元素后返回i+1,未找到返回0,符合顺序表“位序”的定义。

坑3:销毁顺序表后未置空指针(易忽略)

最初的DestroyList函数,只写了free(L);,没有将L置为nullptr。

错误问题:free后,L变成“野指针”(指向已释放的内存),后续如果误操作L,会导致程序崩溃。

解决:free后加上L = nullptr;,避免野指针问题。

四、新手学习建议

1. 先理解“位序”和“数组下标”的区别:顺序表的操作(插入、删除、取值)都是按“位序(从1开始)”,而数组是按“下标(从0开始)”,这是新手最易混淆的点,一定要记牢。

2. 边界检查不能少:所有操作都要判断“位置是否合法”“顺序表是否为空/满”,比如插入时i不能小于1,也不能大于length+1,否则会数组越界。

3. 内存管理要注意:malloc分配的内存一定要free,new分配的一定要delete,避免内存泄漏;销毁指针后记得置空,避免野指针。

4. 多动手调试:建议逐行调试代码,观察length的变化、元素的移动过程,比如插入元素时,看for循环如何将元素后移,能更直观理解顺序表的原理。

五、总结

顺序表作为数据结构的入门知识点,难度不大,但细节很多。本文实现的静态顺序表,覆盖了所有基础操作,修复了新手易踩的坑,代码注释详细,适合新手入门练习。

其实只要掌握“连续存储”“位序与下标转换”“元素移动”这三个核心点,就能轻松实现顺序表的所有操作。后续可以尝试优化成动态顺序表(用new动态分配内存,可扩容),进一步加深对线性结构的理解。

更多推荐