链式线性表,在内存中随机存储;底层实现层无法随机访问内部元素。底层不需要关心应用层的业务逻辑的长什么样的;只需要将一系列的元素给串接起来即可。C模版库不是C++类模版,它无法实现应用层和底层实现层的数据脱离关系,实例没办法复用;而且关联。
应用业务逻辑与底层实现层地址关联。即,只能对其中的实例实现局部赋值,清空操作。
传统链表:
实现层与应用层关联,链表中包含数据域与指针域;无法将数据结构与具体的应用层业务逻辑分离;增删改查,释放集于一体。
数据是死的无法可长可短可宽可瘦,无法将接口给于他人使用。
Linux内核链表:
与传统链表不同的是,传统链表需要自己去包容世界;而Linux内核链表的高超思想是如何让大千世界来包容我。数据域加上固定的偏移量到达指针域,并将应用层业务结点中的指针域与底层实现层的指针链接进行地址重叠;内部组织数据结构,并负责数据的释放。
借助于Linux内核链表的思想,将应用层头部置为指针域,去除数据偏移;并和底层实现层提供的接口层进行靠拢;将双方地址重叠;
即,底层不需要关心具体的业务逻辑,它要做的就是,用一根根的线把结点串接起来。为上层应用层提供所期望的数据逻辑结构。而具体的应用层无法通过底层提供的相应的接口改变其数据,所有的数据都需要应用层自己释放,自己操作与维护。更高级的操作交给其它数据操作接口去。
【底层实现层link.h】
#ifndef _LINKLIST_H_ #define _LINKLIST_H_ //链表的基类型: //仅包含一个需要被业务层重叠地址的next成员 typedef struct node{ /*const*/ struct node *next;//底层要为上层提供服务无需置为只读 }LinkListnode; /**********构造一个"类"结构体作为链表的基类型**********/ typedef struct{ LinkListnode head;//链表的首元结点 int length;//链表的长度(结点的个数) //int size;//链表的数据域大小 }LinkList; /******************对链表进行初始化*******************/ /***************为了不值传二级指针******************** 返回值:初始化后的链表首地址,和业务应用层的首地址 是重合的,业务应用层的数据底层不需要管,但是为了约定俗成 应用层的数据起始地址必须是LinkListnode类型 *****************************************************/ LinkList *init_link(void); /***********对链表清空操作,但保留基类型********/ int clear_list(LinkList *list); /*******对链表删除,删除数据与结构********/ int delete_list(LinkList *list); /***********判断链式线性表是否为空?************* 条件: 1. 首元结点不存在,为错误处理 2.首元结点的next值为NULL,判定为空 返回值:空1,非空0 ***************************************/ int isEmpty(LinkList *list); #if _COMMENT_ 链式存储不存在满的情况 不支持随机访问,每次寻找数据必须找到 相应的地址,其时间复杂度同顺序存储相比,不容忽略 #endif /******************链式线性表的指定插值***************** 规则:按位置进行插值,遍历到指定位置, 并在前一位置停止 统一以pos位置为基准,向后插入一个元素 ********************************************************/ int insert_node(LinkList *list, LinkListnode *node, int pos); /********************链式线性表的查找************************** 每次查找都是一次遍历,不可预估的时间复杂度(暂且忽略,完全轻松跑得起来) *********************************************************/ LinkListnode *find_node(LinkList *list, int pos); /******获取链式线性表的有效元素个数*******/ int get_length(LinkList *list); /******************************************** 删除链式线性表指定位置结点 返回值:成功返回1, 失败或错误找不到结点返回0 ********************************************/ int delete_node(LinkList *list, int pos); /*修改指定位置结点的值*/ int set_node(LinkList *list, LinkListnode *node, int pos); #endif
【link.c】
#include
#include #include #include #include "link.h" LinkList *init_link(void) {//创建链表基类型 LinkList *obj = (LinkList *)malloc(sizeof(LinkList)); //obj->head = (LinkListnode *)malloc(sizeof(LinkListnode)); memset(obj, 0, sizeof(LinkList)); //memset(obj->head, 0, sizeof(*(obj->head))); obj->head.next = NULL; obj->length = 0; if (obj != NULL) { return obj; } return NULL; } /************************************************* 业务层的起始地址是必须和底层匹配的 *************************************************/ int clear_list(LinkList *list) { LinkListnode *p = NULL; int i = 0; if (NULL == list) { fprintf(stderr, "fatal_error"); abort(); } if (isEmpty(list)) { printf("该链表为空链表"); return 1; } p = &list->head; #if 0 因为底层要不关心业务层的具体实现,所以数据域已经和指针域脱离了关系 直接解除这种数据结构的关系即可, 清空与数据无关, 直接首元结点指向NULL 告诉应用层现在你已经不具备数据结构了; 所占用的空间,你自己去释放吧 for (i = 0; i < get_length(list); i++) { p = p->next; q = p; list->head->next = p->next; free(q); printf("%d ", get_length(list)); list->length--; } #endif p->next = NULL; list->length = 0; if (list->length == 0){//长度为0 return 1; } else{ return 0; } } //删除整个链表,释放链表 int delete_list(LinkList *list) { if (NULL == list){ fprintf(stderr, "fatal_error"); abort(); } if (clear_list(list)){ //free(list->head); //list->head = NULL; free(list); list = NULL; } if (NULL == list){ return 1; } else{ return 0; } } //判断链表是否为空 int isEmpty(LinkList *list) { if (NULL == list){ fprintf(stderr, "fatal_error"); abort(); } if (list->head.next == NULL){ return 1; } return 0; } //获取链表的长度 int get_length(LinkList *list) { if (NULL == list){ fprintf(stderr, "fatal_error"); abort(); } if (list->length >= 0){ return list->length; } else{ return -1; } } /************插入结点至顺序表中************/ int insert_node(LinkList *list, LinkListnode *node, int pos) { int i = 0; LinkListnode *p = NULL; int len = 0; if (NULL == list || NULL == node || pos < 0) { fprintf(stderr, "fatal error!"); abort(); } len = get_length(list);//获取之前状态的长度 if (isEmpty(list))//插到首元结点的后面 { node->next = list->head.next; list->head.next = node; list->length++; } else//常规插入,到达指定位置后以指定pos为基准执行后插操作 { p = &list->head; //上面走过等于0的那一步了,后面就不需要再走一便pos = 0了,否则的将导致头结点始终是第一个插入链表的元素 for (i = 0; i < pos; i++) {//大于等于长度会跳出循环,防止发生段错误 if (i >= get_length(list)) break; p = p->next; } node->next = p->next; p->next = node; list->length++; } if (len != get_length(list)) return 1; else return 0; } //断开指定结点位置的链接(应用层失去该节点的数据结构,变成独立的数据,在堆区仍需要手动释放) int delete_node(LinkList *list, int pos) { int i = 0, len = 0;; LinkListnode *p = NULL, *q = NULL; if (NULL == list || pos < 0) { fprintf(stderr, "fatal error"); abort(); } len = get_length(list); p = &list->head; q = &list->head; for (i = 0; i <= pos; i++) { if (i >= get_length(list)) break; q = p; p = p->next; } q->next = p->next; list->length--; if (len != get_length(list)) return 1; else return 0; } int set_node(LinkList *list, LinkListnode *node, int pos) { LinkListnode *p = NULL; int i = 0; if (NULL == list || NULL == node || pos < 0) { fprintf(stderr, "fatal error!"); abort(); } else { p = &list->head; for (i = 0; i <= pos; i++) { if (i >= get_length(list)) break; p = p->next; } p = node;//直接替换 if (p == node) { return 1; } } return 0; } //查找元素 LinkListnode *find_node(LinkList *list, int pos) { LinkListnode *p = NULL; int i = 0; if (NULL == list || pos < 0) { fprintf(stderr, "fatal error"); abort(); } p = &list->head; for (i = 0; i <= pos; i++) { if (i >= get_length(list)) break; p = p->next; } if (p != &list->head)//找到了 return p; else return NULL; return NULL; } 【引用层的业务逻辑实现:main.c】
#include
#include #include #ifdef _WIN_OS #include #endif #include "seqlinklist.h" #define _WIN_OS_ //应用层随意设计自己的数据类型 //但必须在头部包含一个双方约定好的指针域 typedef struct{ const LinkListnode area;//设置为只读,应用层无法直接修改它 int age; char name[20]; }Msg; int main() { LinkList *list = NULL; int i = 0; Msg *obj1, *obj2, *obj3, *obj4, *obj5;//堆区创建5个结点 Msg *display = NULL; obj1 = (Msg *)malloc(sizeof(Msg)); memset(obj1, 0, sizeof(Msg)); obj2 = (Msg *)malloc(sizeof(Msg)); memset(obj2, 0, sizeof(Msg)); obj3 = (Msg *)malloc(sizeof(Msg)); memset(obj3, 0, sizeof(Msg)); obj4 = (Msg *)malloc(sizeof(Msg)); memset(obj4, 0, sizeof(Msg)); obj5 = (Msg *)malloc(sizeof(Msg)); memset(obj5, 0, sizeof(Msg)); obj1->age = 10; strcpy(obj1->name, "小王"); obj2->age = 11; strcpy(obj2->name, "小李"); obj3->age = 12; strcpy(obj3->name, "小张"); obj4->age = 13; strcpy(obj4->name, "小林"); obj5->age = 14; strcpy(obj5->name, "小夏"); list = init_link(); if (list == NULL) { printf("初始化链表失败\n"); free(obj1); free(obj2); free(obj3); free(obj4); free(obj5); exit(-1); } if (insert_node(list, (LinkListnode *)obj1, 0) printf("插入obj1成功\n"); if (insert_node(list, (LinkListnode *)obj2, 0)) printf("插入obj2成功\n"); if (insert_node(list, (LinkListnode *)obj3, 0)) printf("插入obj3成功\n"); if (insert_node(list, (LinkListnode *)obj4, 0)) printf("插入obj4成功\n"); if (insert_node(list, (LinkListnode *)obj5, 0)) printf("插入obj5成功\n"); printf("\n=================遍历链表=================\n"); for (i = 0; i < get_length(list); i++) { display = (Msg *)find_node(list, i); printf("%s的年龄是%d\n", display->name, display->age); } printf("\n==========设置指定位置元素的数据结构,3号位置(4号值)==========\n"); //memset(obj2, 0, sizeof(Msg));这里注意:是不可修改应用层内存的!!! //因为地址和底层实现层的地址是重叠的,一方修改了会相当于链表失去了其中一个元素的后继 obj2->age = 23; strcpy(obj2->name, "halo"); if (set_node(list, (LinkListnode *)obj2, 3)) { printf("设置位置值成功\n"); printf("=================遍历=================\n"); for (i = 0; i < get_length(list); i++) { display = (Msg *)find_node(list, i); printf("%s的年龄是%d\n", display->name, display->age); } } else { printf("设置位置值失败\n"); } printf("\n=======解除数据指定位置元素的数据结构,3号位置============\n");//应用层内存仍然存在 if (delete_node(list, 3)) { for (i = 0; i < get_length(list); i++) { display = (Msg *)find_node(list, i); printf("%s的年龄是%d\n", display->name, display->age); } } printf("\n=======解除所有元素的数据结构============\n"); if (delete_list(list)) { printf("解除成功,释放成功\n"); } else { printf("解除失败\n"); } free(obj1); free(obj2); free(obj3); free(obj4); free(obj5); #ifdef _WIN_OS system("pause"); #endif return 0; } 【Makefile】
.PHONY: all clean clean_a obj = link.o main.o target = test cc = gcc CFLAGS = -g -Wall all:$(target) $(target):$(obj) $(cc) $(obj) -o $@ list.o:link.c link.h $(cc) $(CFLAGS) -c $^ main.o:main.c $(cc) $(CFLAGS) -c $^ clean: rm -rf $(obj) clean_a: rm -rf $(obj) $(target)
发现模版库写到最后就是像在写C++模版类,但实际在应用层上面复用完全没问题。只是相比较C++类模版来实现库会更加灵活,高效,方便。
继续重新学习认识编程语言与底层实现的美学。