从 C 语言动态数组到顺序表-2:实现顺序表的基本操作

动态数组以其灵活的存储空间管理为我们处理可变数量的数据提供了便利,通过动态扩容机制,它解决了静态数组容量固定的局限。而顺序表,作为线性表的一种重要实现形式,正是基于动态数组发展而来的结构化数据管理方式 。本文介绍了如何用 C 语言实现顺序表的各种基本操作,包括初始化、插入、删除、修改、查找和销毁等,并采用多文件结构进行代码组织。

一、顺序表的结构体设计

顺序表的核心是通过数组来存储数据,为了方便管理,我们定义一个结构体来封装顺序表的相关信息:

typedef struct seqlist
{
    datatype* data;  // 指向存储数据的数组
    int length;      // 当前顺序表的长度(元素个数)
    int capacity;    // 当前顺序表的容量(最大可存储元素个数)
} sl;
  • data:指向动态分配的数组,用于存储实际数据元素
  • length:记录当前顺序表中已存储的元素个数
  • capacity:记录顺序表当前的最大容量,用于动态扩容

为了提高代码的通用性,我们使用宏定义datatype来表示数据类型,方便后续修改存储的数据类型:

#define datatype int

二、多文件结构设计

为了使代码结构清晰、易于维护,我们采用三个文件来组织代码:

  1. test_0820.h:头文件,用于声明结构体、宏定义和函数原型
  2. fun_0820.c:源文件,用于实现顺序表的各种操作函数
  3. test_0820.c:测试文件,用于编写主函数,测试顺序表的各种功能

这种分工方式的优点:

  • 头文件集中管理声明,方便其他文件引用
  • 功能实现与测试代码分离,便于代码复用和维护
  • 符合模块化编程思想,提高代码可读性

三、顺序表的基本操作实现

1. 初始化操作(initsl)

初始化操作用于创建一个空的顺序表,设置初始状态:

void initsl(sl* list)
{
    list->data = NULL;    // 初始化为空指针
    list->length = 0;     // 初始长度为0
    list->capacity = 0;   // 初始容量为0
}

初始化后,顺序表处于空状态,没有分配任何存储空间。

2. 容量检查与扩容(checkslspace)

由于顺序表的存储空间是动态分配的,当元素个数达到当前容量时,需要进行扩容:

void checkslspace(sl* list)
{
    if (list->length >= list->capacity)
    {
        // 容量为0时初始化为4,否则翻倍扩容
        int newcapacity = (list->capacity == 0) ? 4 : list->capacity * 2;
        // 重新分配内存
        datatype* newdata = (datatype*)realloc(list->data, newcapacity * sizeof(datatype));
        if (newdata == NULL)
        {
            perror("realloc failed");
            exit(EXIT_FAILURE);
        }
        list->data = newdata;
        list->capacity = newcapacity;
    }
}

扩容策略采用 "倍增法",当容量为 0 时初始化为 4,之后每次扩容都将容量翻倍,这种方式可以减少频繁扩容带来的性能开销。

3. 插入操作(insertsl)

插入操作用于在指定位置插入一个新元素:

int insertsl(sl* list, int location, datatype value)
{
    // 检查插入位置是否合法
    if (location < 0 || location > list->length)
    {
        printf("Invalid location for insertion.\n");
        return -1; // 返回错误码
    }
    // 检查并扩容
    checkslspace(list);
    // 从后往前移动元素,为新元素腾出位置
    for (int i = list->length; i > location; --i)
    {
        list->data[i] = list->data[i - 1]; 
    }
    // 插入新元素
    list->data[location] = value;
    // 长度加1
    list->length++;
    return 0; // 成功返回0
}

插入操作的步骤:

  1. 检查插入位置的合法性(0 ≤ location ≤ length)
  2. 检查容量并按需扩容
  3. 将插入位置及之后的元素向后移动一位
  4. 在目标位置插入新元素
  5. 更新顺序表长度

4. 删除操作(deletesl)

删除操作用于删除指定位置的元素:

int deletesl(sl* list, int location)
{
    // 检查删除位置是否合法
    if(location < 0 || location >= list->length)
    {
        printf("Invalid location for deletion.\n");
        return -1; // 返回错误码
    }
    // 将删除位置之后的元素向前移动一位
    for(int i = location; i < list->length-1; i++)
    {
        list->data[i] = list->data[i+1];
    }
    // 长度减1
    list->length--;
    return 0; // 成功返回0
}

删除操作的步骤:

  1. 检查删除位置的合法性(0 ≤ location < length)
  2. 将删除位置之后的所有元素向前移动一位
  3. 更新顺序表长度

5. 修改操作(revisesl)

修改操作用于更新指定位置的元素值:

int revisesl(sl* list, int location, datatype value)
{
    // 检查位置是否合法
    if (location < 0 || location >= list->length)
    {
        printf("Invalid location for revision.\n");
        return -1; // 返回错误码
    }
    // 更新元素值
    list->data[location] = value;
    return 0; // 成功返回0
}

修改操作比较简单,只需检查位置合法性后直接更新对应位置的元素值即可。

6. 查找操作(findsl)

查找操作用于查找指定值在顺序表中的位置:

int findsl(sl* list, datatype value)
{
    // 遍历顺序表查找元素
    for(int i = 0; i < list->length; i++)
    {
        if(list->data[i] == value)
        {
            printf("找到了,在第%d个元素\n", i + 1);
            return i + 1; // 返回位置(从1开始计数)
        }
    }
    printf("没有找到\n");
    return -1; // 未找到返回-1
}

这里采用的是顺序查找(线性查找),逐个比较元素直到找到目标值或遍历完所有元素。

7. 打印操作(printsl)

打印操作用于输出顺序表中的所有元素:

void printsl(sl* list)
{
    for(int i = 0; i < list->length; i++)
    {
        printf("%d ", list->data[i]);
    }
    printf("\n");
}

8. 销毁操作(destroysl)

销毁操作用于释放顺序表占用的内存空间:

void destroysl(sl* list)
{
    if (list->data != NULL)
    {
        free(list->data);  // 释放动态分配的数组
        list->data = NULL; // 避免野指针
    }
    list->length = 0;    // 重置长度
    list->capacity = 0;  // 重置容量
}

销毁操作是必要的,它可以防止内存泄漏,特别是在长期运行的程序中。

四、测试代码实现

为了验证顺序表的各种操作是否正确,我们编写测试代码:

int main()
{
    sl myList;
    // 初始化
    initsl(&myList);
    checksl(&myList);

    // 插入元素
    insertsl(&myList, 0, 1);
    insertsl(&myList, 0, 2);
    insertsl(&myList, 0, 3);
    insertsl(&myList, 0, 4);
    insertsl(&myList, 0, 5);
    insertsl(&myList, 0, 6);
    printsl(&myList); // 应输出:6 5 4 3 2 1

    // 删除元素
    deletesl(&myList, 0);
    deletesl(&myList, 0);
    deletesl(&myList, 0);
    printsl(&myList); // 应输出:3 2 1

    // 修改元素
    revisesl(&myList, 0, 66);
    printsl(&myList); // 应输出:66 2 1

    // 查找元素
    findsl(&myList, 66);  // 应找到,在第1个位置
    findsl(&myList, 100); // 应找不到

    // 销毁
    destroysl(&myList);
    checksl(&myList);
    return 0;
}

运行结果

List initialized: length = 0, capacity = 0
Data pointer is NULL as expected.
6 5 4 3 2 1 
3 2 1 
66 2 1 
找到了,在第1个元素
没有找到
List initialized: length = 0, capacity = 0
Data pointer is NULL as expected.
Process exited with status 0

五、总结

本文实现了顺序表的完整功能,包括:

  1. 采用结构体封装顺序表的核心信息
  2. 使用多文件结构组织代码,提高可维护性
  3. 实现了初始化、插入、删除、修改、查找、打印和销毁等操作
  4. 加入了容量动态扩展机制,提高空间利用率
  5. 对各种操作进行了参数合法性检查,增强代码健壮性

附:头文件代码

#include <stdio.h>
#include <stdlib.h>
#define datatype int

typedef struct seqlist
{
    datatype* data;
    int length;
    int capacity;
}sl;

void initsl(sl* list);
void checksl(sl* list);
int insertsl(sl* list, int location, datatype value);
void checkslspace(sl* list);
void printsl(sl* list);
int deletesl(sl* list, int location);
int revisesl(sl* list, int location, datatype value);
int findsl(sl* list, datatype value);
void destroysl(sl* list);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值