02.【数据结构-C语言】顺序表(线性表概念、顺序表实现:增删查、前向声明、顺序表实现通讯录项目:增删改查、通讯录数据导入及保存到本地文件)

目录

1. 线性表

2. 顺序表概念及分类

2.1 顺序表的概念

2.2 顺序表分类

2.3 动静态顺序表对比

3. 顺序表的实现(附完整版代码)

3.1 顺序表结构体声明

3.2 初始化&销毁

3.3 插入(尾插、头插、指定位置之前插入)

3.4 扩容

3.5 删除(尾删、头删、指定位置删除)

3.6 查找(查找数据下标)

4. 顺序表实现通讯录(附完整版代码)

4.1 前向声明

4.2 通讯录结构体声明&顺序表结构体修改

4.3 初始化&销毁(直接调用顺序表)

4.4 添加联系人(直接调用顺序表)

4.5 删除联系人

4.6 修改联系人信息

4.7 查找联系人

5. 通讯录保存到文件

5.1 保存函数和加载函数

5.2 通讯录初始化和销毁函数修改


1. 线性表

        线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...

        线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。(物理结构就是物理上的存储结构)

        案例:蔬菜分为绿叶类、瓜类、菌菇类。线性表指的是具有部分相同特性的一类数据结构的集合如何理解逻辑结构和物理结构?

2. 顺序表概念及分类

2.1 顺序表的概念

        顺序表和数组的区别:顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口。

        顺序表的特性:逻辑结构是连续的,物理结构是连续的(顺序表底层是数组,数组是连续的)。

2.2 顺序表分类

        顺序表分为静态顺序表动态顺序表

        静态顺序表:使用定长数组存储元素

struct SeqList
{int arr[100];// 定长数组int size;    // 顺序表当前有效的数据个数
};

        动态顺序表:动态内存开启,数组大小可以动态调整。

struct SeqList
{int* arr;    // 指向动态内存开辟的空间int size;    // 顺序表当前有效的数据个数int capacity;// 动态申请的空间大小
};

2.3 动静态顺序表对比

静态顺序表动态顺序表
数组给小了,空间不够用;数组给大了,空间浪费。动态增容

        动态顺序表相比于静态顺序表有很大优势。

3. 顺序表的实现(附完整版代码)

        完整版顺序表实现代码:【免费】顺序表-C语言实现代码资源-CSDN下载

3.1 顺序表结构体声明

        顺序表的结构体一般有三个成员变量:存储的数据的类型的指针(重定义以下,方便以后对其他类型使用)、顺序表当前有效数据的个数、动态申请的空间的大小

        同时可以给顺序表结构体重定义,方便以后使用。

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int SLDataType;		// 类型重定义,方便以后修改为其他类型的数据typedef struct SeqList
{SLDataType* arr;// 指向动态内存开辟的空间int size;		// 顺序表当前有效的数据个数int capacity;	// 动态申请的空间大小
}SL;			// 重定义结构体类型名,方便以后使用

3.2 初始化&销毁

// 顺序表初始化
void SLInit(SL* ps)
{ps->arr = NULL;ps->size = ps->capacity = 0;
}// 顺序表销毁
void SLDestory(SL* ps)	
{if (ps->arr){free(ps->arr);}ps->arr = NULL;ps->size = ps->capacity = 0;
}

3.3 插入(尾插、头插、指定位置之前插入)

        顺序表插入分为:尾插、头插、指定位置之前插入

        每次插入之前要判断现有空间是否足够,不够时要进行扩容,见3.4节。

        实现了SLInsert指定位置之前插入之后,可以直接复用到尾插、头插中。

注意:

        1. 插入时要对传入的指针判断,避免传入指针为空指针,可以用assert断言。

        2. 指定位置之前插入的形参pos要进行判断,插入的位置是否在顺序表内部。如果不在内部执行插入时可能会越界访问导致程序出错。

//顺序表尾插
void SLPushBack(SL* ps, SLDataType x)	
{assert(ps);		// 避免这种情况的发生SLPushBack(NULL, 2);SLCheckCapacity(ps);ps->arr[ps->size++] = x;
}//顺序表头插
void SLPushFront(SL* ps, SLDataType x)	
{assert(ps);		// 避免这种情况的发生SLPushBack(NULL, 2);SLCheckCapacity(ps);// 顺序表中的内容整体往后挪动一位for (int i = ps->size; i > 0; i--){ps->arr[i] = ps->arr[i - 1];}ps->arr[0] = x;ps->size++;//SLInsert(ps, 0, x);
}//指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{assert(ps);		// 避免这种情况的发生SLPushBack(NULL, 2);assert(pos >= 0 && pos <= ps->size);	// 确保pos在ps->arr的里面SLCheckCapacity(ps);for (int i = ps->size; i > pos; i--){ps->arr[i] = ps->arr[i - 1];}ps->arr[pos] = x;ps->size++;
}

3.4 扩容

        在顺序表空间不够时需要扩容,每一种插入都要判断扩容,所以将扩容单独写成一个函数。

        扩容的规则:动态增容,一般以2倍或3倍的形式增加。

// 扩容
void SLCheckCapacity(SL* ps)	
{if (ps->size == ps->capacity){	// 申请空间int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;int* tmp = (SLDataType*)realloc(ps->arr, sizeof(SLDataType) * newCapacity);if (tmp == NULL){perror("realloc");exit(1);	// 直接退出程序,不在继续执行}ps->arr = tmp;ps->capacity = newCapacity;}
}

3.5 删除(尾删、头删、指定位置删除)

        顺序表的删除分为:尾删、头删、指定位置删除。

注意:

        1. 头删、尾删要保证顺序表不为空。

        2. 指定位置删除,要保证pos在顺序表内部,否则可能出现越界访问导致程序运行出错。

// 尾删
void SLPopBack(SL* ps)	
{assert(ps);assert(ps->size);//保证顺序表不为空ps->size--;
}// 头删
void SLPopFront(SL* ps)
{assert(ps);assert(ps->size);//保证顺序表不为空for (int i = 0; i < ps->size-1; i++){ps->arr[i] = ps->arr[i + 1];}ps->size--;
}//指定位置删除数据
void SLErase(SL* ps, int pos)
{assert(ps);assert(pos >=0 && pos < ps->size);for (int i = pos; i < ps->size - 1; i++){ps->arr[i] = ps->arr[i + 1];}ps->size--;
}

3.6 查找(查找数据下标)

        没查找到时,确保返回值不在ps->size内部。

// 查找数据的位置
int SLFind(SL* ps, SLDataType x)
{assert(ps);for (int i = 0; i < ps->size; i++){if (ps->arr[i] == x)return i;}return -1;
}

4. 顺序表实现通讯录(附完整版代码)

        完整通讯录实现代码:【免费】通讯录程序实现-C语言顺序表实现资源-CSDN下载

4.1 前向声明

4.1.1 前向声明是什么?

        定义:在类型完整定义出现之前,先声明其存在的机制

        本质:告诉编译器"这个类型存在,具体细节稍后提供"

        前向声明是C/C++解决类型声明与定义分离的核心机制,通过允许"先声明后定义":

                1. 解决了头文件循环依赖问题

                2. 实现了接口与实现的分离

                3. 提高了编译效率和代码可维护性

C 语言允许 只声明一个结构体类型而不提供完整定义(称为“不完全类型”或“前向声明”)。typedef struct SeqList Contact; 只是告诉编译器:struct SeqList 是一个合法的类型(但暂时不知道它的具体内容)。Contact 是它的别名。编译器不需要知道 struct SeqList 的完整定义,只要知道它是一个有效的类型名即可。

4.1.2 前向声明的关键特性

特性说明示例
不完整类型编译器知道类型存在,但不知道其大小和结构struct SeqList;
延迟定义具体定义可以稍后提供Contact.h声明→SeqList.h定义
指针友好可以创建指向该类型的指针Contact* ptr;
成员不可访问不能访问结构体成员c.size = 10; // 会报错

4.1.3 为什么这种设计有效?

        单向依赖:

                SeqList.h → 需要 Contact.h(因使用 peoInfo)

                Contact.h → 不需要 SeqList.h(只需声明 struct SeqList 存在)

        编译过程:

                编译 Contact.h 时:知道 struct SeqList 是合法类型名

                编译 SeqList.h 时:已获得 peoInfo 的完整定义

                最终使用者(如 main.c)同时包含两者,获得完整信息

4.1.4 前向声明使用规则

        1. 允许的操作

                ✅ 仅需类型名(不访问成员)

                ✅ 定义类型别名:typedef struct S T;

                ✅ 函数参数/返回值:void func(struct S*);

                ✅ 声明指针/引用:struct S* p;

        2. 禁止的操作

                ❌ 实例化对象:struct S obj;

                ❌ 访问成员:p->member = 0;

                ❌ 计算大小:sizeof(struct S)

                ❌ 非指针类型成员:struct X { struct S s; };

        3. 核心原则

                只声明类型存在,不提供结构细节

                必须在使用前提供完整定义

                主要用于打破头文件循环依赖

        4. 典型用途

                解决相互依赖:A.h 和 B.h 互相引用时

                隐藏实现:接口声明指针,实现文件定义结构体

                减少编译依赖:避免不必要头文件包含

4.1.5 Contach.h 和 SeqList.h 头文件包含问题

✅ Contact.h 不需要包含 SeqList.h

        因为 typedef struct SeqList Contact; 只是声明,不涉及 struct SeqList 的成员访问。

✅ SeqList.h 必须包含 Contact.h

        因为它用到了 peoInfo 的具体定义(typedef peoInfo SLDataType;)。

4.2 通讯录结构体声明&顺序表结构体修改

1. 顺序表头文件修改(arr数组修改为存入数据的类型即可)

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include"Contact.h"//typedef int SLDataType;		// 类型重定义,方便以后修改为其他类型的数据
typedef peoInfo SLDataType;
// 动态顺序表
typedef struct SeqList
{SLDataType* arr;// 指向动态内存开辟的空间int size;		// 顺序表当前有效的数据个数int capacity;	// 动态申请的空间大小
}SL;			// 重定义结构体类型名,方便以后使用

2.通讯录结构体声明(注意前向声明,见4.1详解)

// 宏定义通讯录成员数组的最大长度
#define NAME_MAX 20
#define GENDER_MAX 10
#define TEL_MAX 20
#define ADDR_MAX 100// 通讯录结构体声明
typedef struct personInfo
{char name[NAME_MAX];char gender[GENDER_MAX];int age;char tel[TEL_MAX];char addr[ADDR_MAX];
}peoInfo;// 要用到顺序表相关的方法,对通讯录的实际操作就是对顺序表进行操作
// 给顺序表起个名字,叫做通讯录
// 前向声明
typedef struct SeqList Contact;

4.3 初始化&销毁(直接调用顺序表)

//初始化通讯录
void ContactInit(Contact* con)
{// 实际上要进行的是顺序表的初始化// 顺序表的初始化已经实现好了,直接调用即可SLInit(con);
}//销毁通讯录数据
void ContactDestroy(Contact* con)
{SLDestory(con);
}

4.4 添加联系人(直接调用顺序表)

//添加通讯录数据
void ContactAdd(Contact* con)
{peoInfo info;printf("请输入要添加的联系人姓名:");scanf("%s", info.name);printf("请输入要添加的联系人性别:");scanf("%s", info.gender);printf("请输入要添加的联系人年龄:");scanf("%d", &info.age);printf("请输入要添加的联系人电话:");scanf("%s", info.tel);printf("请输入要添加的联系人地址:");scanf("%s", info.addr);// 往通讯录中添加数据SLPushBack(con, info);printf("添加成功\n\n");
}

4.5 删除联系人

        需要确定:按照什么方式查找要删除的数据(此处按照姓名查找,字符串比较要用strcmp,调用FindByName函数找到对应姓名的下标)

        找到下标后,调用顺序表的指定位置删除函数。

int FindByName(Contact* con, char* name)
{for (int i = 0; i < con->size; i++){if (strcmp(con->arr[i].name, name) == 0)return i;}return -1;
}//删除通讯录数据
void ContactDel(Contact* con)
{// 要删除的数据必须存在才可以执行删除操作char name[NAME_MAX];printf("请输入要删除的数据的姓名:");scanf("%s", name);int find = FindByName(con, name);if (find == -1){printf("要删除的数据的姓名不存在!!\n\n");return;}SLErase(con, find);printf("删除成功!\n\n");
}

4.6 修改联系人信息

        通过FindByName()查找到要修改的联系人姓名的下标之后,直接scanf修改。

//修改通讯录数据
void ContactModify(Contact* con)
{char name[NAME_MAX];printf("请输入要修改的数据的姓名:");scanf("%s", name);int find = FindByName(con, name);if (find == -1){printf("要修改的数据的姓名不存在!!\n\n");return;}printf("请输入新的姓名:");scanf("%s", con->arr[find].name);printf("请输入新的性别:");scanf("%s", con->arr[find].gender);printf("请输入新的年龄:");scanf("%d", &con->arr[find].age);printf("请输入新的电话:");scanf("%s", con->arr[find].tel);printf("请输入新的住址:");scanf("%s", con->arr[find].addr);printf("修改成功!\n\n");
}

4.7 查找联系人

        调用FindByName函数,找到下标后打印对应的联系人信息。

//查找通讯录数据
void ContactFind(Contact* con)
{char name[NAME_MAX];printf("请输入要查找的数据的姓名:");scanf("%s", name);int find = FindByName(con, name);if (find == -1){printf("要查找的数据的姓名不存在!!\n\n");return;}// 姓名  性别  年龄  电话  地址printf("找到了!!\n");printf("%5s %5s %5s %5s %5s\n", "姓名", "性别", "年龄", "电话", "地址");printf("%4s %5s %5d %5s %5s\n\n",con->arr[find].name,con->arr[find].gender,con->arr[find].age,con->arr[find].tel,con->arr[find].addr);
}

5. 通讯录保存到文件

5.1 保存函数和加载函数

        注意,加载函数中的添加通讯录数据是用的 SLPushBack() 函数。

//保存通讯录
void ContactSave(Contact* con)
{FILE* pf = fopen("contact.txt", "wb");if (pf == NULL){perror("fopen");return;}// 将通讯录数据写入文件for (int i = 0; i < con->size; i++){// 此处用二进制写入fwrite(con->arr + i, sizeof(peoInfo), 1, pf);printf("写入第%d个数据\n", i + 1);}printf("通讯录数据保存成功!\n");
}//加载通讯录数据
void ContactLoad(Contact* con)
{FILE* pf = fopen("contact.txt", "rb");if (pf == NULL){perror("fopen");return;}peoInfo info;while (fread(&info, sizeof(peoInfo), 1, pf)){SLPushBack(con, info);}printf("历史数据导入成功\n");
}

5.2 通讯录初始化和销毁函数修改

        初始化需要读取文件中的数据,销毁前需要保存修改后的通讯录数据。

//初始化通讯录
void ContactInit(Contact* con)
{// 实际上要进行的是顺序表的初始化// 顺序表的初始化已经实现好了,直接调用即可SLInit(con);ContactLoad(con);  // 读取文件中的通讯录数据
}//销毁通讯录数据
void ContactDestroy(Contact* con)
{ContactSave(con);  // 保存修改后的通讯录数据到文件中SLDestory(con);
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MyBatis核心配置深度解析:从XML到映射的完整技术指南

&#x1f527; MyBatis核心配置深度解析&#xff1a;从XML到映射的完整技术指南 &#x1f680; 引言&#xff1a;MyBatis作为Java生态中最受欢迎的持久层框架之一&#xff0c;其强大的配置体系是实现灵活数据访问的核心。本文将深入解析MyBatis的配置文件架构、映射机制以及高级…

OpenCV HSV与RGB颜色模型的区别

HSV与RGB颜色模型的区别 HSV&#xff08;Hue, Saturation, Value&#xff09;和 RGB&#xff08;Red, Green, Blue&#xff09;是两种不同的颜色表示方式&#xff0c;主要区别如下&#xff1a;对比项RGBHSV定义基于红、绿、蓝三原色的混合基于色相&#xff08;H&#xff09;、饱…

具有柔性关节的机械臂matlab仿真

柔性关节机械臂MATLAB仿真方案&#xff0c;包含动力学建模、控制器设计和可视化分析。该方案基于拉格朗日方程建立柔性关节模型&#xff0c;并实现了PD控制、滑模控制和自适应控制三种控制策略。 MATLAB仿真 %% 柔性关节机械臂仿真 - 完整系统 % 作者: MATLAB技术助手 % 日期: …

数据结构—队列和栈

1.二级指针的使用二级指针&#xff1a; 1. 在被调函数中&#xff0c;想要修改主调函数中的指针变量&#xff0c;需要传递该指针变量的地址&#xff0c;形参用二级指针接收。 2.指针数组的数组名是一个二级指针&#xff0c;指针数组的数组名作为参数传递时&#xff0c;可用二级指…

均线:从市场脉搏到量子计算的时空密码

一部跨越百年的技术分析进化史,揭示金融市场的数学本质 引言:金融市场的永恒罗盘 在华尔街百年风云中,一个简单的数学工具始终闪耀着智慧光芒——移动平均线(Moving Average)。从杰西利弗莫尔的手绘图表到文艺复兴科技的量子模型,均线系统完成了从经验工具到科学框架的惊…

Python 通过Playwright+OpenCV破解滑动验证码 实例

由于公司最近需要对接某业务系统&#xff0c;涉及到部分数据需要提交至其它平台业务系统&#xff0c;只有其它平台账户&#xff0c;没有接口&#xff0c;因此做此开发。首先通过OpenCV计算出验证验证码滑块距离&#xff0c;根据距离&#xff0c;使用 Playwright 利用滑动距离模…

山东省天地图API申请并加载到QGIS和ArcGIS Pro中

目的&#xff1a;在QGIS/ArcGIS Pro中加载山东省不同时期的历史影像1、申请API 山东省天地图的API和国家天地图的API不通用&#xff0c;需要单独申请。 https://shandong.tianditu.gov.cn/ 打开本地服务资源找到影像的详情页 点击申请地址按照下面的步骤一步一步来&#xff0c;…

qt窗口--02

文章目录qt窗口--02QMessageBoxQColorDialogQFileDialogQFontDialogQInputDialog、结语很高兴和大家见面&#xff0c;给生活加点impetus&#xff01;&#xff01;开启今天的编程之路&#xff01;&#xff01; 作者&#xff1a;٩( ‘ω’ )و260 我的专栏&#xff1a;qt&#…

Linux seLinux

Linux seLinux 1、什么是selinux&#xff0c;security enhanced linux–安全加强的linux。 是由美国国家安全局开发的以及历史。selinux之前是基于自主存取控制方法DAC&#xff0c; 只要符合权限即可&#xff0c;通过suid和sgid特殊权限存在有一定的安全隐患&#xff0c; 甚至一…

Linux: NFS 服务部署与autofs自动挂载的配置

Linux&#xff1a; NFS 服务部署与autofs自动挂载的配置NFS&#xff08;Network File System&#xff0c;网络文件系统&#xff09;是一种基于 TCP/IP 协议的网络文件共享协议&#xff0c;允许不同主机在网络中共享文件资源&#xff0c;实现跨主机的文件访问与管理&#xff0c;…

【深度学习②】| DNN篇

0 序言 本文将系统介绍基于PyTorch的深度神经网络&#xff08;DNN&#xff09;相关知识&#xff0c;包括张量的基础操作、DNN的工作原理、实现流程&#xff0c;以及批量梯度下降、小批量梯度下降方法和手写数字识别案例。通过学习&#xff0c;你将掌握DNN的核心概念、PyTorch实…

Xcode 26 如何在创建的 App 包中添加特定的目录

功能需求 在某些情况下,我们需要将特定文件放在 Xcode 编译链接后 App 包里的指定目录中,比如将 AI 大模型相关文件放在它们对应名称的目录中: 正常情况下,Xcode 会将项目目录中的所有文件都平铺放到 App 包的根目录里。那么,要如何形成上面这种文件目录层级呢? 在本篇…

linux-系统性能监控

linux-系统性能监控一、cpu1.1 查看cpu的信息1.2 cpu性能指标1.3 编写监控cpu使用率的脚本1.4 查找出使用cpu最高的10个进程二、内存2.1 查看内存信息2.2 交换&#xff08;swap&#xff09;分区2.2.1 查看交换分区的积极程度2.2.2 查看交换分区的大小2.2.3 管理交换分区2.3 编写…

AgxOrin平台JetPack5.x版本fix multi-cam race condition 补丁

本文包含三个针对NVIDIA Linux驱动程序的补丁修复: 多摄像头竞争条件修复 在capture-ivc驱动中新增信号量机制,解决多摄像头同时操作时的竞争条件问题(Bug 4425972)。主要修改包括在通道上下文结构中添加信号量,并在通道ID通知和取消注册时进行信号量操作。 内存泄漏修复…

【Go】P3 Go语言程序结构

Go语言程序结构Go语言程序结构命名规则与编程惯例核心规则四种声明语句详解var声明&#xff1a;变量声明const声明&#xff1a;常量声明type声明&#xff1a;类型定义func声明&#xff1a;函数声明简短变量声明(:)使用规则和限制指针&#xff1a;安全的内存地址操作基本概念和操…

【机器学习深度学习】知识蒸馏实战:让小模型拥有大模型的智慧

目录 引言&#xff1a;模型压缩的迫切需求 一、知识蒸馏的核心原理 1.1 教师-学生模式 1.2 软目标&#xff1a;知识传递的关键 1.3 蒸馏损失函数 二、实战&#xff1a;Qwen模型蒸馏实现 2.1 环境配置与模型加载 2.2 蒸馏损失函数实现 2.3 蒸馏训练流程 2.4 训练优化技…

基于MCP提示构建工作流程自动化的实践指南

引言 在现代工作和生活中&#xff0c;我们经常被各种重复性任务所困扰——从每周的膳食计划到代码审查反馈&#xff0c;从文档更新到报告生成。这些任务虽然不复杂&#xff0c;却消耗了大量宝贵时间。MCP&#xff08;Model Context Protocol&#xff09;提示技术为解决这一问题…

apache-tomcat-11.0.9安装及环境变量配置

一、安装从官网上下载apache-tomcat-11.0.9,可以下载exe可执行文件版本&#xff0c;也可以下载zip版本&#xff0c;本文中下载的是zip版本。将下载的文件解压到指定目录&#xff1b;打开tomcat安装目录下“\conf\tomcat-users.xml”文件&#xff1b;输入以下代码&#xff0c;pa…

Java 大视界 -- Java 大数据机器学习模型在电商用户生命周期价值评估与客户关系精细化管理中的应用(383)

Java 大视界 -- Java 大数据机器学习模型在电商用户生命周期价值评估与客户关系精细化管理中的应用&#xff08;383&#xff09;引言&#xff1a;正文&#xff1a;一、电商用户运营的 “糊涂账”&#xff1a;不是所有客户都该被讨好1.1 运营者的 “三大错觉”1.1.1 错把 “过客…

豆包新模型与PromptPilot工具深度测评:AI应用开发的全流程突破

目录引言一、豆包新模型技术解析1.1 豆包新模型介绍1.2 核心能力突破1.2.1 情感交互能力1.2.2 推理与编码能力二、PromptPilot工具深度测评2.1 PromptPilot介绍2.2 工具架构与核心功能2.3 一个案例讲通&#xff1a;市场调研报告2.3.1 生成Prompt2.3.2 批量集生成2.3.3 模拟数据…