链表操作练习
要求
现在有一个双向链表,里面要保存歌曲的名字;例如 蔡琴/渡口.mp3
我们把它定义在一个link.h文件中。
#ifndef LINK_H
#define LINK_H
#include <stdlib.h>
#include <stdio.h>
#include <string.h>typedef struct Node
{//保存歌曲的名字 char music_name[64];struct Node* next;struct Node* prior;
}Node;
#endif
这里的 #ifndef、#define 和 #endif 是 C/C++ 中的预处理指令,用于防止头文件被重复包含。 如果 link.h 被 a.c 和 b.c 同时包含,或者在一个文件中被间接包含两次,没有头文件保护会导致编译错误。 #ifndef LINK_H 检查是否没有定义过 LINK_H 这个宏,如果没有定义过,则执行下面的代码;如果已经定义过,则跳过整个块(直到 #endif)。 #define LINK_H 定义 LINK_H 这个宏。
现在我们要做的,是在这个link.h对应的link.c中实现以下的操作,(为了方便,我先给出各个操作的方法名)
1.初始化链表 init_link() ==》创建一个新节点,并初始化指针前后指针为NULL
2.插入节点 int insert_link(const char *name)
3.遍历链表 void traverse_link()==>使用尾插法
4.根据当前歌曲找到下一首歌 find_next_music(char *cur, char *next)
==》如果歌曲存在,返回0,如果歌曲不存在,返回-1
5.根据当前歌曲找到上一首歌 void find_prior_music(char *cur, char *prior)
6.从链表中提取歌手的名字 void get_singer(char *s)
7.清空列表 void clear_link()
在link.c中我们需要先写下这两个语句,
#include "link.h"
//把main.c中华定义的头指针引进来。
extern Node *head;
接下来开始我们的练习。。。。。。。。。。。。
初始化链表
创建一个新节点,并初始化指针前后指针为NULL。
注意head指针所指头结点没有值;
/*
返回值:创建成功返回0,否则返回-1
*/
int init_link() {head = (Node*)malloc(sizeof(Node));if (head== NULL) {printf("初始化失败");return -1;}head->prior = NULL;head->next = NULL;return 0;
}
插入节点
思路:使用指针p遍历链表至最后一个节点,新建一个节点,并对之赋值;最后将之插在链表尾部;
/*
参数:音乐名
返回值:操作成功返回0,否则返回-1
处理:使用尾插法插入节点
*/
int insert_link(const char* name) {Node* p = head;while (p->next) {p = p->next;}//新建一个节点Node* newNode = (Node*)malloc(sizeof(Node));if (newNode== NULL) {printf("创建节点失败");return -1;}strcpy_s(newNode->music_name, sizeof(newNode->music_name), name);newNode->prior = p;newNode->next = NULL;p->next = newNode;return 0;
}
知识点:
-
strcpy(newNode->music_name, name);
是 C 语言中用于 字符串拷贝 的标准库函数,其作用是将源字符串name
的内容复制到目标字符数组newNode->music_name
中。
遍历链表
v
oid traverse_link() {Node* p = head->next;while (p) {printf("%s\n", p->music_name);p = p->next;}
}
根据当前歌曲找到下一首歌
思路:遍历链表,找到music_name为cur的节点,返回其下一个节点中的歌曲名
int find_next_music(char *cur, char *next)
{Node *p = head->next;while (p){if (strstr(p->music_name, cur) != NULL){break;}p = p->next;}if (p->next != NULL){strcpy(next, p->next->music_name);return 0;}else {return -1;}}
}
知识点:
strstr()
是 C 语言标准库中的一个字符串处理函数,用于在一个字符串中查找另一个字符串的首次出现位置。
提取当前链表中的歌手名
void get_singer(char *s)
{if (head->next == NULL)return;
// 其他/以后的以后.mp3char *begin = head->next->music_name;char *p = begin;while (*p != '/')p++;
strncpy(s, begin, p - begin);s[p - begin] = '\0'; // 确保字符串终止
}
从链表节点中提取歌手名称并存储到 s 指向的缓冲区中 begin 指针指向音乐文件名字符串的开头;循环结束后,p 指向 '/' 字符的位置 使用 strncpy 从 begin 开始拷贝 p - begin 个字符到 s;p - begin 计算的是从字符串开头到 '/' 的字符数(即歌手名的长度)
清空链表
void clear_link()
{Node *p = head->next;while (p){head->next = p->next;free(p);p = head->next;}
}
测试==》main.h和main.c
main.h如下:#ifndef MAIN_H
#define MAIN_H
#include "link.h"
#include<stdlib.h>
#include <stdio.h>
#endif
main.c如下:#include "main.h"
Node* head = NULL; //链表头指针
int main() {//初始化链表if (init_link() == -1){printf("链表初始化失败\n");}insert_link("蔡琴/渡口.mp3");insert_link("蔡琴/你的眼神.mp3");insert_link("蔡琴/绿岛小夜曲.mp3");insert_link("蔡琴/南屏晚钟.mp3");insert_link("蔡琴/给电影人的情书.mp3");traverse_link();char singerName[64];get_singer(singerName);printf("%s", singerName);clear_link();return 0;
}
知识点:
1.在main.c中我定义了Node* head = NULL; 在link.c。player.c等要用到链表的文件中我使用extern Node* head;这样做的用意是什么?
==》
为了实现 全局变量的跨文件共享,同时避免重复定义。
ain.c 中定义:这里是全局变量的 定义(分配内存),整个程序只有这一处定义。 其他文件中声明:告诉编译器:"head 已经在别处定义了,直接使用它"。 如果多个 .c 文件都直接定义 Node* head,链接时会报错(重复定义)。
通过 extern 声明,确保全局变量 只定义一次,但可被多个文件访问。
2.为什么在link.h中我没有引入main.h,extern Node* head;还是有效,即编译器怎么知道main.c中定义了Node* head = NULL;?
1.extern 是声明,不是定义:它不分配内存,只是承诺“这个变量会在别处定义”。
2.类型信息来自 Node 的定义:link.h 中的 typedef struct Node {...} 提供了 Node* 的类型信息。
3.符号匹配由链接器完成:链接器通过变量名(head)匹配定义,与头文件无关。
4.不需要 #include "main.h":main.c 的定义和 link.c 的声明通过链接器关联,无需头文件介入。