大学生/初学者/弱鸡 因此本文可能会有很多基本知识,还请大家斟酌观看
PS:本文仅供有些C语言基础,想回顾知识点的伙伴阅读
Start!(੭•̀ω•́)੭ ̸*✩⁺˚(施法ing
———————————————————————————————————————————————————
注意:本章节大部分讲单链表
一、大杂烩
1、assert()函数包含于 <assert.h> 头文件中,作用是判断后面表达式是否正确,若正确则忽略,若不正确则报错
#include <stdio.h>
#include <assert.h>
int main()
{
assert(5 == 3);
printf("123");
return 0;
}
output:
Assertion failed! //判断失败
Expression: 5 == 3 //此表达式有误
2、可以用字符指针直接指向字符串常量吗?
#include <stdio.h>
int main()
{
char* a = "123";
printf("%s",a);
return 0;
}
Output:123
没错,是可以打印的,但是编译器会警告!
[Warning] deprecated conversion from string constant to 'char*' [-Wwrite-strings]
系统是不支持这种打印方式的,但如果我们依然要这么做,并还想先输入再输出会怎么样呢?
#include <stdio.h>
int main()
{
char* a;
scanf("%s",a);
printf("%s",a);
return 0;
}
Intput:123
Output:
结果是编译器打不出来!
二、单链表
在讲单链表之前我想表明一下我的观点:
能用指针就用指针,能用二维指针就别用普通指针
问:C语言 用结构体定义的变量使用时是不是都要加struct ?
答:定义的时候需要加上struct,使用的时候只需要名字就可以了
1、头插法
可以先去看看我的另一篇博客:构建一个简易图书馆(这是由单链表的头插法构建的)
问:为什么要用二维指针呢?难道不可以用普通指针吗?
答:其实二维指针是必须要用的,因为若用普通指针则会出现下面情况(以简单的例子类比)
#include <stdio.h>
void a(int qq)
{
printf("%d",qq); 相当于 addBook(library)(此处传递普通指针)
qq += 1; 相当于 library = book 的几次迭代(此处也是普通指针)
}
void b(int qq) 相当于 printLibrary() (此处传递普通指针)
{
printf("%d",qq); 相当于对传递的指针取值,但是其实是打印的是 123 ,而不是 124
}
int main()
{
int qq = 123; 头指针 library == NULL(此处定义普通指针)
a(qq); 相当于引用 addBook() 函数(传递指针的值是 123 )
b(qq); 相当于引用 printLibrary() 函数(传递指针的值依然是 123 )
return 0;
}
问:这样造成的结果是什么?
答:会造成在 b( ) 函数中传递的并不是 a( ) 函数结束时改变的普通指针,而是原来定义的指针,同理;在图书馆单链表中,printLibrary( ) 函数中book = library 所接收的 library 是原来指向 NULL 的指针变量,而不是经过 addBook( ) 函数几番迭代后的指针
问:有什么解决的办法吗?
答:当然就是用二维指针啦!利用二维指针定向的将 library 所指向的地址改变,既不会影响 library 的迭代,又不会影响 library 的传递,岂不美哉?
注意:addBook(struct Book** library) 中传递的参数是 &library,这个函数中所表达的 *library 实际等价于是 main( ) 函数中的 library,切勿与main( ) 函数中的 *library搞混杂
2、尾插法
先谈谈宏观体现上的头插法与尾插法的区别:
头插法就是当你按照1 2 3 4 5 的顺序输入,而打印5 4 3 2 1
尾插法就是当你按照1 2 3 4 5 的顺序输入,而打印1 2 3 4 5
再从代码的角度看看它们的主要区别:(还是以构建一个简易图书馆举例)
头插法与尾插法的核心就在 addBook(struct Book** library)这个函数上
👇在头插法中 addBook(struct Book** library)的内容
struct Book* book;
book = (struct Book*)malloc(sizeof(struct Book));
if (book == NULL)
{
printf("内存分配失败!\n");
exit(1);
}
getInput(book);
if (*library != NULL)
{
book->next = *library; 新生成的结构体的 book-> next 指向 *library
*library = book; *library 指向新生成的结构体 book 的地址
}
else 空链表
{
*library = book;
book->next = NULL;
}
👇在尾插法中 addBook(struct Book** library)的内容
struct Book* book;
static struct Book* tail; 设置静态变量 tail 保证每次都以上一次循环结尾的值作为初始值
book = (struct Book*)malloc(sizeof(struct Book));
if (book == NULL)
{
printf("内存分配失败!\n");
exit(1);
}
getInput(book);
if (*library != NULL)
{
tail->next = book; tail-> next 指向新生成的结构体 book 的地址
book->next = NULL; 新生成的结构体 book 指向 NULL
}
else 空链表
{
*library = book;
book->next = NULL;
}
tail = book; tail 指向新生成的结构体 book 的地址
下面以图片形式 展示 头插法 与 尾插法 的区别
👇头插法
👇尾插法
3.搜索单链表
只需要将构建一个简易图书馆稍微修改一下就可以添加新功能,即输入书名或作者的名字,通过搜索打印出相关的书籍
新添加一个函数用于搜索👉 struct Book* searchBook(struct Book*library, char* target)
定义如下:
struct Book* searchBook(struct Book*library, char* target)
{
struct Book* book;
book = library;
while(book != NULL)
{
if(!strcmp(book->author, target) || !strcmp(book->title, target))//strcmp(A, B)表示相同就返回 0,!0为真
{ //而 || 表示只要有一方为真则为真
break;
}
book = book->next;
}
return book;
}
将之前打印所有图书信息的函数👉 printLibrary(struct Book* library)
修改为用于打印搜索的图书的函数👉 printBook(struct Book* book)
定义如下:
void printBook(struct Book* book)
{
printf("书名:%s\n", book->title);
printf("作者:%s\n", book->author);
}
将原来的 main() 函数稍微修改一下👇
如下:
int main()
{
struct Book* library = NULL;
struct Book* book; 定于用于搜索的结构体指针
char input[128]; 定义用于接收用户输入的字符型数组
char ch;
while (1)
{
printf("请问是否需要录入书籍信息(Y/N):");
do
{
ch = getchar();
} while (ch != 'Y' && ch != 'N');
if (ch == 'Y')
{
addBook(&library);
}
else
{
break;
}
}
printf("\n");
printf("请输入书名或作者:");
scanf("%s", input);
printf("\n");
book = searchBook(library, input); 先将第一本符合条件的书找出来
if(book == NULL)
{
printf("抱歉,没能找到!\n");
}
else
{
do
{
printf("已找到符合条件的书籍,如下:\n");
printBook(book); 打印搜索到那本书
printf("\n");
}while((book = searchBook(book->next, input)) != NULL); 再找其他符合条件的书
}
releaseLibrary(&library);
return 0;
}
4.插入节点到指定的位置
下面要求实现在已排好的数字序列中插入节点,并要求插完后也是按照顺序排好队
#include <stdio.h>
#include <stdlib.h>
struct Node
{
int value;
struct Node* next;
};
void insertNode(struct Node** head, int value)
{
struct Node* previous;
struct Node* current;
struct Node* new;
current = *head; 从头开始排列
previous = NULL; 初始化指向NULL
while (current != NULL && current->value < value) 一个个比较
{
previous = current; 先为current赋值,再为previous赋值
current = current->next;
}
new = (struct Node*)malloc(sizeof(struct Node));
if (new == NULL)
{
printf("内存分配失败!\n");
exit(1);
}
new->value = value;
new->next = current;
if (previous == NULL) 证明这是循环的第一次
{
*head = new; 将头指针指向第一个结构体(永久性的,因为用了二维指针)
}
else
{
previous->next = new;
}
}
void printNode(struct Node* head)
{
struct Node* current;
current = head; 若是空链表,则 current == NULL
while (current != NULL)
{
printf("%d ", current->value);
current = current->next;
}
putchar('\n');
}
int main()
{
struct Node* head = NULL;
int input;
printf("开始测试插入整数...\n");
while (1)
{
printf("请输入一个整数(输入-1表示结束):");
scanf_s("%d", &input);
if (input == -1)
{
break;
}
insertNode(&head, input);
printNode(head);
}
return 0;
}
Output
开始测试插入整数...
请输入一个整数(输入-1表示结束):5
5
请输入一个整数(输入-1表示结束):8
5 8
请输入一个整数(输入-1表示结束):0
0 5 8
请输入一个整数(输入-1表示结束):9
0 5 8 9
请输入一个整数(输入-1表示结束):-1
5.在单链表中删除元素
要求实现在已排好的数字序列中删除节点,并要求删除完后也是按照顺序排好队
其实在上一题的基础上加一个函数,在稍微修改一下 main()函数就可以实现了
新添加一个函数用于删除👉 void deleteNode(struct Node** head, int value)
定义如下:
void deleteNode(struct Node** head, int value) 其实删除和插入一个套路,大致是一样的
{
struct Node* previous;
struct Node* current;
current = *head;
previous = NULL;
while (current != NULL && current->value != value) 这里 < 变成了 != ,表达数字一样就结束
{
previous = current;
current = current->next;
}
if (current == NULL) 要么这是一个空链表、要么数字序列中没有符合条件的节点
{
printf("找不到匹配的节点!\n");
return; 还可以这样,Void 类型函数可以写 return 但不返回
}
else
{
if (previous == NULL) 需要预防的情况:要删除的节点是数字序列的第一个数字
{
*head = current->next; 特殊处理:将 head 指针指向该节点
}
else
{
previous->next = current->next;
}
free(current); 删除
}
}
Output
开始测试插入整数...
请输入一个整数(输入-1表示结束):4
4
请输入一个整数(输入-1表示结束):5
4 5
请输入一个整数(输入-1表示结束):-1
开始测试删除整数...
请删除一个整数(输入-1表示结束):5
4
请删除一个整数(输入-1表示结束):4
请删除一个整数(输入-1表示结束):-1