[C和指针]第三部分

 

第八章     结构和联合... 1
第九章     动态内存分配... 9
第十章     使用结构和指针(链表实现)... 14数组

第八章     结构和联合


C中,使用结构能够把不一样类型的值存储在一块儿。数组是经过下标访问,由于数组的元素长度相同,可是结构并非这样,每一个结构成员都有本身的名字,它们是经过名字来访问的。
 
结构变量属于标量类型,并非指针类型,和其余任何标题同样,当结构名在表达式中做为右值使用时,它表示存储在结构中的值。当它做为左值使用时,它表示结构存储的内存位置。可是,当数组名在表达式中做为右值使用时,它的值是一个指向数组第一个元素的指针,因为它的值是一个常量指针,因此数组名不能做为左值使用。能够声明指向结构的指针与结构数组。
 
struct 标识 { 成员列表变量列表 ;
 
struct {
    inta;
    charb;
    floatc;
} x;
struct {
    inta;
    charb;
    floatc;
} y[10], *z;
注意,上面是两种大相径庭的类型,因此不能这样:z = &x
 
struct SIMPLE {
    inta;
    charb;
    floatc;
};
该声明并无提供变量列表,因此它并未建立任何变量,下面才开始建立变量。
struct SIMPLE x;
struct SIMPLE y[10], *z;
但如今与上前面的声明不一样的是 x y z 都是同一种类型的结构变量了,因此z = &x能够
 
typedefstruct {
    inta;
    charb;
    floatc;
} Simple;
Simple如今是一个类型名而不是一个结构标签,因此后续的声明能够是下面这样:
Simple x;
Simple y[10], *z;
 
结构能够嵌套其余结构:
    struct COMPLEX {
       struct SIMPLE sa[10];
       struct SIMPLE *sp;
    } comp, *cp = ∁
 
结构的访问
    typedefstruct EX {
       inta;
       charb[3];
       struct EX2 {
           inta;
           shortb[2]
       } c;
       struct EX *d;
    } EX;
    EX x = { 10, "Hi", { 5, { -1, 25 } }, 0 };
    EX *px = &x;
    EX y;
    x.d = &y;
    printf("%d", x.a);//10//结构体变量的访问方式
    printf("%d", (*px).a);//10//结构体指针的访问方式
    printf("%d", px->a);//10//此种为上面结构指针的简化访问方式
    printf("%d", *px->c.b);//-1//混合使用
    printf("%d", px->d->c.b[1]);//30656
    px->d->c.b[1]=11;
    printf("%d", px->d->c.b[1]);//11 
 
  clip_image002
结构的自引用
    struct SELF_REF1 {
inta;
       struct SELF_REF1 b;//非法
    };
----
    struct SELF_REF2 {
       inta;
struct SELF_REF2 *b;//合法
    };
    printf("%d", sizeof(a.a));//4
    printf("%d", sizeof(a.b));//4
第一个声明会尝试递归下去,但最终结构的长度不能肯定下来而编译时出错误;但第二个是一个指针,其在编译时可以肯定结构体的长度,通常链表和树都是用这种技巧实现。
 
32位机上,一个指针一般占4个字节,而无论它指向什么类型的变量,由于指针是用来存储存储地址的:
    char *p = NULL;
    printf("%d", sizeof(p));//4
    printf("%d", sizeof (char *));//4
 
书上说下面是非法的(缘由就是类型名直到声明的末尾才定义,在结构声明的内部它尚定义),但实际上编译能够经过:
    typedefstruct {
       inta;
       struct SELF_REF3 *b;//合法
    } SELF_REF3;
    SELF_REF3 a = { 3, NULL };
    printf("%d", a.a);//3
 
相互引用的结构
有时须要声明一些相互之间存在依赖的结构,先被引用的结构须要使用不完整声明(以下面的struct B;),来声明一个做为结构标签的标识符,而后咱们能够把这个标签与用在结构指针的声明中(以下面的struct B *partner;):
    struct B;
    struct A {
       inta;
       struct B *partner;
    };
    struct B {
       struct A partner;
    };
经实际测试,struct B;能够注释掉,编译时都不会出错,甚至将后面的B结构的定义一块儿去掉都没有问题,这也进一肯说声明结构体指针时不须要知道它所指向的结构体的具体类型,甚至不须要定义均可以
 
结构体的初始化
结构体的初始化相似于或多维数组的初始化:
    struct A {
       inta;
       shortb[10];
       Simplec;
    } x = { 10, { 1, 2 }, { 25, 'x', 1.9 } };
 
 
结构的地址与第一个成员的地址是相同的:
    struct A {
       inta;
       shortb[10];
    } x;
    printf("%p\n", &x);//0022FF38
    printf("%p\n", &x.a);//0022FF38
    printf("%p\n", &x);//0022FF38
    printf("%p\n", &x.a);//0022FF38
   
结构的存储分配
上面章节中(“结构的访问”),显示的是结构的逻辑存储结构(图中分配的空间好像不连续),那结构在内存中是如何实际存储的呢?上面那张图并不完整,实际上编译器会按照成员列表的顺序(声明的顺序)一个接一个地给每一个成员分配内存,只有当存储成员时须要知足正确的边界要求时,成员之间才可能出现用于填充的额外内存空间(被浪费掉了的),以下面图中的灰色地带:
#include<stddef.h>
       typedefstruct ALIGN {
       chara;
       intb;
       charc;
    }ALIGN;
    printf("%d", offsetof(struct ALIGN,a));//0
    printf("%d", offsetof(struct ALIGN,b));//4
    printf("%d", offsetof(struct ALIGN,c));//8
    printf("%d", sizeof (struct ALIGN));//12 
clip_image002[4]
若是编译器有成员对齐这一要求时,会按照最长类型来分配每个成员,如上面的a成员,在整型类型长度为4字节的机器上,因为a是一个字符型,虽然自己只需一个字节,但也为a分配了4个字节,这将浪费3个字节的空间,c也是同样,此时须要总共12个字节的空间。若是将短的类型放在一块儿,这将会节约空间(此时只须要8个字节的空间):
    typedefstruct ALIGN {
       intb;
       chara;
       char c;
    }ALIGN;
    printf("%d", offsetof(struct ALIGN,b));//0
    printf("%d", offsetof(struct ALIGN,a));//4
    printf("%d", offsetof(struct ALIGN,c));//5
    printf("%d", sizeof (struct ALIGN));//8 
clip_image002[6]
下面与上面也是同样节约空间:
    typedefstruct ALIGN {
       chara;
       char c;
       int b;
    }ALIGN;
    printf("%d", offsetof(struct ALIGN,a));//0
    printf("%d", offsetof(struct ALIGN,c));//1
    printf("%d", offsetof(struct ALIGN,b));//4
    printf("%d", sizeof (struct ALIGN));//8
 
最后看看下面这个程序:
    typedefstruct ALIGN {
       chara;
       charc;
       chard;
       shorte;
       charf;
       charj;
       intb;
    }ALIGN;
    printf("%d", offsetof(struct ALIGN,a));//0
    printf("%d", offsetof(struct ALIGN,c));//1
    printf("%d", offsetof(struct ALIGN,d));//2
    printf("%d", offsetof(struct ALIGN,e));//4
    printf("%d", offsetof(struct ALIGN,f));//6
    printf("%d", offsetof(struct ALIGN,j));//7
    printf("%d", offsetof(struct ALIGN,b));//8
    printf("%d", sizeof (struct ALIGN));//12 
 
  clip_image002[8]
sizeof操做符可以得出一个结构体的总体长度,包括因边界对齐而路过的那些字节,若是想获得每一个成员偏离结构首的字节数,则可使用offsetof宏。
 
降序排列结构成员的声明能够最大限度地减小结构存储中浪费的内存空间。sizeof返回的值包含告终构中浪费的内存空间。
 
参数结构体
把结构体做为参数传递给一个函数是合法的,但这种作法效率低,由于在传递过程当中须要复制整个结构体。
 
void print(registerstruct Trans const * const trans);
在许多的机器中,你能够把参数声明为寄存器变量,从而进一步提升指针传递方案的效率,这对于须要屡次访问的变量有很大的效率提高。
 
位段
signedunsigned整数地声明位段是个好主意,若是只是声明为int类型,它究竟被解释为有符号数仍是无符号数是由编译器决定的。
 
位段的声明与结构相似,但它的成员是一个或多个位的字段,这些不一样长度的字段实际上存储于一个或多个整型变量中
 
位段的成员必须声明为intsigned int、或unsigned int类型。其次,在成员名的后面是一个冒号和一个整数,这个整数指定该位段所占用的位的数目。
 
    struct CHAR {
       unsignedch :7;
       unsignedfont :6;
       unsignedsize :19;
    };
    struct CHAR ch1;
该程序能够处理128个不一样的字符值、64种不一样的字段、524288种字体大小。成员size位段过于庞大,没法容纳于一个短整型中,但其余的位段成员又比一个字符还短,因此这里可以利用存储chfont所剩余的位来用在size位段上,这样就可使用一个32位的整数来存储整个内容了。上面程序在16位机上是非法的,由于最长的位段定义成了19,而最长也只可能为16,但在32位机上,这个声明将根据下面两种可能的方法建立ch1 
clip_image002[10]
位段的好处:它可以把长度不一样的类型数据包装在一块儿,节省存储空间。另外一个好处是,能够很方便地访问一个整型值的部份内容。下面是一个软盘控制器寄存器,每位的结构以下: 
clip_image002[12]
在一个从右向左分配位段的机器上,下面的这个声明容许程序方便地对这个寄存器的不一样位段进行访问:
    struct DISK_REGISTER_FORMAT {
       unsignedcommand :5;
       unsignedsector :5;
       unsignedtrack :9;
       unsignederror_code :8;
       unsignedhead_loaded :1;
       unsignedwrite_protect :1;
       unsigneddisk_spinning :1;
       unsignederror_occurred :1;
       unsignedready :1;
    };
假如寄存器是在内存地址 0xc0200142 ,咱们能够声明下面的指针常量:
#define DISK_REGISTER ((struct DISK_REGISTER_FORMAT*)0xc0200142)
下面开始访问:
    /*
     * 告诉控制器从哪一个扇区哪一个磁道开始读取
     */
    DISK_REGISTER->sector = new_sector;
    DISK_REGISTER->track = new_track;
    DISK_REGISTER->command = READ;
    /*
     * 等待,直到操做完成(ready变量变成真)
     */
    while (!DISK_REGISTER->ready);
    /*
     * 检查错误
     */
    if(DISK_REGISTER->error_code){
      
    }
使用位段只是基于方便的目的,任何能够用位段实现的任务均可以使用移位和屏蔽来实现。下面的代码功能和前面的赋值功能是同样:
#define DISK_REGISTER ((unsignedint *)0xc0200142)
    *DISK_REGISTER &= 0xfffffc1f;//sector字段清零
    *DISK_REGISTER |= (new_sector & 0x1f) << 5;//赋值
在源代码中,用位段表示这个处理过程更简单一些,但在目标中,这两种方法并不存在任何区别,不管是否使用位段,相同的移位和屏蔽都是必需的。位段提供的惟一优势是简化了源代码,但须要与位段的移植性较弱这个缺点进行权衡。
 
注重可移植性的程序应该避免使用位段,位段在不一样的系统中可能有不一样的结果:
一、  int位段被看成有符号数仍是无符号数。
二、  位段中字段的位最大数目。许多编译器把位段成员的位数限制在一个整型的长度以内,因此可以运行于32位整数的机器上的位段声明可能在16位整数的机器上没法运行,如unsignederror_code :32;  确定是不能移植了。
三、  位段中的成员在内存中是从左向右分配的仍是从右向左分配的。
 
联合
联合的全部成员引用的是内存中的相同位置,当你想在不一样的时刻把不一样的东西存储于一个位置时,就以使用联合。经过访问不一样类型的联合成员时,内存中相同的位组合能够被解释为不一样的东西。
 
union u_tag {
    inti;
    floatf;
    char *s;
} u;
int main(int argc, char * argv[]) {
    printf("%d\n", u.i);//0
    u.i=1;
    printf("%d\n", u.i);//1
    printf("%f\n", u.f);//0.000000
    u.f=1.1;
    printf("%f\n", u.f);//1.100000
    //打印的仍是最后一次存储的内容
    printf("%f\n", u.i);//1.100000
    //float转换成int
    printf("%d\n", u.i);//1066192077
    return 0;
}
 
联合与结构能够相互嵌套。
 
实际上,联合就是一个结构,它的全部成员相对于基地址的偏移量都为0,此结构空间要大到足够容纳最“宽”的成员。
 
在一个成员长度不一样的联合里,分配给联合的内存数量取决于它的最长成员的长度。若是成员的长度相关悬殊,会浪费不少的空间,在这种状况下,最好的方法是在联合中存储指向不一样成员的指针而不是直接存储成员自己,由于全部指针的长度都是相同的,这样就解决了内存浪费的问题了。
 
联合会默认使用第一个成员类型的值进行初始化。
 
联合初始化必须是联合第1个成员的类型,并且它必须位于一对花括号里:
    union {
       charc;
       inta;
       floatb;
    } x={'a'};
    printf("%c\n",x.c);//a
    printf("%d\n",x.a);//97ide

第九章     动态内存分配


当你声明数组时,你必须用一个编译时常量指定数组的长度,可是,数组的长度经常在运行时才知道,因此咱们一般采用的方法是声明一个较大的数组,它能够容纳可能出现的最多元素。
 
malloc从内存池中提取一块合适的内存,并向程序返回一个指向这块内存的指针。分配与释放的函数原型以下:
void *malloc(size_t size);
void free(void *pointer);
size_t是一个无符号类型,定义于stdlib.h中,该参数指定了须要分配的内存字节数。
 
malloc所分配的是一块连续的内存。malloc实际分配的内存有可能比你请求的稍微多一点,这是由编译器定义的。
 
若是操做系统没法向malloc提供更多的内存,malloc就返回一个NULL指针。
 
malloc是不知道你所请求的内存是用来存储整数、浮点数、结构仍是数组的,因此它返回的是一个类型为void*的指针,而这个类型的指针能够转换为其余任何类型的指针,在某些老式的编译器中,可能要求你在转换时使用强制类型转换。
 
另外还有两个内存分配函数,原型以下:
void *calloc(size_t num_elements, size_t element_size);
void realloc(void *ptr, size_t new_size);
callocmalloc之间的主要区别是前者在返回指向内存的指针以前把内存初始化为0,另外一个区别是它们请求内存数量的方式不一样,calloc的参数包括所须要元素的数量和每一个元素的字节数,根据这些值,它可以计算出总共须要分配的内存。
 
realloc函数用于修改一个碑已经分配的内存块的大小,可使用一块内存扩大或缩小。扩大时原先的内容依然保留,新增长的内存添加到原先内存块的后面,新内存并未以任何方法进行初始化。缩小时,该内存块尾的部份内存被拿掉,剩余部份内存的原先内容依然保留。若是原先的内存没法改变大小,realloc将分配另外一块正确大小的内存,并把原先那块内存的内容复制到新的块上。所以,在使用realloc以后,你主不能再使用指向旧内存的指针,而是应该改用realloc所返回的新的指针。
 
    int i, *pi, *pi2;
    //这块内存将被看成25个整型元素的数组
    //由于pi是一个指向整型的指针
    pi = malloc(25 * sizeof(int));
    if (pi == NULL) {
       printf("Out of memory!\n");
       exit(1);
    }
    pi2 = pi;
    for (i = 0; i < 25; i++) {
       *pi2++ = 0;
       //或者使用下标,看成数组来使用 pi[i]=*(pi + i)
       //pi[i]=0;
    }
 
常见的动态内存错误:对NULL指针进行解引用操做、对分配的内存进行操做时越过边界、释放并不是动态分配的内存、试图释放一块动态分配的内存的一部分、一块动态内存被释放以后被继续使用。
 
使用MALLOC自定义宏来避免上面这些错误,下面程序由三部分组成:一个是定义接口alloc的头文件alloc.h,第二个是接口,第三个是使用接口:
/*
* 定义一个不易发生错误的内存分配器接口
*/
#include<stdlib.h>
#define malloc 不要直接调用malloc!
#define MALLOC(num,type) (type*)alloc((num) * sizeof(type))
externvoid * alloc(size_t size);//接口
                                                                 ——alloc.h
/*
* 实现
*/
#include<stdio.h>
#include"alloc.h"
#undef malloc
void * alloc(size_t size) {
    void * new_mem;
    //请求所需的内存,并检查确实分配成功
    new_mem = malloc(size);
    if (new_mem == NULL) {
       printf("Out of memory!\n");
       exit(1);
    }
    return new_mem;
}
                                                                 ——alloc.c
#include"alloc.h"
/*
* 使用
*/
void function() {
    int * new_memeory;
    //获取一串整型数的空间
    new_memeory = MALLOC(25,int);
}
                                                                 ——a_client.c
 
 
free的参数必需要么是NULL,要么是一个先前从malloccallocrealloc返回的值,向free传递一个NULL参数不会产生任何效果。free试图释放一块动态分配内存的一部分也有可能引发问题:
pi = malloc(25 * sizeof(int));
//下面会引起问题
free(pi +5);
释放一内存的一部分是不容许的,动态分配的内存必须整块一块儿释放。可是,realloc函数能够缩小一块动态分配的内存,有效地释放它尾部的内存。
 
不能访问已经被free函数释放了的内存。
 
分配内存但在使用完毕后不释放将引发内存泄漏
 
动态分配实例
动态分配最多见的一个应用就是为那些长度在运行时才知道的数组分配内存空间。下面是读取一列整数,并排序:
/*
** 读取、排序和打印一列整数.
*/
#include<stdlib.h>
#include<stdio.h>
 
/*
** 该函数由 qsort 用于比较整型值
*/
int compare_integers(voidconst *a, voidconst *b) {
    registerintconst *pa = a;
    registerintconst *pb = b;
 
    return *pa > *pb ? 1 : *pa < *pb ? -1 : 0;
}
 
int main() {
    int *array;
    int n_values;
    int i;
 
    /*
     ** 观察共有多少个值.
     */
    printf("How many values are there? ");
    if (scanf("%d", &n_values) != 1 || n_values <= 0) {
       printf("Illegal number of values.\n");
       exit(EXIT_FAILURE);
    }
 
    /*
     ** 分配内存,用于存储这些值.
     */
    array = malloc(n_values * sizeof(int));
    if (array == NULL) {
       printf("Can't get memory for that many values.\n");
       exit(EXIT_FAILURE);
    }
 
    /*
     ** 读取这些值.
     */
    for (i = 0; i < n_values; i += 1) {
       printf("? ");
       if (scanf("%d", array + i) != 1) {
           printf("Error reading value #%d\n", i);
           exit(EXIT_FAILURE);
       }
    }
 
    /*
     ** 调用库函数进行排序.
     */
    qsort(array, n_values, sizeof(int), compare_integers);
 
    /*
     ** 输出.
     */
    for (i = 0; i < n_values; i += 1)
       printf("%d\n", array[i]);
 
    /*
     ** 释放内存而且退出.
     */
    free(array);
    return EXIT_SUCCESS;
}
 
动态复制字符串
/*
** 用动态分配内存制做一个字符串的一份拷贝。注意:
** 调用程序应该负责检查这块内存是否成功分配!这样
** 作容许调用程序以任何它所但愿的方式对错误做出反应
*/
#include<stdlib.h>
#include<string.h>
 
char *strdup(charconst *string) {
    char *new_string;
    /*
     ** 请求足够长度的内存,用于存储字符串和它的结尾NUL字节.
     */
    new_string = malloc(strlen(string) + 1);
    /*
     ** 若是咱们获得内存,就复制字符串.
     */
    if (new_string != NULL)
       strcpy(new_string, string);
    return new_string;
}
该程序将输入的字符串存储到缓冲区,每次读取一行。调用此函数时才能够肯定字符串的长度,而后就分配内存用于存储字符串,最后字符串被复制到新内存,这样缓冲区又能够用于读取下一个输入行。这个函数很是方便,也很是有用,尽管标准没有说起,但许多环境都把它做为函数库的一部分。
 
有些C编译器提供了一个称为alloca的函数,它与malloc函数不现是在于它在栈上分配内存,而malloc是在堆上分配内存。在栈上分配内存的主要优势是当分配内存的返回时,这块内存会被自动释放,这是由栈的工做方式决定的,它能够保证不会出现内存泄漏,但这种方式存在缺点,因为函数返回时被分配的内存将消失,因此它不能用于存储那些回传给调用程序的数据。
 函数

第十章     使用结构和指针(链表实现)


单链表
/*
** 单链表的插入,链表为升序
*/
#include<stdlib.h>
#include<stdio.h>
typedefstruct NODE {
    struct NODE *link;
    intvalue;
} Node;
 
#define    FALSE  0
#define TRUE  1
 
/*
* 若是节点插在最前面,则须要使用根指针的值,
* 因此这里使用了二级指针将根指针的地址也传递
* 过去,这样便于修改根指针的指向
*/
int sll_insert(Node **rootp, int new_value) {
    /*
     * 前驱节点,会成为新节点的前驱节点,也可能为NULL
     */
    Node *previous;
    /*
     * next节点,即第一个大于或等于新节点的节点,最后会
     * 成会新节点的后继节点,可能为NULL
     */
    Node *next;
    Node *new;//新节点
    previous = NULL;//刚开始时前驱节点指针指向NULL
//初始化后继节点,刚开始时与根节点同样指向第一个节点
    next = *rootp;
 
    /*
     ** 若是没有到达链尾,且没有找到一个大于或等于新节点
     ** 的节点时,继续往下找
     */
    while (next != NULL && next->value < new_value) {
       previous = next;
       next = next->link;
    }
 
    //动态建立新的节点
    new = (Node *) malloc(sizeof(Node));
    if (new == NULL)
       return FALSE;
    new->value = new_value;
 
    //设定next域,让next节点成为新节点的下一节点
    new->link = next;
   
    //若是须要在最前面插入时
    if (previous == NULL)
       //此时须要修改根节点的指向,让它指向新的节点
       *rootp = new;
    else//若是插入是在链表的中间或者末尾时
       previous->link = new;
    return TRUE;
}
int main(int argc, char **argv) {
    Node *root = NULL;
    Node **rootp = &root;
    sll_insert(rootp, 2);
    sll_insert(rootp, 5);
    sll_insert(rootp, 3);
    sll_insert(rootp, 0);
    sll_insert(rootp, 1);
    sll_insert(rootp, 4);
    while (root != NULL) {
       printf("%d", root->value);//012345
       root = root->link;
    }
} 测试

 

clip_image002[14]

clip_image004

clip_image006
 
单链表的优化插入操做
看上去,把一个节点插入到链表的起始位置必须做为一种特殊状况进行处理,由于此时插入新节点须要修改的是指针是根指针,而对于其余任何节点,修改的是前一个节点previouslink字段,这两个看上去不一样的操做其实是同样的。
消除这种特殊状况的关键在于:咱们必须认识到,链表中的每一个节点都有一个指向它的指针。对于第1个节点,这个指针是根指针;对于其余节点,这个指针是前一节点的link字段,相同点是每一个节点都有一个指针指向它,至于该指针是否是位于一个节点内部则不重要,咱们彻底能够将root与节点内部的link指针同等看待,root便可当作某个节点的link字段 字体

让咱们再次观察这个链表,弄清这个概念,这是第1个节点和指向它的指针:优化

clip_image002[16]

若是新值插入到第1个节点以前,这个指针就必须进行修改。下面是第2个节点和指向它的指针:spa

clip_image004[4]

若是新值须要插入到第2个节点以前,那么前一节点的link指针必须进行修改。操作系统

如今咱们只须要拥有一个指向插入位置的下一节点的指针(next),以及一个“指向next节点的link字段”的指针(linkp),除此外,咱们就再也不须要一个用来保存指向插入位置的前一节点的指针previous),下面是赋值语句(next = *linkp)执行后的各变量状况:指针

clip_image006[4]

当移动到下一个节点时,咱们保存一个“指向next节点的link字段的”指针(linkp),而不是保存一个指向前一个节点的指针(previous):code

clip_image008

注,这里的linkp并不指向节点自己,与上面实现不现的是,它是指向节点内部的link字段,这是简化插入操做的关键所在,这能够将root指针与节点内的link指针字段同等看待,咱们能够用一种和修改节点的link字段彻底同样的方式来修改root变量。插入函数的原型与上面的实现仍是同样,只是将rootp的名称改为了linkp,由于这里的实现是让rootp能够指向其余任何节点内部的link字段,因此就形象的称为linkp,而不只仅是根指针了。咱们不再须要previous指针了,由于咱们的linkp指针能够负责寻找到须要修改的link字段,下面是实现:
result = sll_insert(&root, 12);
int sll_insert(registerNode **linkp, int new_value) {
    registerNode *next;
    registerNode *new;
 
    next = *linkp;//刚开始时nextroot同指向第一个节点
    //若是没有到达链尾且next节点的值小于或等于新节点值时继续
    while (next != NULL && next->value < new_value) {
       /*
        * 这里与上面的实现是不同的,这里保存的是节点内link字段的
        * 地址,而不像上面那样保存的是节点自己,这是简化的关键所在
        */
       linkp = &next->link;
       next = *linkp;
    }
 
    new = (Node *) malloc(sizeof(Node));
    if (new == NULL)
       return FALSE;
    new->value = new_value;
 
    //这个与上面的实现仍是同样,让当前节点成为新节点的下一节点
    new->link = next;
    /*
     * 即便在空链表时,能够将root根指针与节点中的link字段同等看待
     * 链表为空时,修改的就是root的指向,不然修改的就是其余节点的
     * link指向
     */
    *linkp = new;
    return TRUE;
}
节点的删除与查找也可使用上面这种简化的操做来实现。
 
 
双链表(非循环) 
clip_image002[18]
双链表的根节点容许咱们能够从链表的任何一端(第一个节点仍是最后一个节点)开始遍历链表。根节点的fwd字段指向链表的第1个节点,根节点的bwd字段指向链表最后一个节点(若是只有一个节点,则这两个都指向第一个节点)。若是链表为空,这两个字段都为NULL链表第1个节点的bwd字段和最后一个节点的fwd字段都为NULL。并将根节点与其余节点等同看待,只是value没有值而已。
 
一、  若是链表为空,则新增节点的fwdbwd字段都为NULL
二、  若是新增节点位于起始位置,则新增节点的bwd字段为NULL,新节点的fwd字段指向下一节点(next指向的节点),下一节点(next指向的节点)的bwd字段指向这个新的节点。
三、  若是新增节点位于结束位置,则新增节点的fwd字段为NULL,新节点的bwd字段指向前一节点(previous指向的节点),前一节点(previous指向的节点)的fwd字段指向这个新的节点。
四、  若是新增节点位于链表中间,则新增节点的fwd字段为下一节点(next指向的节点),新节点的bwd指向前一节点(previous指向的节点),下一节点(next指向的节点)的bwd指向新节点,前一节点(previous指向的节点)的fwd也指向新节点。
 
/*
** 把一个值插入到一个双链表,rootp是一个指向根节点的指针,
** value是欲插入的新值
** 返回值:若是欲插值已存在于链表中,函数返回0;若是内存不
** 足,返回-1;若是插入成功,函数返回1
*/
#include<stdlib.h>
#include<stdio.h>
 
typedefstruct NODE {
    struct NODE *fwd;
    struct NODE *bwd;
    intvalue;
} Node;
 
int dll_insert(Node *rootp, int value) {
    Node *previous;//指向待插入位置的前一节点,
    Node *next;//指向待插入位置的后一节点,
    Node *new;//指向新的节点
 
    previous = rootp;//初始化时指向根节点
    //previous不能为NULLrootp不能传递为NULL
    next = previous->fwd;//初始化时指向第一个节点或NULL
    /*
     * 若是没有达到链尾,且新的值比next值大时继续向后找
     */
    while (next != NULL && next->value < value) {
       //查看value是否已经存在于链表中,若是是就返回
       if (next->value == value) {
           return 0;
       }
       previous = next;
       next = previous->fwd;
    }
 
    new = (Node *) malloc(sizeof(Node));
    if (new == NULL) {
       return -1;
    }
    new->value = value;
 
    if (rootp->fwd == NULL && rootp->bwd == NULL) {//若是链表为空
       new->fwd = NULL;
       rootp->fwd = new;
       new->bwd = NULL;
       rootp->bwd = new;
    } elseif (previous == rootp) {//若是插入位置为链表首时
       new->fwd = next;
       rootp->fwd = new;
       new->bwd = NULL;
       next->bwd = new;
    } elseif (next == NULL) {//若是插入位置为链表尾时
       new->fwd = NULL;
       previous->fwd = new;
       new->bwd = previous;
       rootp->bwd = new;
    } else {
       //若是插入位置为链表中间时
       new->fwd = next;
       previous->fwd = new;
       new->bwd = previous;
       next->bwd = new;
    }
    return 1;
}
 
int main(int argc, char **argv) {
    //  Node root = { NULL, NULL, 0 };
 
    /*
     * 为了节省空间,root的值成员是多余的,
     * 可使用动态分配出来
     *
     * 若是不是动态分配出来的,则可使用如
     * 下结构来实现:
     * struct DLL_NODE;
     * struct DLL_POINTERS{//根节点结构
     *      struct DLL_NODE * fwd;
     *      struct DLL_NODE * bwd;
     * };
     * struct DLL_NODE{//节点
     *      struct DLL_POINTERS pointers;
     *      int value;
     * };
     *
     */
    Node* root2 = malloc(sizeof(Node) - sizeof(int));
    root2->bwd = NULL;
    root2->fwd = NULL;
    dll_insert(root2, 2);
    dll_insert(root2, 5);
    dll_insert(root2, 3);
    dll_insert(root2, 0);
    dll_insert(root2, 1);
    dll_insert(root2, 4);
    //从前向后遍历
    Node* tmp = root2->fwd;
    while (tmp != NULL) {
       printf("%d", tmp->value);//012345
       tmp = tmp->fwd;
    }
    //从后向前遍历
    tmp = root2->bwd;
    while (tmp != NULL) {
       printf("%d", tmp->value);//543210
       tmp = tmp->bwd;
    }
} 
 
  clip_image002[20]

相关文章
相关标签/搜索