探索C语言数据结构:利用顺序表完成通讯录的实现

简介: 本文介绍了如何使用C语言中的顺序表数据结构实现一个简单的通讯录,包括初始化、添加、删除、查找和保存联系人信息的操作,以及自定义结构体用于存储联系人详细信息。

在好久之前我就已经学习过顺序表,但是在前几天再次温习顺序表的时候,我惊奇的发现顺序编表可以完成我们日常使用的通讯录的功能,那么今天就来好好通过博客总结一下通讯录如何完成吧。

顺序表


了解过顺序表的就可以跟着往下看看如何使用顺序表来实现通讯录,下面先给大家展示一下顺表的C语言代码,不然等下就对通讯录的一些知识点给搞迷糊了。

顺序表是我们对数据进行管理的一种简单结构,他的底层就是数组,这里我们对数组进行封装就形成了我们的通讯录。

顺序表的头文件 "SL.h"

#define _CRT_SECURE_NO_WARNINGS 1
 
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
#include"Contact.h"
 
typedef struct Contact Datetype;
 
struct SEQlist
{
  Datetype*  a;
  int size;
  int capacity;
 
};
 
 
 
 
 
typedef struct SEQlist SL;
void Init(SL* s);
void Print(SL* s);
void pushFront(SL* s, Datetype x);
 
void INti(SL* s);
void tCheckCapacity(SL* s);
void SeqListPushBack(SL* s, Datetype x);
void SeqListPopBack(SL* s);
void SeqListPushFront(SL* s, Datetype x);
void SeqListPopFront(SL s);
void SeqListDestory(SL* s);
void SeqListinsert(SL* s, int pos,Datetype x);
void SeqListdel(SL* s, int pos);

顺序表的“.c”文件SL.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SL.h"
 
//void Print(SL* s)
//{
//  for (int i = 0; i < s->size; i++)
//  {
//    printf("%d ", s->a[i]);
//  }
//  printf("\n");
//}
 
 
 
 
void Init(SL* s)
{
 
  s->a = NULL;
  s->size = 0;         // 表示数组中存储了多少个数据
  s->capacity = 0;   数组实际能存数据的空间容量是多大 
 
 
}
 
void CheckCapacity(SL* s)
{
  // 如果没有空间或者空间不足,那么我们就扩容
  if (s->size == s->capacity)
  {
    int newcapacity = s->capacity == 0 ? 4 : s->capacity * 2;
    Datetype* tmp = (Datetype*)realloc(s->a, newcapacity * sizeof(Datetype));
    if (tmp == NULL)
    {
      printf("realloc 失败\n");
      exit(-1);
    }
 
    s->a = tmp;
    s->capacity = newcapacity;
  }
}
 
 
void SeqListPushBack(SL* s, Datetype x)
{
  CheckCapacity(s);
 
  s->a[s->size] = x;
  s->size++;
}
 
void SeqListPopBack(SL* s)
{
 
  // 暴力处理方式
  assert(s->size > 0);
  s->size--;
}
 
 
void SeqListPushFront(SL* s, Datetype x)
{
  CheckCapacity(s);
 
  // 挪动数据
  int end = s->size - 1;
  while (end >= 0)
  {
    s->a[end + 1] = s->a[end];
    --end;
  }
  s->a[0] = x;
  s->size++;
}
 
void SeqListPopFront(SL* s)
{
  assert(s->size > 0);
 
  // 挪动数据
  int begin = 1;
  while (begin < s->size)
  {
    s->a[begin - 1] = s->a[begin];
    ++begin;
  }
 
  s->size--;
}
 
 
void SeqListDestory(SL* s)
{
  free(s->a);
  s->a = NULL;
  s->capacity = s->size = 0;
}
 
 
void SeqListinsert(SL* s,int pos, Datetype x)
{
 
  assert(s);
  assert(pos >= 0 && pos<=s->size);
  CheckCapacity(s);
 
  for (int i = s->size; i > pos; i--)
  {
    s->a[i] = s->a[i - 1];
 
  }
  s->a[pos] = x;
  s->size++;
 
}
 
 
void SeqListdel(SL* s, int pos)
{
 
  assert(s);
  assert(pos >= 0 && pos < s->size);
 
  for (int i = pos; i < s->size-1; i++)
  {
    s->a[i] = s->a[i + 1];
  }
  
  
  s->size--;
 
}


上面的两段代码包含了顺序表的,格式化,销毁,头插,尾插,头删,尾删,指定位置插入。


通讯录的实现

首先我们要知道通讯录里有什么?我们当然知道里面有联系人的姓名,性别,年龄,电话,地址。


我们假设这个通讯录就储存这么些信息,那我们这些信息是不是我们所说的数据,前面我们往顺序表里储存的是整形数据那么现在我们数据类型改为这些信息不就好了,可能有人就会想这些信息有那么多,那我们的数据变量要一个一个建立出来吗?当然不是,代码要求的是简便,既然这些信息可能是不同数据类型,而且还不少,那么我们就可以用一个自定义类型-----结构体来作为数据类型的变量,然后结构体内部就可以储存这些信息,这样一来就简单多了。如下图所示:


有了大概的思路,然后就开始封装顺序表,然后就让它成为一个成熟的通讯录。


创建通讯录的信息结构体和通讯录节点

具体思路:首先我们在建立两个文件一个“Contact.c”,“Contact.h”文件,其中在“Contact.h”文件里定义好储存联系人信息的结构体和声明函数和头文件,


“Contact.h”

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#define MAX_NAME 18
#define MAX_TEL 18
#define MAX_GENDER  10
#define MAX_ADDR 100
 
 
 
typedef struct Contact
{
  char name[MAX_NAME];
  char gender[MAX_GENDER];
  int age;
  char tel[MAX_TEL];
  char addr[MAX_ADDR];
 
}con;
 
typedef struct SEQlist Ct;
 
//通讯录的初始化
void ContactInit(Ct* pf);
 
//通讯录的销毁
void ContactDestory(Ct* pf);
 
//通讯录数据的添加
void ContactAdd(Ct* pf);
 
//通讯录数据的删除
void ContactDel(Ct* pf,char arr[]);
 
//通讯录的展示
void ContactShow(Ct* pf);
 
//通讯录的查找
void ContactFind(Ct* pf,char arr[]);


这里我们加了一个typedef struct SEQLIST Ct;是为了让人一眼看出这是通讯录而不是一个普通的顺序表,也让我们能够好理解一些。

以及在“Contact.c”文件里写下完成通讯录的一些基本功能的代码。

那么我们可以写的通讯录功能有哪些呢?

通讯录的格式化

void ContactInit(Ct* pf)
{
  Init(pf);
 
}
void Init(SL* s)
{
 
  s->a = NULL;
  s->size = 0;         // 表示数组中存储了多少个数据
  s->capacity = 0;   数组实际能存数据的空间容量是多大 
 
 
}

其实这里的格式化就是将顺序表给格式化,会先将结构体数组a给赋为NULL,然后将容量

(int capacity)和有效数据数目(int size)置为0

通讯录的销毁

void ContactDestory(Ct* pf)
{
 
  SeqListDestory(pf);
 
}
void SeqListDestory(SL* s)
{
  free(s->a);
  s->a = NULL;
  s->capacity = s->size = 0;
}

通讯录的销毁也是一样的,直接调用链表的销毁就好了

通讯录数据的添加

void ContactAdd(Ct* pf)
{
 
  assert(pf);
  struct Contact a;
  printf("请输入你要添加联系人的姓名\n");
  scanf("%s", a.name);
 
  printf("请输入你要添加联系人的性别\n");
  scanf("%s", a.gender);
 
  printf("请输入你要添加联系人的年龄\n");
  scanf("%d", &(a.age));
 
  printf("请输入你要添加联系人的电话\n");
  scanf("%s", a.tel);
 
  printf("请输入你要添加联系人的地址\n");
  scanf("%s", a.addr);
 
  //往联系人里添加数据
   SeqListPushBack(pf, a);
 
}


void SeqListPushBack(SL* s, Datetype x)
{
  CheckCapacity(s);
 
  s->a[s->size] = x;
  s->size++;
}

因为要添加数据,那我们首先就需要获取这个数据,这里我们运用scanf来进行获取数据放到我们用来储存数据的结构体(struct Contact)中,然后再运用链表的尾插功能,将数据添加到我们的顺序表中。


通讯录数据的删除

我们要删除通讯录里的联系人,首先就得核对通讯录里是否有我们操作者想要删除的人,所以这里我们在写一个函数来进行找人,如果找到此人则返回此人在数组a的下标。,没有则打印“未找到该联系人”,并返回-1;然后在ContactDel中进行一下判断是否为-1,如果不为-1,就执行链表中的指定位置删除

//找到某位联系人的位置
int Findname(Ct* pf,char arr[])
{
  assert(pf);
  int i = 0;
  for (i = 0; i < pf->size; i++)
  {
    if (strcmp(arr, pf->a[i].name) == 0)
    {
      return i;
 
    }
 
  }
  printf("未找到该联系人\n");
 
 
}
//删除联系人
void ContactDel(Ct* pf)
{
  assert(pf);
  int arr[MAX_NAME];
  scanf("%s", arr);
  printf("请输入你要删除的联系人: ");
 
  int find=Findname(pf,arr);
  if (find != -1)
  {
    SeqListdel(pf, find);
  }
 
  
}
void SeqListdel(SL* s, int pos)
{
 
  assert(s);
  assert(pos >= 0 && pos < s->size);
 
  for (int i = pos; i < s->size-1; i++)
  {
    s->a[i] = s->a[i + 1];
  }
  
  
  s->size--;
 
}

通讯录的查找

这里我们scanf输入想要查找的联系人,调用查找函数返回得到该联系人的下标,之后再打印出来即可。

//找到某位联系人的位置
int Findname(Ct* pf,char arr[])
{
  assert(pf);
  int i = 0;
  for (i = 0; i < pf->size; i++)
  {
    if (strcmp(arr, pf->a[i].name) == 0)
    {
      return i;
 
    }
 
  }
  printf("未找到该联系人\n");
 
 
}
void ContactFind(Ct* pf)
{
  assert(pf);
  char arr[MAX_NAME];
  printf("请输入你要查找的联系人: ");
  scanf("%s", arr);
  int find=Findname(pf,arr);
 
  if (find != -1)
  {
    printf("找到该联系人了,其信息如下:\n");
    printf("姓名    性别     年龄     电话       地址\n");
    printf("%s      %s       %d        %s         %s",
    pf->a[find].name, pf->a[find].gender, pf->a[find].age, pf->a[find].tel, pf->a[find].addr);
 
  }

通讯录的展示

这里就是遍历顺序表,将每个数据给打印出来,只要注意打印格式即可,想要美观可以调好打印的距离。

 
//通讯录的展示
void ContactShow(Ct* pf)
{
 
  assert(pf);
  int i = 0;
 
  for (i = 0; i < (pf->size); i++)
  {
    printf("姓名    性别     年龄     电话       地址\n");
    printf("%s      %s       %d        %s         %s\n",
      pf->a[i].name, pf->a[i].gender, pf->a[i].age, pf->a[i].tel, pf->a[i].addr);
 
 
  }
 
 
}

可能有人会问,我储存的数据程序一结束,数据就不见了,这还叫通讯录吗?这样确实显得这个通讯录有点鸡肋,所以我们现在就可以再写一个函数,将数据放到文本文件中进行储存

void SaveContact(Ct* con) {
 FILE* pf = fopen("contact.txt", "wb");
 if (pf == NULL) {
 perror("fopen error!\n");
 return;
 }
 //将通讯录数据写⼊⽂件
 for (int i = 0; i < con->size; i++)
 {
 fwrite(con->a + i, sizeof(PeoInfo), 1, pf);
 }
 printf("通讯录数据保存成功!\n");
}


当然这里是以二进制文件储存的。

这些功能都是在“Contact.c”里来完成的。

目录
相关文章
|
24天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
114 9
|
23天前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
61 16
|
23天前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
78 8
|
26天前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
51 4
|
27天前
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之顺序表【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找等具体详解步骤以及举例说明
|
27天前
|
存储 C语言
【数据结构】顺序表(c语言实现)(附源码)
本文介绍了线性表和顺序表的基本概念及其实现。线性表是一种有限序列,常见的线性表有顺序表、链表、栈、队列等。顺序表是一种基于连续内存地址存储数据的数据结构,其底层逻辑是数组。文章详细讲解了静态顺序表和动态顺序表的区别,并重点介绍了动态顺序表的实现,包括初始化、销毁、打印、增删查改等操作。最后,文章总结了顺序表的时间复杂度和局限性,并预告了后续关于链表的内容。
56 3
|
27天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之顺序表习题精讲【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找习题精讲等具体详解步骤以及举例说明
|
26天前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
40 0
|
2月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
35 3
|
16天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
30 6