通讯录实现

实现通讯录可以采用顺序表,单链表,双链表等数据结构实现,所以我们需要先写出顺序表,单链表,双链表的代码实现。

顺序表:

定义:顺序表(Sequence List)是一种线性表的存储结构,它采用一组地址连续的存储单元依次存储线性表的数据元素。顺序表中的元素在内存中是连续存储的,通过元素的下标可以直接访问到对应的元素,因此支持随机访问。常见的顺序表实现包括数组。

个人理解:

当你想象一张书架时,你其实已经在思考顺序表了!想象一下,书架上排列着一排排的书籍,每本书都有一个唯一的编号(索引),你可以通过这个编号快速找到并拿起任何一本书。这种布局让你能够轻松地查找特定的书籍,就像顺序表中通过索引能够快速访问特定位置的元素一样。

在这个比喻中:

  • 书架 就相当于顺序表的存储空间,它是由一系列连续的存储单元(书架上的位置)组成的。
  • 每本书 对应于顺序表中的一个元素,它们依次排列在书架上。
  • 书的编号 就相当于顺序表中元素的索引,它能够唯一标识每一本书(或元素)的位置。

顺序表通常包含以下几个要素:

  1. 数据存储区域(数组): 顺序表的主体是一个一维数组,用于存储线性表的元素。数组的大小通常是固定的,但某些情况下也可以动态扩展。

  2. 长度(Length): 顺序表中元素的个数,即表的长度。长度通常作为一个属性存储在顺序表的控制信息中。

  3. 容量(Capacity): 数组的大小,即数组能够容纳的元素个数。当元素数量达到容量上限时,如果需要插入更多的元素,可能需要进行扩容操作。

  4. 控制信息: 包括表的长度和数组的容量等信息,用于管理顺序表的操作。

顺序表的优点包括:

  • 支持随机访问: 由于元素在内存中是连续存储的,可以通过下标直接访问到指定位置的元素,因此支持高效的随机访问。
  • 空间利用率高: 顺序表只需要额外存储控制信息,数据元素本身在内存中是连续存储的,不需要额外的指针来建立关联关系,因此空间利用率相对较高。
  • 操作简单: 插入和删除操作可能需要移动元素,但是在大多数情况下,这些操作的时间复杂度为 O(n),操作相对简单。

但是顺序表也存在一些缺点:

  • 插入和删除操作的效率低: 当需要在中间位置插入或删除元素时,需要移动大量元素,导致时间复杂度为 O(n)。
  • 容量固定: 顺序表的容量通常是固定的,如果元素数量超过了容量上限,可能需要进行扩容操作,这会带来额外的开销。

总的来说,顺序表适用于元素数量相对固定,需要频繁进行随机访问的场景,但不适用于频繁插入和删除元素的场景。

对于使用 C 语言实现顺序表,以下是一种基本的思路:

  1. 定义结构体:首先,你需要定义一个结构体来表示顺序表。这个结构体应该包含一个数组用于存储数据,以及一个变量来表示当前数组中元素的数量。

  2. 初始化:编写一个初始化函数,用于创建一个空的顺序表。在初始化函数中,将顺序表的长度设置为0。

  3. 插入元素:实现一个插入函数,用于向顺序表中插入元素。插入元素时,需要考虑插入位置的合法性,如果位置合法,则将插入位置后的所有元素依次向后移动一位,然后将新元素插入到指定位置。

  4. 删除元素:编写一个删除函数,用于从顺序表中删除指定位置的元素。删除元素时,同样需要考虑删除位置的合法性,如果位置合法,则将删除位置后的所有元素向前移动一位。

  5. 访问元素:如果需要,可以编写一个函数来访问顺序表中指定位置的元素。

  6. 打印顺序表:编写一个函数,用于将顺序表中的所有元素打印出来,方便查看顺序表的内容。

  7. 释放资源:如果需要动态分配内存,则需要编写一个函数来释放顺序表占用的内存。

  8. 主函数:在主函数中调用上述函数来测试顺序表的各种操作,确保它们能够正常工作。

代码实现:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

#define MAX_SIZE 100 // 定义顺序表的最大容量

// 定义顺序表结构体
typedef struct {
    int data[MAX_SIZE]; // 存储元素的数组
    int length;         // 当前顺序表的长度
} SeqList;

// 初始化顺序表
void init(SeqList *list) {
    list->length = 0; // 初始长度为0
}

// 在指定位置插入元素
int insert(SeqList *list, int index, int element) {
    // 判断插入位置的合法性
    if (index < 0 || index > list->length || list->length == MAX_SIZE) {
        return 0; // 插入失败
    }

    // 将插入位置后的所有元素后移一位
    for (int i = list->length; i > index; i--) {
        list->data[i] = list->data[i - 1];
    }

    // 在插入位置处放入新元素
    list->data[index] = element;
    list->length++; // 长度加1
    return 1; // 插入成功
}

// 删除指定位置的元素
int delete(SeqList *list, int index) {
    // 判断删除位置的合法性
    if (index < 0 || index >= list->length) {
        return 0; // 删除失败
    }

    // 将删除位置后的所有元素前移一位
    for (int i = index; i < list->length - 1; i++) {
        list->data[i] = list->data[i + 1];
    }

    list->length--; // 长度减1
    return 1; // 删除成功
}

// 输出顺序表中的所有元素
void display(SeqList *list) {
    printf("顺序表中的元素为:");
    for (int i = 0; i < list->length; i++) {
        printf("%d ", list->data[i]);
    }
    printf("\n");
}

int main() {
    SeqList list;
    init(&list); // 初始化顺序表

    // 插入元素
    insert(&list, 0, 1);
    insert(&list, 1, 2);
    insert(&list, 2, 3);

    // 输出顺序表中的元素
    display(&list);

    // 删除元素
    delete(&list, 1);

    // 再次输出顺序表中的元素
    display(&list);

    return 0;
}

单链表:

单链表(Singly Linked List)是一种基本的链式数据结构,由一系列节点组成,每个节点包含两部分:数据域和指针域。数据域用于存储节点的数据,指针域用于指向下一个节点,从而将各个节点连接在一起形成链表。下面详细解释单链表的定义、性质、优缺点等:

定义:

单链表由节点组成,每个节点包含两个字段:数据域和指针域。数据域用于存储节点的数据,指针域用于指向下一个节点。单链表的最后一个节点的指针域通常指向空(NULL),表示链表的结束。

个人理解:

用一个简单的图示来比喻单链表:

假设你有一本书,每一页都是一个节点,每一页的最后都有一个箭头指向下一页。这本书的第一页就是链表的头节点,而最后一页则指向空白的页面(表示链表结束)。

cpp 复制代码
Book Pages --> Node 1 --> Node 2 --> Node 3 --> ... --> Node n --> NULL

其中,箭头表示指针,指向下一个节点,最后一个节点指向 NULL,表示链表结束。

这个比喻可以让你更直观地理解单链表的结构:每个节点都包含一些信息(就像书页上的内容),并且通过指针与下一个节点相连,形成一个链条。

性质:

  1. 动态大小:单链表的大小可以动态地增加或减少,根据需要灵活分配内存。

  2. 插入和删除高效:在单链表中,插入和删除节点的时间复杂度为 O(1),只需修改指针即可,不需要移动其他节点。

  3. 不连续存储:单链表中的节点在内存中不必连续存储,因此可以灵活地利用内存空间。

  4. 顺序访问:单链表只能顺序访问,不能像数组一样随机访问,需要从头节点开始逐个访问直到目标节点。

优点:

  1. 动态内存分配:单链表的大小可以动态调整,根据需要灵活地分配和释放内存,节约内存空间。

  2. 高效的插入和删除操作:在单链表中,插入和删除节点的时间复杂度为 O(1),只需修改指针即可,效率高。

  3. 不需要预先分配内存空间:与数组不同,单链表不需要预先分配内存空间,可以根据需要动态地分配内存。

缺点:

  1. 无法随机访问:单链表只能顺序访问,不能像数组一样根据索引随机访问元素,查找效率较低。

  2. 占用额外空间:每个节点都需要额外的指针域来指向下一个节点,占用了额外的存储空间。

  3. 查找节点效率低:查找特定节点需要从头节点开始逐个访问,效率较低,尤其是对于大型链表。

  4. 不利于缓存性能:单链表的节点在内存中存储不连续,可能导致缓存性能较差,增加了访问时间。

总的来说,单链表适用于需要频繁执行插入和删除操作,且对内存动态分配要求较高的情况。它的灵活性和高效的插入、删除操作使其在某些场景下优于数组。

思路:

  1. 节点结构体定义 :首先定义了一个节点结构体 struct Node,每个节点包含一个整数数据 data 和一个指向下一个节点的指针 next

  2. 节点插入功能

    • append() 函数用于在链表末尾插入新节点。
    • prepend() 函数用于在链表头部插入新节点。
  3. 节点删除功能deleteNode() 函数用于删除指定数据的节点。

  4. 节点查找功能search() 函数用于查找包含指定数据的节点。

  5. 节点数据修改功能modifyNode() 函数用于修改节点中的数据。

  6. 链表打印功能printList() 函数用于打印链表的所有节点数据。

  7. 主函数 :在 main() 函数中,初始化了一个空链表,然后依次进行了如下操作:

    • 在链表末尾插入了数据 1、2、3;
    • 在链表头部插入了数据 0;
    • 删除了数据为 2 的节点;
    • 修改了数据为 3 的节点的数据为 4;
    • 最后打印了链表的所有节点数据。

代码实现:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

// 定义节点结构体
struct Node {
    int data;           // 节点数据
    struct Node* next;  // 指向下一个节点的指针
};

// 在链表末尾插入新节点
void append(struct Node** head_ref, int new_data) {
    // 分配新节点的内存
    struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
    if (new_node == NULL) {
        printf("内存分配失败\n");
        return;
    }

    // 将数据赋值给新节点
    new_node->data = new_data;
    new_node->next = NULL;

    // 如果链表为空,则将新节点设为头节点
    if (*head_ref == NULL) {
        *head_ref = new_node;
        return;
    }

    // 找到链表的最后一个节点
    struct Node* last = *head_ref;
    while (last->next != NULL) {
        last = last->next;
    }

    // 将新节点连接到最后一个节点
    last->next = new_node;
}

// 在链表头部插入新节点
void prepend(struct Node** head_ref, int new_data) {
    // 分配新节点的内存
    struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
    if (new_node == NULL) {
        printf("内存分配失败\n");
        return;
    }

    // 将数据赋值给新节点
    new_node->data = new_data;

    // 将新节点插入链表头部
    new_node->next = *head_ref;
    *head_ref = new_node;
}

// 删除指定数据的节点
void deleteNode(struct Node** head_ref, int key) {
    // 如果链表为空,直接返回
    if (*head_ref == NULL) {
        return;
    }

    // 如果要删除的节点是头节点
    if ((*head_ref)->data == key) {
        struct Node* temp = *head_ref;
        *head_ref = (*head_ref)->next;
        free(temp);
        return;
    }

    // 找到要删除节点的前一个节点
    struct Node* prev = *head_ref;
    while (prev->next != NULL && prev->next->data != key) {
        prev = prev->next;
    }

    // 如果找不到要删除的节点,则直接返回
    if (prev->next == NULL) {
        return;
    }

    // 将要删除的节点从链表中移除
    struct Node* temp = prev->next;
    prev->next = prev->next->next;
    free(temp);
}

// 查找节点
struct Node* search(struct Node* head, int key) {
    struct Node* current = head;
    while (current != NULL) {
        if (current->data == key) {
            return current;
        }
        current = current->next;
    }
    return NULL;
}

// 修改节点数据
void modifyNode(struct Node* head, int old_data, int new_data) {
    struct Node* node = search(head, old_data);
    if (node != NULL) {
        node->data = new_data;
    }
}

// 打印链表
void printList(struct Node* node) {
    while (node != NULL) {
        printf("%d ", node->data);
        node = node->next;
    }
    printf("\n");
}

int main() {
    // 初始化空链表
    struct Node* head = NULL;

    // 在链表末尾插入节点
    append(&head, 1);
    append(&head, 2);
    append(&head, 3);

    // 在链表头部插入节点
    prepend(&head, 0);

    // 删除节点
    deleteNode(&head, 2);

    // 修改节点数据
    modifyNode(head, 3, 4);

    // 打印链表
    printf("链表内容:");
    printList(head);

    return 0;
}

双链表:

双链表是一种链表数据结构,与单链表不同的是,每个节点除了包含指向下一个节点的指针外,还包含指向前一个节点的指针。这使得双链表可以从头到尾或者从尾到头遍历链表,而不需要像单链表那样从头开始遍历。以下是双链表的结构及其操作:

结构定义:

cpp 复制代码
struct Node { int data; struct Node* next; struct Node* prev; };

基本操作:

  1. 节点插入

    • 头部插入:将新节点插入到链表头部。
    • 尾部插入:将新节点插入到链表尾部。
    • 任意位置插入:在指定位置后面插入新节点。
  2. 节点删除

    • 按值删除:删除具有特定值的节点。
    • 按位置删除:删除链表中指定位置的节点。
  3. 节点查找

    • 按值查找:查找具有特定值的节点。
    • 按位置查找:查找链表中指定位置的节点。
  4. 遍历

    • 正向遍历:从头到尾遍历链表。
    • 反向遍历:从尾到头遍历链表。
  5. 修改节点数据:修改链表中指定节点的数据。

双链表优点:

  • 双向遍历:可以从头到尾或者从尾到头遍历链表,而单链表只能从头到尾遍历。
  • 节点删除效率高:删除节点时,可以直接通过节点的前一个指针找到前一个节点,不需要从头开始遍历链表找到前一个节点。

双链表缺点:

  • 占用更多空间:每个节点需要额外存储指向前一个节点的指针,相比单链表会占用更多的内存空间。
  • 操作复杂度高:双链表的操作相对复杂,需要维护额外的指针。

实现双链表的关键在于正确维护节点之间的指针关系,在插入和删除操作时要注意更新前后节点的指针,以确保链表结构的正确性。

思路:

  1. 初始化链表 :使用 initializeList() 函数来创建一个空链表,返回指向链表头部的指针。

  2. 在链表头部插入新节点 :使用 insertAtBeginning() 函数在链表的开头插入新节点。这个函数会创建一个新节点,并将其链接到当前头部节点之前,然后更新头部指针。

  3. 在链表尾部插入新节点 :使用 insertAtEnd() 函数在链表的末尾插入新节点。这个函数会遍历链表直到找到最后一个节点,然后在其后插入新节点。

  4. 查找节点 :使用 searchNode() 函数根据给定的关键字在链表中查找节点。这个函数会遍历链表,找到匹配的节点并返回指向该节点的指针。

  5. 修改节点值 :使用 modifyNode() 函数根据给定的旧数据值修改节点的值为新数据值。这个函数会先调用 searchNode() 找到要修改的节点,然后更新其数据值。

  6. 删除节点 :使用 deleteNode() 函数删除指定的节点。这个函数会先判断链表是否为空或者要删除的节点是否为 NULL,然后分情况处理要删除的节点,最后释放其内存。

  7. 打印链表 :使用 printList() 函数遍历链表并打印其中的节点数据值。

  8. 释放链表内存 :使用 freeList() 函数释放链表所占用的内存,防止内存泄漏。

代码:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

// 定义双链表节点结构体
struct Node {
    int data;
    struct Node* next;
    struct Node* prev;
};

// 初始化双链表为空链表
struct Node* initializeList() {
    return NULL;
}

// 在双链表头部插入新节点
struct Node* insertAtBeginning(struct Node* head, int data) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->data = data;
    newNode->prev = NULL;
    newNode->next = head;

    if (head != NULL) {
        head->prev = newNode;
    }

    return newNode;
}

// 在双链表尾部插入新节点
struct Node* insertAtEnd(struct Node* head, int data) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    struct Node* temp = head;

    newNode->data = data;
    newNode->next = NULL;

    if (head == NULL) {
        newNode->prev = NULL;
        return newNode;
    }

    while (temp->next != NULL) {
        temp = temp->next;
    }

    temp->next = newNode;
    newNode->prev = temp;

    return head;
}

// 查找节点
struct Node* searchNode(struct Node* head, int key) {
    struct Node* current = head;

    while (current != NULL) {
        if (current->data == key) {
            return current;
        }
        current = current->next;
    }

    return NULL; // 没有找到
}

// 修改节点值
void modifyNode(struct Node* head, int oldData, int newData) {
    struct Node* nodeToModify = searchNode(head, oldData);
    if (nodeToModify != NULL) {
        nodeToModify->data = newData;
    } else {
        printf("Node with value %d not found\n", oldData);
    }
}

// 删除指定节点
struct Node* deleteNode(struct Node* head, struct Node* delNode) {
    if (head == NULL || delNode == NULL) {
        return head;
    }

    if (head == delNode) {
        head = delNode->next;
    }

    if (delNode->next != NULL) {
        delNode->next->prev = delNode->prev;
    }

    if (delNode->prev != NULL) {
        delNode->prev->next = delNode->next;
    }

    free(delNode);
    return head;
}

// 打印双链表
void printList(struct Node* head) {
    struct Node* temp = head;

    while (temp != NULL) {
        printf("%d ", temp->data);
        temp = temp->next;
    }

    printf("\n");
}

// 释放双链表内存
void freeList(struct Node* head) {
    struct Node* temp;

    while (head != NULL) {
        temp = head;
        head = head->next;
        free(temp);
    }
}

int main() {
    struct Node* head = initializeList();

    head = insertAtEnd(head, 1);
    head = insertAtEnd(head, 2);
    head = insertAtEnd(head, 3);

    printf("Original List: ");
    printList(head);

    modifyNode(head, 2, 5);
    printf("List after modifying node with value 2 to 5: ");
    printList(head);

    struct Node* searchResult = searchNode(head, 3);
    if (searchResult != NULL) {
        printf("Node with value 3 found\n");
    } else {
        printf("Node with value 3 not found\n");
    }

    head = deleteNode(head, head->next);
    printf("List after deleting second node: ");
    printList(head);

    freeList(head); // 释放链表内存

    return 0;
}

在这里我想再添加一些文件操作来实现设置密码和通讯录数据储存的功能,既能增加安全性也能提高实用性。

文件操作:

文件操作是指在计算机中对文件进行读取、写入和修改等操作的一系列行为。在 C 语言中,文件操作主要通过标准库中的 <stdio.h> 头文件中提供的函数来完成。

以下是一些常见的文件操作函数及其作用:

  1. fopen()

    • 作用:打开文件。
    • 原型:FILE *fopen(const char *filename, const char *mode);
    • 参数:
      • filename:要打开的文件的路径。
      • mode:打开文件的模式,包括读取("r")、写入("w")、追加("a")等。详细模式可参考文档。
    • 返回值:成功时返回文件指针,失败时返回 NULL
  2. fclose()

    • 作用:关闭文件。
    • 原型:int fclose(FILE *stream);
    • 参数:要关闭的文件指针。
    • 返回值:成功时返回 0,失败时返回 EOF
  3. fprintf()

    • 作用:向文件中写入格式化数据。
    • 原型:int fprintf(FILE *stream, const char *format, ...);
    • 参数:文件指针、格式化字符串及对应的数据。
    • 返回值:成功写入的字符数。
  4. fscanf()

    • 作用:从文件中读取格式化数据。
    • 原型:int fscanf(FILE *stream, const char *format, ...);
    • 参数:文件指针、格式化字符串及对应的变量地址。
    • 返回值:成功读取的数据项数。
  5. fgets()

    • 作用:从文件中读取一行数据。
    • 原型:char *fgets(char *str, int n, FILE *stream);
    • 参数:字符数组、最大读取字符数(包括空字符)、文件指针。
    • 返回值:成功时返回 str,失败时返回 NULL
  6. fputs()

    • 作用:向文件中写入字符串。
    • 原型:int fputs(const char *str, FILE *stream);
    • 参数:要写入的字符串、文件指针。
    • 返回值:成功时返回非负数,失败时返回 EOF
  7. fread()fwrite()

    • 作用:分别用于二进制文件的读取和写入。
    • 原型:
      • size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
      • size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
    • 参数:数据缓冲区指针、每个数据项的大小、数据项的数量、文件指针。
    • 返回值:成功读取或写入的数据项数量。
  8. feof()

    • 作用:检查文件是否到达文件末尾。
    • 原型:int feof(FILE *stream);
    • 参数:文件指针。
    • 返回值:如果到达文件末尾则返回非零值,否则返回 0。

设置密码:

要通过文件操作来实现设置密码的功能,可以将密码存储在一个文件中,并提供相应的功能来读取和更新密码。以下是一个简单的示例代码,演示了如何实现这一功能:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_LENGTH 50

// 读取密码文件中的密码
char* readPasswordFromFile(const char* filename) {
    FILE* file = fopen(filename, "r");
    if (file == NULL) {
        perror("Error opening file");
        exit(EXIT_FAILURE);
    }

    char* password = (char*)malloc(MAX_LENGTH * sizeof(char));
    if (fgets(password, MAX_LENGTH, file) == NULL) {
        perror("Error reading password");
        exit(EXIT_FAILURE);
    }

    fclose(file);
    return password;
}

// 更新密码文件中的密码
void updatePasswordInFile(const char* filename, const char* newPassword) {
    FILE* file = fopen(filename, "w");
    if (file == NULL) {
        perror("Error opening file");
        exit(EXIT_FAILURE);
    }

    fprintf(file, "%s", newPassword);

    fclose(file);
}

int main() {
    const char* filename = "password.txt";

    // 读取密码
    char* password = readPasswordFromFile(filename);
    printf("Current Password: %s", password);

    // 设置新密码
    char newPassword[MAX_LENGTH];
    printf("Enter new password: ");
    fgets(newPassword, MAX_LENGTH, stdin);
    newPassword[strcspn(newPassword, "\n")] = '\0'; // 移除换行符

    // 更新密码文件中的密码
    updatePasswordInFile(filename, newPassword);
    printf("Password updated successfully.\n");

    // 释放内存
    free(password);

    return 0;
}

数据储存:

要实现内容存储的功能,你可以通过文件操作来创建、读取、写入和修改文件中的内容。以下是一个简单的示例,演示了如何使用文件操作来实现内容存储的功能:

cpp 复制代码
#include <stdio.h>

#define MAX_LEN 100 // 假设内容的最大长度为 100

// 函数声明
void saveContent(const char *filename, const char *content);
void readContent(const char *filename);

int main() {
    const char *filename = "content.txt"; // 文件名
    const char *content = "这是要保存的内容。"; // 要保存的内容

    // 保存内容到文件
    saveContent(filename, content);

    // 从文件中读取内容并显示
    readContent(filename);

    return 0;
}

// 保存内容到文件
void saveContent(const char *filename, const char *content) {
    FILE *file = fopen(filename, "w"); // 以写入模式打开文件
    if (file == NULL) {
        printf("无法打开文件 %s\n", filename);
        return;
    }

    // 写入内容到文件
    fputs(content, file);

    // 关闭文件
    fclose(file);

    printf("内容已保存到文件 %s\n", filename);
}

// 从文件中读取内容并显示
void readContent(const char *filename) {
    FILE *file = fopen(filename, "r"); // 以读取模式打开文件
    if (file == NULL) {
        printf("无法打开文件 %s\n", filename);
        return;
    }

    char buffer[MAX_LEN]; // 缓冲区
    // 从文件中读取内容,并逐行显示
    while (fgets(buffer, MAX_LEN, file) != NULL) {
        printf("%s", buffer);
    }

    // 关闭文件
    fclose(file);
}

通讯录的代码实现:

接下是我以顺序表为例写出的通讯录代码:

头文件1:

cpp 复制代码
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include"con.h"
#include<string.h>

typedef pn seq;

typedef struct sequce
{
	seq* arr;
	int size;
	int cap;
}sl;

void Sequce_set(sl* s);//初始化顺序表
void Sequce_front(sl* s, seq x);//前插
void Print(sl s);//打印顺序表
void En(sl* s);//动态内存实现
void Sequce_back(sl* s, seq x);//尾插
void Sequce_popback(sl* s);//尾删
void Sequce_popfront(sl* s);//首删
void N_ull(sl* s);//判断空指针
void Zd_set(sl* s, int n, seq x);//指定位置插入
void Zd_del(sl* s, int n);//指定位置删除
void des(sl* s);//内存销毁

头文件2:

cpp 复制代码
#pragma once

#define NAME_MAX 20
#define SAX_MAX 10
#define PH_MAX 20
#define AD_MAX 20
#define PASSWORD_MAX 20

//姓名 性别 年龄 电话 地址
typedef struct contact {
	char name[NAME_MAX];
	char sex[SAX_MAX];
	int age;
	char ph[PH_MAX];
	char ad[AD_MAX];
}pn;

typedef struct sequce Contact;
void Con_set(Contact* con);//初始化
void Con_des(Contact* con);//销毁
void Con_fset(Contact* con, pn x);//前插
void Con_bset(Contact* con, pn x);//尾插
//void Con_zset(Contact* con, seq x, int n);//指定位置插入
//void Con_fdel(Contact* con);//前删
//void Con_bdel(Contact* con);//尾删
void Con_zdel(Contact* con);//删除数据
void Con_zchange(Contact* con);//修改数据
int Con_namefind(Contact con);//按名字查找通讯人
void Print_con(Contact con);//打印通讯录
void Con_xset(Contact* con);//添加数据
void Print_zd(Contact con, int n);//打印指定通讯人信息
void Sort_age(Contact* con);//根据年龄大小排序

源文件1:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1

#include "seq.h"

//初始化顺序表
void Sequce_set(sl* s)
{
	s->arr = (seq*)malloc(sizeof(seq));
	s->size = 0;
	s->cap = 1;
}


//表首插入
void Sequce_front(sl* s, seq x)
{
	N_ull(s);
	En(s);
	for (int i = s->size; i > 0; i--)
	{
		s->arr[i] = s->arr[i - 1];
	}
	s->arr[0] = x;
	s->size++;
}

//表尾插入
void Sequce_back(sl* s, seq x)
{
	N_ull(s);
	En(s);
	s->arr[s->size] = x;
	s->size++;
}

//表首删除
void Sequce_popfront(sl* s)
{
	N_ull(s);
	if (s->size == 0)
	{
		return;
	}
	for (int i = 1; i < s->size; i++)
	{
		s->arr[i - 1] = s->arr[i];
	}
	s->size--;
}

//表尾删除
void Sequce_popback(sl* s)
{
	N_ull(s);
	if (s->size == 0)
	{
		return;
	}
	s->size--;
}

//指定位置添加
void Zd_set(sl* s, int n, seq x)
{
	N_ull(s);
	En(s);
	if (s->size < n)
	{
		return;
	}
	for (int i = s->size; i > n; i--)
	{
		s->arr[i] = s->arr[i - 1];
	}
	s->arr[n] = x;
	s->size++;
}

//指定位置删除
void Zd_del(sl* s, int n)
{
	if (s->size == 0)
	{
		return;
	}
	N_ull(s);
	for (int i = n; i < s->size - 1; i++)
	{
		s->arr[i] = s->arr[i + 1];
	}
	s->size--;
}

//打印顺序表
//void Print(sl s)
//{
//	for (int i = 0; i < s.size; i++)
//	{
//		printf("%d ", s.arr[i]);
//	}
//	printf("\n");
//}

//判断容量
void En(sl* s)
{
	N_ull(s);
	while (s->cap <= s->size)
	{
		int newcap = 2 * s->cap;
		seq* ps = (seq*)realloc(s->arr, sizeof(seq) * newcap);
		if (!ps)
		{
			exit(1);
		}
		s->arr = ps;
		s->cap = newcap;
	}
}

//判断空指针
void N_ull(sl* s)
{
	if (!s)
	{
		exit(1);
	}
}

//顺序表销毁
void des(sl* s)
{
	free(s->arr);
	s->arr = NULL;
}

源文件2:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"seq.h"

//初始化数据
void Con_set(Contact* con)
{
	Sequce_set(con);
}

//数据销毁
void Con_des(Contact* con)
{
	des(con);
}

void Con_fset(Contact* con, seq x)//前插
{
	Sequce_front(con, x);
}

//添加数据
void Con_xset(Contact* con)
{
	pn l;
	printf("请输入通讯人姓名:\n");
	scanf("%s", l.name);

	printf("请输入通讯人性别:\n");
	scanf("%s", l.sex);

	printf("请输入通讯人年龄:\n");
	scanf("%d", &l.age);

	printf("请输入通讯人电话:\n");
	scanf("%s", l.ph);

	printf("请输入通讯人地址:\n");
	scanf("%s", l.ad);

	Sequce_back(con, l);

	Print_con(*con);
}

//按名字删除数据
void Con_zdel(Contact* con)
{
	//char name[NAME_MAX];
	//scanf("%s", name);
	int k = Con_namefind(*con);
	if (k >= 0)
	{
		Zd_del(con, k);
		printf("删除成功!\n");
		Print_con(*con);
	}
	else
	{
		printf("未找到你要删除的通讯人!\n");
	}
}


//根据名字查找通讯人
int Con_namefind(Contact con)
{
	char name[NAME_MAX];
	printf("请输入你要操作的通讯人姓名:");
	scanf("%s", name);
	for (int i = 0; i < con.size; i++)
	{
		if (!strcmp(con.arr[i].name, name))
		{
			printf("找到了!\n");
			return i;
		}
	}
	return -1;
}

void Print_con(Contact con)//打印通讯录
{
	printf("姓名:");
	for (int i = 0; i < con.size; i++)
	{
		printf("%-20s", con.arr[i].name);
	}
	printf("\n");
	printf("性别:");
	for (int i = 0; i < con.size; i++)
	{
		printf("%-20s", con.arr[i].sex);
	}
	printf("\n");
	printf("年龄:");
	for (int i = 0; i < con.size; i++)
	{
		printf("%-20d", con.arr[i].age);
	}
	printf("\n");
	printf("电话:");
	for (int i = 0; i < con.size; i++)
	{
		printf("%-20s", con.arr[i].ph);
	}
	printf("\n");
	printf("地址:");
	for (int i = 0; i < con.size; i++)
	{
		printf("%-20s", con.arr[i].ad);
	}
	printf("\n");
}

//打印查找到的通讯人
void Print_zd(Contact con, int n)
{
	if (n >= 0)
	{
		printf("姓名:");
		printf("%s\n", con.arr[n].name);
		printf("性别:");
		printf("%s\n", con.arr[n].sex);
		printf("年龄:");
		printf("%d\n", con.arr[n].age);
		printf("电话:");
		printf("%s\n", con.arr[n].ph);
		printf("地址:");
		printf("%s\n", con.arr[n].ad);
	}
	else
	{
		printf("没有找到该通讯人!\n");
	}
}

void Sort_age(Contact* con)//根据年龄大小排序
{
	printf("****************1.升序****************\n");
	printf("****************2.降序****************\n");
	int select;
	scanf("%d", &select);
	if (select == 1)
	{
		for (int i = 0; i < con->size; i++)
		{
			for (int j = 0; j < con->size - 1 - i; j++)
			{
				if (con->arr[j].age > con->arr[j + 1].age)
				{
					seq t = con->arr[j];
					con->arr[j] = con->arr[j + 1];
					con->arr[j + 1] = t;
				}
			}
		}
		printf("******************升序结果:*******************\n");
	}
	else if (select == 2)
	{
		for (int i = 0; i < con->size; i++)
		{
			for (int j = 0; j < con->size - 1 - i; j++)
			{
				if (con->arr[j].age < con->arr[j + 1].age)
				{
					seq t = con->arr[j];
					con->arr[j] = con->arr[j + 1];
					con->arr[j + 1] = t;
				}
			}
		}
		printf("******************降序结果:*******************\n");
	}
	Print_con(*con);//打印通讯录
}

//修改通讯人信息
void Con_zchange(Contact* con)
{
	int k = Con_namefind(*con);
	pn l;
	if (k >= 0)
	{
		printf("请输入通讯人姓名:\n");
		scanf("%s", l.name);

		printf("请输入通讯人性别:\n");
		scanf("%s", l.sex);

		printf("请输入通讯人年龄:\n");
		scanf("%d", &l.age);

		printf("请输入通讯人电话:\n");
		scanf("%s", l.ph);

		printf("请输入通讯人地址:\n");
		scanf("%s", l.ad);
		con->arr[k] = l;
		printf("修改成功!\n");
		Print_con(*con);
	}
	else
	{
		printf("未找到你查找的通讯人!\n");
	}
}

源文件3:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"seq.h"

void Memu1()
{
	printf("********************通讯录********************\n");
	printf("*****************1.进入通讯录*****************\n");
	printf("*****************2.退出通讯录*****************\n");
	printf("**********************************************\n");
}

void Memu2()
{
	printf("********************通讯录功能列表********************\n");
	printf("********************1.添加通讯人**********************\n");
	printf("********************2.删除通讯人**********************\n");
	printf("********************3.查找通讯人**********************\n");
	printf("********************4.根据通讯人年龄排序**************\n");
	printf("********************5.修改通讯人信息******************\n");
	printf("********************通讯录功能列表********************\n");
}

void Memu3()
{
	printf("请选择:");
}

int main()
{
	char word[PASSWORD_MAX];
	FILE* ol = fopen("password.txt", "r");
	if (ol == NULL)
	{
		perror("fopen");
		return 1;
	}
	int u;
	if (fscanf(ol, "password: %s\n", word) != 1)
	{
		u = 0;
	}
	else
	{
		printf("请输入密码:\n");
		char guess[PASSWORD_MAX];
		scanf("%s", guess);
		u = strcmp(guess, word);
	}
	fclose(ol);
	if (!u)
	{
		Contact con;
		Con_set(&con);
		FILE* p = fopen("con.txt", "r");
		if (p == NULL)
		{
			perror("fopen");
			return 1;
		}
		// 使用循环读取文件中的每条记录
		int count = 0;
		fscanf(p, "Size: %d\nCap: %d\n", &con.size, &con.cap);
		con.cap = 1;
		En(&con);
		if (con.size != 0)
		{
			while (fscanf(p, "Name: %s\nSex: %s\nAge: %d\nph: %s\nad: %s\n", con.arr[count].name, con.arr[count].sex, \
				& con.arr[count].age, con.arr[count].ph, con.arr[count].ad) == 5) {
				count++;
				if (count >= con.size) {
					break; // 如果达到数组容量上限,则停止读取
				}
			}
		}
		fclose(p);
		Print_con(con);
		//Con_set(&con);
		do {
			//system("cls");
			Memu1();
			int a = 0;
			Memu3();
			scanf("%d", &a);
			if (a == 1)
			{
				Memu2();
				Memu3();
				scanf("%d", &a);
				switch (a) {
				case 1:
					printf("********************添加通讯人**********************\n");
					Con_xset(&con);
					break;
				case 2:
					printf("********************删除通讯人**********************\n");
					Con_zdel(&con);
					break;
				case 3:
					printf("********************查找通讯人**********************\n");
					Print_zd(con, Con_namefind(con));
					break;
				case 4:
					printf("********************根据通讯人年龄排序**************\n");
					Sort_age(&con);//根据年龄大小排序
					break;
				case 5:
					printf("********************修改通讯人信息**********************\n");
					Con_zchange(&con);
					break;
				default:
					printf("输入错误,请重新选择:\n");
				}
			}
			else if (a == 2)
			{
				break;
			}
			else
			{
				printf("输入错误,请重新选择!\n");
			}
		} while (1);
		FILE* f = fopen("con.txt", "w");
		if (f == NULL)
		{
			perror("fopen");
			return 1;
		}
		fprintf(f, "Size: %d\nCap:  %d\n", con.size, con.cap);
		for (int i = 0; i < con.size; i++)
		{
			fprintf(f, "Name: %s\nSex: %s\nAge: %d\nph: %s\nad: %s\n", con.arr[i].name, con.arr[i].sex, \
				con.arr[i].age, con.arr[i].ph, con.arr[i].ad);
		}
		Con_des(&con);//销毁
		fclose(f);
		printf("重新设置密码请按1,退出可按任意数字!\n");
		int df;
		scanf("%d", &df);
		if (df == 1)
		{
			mm:
			printf("新密码:");
			FILE* gh = fopen("password.txt", "w");
			if (gh == NULL)
			{
				perror("fopen");
				return 1;
			}
			char newword[PASSWORD_MAX];
			char newword2[PASSWORD_MAX];
			scanf("%s", newword);
			printf("请再输入一次新密码:");
			scanf("%s", newword2);
			if (strcmp(newword, newword2))
			{
				printf("第二次输入错误, 请重新设置!\n");
				goto mm;
			}
			fprintf(gh, "password: %s\n", newword);
			fclose(gh);
		}
		
#if 0
		Con_set(&con);

		//添加数据:
		Con_xset(&con);
		Con_xset(&con);
		Con_xset(&con);

		//按名字删除数据
		Con_zdel(&con);

		//按姓名查找通讯人:
		Print_zd(con, Con_namefind(con));

		Con_des(&con);//销毁
#endif
	}
	else
	{
		printf("密码错误,直接退出程序!\n");
	}
	return 0;
}

运行演示:

第一次进入运行

第二次进入程序:

从中可以看出数据能够成功地储存在文件中。

如果感兴趣可以自己用单链表和双链表都实现一遍。

完。

相关推荐
诚丞成2 分钟前
滑动窗口篇——如行云流水般的高效解法与智能之道(1)
算法
Alive~o.08 分钟前
Go语言进阶&依赖管理
开发语言·后端·golang
花海少爷10 分钟前
第十章 JavaScript的应用课后习题
开发语言·javascript·ecmascript
手握风云-11 分钟前
数据结构(Java版)第二期:包装类和泛型
java·开发语言·数据结构
喵叔哟31 分钟前
重构代码中引入外部方法和引入本地扩展的区别
java·开发语言·重构
尘浮生37 分钟前
Java项目实战II基于微信小程序的电影院买票选座系统(开发文档+数据库+源码)
java·开发语言·数据库·微信小程序·小程序·maven·intellij-idea
hopetomorrow1 小时前
学习路之PHP--使用GROUP BY 发生错误 SELECT list is not in GROUP BY clause .......... 解决
开发语言·学习·php
系统之家装机大师1 小时前
Win11 22H2/23H2系统11月可选更新KB5046732发布!
windows·电脑
系统之家装机大师1 小时前
微软发布Win11 24H2系统11月可选更新KB5046740!
windows·电脑
小牛itbull1 小时前
ReactPress vs VuePress vs WordPress
开发语言·javascript·reactpress