深度学习C语言之一链表


转行小白一枚,努力改变命运中,望各位赐教。

指针分析

Linux是王国,C是基石。
C语言中指针存放的是对象在内存中的地址。执行文件存在硬盘、网络或其他介质中,执行时会在注册一个进程并向操作系统申请地址空间,执行文件按照需要复制到内存,运行时将临时可变变量入栈或放入临时申请的堆空间,其中文件存放位置、栈、堆都有地址。

单重指针

为了方便理解,可以把指针理解为游戏里面NPC给的藏宝图,上面有五角星标记目的地地址。目的地当然就是我们想要的宝藏。
声明一个单指针:

// declare a pointer
struct list_head *head;
char *s;
void *p; 
int (*max)(int a, int b);

......

next一般是int类型大小的指针,64位机器占用8字节,其中存放的是某个sturct list_head 对象的地址。可以想象,现在我们只说明有五角星但确没有标出具体的地址,这句是所谓的野指针,有的编译器自动将指针地址指向NULL(0x00000000)这个无效地址。GCC会指向nil表未定义,Clang不显示初始为NULL会指向一个野地址。

//head is a pointer, tail is a struct
struct list_head *head, tail;
// allocate a size in heap
struct list_head *new_node = (struct list_head *)
                         malloc(sizeof(struct list_head));
head = new_node;  
tail = *new_node;            

上面首先在堆栈中开辟一个struct list_head大小的基本连续的存储空间,并返回首地址new_node,new_node的地址再复制一份给head,单纯看head有点多余了。new_node, head指针也占有一个地址,就像地图实际存在一样。new_node地址指向的对象在复制一份给tail。假设head和tail运行是都存储于栈上,在64位机器中head占据8字节,而tail占据sizeof(struct list_head)。

多重指针

多重指针就相当于藏宝图地址指向的是另一份藏宝图,不是宝藏。

// declare a pointer
struct hlist_head **prev;
char **argv, *argv[];
unsigned long int **state;
void (**printit)(char *s, int n)
......

多重指针可以用于复杂的结构数据类型,比如linux头文件中的Hash链表。

链表

Linux提供了很好的链表实现方式。

struct list_head {
	struct list_head *next;
	struct list_head *prev;
};
//inlude struct list_head
struct user_struct {
	struct info information;
	volatile unsigned int state;
	......
	struct list_head list;
};

链表结构struct list_head放入用户自己的结构中。
图片:
struct list_head
这种方式简化了链表的插入和删除。但是怎么取得用户结构体的数据呢?上述链表移动的是struct list_head结构体指针,它在整个结构体中偏移量是固定的,指针地址减去偏移量就可以得到用户结构的首地址,从而进行数据的访问。这个方式定义在宏中。

#define offsetof(TYPE, MEMBER) \
		( (void *) &( ((TYPE *)0)->MEMBER ) )
		
#define container_of(ptr, type, member) ({ \
		void *__mptr = (ptr); \
		(type *)(__mptr - offsetof(type, member)); })
//for example
struct user_struct {
	volatile unsigned int state;
	pid_t pid;
	struct list_head list;
};
size_t offset_n = offsetof(struct user_struct, list);
struct user_struct *user = container_of(struct list_head *current, struct user_struct, list);

将offsetof分开程序更加高效,因为偏移量是一个常量。可以都编译器直接计算出然后在程序中替换掉对应部分。

链表头文件list.h

\include\linux\list.h
初始化链表:

LIST_HEAD(head);// struct list_head head = {&(head), &(head)}
INIT_LIST_HEAD(&head); //argument is a pointer

添加一项:

static inline void __list_add(struct list_head *new,
		struct list_head *prev, struct list_head *next);

//insert after head for implementing stacks
static inline void list_add(struct list_head *new,
		struct list_head *head);

//insert before head for implementing queues
static inline void list_add_tail(struct list_head *new, 
		struct list_head *head);

删除一项:

static inline void __list_del(struct list_head *prev, struct list_head *next);
static inline void __list_del_entry(struct list_head *entry);

static inline void __list_del_clearprev(struct list_head *entry); //for network
static inline void list_del(struct list_head *entry); 
//entry pointer to 0x00 and 0xaa for safe;
static inline void list_del_init(struct list_head *entry);//deletes entry from list and reinitialize it.

替换一项:

static inline void list_replace(struct list_head *old,
					struct list_head *new);
static inline void list_replace_init(struct list_head *old,
                    struct list_head *new);  //replace and initialize the old one

交换两项:交换机制是先删除在替换然后添加,可以在最大程度上减少对链表的破坏。

static inline void list_swap(struct list_head *entry1,
			                 struct list_head *entry2);

移动几项:

//delete from one list and add as another's head
static inline void list_move(struct list_head *list, 
                 struct list_head *head);
//delete from one list and add as another's tail
static inline void list_move_tail(struct list_head *list,
				  struct list_head *head)
//move a subsection of a list to its tail
static inline void list_bulk_move_tail(struct list_head *head,
				       struct list_head *first,
				       struct list_head *last)

判断:

//tests whether @list is the first entry in list @head
static inline int list_is_first(const struct list_head *list,
					const struct list_head *head);
//tests whether @list is the last entry in list @head
static inline int list_is_last(const struct list_head *list,
				    const struct list_head *head);
//tests whether a list is empty
static inline int list_empty(const struct list_head *head);
//tests whether a list is empty _and_ checks that no other CPU might be 
//in the process of modifying either member (next or prev)
static inline int list_empty_careful(const struct list_head *head);
//tests whether a list has just one entry.
static inline int list_is_singular(const struct list_head *head);

反转列表:

static inline void list_rotate_left(struct list_head *head);
static inline void list_rotate_to_front(struct list_head *list,
					struct list_head *head);

分裂:

static inline void __list_cut_position(struct list_head *list,
		struct list_head *head, struct list_head *entry);

static inline void list_cut_position(struct list_head *list,
		struct list_head *head, struct list_head *entry);

static inline void list_cut_before(struct list_head *list,
				   struct list_head *head,
				   struct list_head *entry);

合并:

static inline void __list_splice(const struct list_head *list,
				 struct list_head *prev,
				 struct list_head *next);
//join two lists, this is designed for stacks
static inline void list_splice(const struct list_head *list,
				struct list_head *head);
static inline void list_splice_tail(struct list_head *list,
				struct list_head *head);
static inline void list_splice_init(struct list_head *list,
				    struct list_head *head);
static inline void list_splice_tail_init(struct list_head *list,
					 struct list_head *head);

其他宏:

通过链表指针找寻结构体指针:

//@ptr: the list head to the element from
#define list_entry(ptr, type, member) \
		container_of(ptr, type, member)
//(ptr)->next: the first entry, (ptr)->prev: the last entry
// next entry
#define list_next_entry(pos, member) \
		list_entry((pos)->member.next, typeof(*(pos)), member)

遍历列表:

#define lis_for_each(pos, head) \
	for(pos = (head)->next; pos!=(head); pos = pos->next)
//iterate over a list safe against removal of list entry
//@n: a temporary struct list_head pointer
#define list_for_each_safe(pos, n, head) \
	for (pos = (head)->next, n = pos->next; pos != (head); \
		pos = n, n = pos->next)
.......

小练习

用链表存储用户的基本信息: 身份证号,姓名,年龄,性别,籍贯。然后根据身份证查询或删除某人信息。
最重要的是设计数据结构,年龄不是固定的,随着时间而改变,设计不是很合理。可以将年龄变成指针,所有人年龄都指向同一个数组某一个元素,但数据太多,查询负担大容错性小。这里可以将年龄用出生年月替换。

身份证号:513822 19451022 8234(X)
+++++++地区号 ++出生年月++个人识别号
这里采用一个非负整数存储个人识别号,比如
1024 对应内存形式为 0x 0000 0000 0000 0400, 采用后16位表示
256X 对应的内存形式为 0x 0000 000 0010 0000,去掉X再左移16位显示
姓名:如胡小月,采用16个字符表示宽字符
性别:用 0和1,这里占据一个char
出生年月: 2025年10月1日
采用整数

籍贯采用统一标号表示 。

// personinfo.h
#ifndef PERSONINFO_H_
#define PERSONINFO_H_
#include <wctype.h>
#include <wchar.h>
struct personinfo {
	unsigned int id;  //identification number
	struct {
		wchar_t firstname[2];
		wchar_t lastname[2];
	} name;
	unsigned char gender;
	unsigned int birthday; //y-m-d -h-m-s
	unsigned int birthplace;  
	struct list_head list;
};	
// input a entry
void inputValue(struct personinfo *new);

#endif
//personinfo.c
#include "personinfo.h"
#include <stdio.h>

void getID(struct personinfo *new, char *s);
unsigned int stoi(char *s, int n);

void inputValue(struct personinfo *new)
{
	//
}

void getID(struct personinfo *new, char *s)
{
  //

}

unsigned int stoi(char *s, int n)
{
	//
}
	
// main.c
#include "list.h"
#include "person.h"

int main(void)
{
	char command = NULL;
	struct personinfo *head = NULL;
	do {
		scanf("%s", &command);
		switch (command) {
		case 'i':
			{
				struct personinfo *new = (struct personinfo *)malloc(sizeof(struct personinfo));
				inputValue(new);
				if( NULL == head) {
					head = new;
					INIT_LIST_HEAD(head);
				}
				else {
					list_add(new, head);
				}
					
				break;
			}
		case 's':
			{
				//search
				break;
			}
		case 'p':
			{
				 list_for_each(pos, head, member) {
				 // print something
				 }
		default:
			break;
		}
	} while( 'b' != command);
}
		
			
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值