1.结构体数据类型
a.定义形式:struct 结构体类型名{
成员说明列表
};
struct date{ int year; int month; int day; };
b.结构体变量的使用
①先定义结构,再说明结构体变量
struct stu{ int num; char name[20]; char sex; float score; }; struct stu boy1,boy2;
②在定义结构类型的同时说明结构体变量
struct stu{ int num; char name[20]; char sex; float score; }boy1,boy2; /*变量名表列*/
③直接说明结构体变量
struct { int num; char name[20]; char sex; float score; }boy1,boy2;
调用时:boy1.sex; boy2.sex;
c.结构体数组
①结构体数组的定义:struct employee workers[50];
②在定义结构体的同时,定义结构体数组
struct employee workers[50]; struct employee{ long no; char name[10]; char sex; char addr[40]; }workers[50];
②直接定义结构体数组而不定义结构体名
struct employee workers[50]; struct{ long no; char name[10]; char sex; char addr[40]; }workers[50];
d.结构体数组的使用
①引用某一结构体数组元素的一个成员结构体数组名 [元素下标].结构体成员名。
workers[i].no
②可以将一个结构体数组赋给同一结构体类型数组的另一元素,或赋给同一类型的变量。
struct employee workers[5],workman; workers[3]=workers[0]; workman=workers[1]; workers[4]=workman;
③不能把结构体数组元素作为一个整体直接进行输入或输出,只能以单个成员对象进行输入或输出。
scanf("%d",&workers[0].no); printf("%d",workers[0].no);
2.结构体指针
声明形式:struct 结构名 *结构指针变量名
struct stu *pstu
访问形式:(*结构指针变量).成员名 或 结构指针变量->成员名
(*pstu).num
pstu->num
应用实例:
#include<stdio.h> struct stu{ int num; char *name; char sex; float score; }boy1=(102,"zhang ping",'M',78.5),*pstu; void main(){ pstu=&boy1; printf("Number=%d\n Name=%s\n ",boy1.num,boy1.name); printf("Sex=%c\n Score=%f\n ",boy1.sex,boy1.score); printf("Number=%d\n Name=%s\n ",(*pstu).num,(*pstu).name); printf("Sex=%c\n Score=%f\n ",(*pstu).sex,(*pstu).score);\ printf("Number=%d\n Name=%s\n ",pstu->num,pstu->name); printf("Sex=%c\n Score=%f\n ",pstu->sex,pstu->score); }
3.链表
a.什么是链表:(1)头指针变量head----指向链表的首结点
(2)每个结点由两个域组成
①数据域---存储结点本身的信息
②指针域---指向后继结点的指针
(3)尾结点的指针域置为“NULL(空)”,作为链表结束的标志。
例:一个存放学生学号和成绩的结点:
struct stu{ int num; int score; /*数据域*/ struct stu *next; /*指针域,它是一个指向stu类型结构的指针变量*/ }
b.链表和数组的主要区别
二者都属于一种数据结构
1.从逻辑结构分析
(1)数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;
当数据减少时,造成内存浪费;数组可以根据下标直接存取。
(2)链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便的插入、删除数据项。(数组中插入、删除数据项时,需要移动其他数据项,非常繁琐)
链表必须根据 next 指针找到下一个元素。
2.从内存存储分析
(1)(静态)数组从栈中分配空间,对于程序员方便快速,但是自由度小。
(2)链表从堆中分配空间自由度大但是申请管理比较麻烦。
如果需要快速访问数据,很少或不插入和删除元素,就应该用数组;相反,如果需要经常插入和删除元素就需要用连边数据结构。
c.链表的基本操作
创建链表
//声明节点结构 typedef struct Link { int elem;//存储整形元素 struct Link *next;//指向直接后继元素的指针 }link; //创建链表的函数 link * initLink() { link * p = (link*)malloc(sizeof(link));//创建一个头结点 link * temp = p;//声明一个指针指向头结点,用于遍历链表 int i = 0; //生成链表 for (i = 1; i < 5; i++) { //创建节点并初始化 link *a = (link*)malloc(sizeof(link)); a->elem = i; a->next = NULL; //建立新节点与直接前驱节点的逻辑关系 temp->next = a; temp = temp->next; } return p; }
链表插入元素
同顺序表一样,向链表中增添元素,根据添加位置不同,可分为以下 3 种情况:
- 插入到链表的头部(头节点之后),作为首元节点;
- 插入到链表中间的某个位置;
- 插入到链表的最末端,作为链表中最后一个数据元素;
虽然新元素的插入位置不固定,但是链表插入元素的思想是固定的,只需做以下两步操作,即可将新元素插入到指定的位置:
- 将新结点的 next 指针指向插入位置后的结点;
- 将插入位置前结点的 next 指针指向插入结点;
例如,我们在链表 {1,2,3,4}
的基础上分别实现在头部、中间部位、尾部插入新元素 5,其实现过程如图 1 所示:
从图中可以看出,虽然新元素的插入位置不同,但实现插入操作的方法是一致的,都是先执行步骤 1 ,再执行步骤 2。
注意:链表插入元素的操作必须是先步骤 1,再步骤 2;反之,若先执行步骤 2,会导致插入位置后续的部分链表丢失,无法再实现步骤 1。
通过以上的讲解,我们可以尝试编写 C 语言代码来实现链表插入元素的操作:
//p为原链表,elem表示新数据元素,add表示新元素要插入的位置 link * insertElem(link * p, int elem, int add) { link * temp = p;//创建临时结点temp link * c = NULL; int i = 0; //首先找到要插入位置的上一个结点 for (i = 1; i < add; i++) { if (temp == NULL) { printf("插入位置无效\n"); return p; } temp = temp->next; } //创建插入结点c c = (link*)malloc(sizeof(link)); c->elem = elem; //向链表中插入结点 c->next = temp->next; temp->next = c; return p; }
提示,insertElem 函数中加入一个 if 语句,用于判断用户输入的插入位置是否有效。例如,在已存储 {1,2,3}
的链表中,用户要求在链表中第 100 个数据元素所在的位置插入新元素,显然用户操作无效,此时就会触发 if 语句。
链表查找元素
在链表中查找指定数据元素,最常用的方法是:从表头依次遍历表中节点,用被查找元素与各节点数据域中存储的数据元素进行比对,直至比对成功或遍历至链表最末端的 NULL
(比对失败的标志)。
因此,链表中查找特定数据元素的 C 语言实现代码为:
//p为原链表,elem表示被查找元素、 int selectElem(link * p, int elem) { //新建一个指针t,初始化为头指针 p link * t = p; int i = 1; //由于头节点的存在,因此while中的判断为t->next while (t->next) { t = t->next; if (t->elem == elem) { return i; } i++; } //程序执行至此处,表示查找失败 return -1; }
注意,遍历有头节点的链表时,需避免头节点对测试数据的影响,因此在遍历链表时,建立使用上面代码中的遍历方法,直接越过头节点对链表进行有效遍历。
链表更新元素
更新链表中的元素,只需通过遍历找到存储此元素的节点,对节点中的数据域做更改操作即可。
直接给出链表中更新数据元素的 C 语言实现代码: