高级 高级 还是 高级
🤖 结构和联合
C提供了两种类型的聚合数据类型(aggregate data type)。数组是相同类型的元素的集合,结构体是可具有不同类型的元素的集合。
数组可以通过下标访问,且数据不能相互赋值,只能通过循环逐个赋值。
结构体通过名字访问,相同类型的结构体变量可以相互赋值。
10.1 结构基础知识
结构体是一些值的集合,这些值称为它的成员(member),但一个结构体的各个成员可能具有不同的类型。
结构变量属于标量类型,所以你可以像对待其他标量类型那样执行相同类型的操作。
10.1.1 结构声明
在声明结构时,必须列出它包含的所有成员。
struct tag {member-list} variable-list;结构声明语法的不同
struct {
int a;
char b;
float c;
}x;
struct {
int a;
char b;
float c;
} y[20],*z;这两个声明被编译器当作两种截然不同的类型,即使它们的成员列表完全相同。
z = &x;这条语句是非法的。
- 使用 标签(tag) 和 类型定义别名(typedef) 来更方便地声明和定义一个结构体变量。
标签(tag) 字段允许为成员列表提供一个名字。
struct SIMPLE{
int a;
char b;
float c;
};
struct SIMPLE x,y[20],*z;这个声明使用标签来创建变量,且现在x,y,z都是同一种类型的结构变量。
类型定义别名(typedef) 字段可以创建一个新的类型。
typedef struct {
int a;
char b;
float c;
} Simple;
Simple x;
Simple y[20], *z;Simple现在是一个类型而不是个结构标签。
如果你想在多个源文件中使用同一种类型的结构,你应该把标签声明或
typedef形式的声明放在一个头文件中。当源文件需要这个声明时可以使用#include指令把那个头文件包含进来。
10.1.2 结构成员
struct COMPLEX{
float f;
int a[20];
long *lp;
struct SIMPLE s;
struct SIMPLE sa[10];
struct SIMPLE *sp;
};结构成员可以是标量、数组、指针甚至是其他结构体。
10.1.3 结构成员的直接访问
- 结构变量的成员是通过点操作符
.访问的。 - 点操作符接受两个参数,左操作数就是结构变量的名字,右操作数就是需要访问的成员的名字。这个表达式的结果就是指定的成员。
struct COMPLEX comp;
(comp.s).a; // 类型为struct SIMPLE
(comp.sa)[4]; // 同上类型的数组
((comp.sa)[4]).c // 取出数组元素
结合性都是从左到右
comp.sa[4].c; equals ((comp.sa)[4]).c;10.1.4 结构成员的间接访问
- 对于指向结构体的指针要访问其元素应该执行间接访问操作
->。
void func(struct COMPLEX *cp);
// 第一种访问方式
(*cp).f;
// 第二种访问方式
cp->f;10.1.5 结构的自引用
// 作为结构这种声明是非法的,程序内部会无限包含自身和结构的成员。(永不终止的递归程序)
// 如果我定义了 struct SELF_REF1 b; 那么 sizeof(SELF_REF1) = sizeof(int) + (4+4+4+.....) + sizeof(int)
// 无法计算,无法在内存中分配一个固定大小的空间。
struct SELF_REF1 {
int a;
struct SELF_REF1 b; // 非法,不能这样定义。
int c;
};
// 作为指针这个声明是合法的,因为指针的长度在编译器确定结构体长度前就知道了。
// sizeof(SELF_REF2) = sizeof (int) + sizeof(SELF_REF2*) + sizeof(int)
// 可以计算固定大小空间
struct SELF_REF2{
int a;
struct SELF_REF2 *b;
int c;
};事实上一个结构内指向自身结构的指针所指向的是同一种类型的不同结构。 更高级的数据结构如链表和树,都是用这些技巧实现的。每个结构指向链表的下一个元素或树的下一个分支。
// 这个结构体创建失败了,因为SELF_REF3 直到声明的末尾才定义,所以在结构
// 声明的内部时还尚未定义。
typedef struct {
int a;
SELF_REF3 *b;
int c;
}SELF_REF3;
// 解决方案是定义一个结构标签来声明b
typedef struct SELF_REF3_TAG {
int a;
struct SELF_REF3_TAG *b;
int c;
}SELF_REF3
// 这次正确定义了结构体。10.1.6 不完整的声明
- 在声明一些相互之间存在依赖的结构时使用不完整声明(incomplete declaration)
struct B;
struct A {
struct B *parnter;
};
struct B {
struct A *partner;
};在A的成员列表中需要标签B的不完整声明。一旦A被声明之后,B的成员列表也可以被声明。
10.1.7 结构的初始化
- 位于花括号,由逗号分隔。
struct INI_EX {
int a;
short b[10];
Simple c;
}x = {
10,
{1,2,3,4,5},
{25,'x',1.9},
};
// 另一种初始化
struct INI_EX x1 = {
10,
{1,2,3,4,5},
{25,'x',1.9},
};10.2 结构、指针和成员
声明和定义一些结构体和结构体变量
typedef struct {
int a;
short b[2];
}Ex2;
typedef struct EX{
int a;
char b[3];
Ex2 c;
struct EX *d;
}Ex;
// 定义并初始化
Ex x = {10, "Hi", {5,{ -1 , 25 }}, 0};
Ex *px = &x;10.2.1 访问指针
step1: px是一个指针变量,px的表达式Ex *px = x;表示作为左值的px旧值将被一个新值取代。
考虑表达式
px + 1。这个表达式并不是一个合法的左值,因为它的值并不存储于任何可标识的内存位置。px的右值更有意思,如果px指向一个结构数组的元素,这个表达式将指向该数组的下一个结构。就算如此px + 1仍是非法的,因为我们没办法分辨内存下一个位置所存储的是这些结构元素之一还是其他东西。编译器无法检测到这类错误。
10.2.2 访问结构
step2: *px的右值是px所指向的整个结构。可以用于同类型结构体赋值,作为点操作符的左操作数,访问一个指定的成员,作为参数传递给函数,作为函数的返回值返回。px的左值是从x接收来的新值,它将接受它的所有成员的新值。
- 作为左值,重要的是位置,而不是这个位置所保存的值。
表达式*px + 1是非法的,因为*px的结果是一个结构。C语言并没有定义结构体和整型值之间的加法运算。但表达式*(px+1)中的px+1表示结构体指针但x是一个标量所以这个表达式也是非法的。
10.2.3 访问结构成员
step3: 表达式px->a右值是10,x.a和px->a值相同。
*px和px->a之间的关系。在这两个表达式中px所保存的地址都用于寻找这个结构。但结构体的第一个成员是a。所以a的地址和结构的地址是一样的。这样px看上去是指向整个结构,同时指向结构的第一个成员。但是他们的类型不同,变量px被声明为一个指向结构的指针,所以表达式*px的结果是整个结构而不是它的第一个成员。
int *pi;
pi = px;
// 这个操作是非法的,因为它们的类型不匹配。
pi = (int *)px;
// 使用强制类型转换就能奏效
// 但是这种方法很危险,因为它避开了编译器的类型检查。
// 正确的表达式更为简单
pi = &px->a;
// -> 操作符的优先级高于&操作符的优先级,所以这个表达式无需使用括号。px->b的值是一个指针常量,因为b是一个数组这个表达式px->b不是一个合法的左值。
char *pc;
pc = px->b; // 一个指针常量
pc = px->b[1]; // 指向数组的第二个元素10.2.4 访问嵌套的结构
step4: 表达式px->c也是指向一个结构体,它的左值是整个结构。
int num = px->c.a; 指向结构体内结构体并访问结构体成员a
short *num1 = px->c.b;
int num2 = *px->c.b;10.2.5 访问指针成员
step5: px->d的右值是0,左值是本身的内存位置。*px->d是非法的操作,因为d内包含了一个NULL指针,所以它不指向任何东西。
Ex te;
te = *px->d;
x.d = &te;空指针的本质:地址0
空指针是一个特殊的指针值,它表示该指针不指向任何有效的内存对象。 解引用空指针后CPU会尝试访问地址0-->操作系统会捕获异常-->触发硬件异常(Page Fault)或(Segmentation Fault)-->内核终止程序。
10.3 结构的存储分配
- 编译器按照成员列表的顺序一个接一个地给每个成员分配内存。只有当存储成员时需要满足正确的边界要求时,成员之间才可能出现用于填充的额外内存空间。
struct ALIGN{
char a;
int b;
char c;
};这个结构体实际分配了12个字节的内存空间但是有6个字节空间实际上不能访问。
struct ALIGN2{
int b;
char a;
char c;
};这个结构体实际分配了8个字节的内存空间。(两个字符可以紧挨着存储,最后有2个字节被浪费)
但是实际上依程序的可维护性和可读性而言不是特别大的内存损失都不需要重新排。
- 在程序创建成百上千个结构体时内存浪费问题就更为明显。
- 可以使用
offsetof宏(定义于stddef.h)判断结构体内成员距离首地址的偏移值
offsetof(type,member) // type 是结构体类型名,member是结构体里面的成员名
offsetof(struct ALIGN, b) // 返回值是 4,也就是成员a占用了4个字节用于结构体内内存对齐10.4 作为函数参数的结构
- 非必要不建议将结构体作为函数参数传递
- 结构体作为一个标量的大小可能会比想象中的大
typedef struct
{
char product[PRODUCT_SIZE];
int quantity;
float unit_price;
float total_amount;
};
void print_receipt(Transaction trans);
void print_receipt(Transaction *trans);一个传递的是结构体的拷贝,一个传递的是结构体指针。就大小而言指针比结构体小得多,效率更高。
Transaction print_receipt(Transaction trans);
void print_receipt(Transaction *trans);如果结构体作为函数返回值在堆栈上的操作效率会更低,传递结构体指针可以直接在函数内部完成结构体成员的修改。
10.5 位段
- 结构体具有实现 位段(bit field) 的能力
- 位段的成员是一个或多个位的字段,这些不同长度的字段实际上存储于一个或多个整型变量中。
- 位段成员必须声明为
int,unsigned int,signed int,_Bool(C99)类型,在成员名的后面是一个冒号和一个整数。 - 不能对位段成员取地址(不能使用
&运算符) - 位段不能是数组
- 可移植性的程序应该避免使用位段。
- 位段和结构体成员一样之间需要内存对齐(在位段与位段之间插入填充位(Padding))
struct CHAR {
unsigned ch : 7;
unsigned font : 6;
unsigned size : 19;
};因为最后一个位段size过于庞大(大于短整型的长度),所以可以利用前两个位段ch和font所剩余的位来增加size的位数,这样就避免了声明一个32位的整数来存储size位段。
CHAR这个结构体内的位段说明了位段可以把长度为奇数的数据包装在一起,节省存储空间
10.6 联合
- 联合所有成员引用的是内存中的相同位置
- 适用于在不同时刻把不同的东西存储于同一个位置时
union {
float f;
int i;
} fi;
fi.f = 3.14159;
printf("%d\n", fi.i);在一个浮点型和整型都是32位的机器上,变量 fi 只占据内存中的一个32位的字。最后用占位符%d输出一个浮点数不是3而是1078530000,与IEEE754单精度浮点标准有关。
10.6.1 变体记录
- 内存中某个特定的区域将在不同的时刻存储不同类型的值
在 C 语言中,可以使用 联合体(Union) 和 结构体(Struct) 结合的方式来模拟 Pascal 语言中的变体记录(Variant Record),也称为带标签的联合体(Tagged Union)。
这种模式是 C 语言处理异构数据集合的标准方法,同时提供了类型安全性和可预测性。
一个存货系统的变体记录
// 前两个结构指定每个零件和装配件必须存储的内容
struct PARTINFO {
int cost;
int supplier;
};
struct SUBASSYINFO {
int n_parts;
struct {
char partno[10];
short quan;
}part[MAXPARTS];
};
// 存货记录包含每个项目的一般信息并包含了一个联合
struct INVREC {
char partno[10];
int quan;
enum { PART, SUBPASSY } type;
union {
struct PARTINFO part;
struct SUBASSYIINFO subassy;
}info;
};
// 操作名为 rec 的 INVERC 结构变量
if (rec.type == PART){
y = rec.info.part.cost;
z = rec.info.part.supplier;
}
else{
y = rec.info.subpassy.nparts;
z = rec.info.subpassy.parts[0].quan;
}10.6.2 联合的初始化
- 联合初始化的初始值必须是联合第一个成员的类型,且必须位于一对花括号里
union {
int a;
float b;
char c[4];
} x = { 5 };把
x.a初始化为 5,如果给出的初始值是任何其他类型都会被转换为一个整数并赋值给x.a
♿ 动态内存分配
- 数组的元素存储于内存中连续的位置上。当一个数组被声明时,它所需要的内存在编译时就被分配。但是也可以使用动态内存分配在运行时为它分配内存。
11.1 为什么使用动态内存分配
如果是已经知道数量大小的数组分配发生在编译时,但如果在编译时不能确定数组长度(数组的长度常常在运行时才知道),因为所需内存空间取决于输入数据。
11.2 malloc和free
malloc执行动态内存分配free执行分配内存的释放。这些函数维护一个可用内存池。malloc分配的动态内存没有初始化,可以使用calloc函数初始化也可以手动初始化。
函数原型(在stdlib.h中声明)
void *malloc(size_t size);
void *free(void *pointer);malloc分配的是一块连续的内存,如果请求分配100字节的内存那么实际分配的内存就是100个连续的字节。
malloc分配的内存可能比请求的内存大小稍微多一点,这个行为是由编译器定义的。
内存池如果是空的(可用内存无法满足请求)malloc函数会像操作系统请求得到更多的内存。并在这块新的内存上执行分配任务。如果操作系统无法向malloc提供更多的内存,malloc就返回一个NULL指针。因此对malloc所分配的内存确保其是非空(NULL)是非常重要的。
int *a_pointer = (int*)malloc(sizeof(int) * 100);
if (a_pointer == NULL)
return -1; // 在函数内提前退出并返回错误值-1free的参数只能是NULL或是之前请求分配内存函数malloc,calloc或realloc的返回值。向free函数传递一个NULL参数没有任何意义。
因为malloc的返回值是一个
void*类型,在比较老的编译器(C89或之前)可能会要求对返回值进行强制类型转换(int*)。
二次释放和悬空指针:对同一块内存调用两次
free(ptr)会导致堆损坏和程序崩溃;free(ptr)后ptr仍然指向已释放的内存。为了安全应立即执行ptr=NULL将指针置为空指针,避免后续误用。
11.3 calloc和realloc
函数原型(在stdlib.h中声明)
void *calloc(size_t num_elements, size_t element_size);
void *realloc(void *ptr,size_t new_size);calloc也用于分配内存,而realloc用于修改一个原先已经分配的内存块大小,使用realloc可以扩大和缩小内存大小。
malloc分配的内存是未初始化的,内容是随机的垃圾值;calloc分配的内存会被初始化为全0。realloc重新分配内存大小失败时会返回NULL但原始指针ptr指向的内存块仍有效,数据保持不变。
realloc(NULL,size) == malloc(size)
realloc(ptr,0) == free(ptr)并返回NULL
11.4 使用动态分配的内存
int *pi;
...
pi = malloc(100); // 如果分配成功,在整型为 4 个字节大小的机器上被当作25个整型元素的数组
pi = malloc(25 * sizeof(int)); // 这种分配方式更好一些因为它是可移植的
...
// 使用内存:为内存分配元素
int *pi2, i;
pi2 = pi;
for(;pi2 != pi + 25;)
*pi2++ = 0;
// 使用下标
for(i = 0; i < 25; i++)
pi[i] = 0;11.5 常见的动态内存错误
- 释放内存的一部分是不允许的,动态分配的内存必须一起释放。可以使用
realloc函数缩小一块动态分配的内存并有效地释放尾部的部分内存(还是用原分配函数的返回值)
pi = malloc(10 * sizeof(int));
free(pi + 5); // 释放部分内存内存泄露
分配内存但在使用完毕后不释放将引起内存泄露(memory leak)。在那些所有执行程序共享一个通用内存池的操作系统中,内存泄露将一点点地榨干可用内存。
其他操作系统能够记住每个程序当前拥有的内存段,这样当一个程序终止时,所有分配给它但未被释放的内存都归还给内存池。
🤧使用结构和指针
12.1 链表
- 链表(linked_list) 就是一些包含数据的独立数据结构的集合。链表中的每个节点通过链或指针连接在一起。程序通过指针访问链表中的节点。通常节点是动态分配的,也有由节点数组构建的链表。
12.2 单链表
- 在单链表中,每个节点包含一个指向链表下一节点的指针。链表最后一个节点的指针字段的值为
NULL,提示链表后面不在有其他节点。在找到链表的第一个节点后,指针就可以带你访问剩余的所有节点。为了记住链表的起始位置,可以使用一个根指针(root pointer)。根指针指向链表的第一个节点。注意根指针只是一个指针,它不包含任何数据。
typedef struct NODE{
struct NODE *link; // 指向下一个节点的指针
int value; // 存储数据的变量
} Node;单链表无法从结束位置往前遍历
12.2.1 在单链表中插入
// 插入到一个有序的单链表。函数的参数是一个指向链表第一个节点的指针以及需要插入的值。
#include <stdlib.h>
#include <stdio.h>
#include "sll_node.h"
#define FALSE 0
#define TRUE 1
int sll_insert(Node *current, int new_value)
{
Node *previous;
Node *new;
// 寻找正确的插入位置,方法是按顺序访问链表,直到到达其值大于或等于新插入值的节点。
while(current->value < new_value)
{
previous = current;
current = current->link;
}
// 为新节点分配内存,并把新值存储到新节点中,如果内存分配失败。
// 函数返回false
new = (Node*)malloc(sizeof(Node));
if (new == NULL)
return FALSE;
new->value = new_value;
// 把新节点插入到链表中,并返回true
new->link = current;
previous->link = new;
return TRUE;
}result = sll_insert(root,12); // 假设有一个节点存储15,插入在这个节点前- 这个插入函数是不正确的,它不能处理插入最后一个节点后的场景(最后一个节点的link为NULL),也不能处理插入第一个节点前的场景(函数不能访问root,previous未定义)
- 可以将一个指向root的指针作为参数传递给函数。然后使用间接访问,函数不仅可以获得root(指向链表第一个节点的指针,也就是根指针)的值,也可以向它存储一个新的指针值(解决current和previous分开的问题,函数总是可以判断Node**是否满足条件并间接访问root判断值大小是否满足。Node**总是作为当前节点的前一个链接字段。)
#include <stdlib.h>
#include <stdio.h>
#include "sll_node.h"
#define FALSE 0
#define TRUE 1
int sll_insert(Node **rootp, int new_value)
{
Node *current;
Node *previous;
Node *new;
// 得到指向第一个节点的指针
current = *rootp;
previous = NULL;
// 寻找正确的插入位置,方法是按序访问链表,直到到达一个其值大于或等于新值的节点
while(current != NULL && current->value < new_value)
{
previous = current;
current = current->link;
}
// 为新节点分配内存,并把新值存储到新节点中,如果内存分配失败,
// 函数返回false
new = (Node*)malloc(sizeof(Node));
if (new == NULL)
return FALSE;
new->link = current;
if (previous == NULL)
*rootp = new;
else
previous->link = new;
return TRUE;
}
int sll_insert_ease(Node **head, int new_value)
{
Node *new_node;
// current_ptr 现在指向的是一个指针 (head 或 link 字段)
// 初始时指向调用者的 head 指针变量
Node **current_ptr = head;
// 1. 寻找插入位置:循环直到指针指向NULL(末尾)或指向的值 >= new_value
while (*current_ptr != NULL && (*current_ptr)->value < new_value)
current_ptr = &(*current_ptr)->link;
// 2. 分配新节点
new_node = (Node*)malloc(sizeof(Node));
if (new_node == NULL)
return FALSE;
new_node->value = new_value;
// 3. 插入:新节点指向 current_ptr 当前指向的那个节点
new_node->link = *current_ptr;
// 4. 核心:更新current_ptr 指向的指针变量,让它指向新节点
*current_ptr = new_node; // *current_ptr 其实就是上一个节点的link
return TRUE;
}
// 书上的优化
int sll_insert(register Node **linkp, int new_value)
{
register Node *current;
register Node *new;
while ((current = *linkp) != NULL && current->value < new_value)
linkp = ¤t->link;
new = (Node*)malloc(sizeof(Node));
if (new == NULL)
return FALSE;
new->value = new_value;
new->link = current;
*linkp = new;
return TRUE;
}12.3 双链表
- 单链表的替代方案是双链表。在一个双链表中,每个节点都包含两个指针,指向前一个节点的指针和指向后一个节点的指针。这样就可以以任何方向遍历双链表,甚至可以忽前忽后地在双链表中访问。
typedef struct NODE {
struct NODE *fwd;
struct NODE *bwd;
int value;
};现在存在两个指针:一个指向链表的第一个节点(*fwd),另一个指向最后一个节点(*bwd)。如果当前链表为空,这两字段都为NULL。
12.3.1 在双链表中插入
dll_insert函数接受两个参数:一个指向根节点的指针和一个整型值。- 先前所编写的单链表插入函数把重复的值也添加到链表中。在有些应用程序中,不插入重复的值可能更为合适。
dll_insert函数只有当欲插入的值原先不存在于链表中时才将其插入。
考虑四种情况:
- 新值可能必须插入到链表的中间位置。
- 新值可能必须插入到链表的起始位置。
- 新值可能必须插入到链表的结束位置。
- 新值可能必须既插入到链表的初始位置,又插入到链表的结束位置(即原链表为空)。
/*
把一个值插入到一个双链表,rootp是一个指向根节点的指针,
value是欲插入的新值
返回值:如果欲插值原先已存在于链表中,函数返回0;
如果内存不足导致无法插入,函数返回-1;如果插入成功,函数返回1。
*/
#include <stdlib.h>
#include <stdio.h>
#include "doubly_linked_list_node.h"
int dll_insert(Node *rootp, int value)
{
Node *this;
Node *next;
Node *newnode;
/*
查看value是否已经存在于链表中,如果是就返回
否则,为新值创建一个新节点("newnode"将指向它)
"this"将指向应该在新节点之前的那个节点。
"next"将指向应该在新节点之前的那个节点。
*/
for (this = rootp; (next = this->fwd) != NULL; this = next){
if (next->value == value)
return 0;
if (next->value > value)
break;
}
newnode = (Node*)malloc(sizeof(Node));
if (newnode == NULL)
return -1;
newnode->value = value;
// 把新值添加到链表中
if (next != NULL)
{
// 情况1或2:并非位于链表尾部
if (this != rootp) // 情况1:并非位于链表起始位置
{
newnode->fwd = next;
this->fwd = newnode;
newnode->bwd = this;
next->bwd = newnode;
}
else // 情况2:位于链表的起始位置
{
newnode->fwd = next;
rootp->fwd = newnode;
newnode->bwd = NULL;
next->bwd = newnode;
}
}
else
{
// 情况3或4:位于链表的尾部
if (this != rootp) // 情况3:并非位于链表的起始位置
{
newnode->fwd = NULL;
this->fwd = newnode;
newnode->bwd = this;
rootp->bwd = newnode;
}
else // 情况4:位于链表的起始位置
{
newnode->fwd = NULL;
rootp->fwd = newnode;
newnode->bwd = NULL;
rootp->bwd = newnode;
}
}
return 1;
}语句提炼(statement factoring)
上面的双链表在最后判断节点插入位置时各个嵌套的if语句群存在大量的相似之处,可以使用语句提炼技巧消除这些重复代码
if (x == 3)
{
i = 1;
some statement;
j = 2;
}
else
{
i = 1;
some statement different;
j = 2;
}这里不管
x == 3的值是真是假,语句i = 1和j = 2都将执行。且这两天语句在if条件判断前都不会执行,所以:
i = 1;
if (x == 3)
some statement;
else
some statement different;
j = 2;但是如果是对测试的结果有所影响的语句千万不能提炼出来!
/*
双链表部分代码使用语句提炼
*/
// 把新节点添加到链表中
if(next != NULL)
{
newnode->fwd = next;
if (this != rootp)
{
this->fwd = newnode;
newnode->bwd = this;
}
else
{
rootp->fwd = newnode;
newnode->bwd = NULL;
}
next->bwd = newnode;
}
else
{
newnode->fwd = NULL;
if (this != rootp)
{
this->fwd = newnode;
newnode->bwd = this;
}
else
{
rootp->fwd = newnode;
newnode->bwd = NULL;
}
rootp->bwd = newnode;
}第二个简化技巧
if (pointer != NULL)
field = pointer;
else
field = NULL;这段代码的意思是设置一个和pointer相等的变量,如果pointer未指向任何东西,这个变量就设置为NULL。但是:
field = pointer;这个代码的意思其实和上面的一模一样,也就是第三四种情况的else语句内的newnode->fwd = NULL可以写成newnode->fwd = next;同理rootp->fwd = newnode也可以写成this->fwd = newnode。
/*
最终提炼的双链表插入函数
*/
#include <stdio.h>
#include <stdlib.h>
#include "doubly_linked_list_node.h"
int dll_insert(register Node *rootp, int value)
{
register Node *this;
register Node *next;
register Node *newnode;
/*
查看value是否已经存在于链表中,如果是就返回。
否则,为新值创建一个新节点("newnode"将指向它)。
"this"将指向应该在新节点之前的那个节点,
"next"将指向应该在新节点之后的那个节点。
*/
for (this = rootp; (next = this->fwd) != NULL; this = next)
{
if (next->value == value)
return 0;
if (next->value > value)
break;
}
newnode = (Node*)malloc(sizeof(Node));
if (newnode == NULL)
return -1;
newnode->value = value;
// 把新节点添加到链表中
newnode->fwd = next;
this->fwd = newnode;
//if (this != rootp)
// newnode->bwd = this;
//else
// newnode->bwd = NULL;
newnode->bwd = (this != rootp) ? this : NULL;
//if (next != NULL)
// next->bwd = newnode;
//else
// rootp->bwd = newnode;
(next != NULL ? next : rootp)->bwd = newnode;
return 1;
}