C语言基础学习:抽象数据类型(ADT)

基础概念

抽象数据类型(ADT)是一种数据类型,它定义了一组数据以及可以在这组数据上执行的操作,但隐藏了数据的具体存储方式和实现细节。在C语言中,抽象数据类型(ADT)是一种非常重要的概念,它允许程序员定义和操作自定义的数据结构,而无需关心其底层实现细节。通过ADT可以创建出既安全又高效的数据管理方案,为复杂问题的解决提供有力支持。
使用ADT的优点:
封装性:隐藏数据表示和实现细节,只暴露操作接口,提高了代码的安全性和可维护性。
复用性:ADT可以作为独立的模块进行开发和测试,方便在不同项目中复用。
抽象性:通过ADT,我们可以更关注于数据操作的逻辑,而不是数据的具体存储方式。

ADT由以下两部分组成:
数据表示:定义数据的存储结构,通常使用结构体来封装数据成员。
操作接口:定义可以在数据上执行的操作,如创建、销毁、访问、修改等,这些操作通过函数来实现。

基于链表的ADT实现数据封装

这里使用基于链表的ADT实现数据封装来进行展示,数据封装是一种把数据和操作数据的函数捆绑在一起的机制,在C语言中,可以通过结构体和函数来实现数据封装,结构体用于存储数据,而函数则用于操作这些数据。
操作步骤如下:
1.定义链表节点的结构体:包含数据域和指针域。
2.定义链表ADT的操作函数:如初始化链表、在链表末尾添加元素、清空链表等。
3.实现这些操作函数:通过函数来操作链表,隐藏链表的具体实现细节。

#include <stdio.h>
#include <stdlib.h>

// 定义链表节点的结构体
typedef struct ListNode {
    int data;
    struct ListNode* next;
} ListNode;

// 定义链表ADT的操作函数
typedef struct {
    ListNode* head;
} List;

// 初始化链表
void initList(List* list) {
    list->head = NULL;
}

// 在链表末尾添加一个元素
void appendToList(List* list, int value) {
    ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
    newNode->data = value;
    newNode->next = NULL;
    if (list->head == NULL) {
        list->head = newNode;
    } else {
        ListNode* current = list->head;
        while (current->next != NULL) {
            current = current->next;
        }
        current->next = newNode;
    }
}

// 清空链表
void clearList(List* list) {
    ListNode* current = list->head;
    while (current != NULL) {
        ListNode* next = current->next;
        free(current);
        current = next;
    }
    list->head = NULL;
}

// 打印链表
void printList(List* list) {
    ListNode* current = list->head;
    while (current != NULL) {
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");
}

int main() {
    List myList;
    initList(&myList);
    appendToList(&myList, 1);
    appendToList(&myList, 2);
    appendToList(&myList, 3);
    printList(&myList);
    clearList(&myList);
    printList(&myList);

    return 0;
}

运行后在终端显示以下内容
在这里插入图片描述

接口实现

接口是ADT与用户之间的桥梁。它规定了用户可以如何访问和操作ADT中的数据,而不涉及数据的内部表示。在C语言中,接口通常通过头文件(.h文件)来定义,其中包含了数据类型的声明和函数原型的声明。实现接口意味着为ADT定义具体的操作。这些操作在C语言中通过函数来实现。函数的定义通常放在源文件(.c文件)中,并且这些函数会操作ADT的内部数据。
这里通过定义一个栈的ADT来实现数据封装,并通过接口来访问栈的操作。
步骤如下:
定义栈的ADT:在头文件中声明栈的结构体和函数原型。
实现栈的操作:在源文件中定义栈的操作函数。
使用栈的ADT:在主程序中通过接口来操作栈。

先定义一个栈操作的头文件

// stack.h
#ifndef STACK_H
#define STACK_H

// 定义栈的ADT
typedef struct {
    int* data;
    int top;
    int capacity;
} Stack;

// 栈的操作函数原型
void initStack(Stack* stack);
int isStackEmpty(Stack* stack);
void push(Stack* stack, int value);
int pop(Stack* stack);
int peek(Stack* stack);
void clearStack(Stack* stack);

#endif

在编写一个用来实现栈操作的C语言文件

// stack.h
#include <stdio.h>
#include <stdlib.h>
#include "stack.h"

// 栈的内部表示
struct Stack {
    int* data;
    int top;
    int capacity;
};

// 初始化栈
void initStack(Stack* stack) {
    stack->data = (int*)malloc(100 * sizeof(int));
    stack->top = -1;
    stack->capacity = 100;
}

// 检查栈是否为空
int isStackEmpty(Stack* stack) {
    return stack->top == -1;
}

// 压栈
void push(Stack* stack, int value) {
    if (stack->top < stack->capacity - 1) {
        stack->data[++stack->top] = value;
    } else {
        // 如果栈满,输出提示
        printf("被填满了\n");
    }
}

// 出栈
int pop(Stack* stack) {
    if (!isStackEmpty(stack)) {
        return stack->data[stack->top--];
    } else {
        // 栈空,处理栈下溢,返回特殊值表示错误
        return -1; // -1不是栈中的有效值
    }
}

// 查看栈顶元素
int peek(Stack* stack) {
    if (!isStackEmpty(stack)) {
        return stack->data[stack->top];
    } else {
        return -1; 
    }
}

// 清空栈
void clearStack(Stack* stack) {
    free(stack->data);
    stack->top = -1;
    stack->capacity = 0;
}

最后编写出主文件

// main.c
#include <stdio.h>
#include "stack.h"

int main() {
    Stack myStack;
    initStack(&myStack);

    push(&myStack, 10);
    push(&myStack, 20);
    push(&myStack, 30);

    printf("栈顶部的元素是: %d\n", peek(&myStack));

    while (!isStackEmpty(&myStack)) {
        printf("弹出的元素是: %d\n", pop(&myStack));
    }
    clearStack(&myStack);

    return 0;
}

代码运行后在终端输出以下内容:
在这里插入图片描述

队列ADT

队列是一种先进先出(FIFO)的线性表,它只允许在表的一端进行插入(队尾),在另一端进行删除(队头),任务按照它们被添加到队列中的顺序被调度执行。

队列ADT的操作步骤如下
入队(EnQueue)
将一个元素添加到队列的末尾(队尾)。
这是队列的核心操作之一,用于在队列中插入新元素。
出队(DeQueue)
从队列的开头(队头)移除一个元素,并返回该元素的值。
出队操作遵循先进先出(FIFO)的原则,即最先入队的元素最先被移除。
判空(QueueEmpty)
检查队列是否为空。
这是一个常用的辅助操作,用于确定队列中是否还有元素。
获取队头元素(GetHead 或 Front)
返回队列开头元素的值,但不移除该元素。
这允许用户查看队列的当前状态,而不改变队列的内容。
队列长度(QueueLength)
返回队列中元素的数量。
这个操作对于需要知道队列大小的情况非常有用。
清空队列(ClearQueue)
移除队列中的所有元素,使队列变为空。
这在需要重置队列或释放内存时很有用。
销毁队列(DestroyQueue)
释放队列所占用的所有资源,包括内存。
这是在队列不再需要时进行的清理操作。

以下代码运行后程序会提示用户输入队列元素的数量,然后输入具体的元素。接着程序会遍历队列、出队一个元素、获取队头元素、显示队列长度,并判断队列是否为空。最后销毁队列。

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef int QElemType;

typedef struct QNode {
    QElemType data;
    struct QNode* next;
} QNode, *QueuePtr;

typedef struct {
    QueuePtr front;
    QueuePtr rear;
} LinkQueue;

// 初始化队列
bool InitQueue(LinkQueue* q) {
    if (!q) {
        printf("队列不存在!\n");
        return false;
    }
    q->front = q->rear = (QueuePtr)malloc(sizeof(QNode));
    if (!q->front) {
        printf("内存分配失败!\n");
        return false;
    }
    q->front->next = NULL;
    return true;
}

// 销毁队列
bool DestroyQueue(LinkQueue* queue) {
    if (!queue) {
        printf("队列不存在!\n");
        return false;
    }
    while (queue->front != NULL) {
        queue->rear = queue->front->next;
        free(queue->front);
        queue->front = queue->rear;
    }
    return true;
}

// 清空队列
bool ClearQueue(LinkQueue* q) {
    if (!q) {
        printf("队列不存在!\n");
        return false;
    }
    QueuePtr p = q->front->next, tmp;
    while (p) {
        tmp = p->next;
        free(p);
        p = tmp;
    }
    q->front = q->rear;
    return true;
}

// 判断队列是否为空
bool QueueEmpty(LinkQueue q) {
    if (!&q) {
        printf("空队列!\n");
        return false;
    }
    if (q.front == q.rear) {
        return true;
    }
    return false;
}

// 插入元素e为q的新队尾元素
bool EnQueue(LinkQueue* q, QElemType e) {
    QueuePtr p = (QueuePtr)malloc(sizeof(QNode));
    if (!p) {
        printf("内存分配失败!\n");
        return false;
    }
    p->data = e;
    p->next = NULL;
    q->rear->next = p;
    q->rear = p;
    return true;
}

// 出队
bool DeQueue(LinkQueue* q, QElemType* e) {
    if (!q) {
        printf("队列不存在!\n");
        return false;
    }
    if (QueueEmpty(*q)) {
        printf("空队列!\n");
        return false;
    }
    QueuePtr p = q->front->next;
    *e = p->data;
    q->front->next = p->next;
    if (q->front == q->rear) {
        q->rear = q->front;
    }
    free(p);
    return true;
}

// 获取队首元素
bool GetHead(LinkQueue q, QElemType* e) {
    if (!(&q)) {
        printf("队列不存在!\n");
        return false;
    }
    if (QueueEmpty(q)) {
        printf("空队列!\n");
        return false;
    }
    *e = q.front->next->data;
    return true;
}

// 队列长度
int QueueLength(LinkQueue q) {
    if (!&q) {
        printf("队列不存在!\n");
        return 0;
    }
    int len = 0;
    QueuePtr p = q.front->next;
    while (p) {
        len++;
        p = p->next;
    }
    return len;
}

// 遍历队列
void QueueTraverse(LinkQueue q) {
    if (!(&q)) {
        printf("队列不存在!\n");
        return;
    }
    if (QueueEmpty(q)) {
        printf("队列为空!\n");
        return;
    }
    QueuePtr p = q.front->next;
    while (p) {
        printf("%d ", p->data);
        p = p->next;
    }
    printf("\n");
}

int main() {
    LinkQueue que;
    QElemType data;
    int n;

    if (!InitQueue(&que)) {
        return 1;
    }

    printf("输入队列元素数量:\n");
    scanf("%d", &n);
    printf("输入队列中元素:\n");
    while (n--) {
        scanf("%d", &data);
        EnQueue(&que, data);
    }

    printf("遍历队列:\n");
    QueueTraverse(que);

    if (DeQueue(&que, &data)) {
        printf("出队元素: %d\n", data);
    }

    if (GetHead(que, &data)) {
        printf("队头元素: %d\n", data);
    }

    printf("队列长度: %d\n", QueueLength(que));

    if (QueueEmpty(que)) {
        printf("队列为空!\n");
    } else {
        printf("队列不为空!\n");
    }

    DestroyQueue(&que);

    return 0;
}

代码运行后显示:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/921579.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于深度学习CNN算法的花卉分类识别系统01--带数据集-pyqt5UI界面-全套源码

文章目录 基于深度学习算法的花卉分类识别系统一、项目摘要二、项目运行效果三、项目文件介绍四、项目环境配置1、项目环境库2、环境配置视频教程 五、项目系统架构六、项目构建流程1、数据集2、算法网络Mobilenet3、网络模型训练4、训练好的模型预测5、UI界面设计-pyqt56、项目…

Bokeh实现大规模数据可视化的最佳实践

目录 引言 一、Bokeh简介 二、安装Bokeh 三、数据准备 四、性能优化 五、创建图表 六、添加交互功能 七、应用案例 八、高级技巧 九、总结 引言 在数据科学领域,数据可视化是一个至关重要的环节。通过可视化,我们可以直观地理解数据的特征和趋势,为数据分析和决策…

Easyexcel(4-模板文件)

相关文章链接 Easyexcel&#xff08;1-注解使用&#xff09;Easyexcel&#xff08;2-文件读取&#xff09;Easyexcel&#xff08;3-文件导出&#xff09;Easyexcel&#xff08;4-模板文件&#xff09; 文件导出 获取 resources 目录下的文件&#xff0c;使用 withTemplate 获…

【山大909算法题】2014-T1

文章目录 1.原题2.算法思想3.关键代码4.完整代码5.运行结果 1.原题 为带表头的单链表类Chain编写一个成员函数Reverse&#xff0c;该函数对链表进行逆序操作&#xff08;将链表中的结点按与原序相反的顺序连接&#xff09;&#xff0c;要求逆序操作就地进行&#xff0c;不分配…

[Redis#2] 定义 | 使用场景 | 安装教程 | 快!

目录 1. 定义 In-memory data structures 在内存中存储数据 2. 优点&#xff01;快 Programmability 可编程性 Extensibility 扩展性 Persistence 持久化 Clustering 分布式集群 High availability 高可用性 ⭕快速访问的实现 3. 使用场景 1.Real-time data store …

学习编程,学习中间件,学习源码的思路

01 看的多&#xff0c;内化不足 最近想复习一下编程相关的知识&#xff0c;在复习前我翻开了之前的一些笔记&#xff0c;这些笔记基本都是从书本、视频、博客等摘取记录的&#xff0c;看着这些笔记心里总结&#xff1a;看的多&#xff0c;内化不足。 02 整理大纲 为了解决这个…

hhdb数据库介绍(10-2)

集群管理 计算节点集群 集群管理主要为用户提供对计算节点集群的部署、添加、启停监控、删除等管理操作。 集群管理记录 集群管理页面显示已部署或已添加的计算节点集群信息。可以通过左上角搜索框模糊搜索计算节点集群名称进行快速查找。同时也可以通过右侧展开展开/隐藏更…

AG32既可以做MCU,也可以仅当CPLD使用

Question: AHB总线上的所有外设都需要像ADC一样&#xff0c;通过cpld处理之后才能使用? Reply: 不用。 除了ADC外&#xff0c;其他都是 mcu可以直接配置使用的。 Question: DMA和CMP也不用? Reply: DMA不用。 ADC/DAC/CMP 用。 CMP 其实配置好后&#xff0c;可以直…

贪心算法(1)

目录 柠檬水找零 题解&#xff1a; 代码&#xff1a; 将数组和减半的最少操作次数&#xff08;大根堆&#xff09; 题解&#xff1a; 代码&#xff1a; 最大数&#xff08;注意 sort 中 cmp 的写法&#xff09; 题解&#xff1a; 代码&#xff1a; 摆动序列&#xff0…

网络爬虫——综合实战项目:多平台房源信息采集与分析系统

1. 项目背景与目标 1.1 项目背景 随着房产市场的快速发展&#xff0c;各大平台上充斥着大量房源信息。为了帮助用户快速掌握市场动态&#xff0c;需要通过爬虫技术自动采集多平台数据&#xff0c;清洗后进行存储和分析&#xff0c;为用户提供有价值的洞察。开发者通过这一实战…

数据结构-7.Java. 对象的比较

本篇博客给大家带来的是java对象的比较的知识点, 其中包括 用户自定义类型比较, PriorityQueue的比较方式, 三种比较方法...... 文章专栏: Java-数据结构 若有问题 评论区见 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作的动力 .…

NVR录像机汇聚管理EasyNVR多品牌NVR管理工具/设备如何使用Docker运行?

在当今的安防监控领域&#xff0c;随着视频监控技术的不断发展和应用范围的扩大&#xff0c;如何高效、稳定地管理并分发视频流资源成为了行业内外关注的焦点。EasyNVR作为一款功能强大的多品牌NVR管理工具/设备&#xff0c;凭借其灵活的部署方式和卓越的性能&#xff0c;正在引…

LSTM原理解读与实战

在RNN详解及其实战中&#xff0c;简单讨论了为什么需要RNN这类模型、RNN的具体思路、RNN的简单实现等问题。同时&#xff0c;在文章结尾部分我们提到了RNN存在的梯度消失问题&#xff0c;及之后的一个解决方案&#xff1a;LSTM。因此&#xff0c;本篇文章主要结构如下&#xff…

Springboot之登录模块探索(含Token,验证码,网络安全等知识)

简介 登录模块很简单&#xff0c;前端发送账号密码的表单&#xff0c;后端接收验证后即可~ 淦&#xff01;可是我想多了&#xff0c;于是有了以下几个问题&#xff08;里面还包含网络安全问题&#xff09;&#xff1a; 1.登录时的验证码 2.自动登录的实现 3.怎么维护前后端…

使用Element UI实现前端分页,前端搜索,及el-table表格跨页选择数据,切换分页保留分页数据,限制多选数量

文章目录 一、前端分页1、模板部分 (\<template>)2、数据部分 (data)3、计算属性 (computed)4、方法 (methods) 二、前端搜索1、模板部分 (\<template>)2、数据部分 (data)3、计算属性 (computed)4、方法 (methods) 三、跨页选择1、模板部分 (\<template>)2、…

VMware Workstation 17.6.1

概述 目前 VMware Workstation Pro 发布了最新版 v17.6.1&#xff1a; 本月11号官宣&#xff1a;针对所有人免费提供&#xff0c;包括商业、教育和个人用户。 使用说明 软件安装 获取安装包后&#xff0c;双击默认安装即可&#xff1a; 一路单击下一步按钮&#xff1a; 等待…

探索PyMuPDF:Python中的强大PDF处理库

文章目录 **探索PyMuPDF&#xff1a;Python中的强大PDF处理库**第一部分&#xff1a;背景第二部分&#xff1a;PyMuPDF是什么&#xff1f;第三部分&#xff1a;如何安装这个库&#xff1f;第四部分&#xff1a;至少5个简单的库函数使用方法第五部分&#xff1a;结合至少3个场景…

go语言range的高级用法-使用range来接收通道里面的数据

在 Go 语言中&#xff0c;可以使用 for ... range 循环来遍历通道&#xff08;channel&#xff09;。for ... range 循环会一直从通道中接收值&#xff0c;直到通道关闭并且所有值都被接收完毕。 使用 for ... range 遍历通道 示例代码 下面是一个使用 for ... range 遍历通…

14.C++STL1(STL简介)

⭐本篇重点&#xff1a;STL简介 ⭐本篇代码&#xff1a;c学习/7.STL简介/07.STL简介 橘子真甜/c-learning-of-yzc - 码云 - 开源中国 (gitee.com) 目录 一. STL六大组件简介 二. STL常见算法的简易使用 2.1 swap ​2.2 sort 2.3 binary_search lower_bound up_bound 三…

5G CPE与4G CPE的主要区别有哪些

什么是CPE&#xff1f; CPE是Customer Premise Equipment&#xff08;客户前置设备&#xff09;的缩写&#xff0c;也可称为Customer-side Equipment、End-user Equipment或On-premises Equipment。CPE通常指的是位于用户或客户处的网络设备或终端设备&#xff0c;用于连接用户…