链表的应用

双向链表的引用

双向有头链表的创建

Dou_node *create_doulink()

{

Dou_node *pnode = malloc(sizeof(Dou_node));

if (NULL == pnode)

{

printf("fail malloc");

return NULL;

}

pnode->ppre = NULL;

pnode->pnext = NULL;

return pnode;

}

链表是否为空的判断

int is_empty_doulink(Dou_node *phead)

{

if (NULL == phead->pnext)

{

return 1;

}

return 0;

}

 双向链表的头插

int insert_head_doulink(Dou_node *phead, Data_type data)

{

Dou_node *pinsert = malloc(sizeof(Dou_node));

if (NULL == pinsert)

{

printf("fail malloc");

return -1;

}

pinsert->data = data;

pinsert->ppre = NULL;

pinsert->pnext = NULL;

if (is_empty_doulink(phead))

{

phead->pnext = pinsert;

pinsert->ppre = phead;

}

else

{

pinsert->pnext = phead->pnext;

phead->pnext->ppre = pinsert;

phead->pnext = pinsert;

pinsert->ppre = phead;

}

return 0;

}

双向链表的尾插

int insert_tail_doulink(Dou_node *phead, Data_type data)

{

Dou_node *pinsert = malloc(sizeof(Dou_node));

if (NULL == pinsert)

{

printf("fail malloc\n");

return -1;

}

pinsert->data = data;

pinsert->pnext = NULL;

pinsert->ppre = NULL;

Dou_node *p = phead;

while (p->pnext != NULL)

{

p = p->pnext;

}

p->pnext = pinsert;

pinsert->ppre = p;

return 0;

}

双向链表的遍历

void doulink_for_each(Dou_node *phead, int dir)
{
	if (is_empty_doulink(phead))
	{
		return ;
	}
	
	Dou_node *p = phead->pnext;
	if (dir)
	{
		while (p)
		{
			printf("%d %s %d\n", p->data.id, p->data.name, p->data.score);
			p = p->pnext;
		}
	}
	else
	{
		while (p->pnext != NULL)
		{
			p = p->pnext;
		}
		while (p->ppre != NULL)
		{	
			printf("%d %s %d\n", p->data.id, p->data.name, p->data.score);
			p = p->ppre;
		}
	}
	printf("\n");
}

 双向链表的删除

int delete_head_doulink(Dou_node *phead)    //头删
{
	if (is_empty_doulink(phead))
	{
		return 0;
	}

	Dou_node *pdel = phead->pnext;

	phead->pnext = pdel->pnext;
	if (pdel->pnext != NULL)
	{
		pdel->pnext->ppre = phead;
	}
	free(pdel);

	return 1;
}

int delete_tail_doulink(Dou_node *phead)      //尾删
{
	if (is_empty_doulink(phead))
	{
		return 0;
	}
	
	Dou_node *pdel = phead->pnext;

	while (pdel->pnext != NULL)
	{
		pdel = pdel->pnext;
	}
	pdel->ppre->pnext = NULL;
	free(pdel);
	
	return 1;
}

int delete_point_node(Dou_node *phead, char *name)     //指定位置的删除
{
	if (is_empty_doulink(phead))
	{
		return 0;
	}

	Dou_node *pdel = find_doulink_by_name(phead, name);
	if (NULL == pdel)
	{
		return 0;
	}
	
	if (NULL == pdel->pnext)
	{
		delete_tail_doulink(phead);
	}
	else
	{
		pdel->ppre->pnext = pdel->pnext;
		pdel->pnext->ppre = pdel->ppre;
		free(pdel);
	}
	
	return 1;
}

void destroy_doulink(Dou_node **pphead)       //双向链表的销毁
{
	while (!is_empty_doulink(*pphead))
	{
		delete_head_doulink(*pphead);
	}
	free(*pphead);
	*pphead = NULL;
}	

双向链表数据的查找

Dou_node *find_doulink_by_name(Dou_node *phead, char *name)
{
	Dou_node *p = phead->pnext;
	while (p)
	{
		if (0 == strcmp(p->data.name, name))
		{
			return p;
		}
		p = p->pnext;
	}

	return NULL;
}

认识调试工具  gdb

gdb的作用

1.定位段错误

2.用来调试逻辑错误

gdb使用方法

-g:允许gdb经行调式

gdb可执行程序

一些常用指令

l   (查看源代码)

b    断点

r    运行程序

n    继续执行下一段代码

s    进入函数里面

p 变量名   (查看该变量)

q  退出函数

where   查看段错误的栈信息

 内核链表

普通链表:结构简单,易于实现,适用于大多数用户级应用,但在性能和内存管理上有所限制,特别是对于需要频繁访问和操作大量数据的情况。

内核链表:本质是一个双向有头的循环链表。:在内核环境中表现优越,通过优化操作、节省内存、与内存管理机制结合,提供更高效的链表实现。适合于操作系统内部或高性能计算环境中

普通链表
优点:
  • 实现简单:普通链表的结构清晰,易于理解和实现。
  • 内存管理灵活:每个节点可以单独分配内存,适合动态数据结构。
  • 操作方便:节点数据和指针的结构非常直观,增加、删除操作简单,特别是在头部和尾部。
缺点:
  • 随机访问效率差:要访问链表的第N个节点,必须从头开始遍历,时间复杂度为 O(n)。
  • 内存开销:每个节点需要额外的空间来存储指针,这可能导致内存浪费。
  • 性能不高:在涉及频繁插入/删除的场景下,普通链表较为高效,但在内存和 CPU 资源有限的环境中可能会表现不佳。
内核链表
优点:
  • 高效的链表操作:内核链表提供了一组优化的操作宏,避免了频繁的内存分配和释放,适用于内核的高性能要求。
  • 轻量级:内核链表的节点只包含指向前后节点的指针,不包含额外的数据部分。这样,节点的存储开销较小,可以节省内存。
  • 适合内核环境:由于内核链表的设计,能够与内核的内存管理机制紧密结合,使得链表在内核环境下更为高效。
缺点:
  • 不够直观:相比普通链表,内核链表的操作需要使用宏,且节点本身并不直接包含数据,可能需要更多的间接操作来获取实际数据。
  • 复杂度较高:内核链表的使用相对较为复杂,特别是对于新手而言,需要熟悉内核的内存管理和链表操作宏。

认识两个宏 

offsetof 宏用于计算结构体成员相对于结构体起始位置的字节偏移量

container_of 宏用于根据结构体成员的地址(通常是指向某个成员的指针)反向推算出包含该成员的整个结构体对象的地址。这个宏常用于链表遍历、内核数据结构中的容器结构体操作等场景。

c语言中比教容易混淆的内容

1. 指针函数(Pointer Function)

指针函数是返回指针的函数。也就是说,这种函数的返回值是指针类型,它可以指向某种数据类型。

定义:

指针函数的返回类型是一个指针,表示该函数返回一个指向某个数据类型的指针。

int* func();  // 定义一个返回 int 指针的函数

#include 

int* pointer_func() {
    static int num = 10;  // 使用 static 来保证变量 num 在函数外部可以访问
    return #  // 返回 num 的地址
}

int main() {
    int *ptr = pointer_func();
    printf("Value: %d\n", *ptr);  // 输出:10
    return 0;
}

在这个例子中,pointer_func 是一个指针函数,返回一个指向 int 类型的指针。在 main 函数中,我们调用 pointer_func 并获取返回的指针,最终通过解引用 *ptr 来访问值。

特点:
  • 返回指针:指针函数的核心就是返回指针。
  • 可以返回指向局部变量的指针:注意,如果返回指向局部变量的指针,必须确保这些局部变量使用 static 声明(否则局部变量会在函数返回后被销毁)。

2. 函数指针(Function Pointer)

函数指针是指向函数的指针,允许通过指针间接调用函数。这是 C 语言中非常强大的特性,广泛应用于回调函数、动态调用等场景。

定义:

函数指针的类型与其指向的函数的签名(返回类型和参数类型)一致。例如,指向返回 int 且接受两个 int 参数的函数的指针类型为:

int (*func_ptr)(int, int);  // 定义一个函数指针,指向一个接收两个 int 参数并返回 int 的函数
#include 

// 定义一个普通的函数
int add(int a, int b) {
    return a + b;
}

int main() {
    // 定义函数指针并将其指向 add 函数
    int (*func_ptr)(int, int) = add;
    
    // 使用函数指针调用函数
    int result = func_ptr(2, 3);  // 相当于调用 add(2, 3)
    
    printf("Result: %d\n", result);  // 输出:Result: 5
    return 0;
}
特点:
  • 通过指针调用函数:函数指针允许在运行时动态选择函数。
  • 回调函数:函数指针常用于实现回调机制,特别是在库函数中,通过函数指针将用户定义的函数作为参数传入。
使用场景:
  • 回调函数:如在排序函数中传入比较函数。
  • 动态函数调用:如插件机制、事件驱动编程。

3. 指针数组(Pointer Array)

指针数组是一个数组,每个元素都是一个指向某种类型的指针。指针数组的元素可以指向同一类型的数据。

定义:

指针数组的定义方式类似于普通数组,只不过数组的元素是指针类型

int* arr[5];  // 定义一个指向 int 的指针数组,包含 5 个元素
#include 

int main() {
    int a = 10, b = 20, c = 30;
    
    // 定义一个指针数组
    int* arr[3];
    
    // 让数组的每个元素指向不同的变量
    arr[0] = &a;
    arr[1] = &b;
    arr[2] = &c;
    
    // 通过指针数组访问数据
    printf("%d %d %d\n", *arr[0], *arr[1], *arr[2]);  // 输出:10 20 30
    return 0;
}
特点:
  • 数组元素是指针:指针数组中的每个元素都是指向某个数据的指针。
  • 数组大小固定:指针数组的大小是固定的,不能动态增加。
使用场景:
  • 多指针管理:用于管理多个指针,特别是在动态内存分配或指向不同数据的场景中。
  • 函数数组:通过指针数组来存储函数指针,常用于回调机制。

4. 数组指针(Array Pointer)

数组指针是一个指向数组的指针。与指针数组不同,数组指针指向一个整体的数组而不是数组中的单个元素。数组指针的类型是指向数组的指针。

定义:

数组指针的定义形式如下

int (*ptr)[5];  // ptr 是一个指向包含 5 个整数的数组的指针
#include 

int main() {
    int arr[3] = {1, 2, 3};
    
    // 定义一个指向数组的指针
    int (*ptr)[3] = &arr;
    
    // 通过数组指针访问数组元素
    printf("%d %d %d\n", (*ptr)[0], (*ptr)[1], (*ptr)[2]);  // 输出:1 2 3
    return 0;
}
特点:
  • 指向整个数组:数组指针指向的是整个数组,而不是数组中的单个元素。
  • 访问方式:通过解引用数组指针后使用 [] 运算符来访问数组的元素。
使用场景: 
  • 二维数组操作:数组指针非常适合用于处理二维数组等复杂数组结构,因为它能够表示数组的完整维度。

总结

  • 指针函数:返回指针的函数,通常用于返回某些数据的地址。
  • 函数指针:指向函数的指针,允许在运行时选择调用的函数,是实现回调和动态函数调用的基础。
  • 指针数组:数组中的每个元素都是指向某种类型的指针,通常用于存储多个指针,常见于多指针管理。
  • 数组指针:指向整个数组的指针,常用于处理多维数组,特别是二维数组。

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(链表,java,前端)