链表和结构体算是比较难的东西了,而且,这些还是为了后面学数据结构打基础,可谓是承上启下的作用,一定要把它学好。
首先要说一下的就是,结构体与指针的联系是比较多的,也就是说,你的指针方面的知识是必须也要过关的。
我们现在一点一点慢慢来吧。
一,结构体
一,结构体的定义
/
struct s { int i; char c; };
struct s是类型名,还未定义变量,i和c都是结构体的成员变量。要知道的是,结构体是一种自定义类型,(可以往前面看一看我的初始C语言2.C语言的四种结构)。所以说,struct s只是类型名,并不是说这是一个结构体的名字叫做s,我这时候还没有定义新的结构体变量名。结构体类型的定义记得要加分号;
可以在之后再去定义,也可以直接就在花括号的分号后面加上变量名。(可定义多个)
struct s { int i; char c; }s1,s2;
也可以定义一种叫做匿名结构体的结构体。
struct { int i; char c; };
就是不给变量名的结构体
下面是我在csdn上找到的别人的对匿名结构体运用的博文。大家可以看一下。(3条消息) 匿名结构体与匿名联合体_kuikuitage的博客-CSDN博客_匿名结构体
#include <stdio.h> struct person { char *name; char gender; int age; int weight; struct { int area_code; long phone_number; }; }; int main(void) { struct person jim = {"jim", 'F', 28, 65, {21, 58545566}}; printf("%d\n", jim.area_code); } 1#include <stdio.h> struct person { char *name; char gender; int age; int weight; struct { int area_code; long phone_number; }; }; int main(void) { struct person jim = {"jim", 'F', 28, 65, {21, 58545566}}; printf("%d\n", jim.area_code); } 如果不使用匿名结构体,则上述例子对应的代码如下: #include <stdio.h> struct phone { int area_code; long phone_number; }; struct person { char *name; char gender; int age; int weight; struct phone office; }; int main(void) { struct person jim = {"jim", 'F', 28, 65, {21, 58545566}}; printf("%d\n", jim.office.area_code); } 对比上述两个例子可以看出: 使用匿名结构体,结构体对象 jim 可以通过 jim.area_code 直接访问匿名结构体成员变量 area_code,代码相对比较简洁 反之则必须通过 jim.office.area_code 来访问结构体成员变量
二,结构体的概念
1.
结构类型是一种允许程序员把一些数据分量聚合成一个整体的数据类型。一个结构体中包含的每个数据分量都有名字,这些数据分量被称为结构成员或者结构分量。
结构成员可以是C语言中的任意变量类型。
2.
C语言中把结构的定义看作一条语句。
3.
与数组相比,结构提供了一种更便利的手段,将不同类型的数据组织在一起。增加了可读性,使程序更加清晰。
4.
结构体是可以嵌套定义的,不像函数,函数是不能嵌套定义的。但是,在定义嵌套的结构类型时,必须先定义成员的结构类型,再去定义主结构类型。
注意,这个是有先后之分的。
5.
关键字struct和结构名s必须联合使用,因为他们只有合起来以后才能表示一个变量名。
6.
C语言规定,结构体类型变量的存储布局按照其类型定义中的成员的顺序先后排列。
给你们看两个例子。
#define _CRT_SECURE_NO_WARNINGS 1 //输出平均分最高的学生信息 #include<stdio.h> struct student { int num; char name[10]; int computer, english, math; double average; }; int main() { int i, n; //定义结构变量 struct student max, stu; printf("Input n:"); scanf("%d",&n); printf("Input the student's information:"); //这里与已知长度和数据的数组的选择法排序不同,这里数据都是未知的。 for (i = 1; i <= n; i++) { printf("NO.%d:",i); scanf("%d%s%d%d%d",&stu.num,stu.name,&stu.math,&stu.english,&stu.computer); stu.average = (stu.math + stu.english + stu.computer) / 3.0; if (i == 1) { max = stu; } else if (max.average < stu.average) max = stu; } printf("num:%d,name:%s.average:%2lf\n",max.num,max.name,max.average); return 0;
#include <stdio.h> #define MAXN 10 struct student{ int num; char name[20]; int score; char grade; }; int set_grade( struct student *p, int n ); int main() { struct student stu[MAXN], *ptr; int n, i, count; ptr = stu; scanf("%d\n", &n); for(i = 0; i < n; i++){ scanf("%d%s%d", &stu[i].num, stu[i].name, &stu[i].score); } count = set_grade(ptr, n); printf("The count for failed (<60): %d\n", count); printf("The grades:\n"); for(i = 0; i < n; i++) printf("%d %s %c\n", stu[i].num, stu[i].name, stu[i].grade); return 0; } int set_grade( struct student *p, int n ){ int count=0; for(int i=0;i<n;i++){ if((p->score)<60){ count++; p->grade='D'; }else if(p->score>=60 && p->score<=69){ p->grade='C'; }else if(p->score>=70 && p->score<=84) p->grade='B'; else p->grade='A'; p++; } return count; }
三,结构变量的使用
1.结构变量的操作符‘->’和‘.’
‘->’是指针操作结构变量时的操作符,而‘.’是普通变量操作结构变量的操作符。
由于结构成员运算符的优先级属于最高级别,所以一般情况下都是优先执行。
2.结构变量的赋值
如果两个两个结构变量具有相同的类型,则允许将一个结构变量的值直接赋给另一个结构变量。
这是结构中唯一的整体操作方式。
即,只有相同结构类型的变量之间才可以直接赋值。
3.结构变量作为函数参数
特点:可以传递多个数据且参数形式较简单,但是,对于成员较多的大型结构,参数传递时所进行的结构数据复制使得效率较低。
这个时候更合适的方法就是定义一个结构体指针,把指针作为参数就好了
4.结构指针
指针可以指向任何一种类型的变量。结构指针的值实际上时结构变量的首地址。即第一个成员的地址。
5.结构体的大小
这就涉及一个叫做对齐数的概念了。
(3条消息) C语言之内存对齐数_微风-CSDN博客_c语言内存对齐
这个我看过还不错,你们可以看看。
二,动态内存分配
一,动态内存管理函数
1.malloc函数
malloc函数后面的参数是要分配的长度(字节),返回值是void*类型,故,使用的时候还需要强制类型转换。
2.calloc函数
这里的num指的是分配size个字节的空间,分配了num个。(连续空间)
3.free函数
free函数的作用是释放之前分配给指针变量ptr的动态空间,但是指针变量ptr不会自动变为空指针,还是指向之前指向的地方。故,释放完之后,如果还会用到ptr指针,记得要置空,不然ptr就是野指针。
注意事项:
1.记得每次动态申请完空间后都要释放空间。
2.动态内存分配中得到的是一个没有名字,只有首地址的连续空间,相当于一个无名的一维数组。
3.malloc对所分配的存储块不做任何操作。但是calloc会自动将整个区域进行初始化为0。
4.动态内存分配的是堆区的空间,要么操作者释放,要么程序结束时系统将其释放。
二,内存处理函数
1.memcpy函数
num指的是长度(字节),destination指的是目的地,source指的是用来传递的变量地址。
在C语言规定中,memcpy应该是拷贝内存不重叠的内存。
2.memmove函数
跟上面是差不多的,两者到底有什么差别?我之前写过一个模拟实现他们的章节,可以去看看。
三,链表
一,链表的定义和使用
1.链表是结构体最重要的应用,
它是一种非固定长度的数据结构,是一种动态存储技术,只能作顺序访问,不能作随机访问。数组在结构体中 易浪费空间且不便于操作。
结构体的自引用
结构体的自引用不是在结构体中再引用一个自己,而是引用一个同类型的结构体指针(这个也就是 链表的表现形式)
2.链表的建立
1)申请内存m:内存分为两部分,一部分叫做数据域,另一部分叫做指针域,这里的域就是存储的地方,数据域存放一些普通数据,而指针域存放的是一个指针,该指针指向下一个链表节点的首地址。
2)将数据存储进去
3)若当前是第一个节点,则将m的地址保存在指针head中,否则将m的首地址保存在上一个结点的指针域中。
4)重复上述过程,直到数据存储完毕,在最后一个节点的指针域存储结束标志NULL;
再来两个题看看
1.
//求单链表结点的阶乘和 #include <stdio.h> #include <stdlib.h> typedef struct Node* PtrToNode; struct Node { int Data; /* 存储结点数据 */ PtrToNode Next; /* 指向下一个结点的指针 */ }; typedef PtrToNode List; /* 定义单链表类型 */ int FactorialSum(List L); #define _CRT_SECURE_NO_WARNINGS 1 int FactorialSum(List L) { int sum = 0; PtrToNode p; for (p = L; p; p = p->next) { int fact = 1; for (i = 1; i <= p->date; i++) { fact *= i; } sum += fact; } return sum; } int main() { int N, i; List L, p; scanf("%d", &N); L = NULL; for (i = 0; i < N; i++) { p = (List)malloc(sizeof(struct Node)); scanf("%d", &p->Data); p->Next = L; L = p; } printf("%d\n", FactorialSum(L)); return 0; }
2.
//建立学生信息链表 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <stdlib.h> #include <string.h> struct stud_node { int num; char name[20]; int score; struct stud_node* next; }; struct stud_node* head, * tail; void input(); int main() { struct stud_node* p; head = tail = NULL; input(); for (p = head; p != NULL; p = p->next) printf("%d %s %d\n", p->num, p->name, p->score); return 0; } void input() { struct stud_node* p; int x; scanf("%d", &x); while (x) { p = (struct stud_node*)malloc(sizeof(struct stud_node)); p->num = x; scanf("%s%d",p->name, &p->score); p->next = NULL; if (head == NULL) head = p; else tail->next = p; tail = p; scanf("%d", &x); } }
3.
//建立一个有N个学生信息数据的链表 #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<stdlib.h> #define N 4 struct student { //数据域 int num; char name[20]; char sex; int score; //指针域 struct student* next; }; int main() { //定义指针变量 struct student* head, * p, * q; //定义结点大小 p = (struct student*)malloc(sizeof(struct student)); //初始化结点 scanf("%d%s%c%d",&p->num,p->name,&p->sex,&p->score); //将第一个结点的地址给head。 head = p; head->next=NULL; //用一个for循环,初始化所有的结点,即整个链表。 for (int i = 0; i < N; i++) { //要重新定义第二个结点,然后初始化 q = (struct student*)malloc(sizeof(struct student)); scanf("%d%s%c%d", &q->num, q->name, &q->sex, &q->score); //让前一个结点的指针域指向下一个结点的地址 p->next = q; //再让p指向已定义好的结点,q指针继续去处理下一个结点。 p = q; } //循环结束后,最后一个结点的指针域为空。 p->next = NULL; //用一个for循环输出所有结点中的信息 for (p = head; p; p = p->next) { printf("%d %s %c %d\n",p->num,p->name,p->sex,p->score); } return 0; }
4.
//链表的比较及排序 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #define N 10 struct student { int num; int score; struct student* next; }; int main() { struct student* head, * p, * q, * k; k = (struct student*)malloc(sizeof(struct student)); //以后内存函数的使用都要判断一下,是否开辟成功,如果未开辟成功,则结束进程 if (!k) exit(1); scanf("%d%d",&k->num,&k->score); //初始化第一个结点,因为结点是不连续的,仅仅是靠指针域中的地址来进行联系,所以,这既是第一个结点,也是最后一个结点,对指针域进行初始化 k->next = NULL; head = k; //接下来对后面的N-1个结点进行定义初始化 //这一整个片段是很关键的一环,写的是真的好,主要是我没见过 for (int i = 1; i < N; i++) { k = (struct student*)malloc(sizeof(struct student)); //判断语句 if (!k) exit(1); scanf("%d%d", &k->num, &k->score); //让p指向第一个结点 p = head; q = NULL; //插入操作 //此时的while起一个比较的作用(升序) while (p && (p->num <= k->num)) { q = p; p = p->next; } if (p == head) { k->next = head; head = k; } else { k->next = q->next; q->next = k; } } /*首先,让p指向第一个结点,k指向的是一个新结点,二者相比较,如果k指向的结点大于p指向的,先把初始结点的地址给q,p就指向下一位, 执行else语句。即在q指向的结点后面插入k指向的结点。 如果p指向的结点数据大于k指向的结点数据,则将k指向的结点插到第一个结点前面,也就是插到相比较结点的前面。 交换完数据以后,利用for循环再次重新定义一个新节点,再次进行比较。 */ free(k);//一定要记得释放申请的内存 //输出整个的链表 for (p = head; p; p = p->next) { printf("%d %d\n", p->num,p->score); } return 0; }
总结一下上面的几道题,
链表的建立方式有两种,一种是用for循环,先定义一个头节点,再用for循环继续创造后续节点,另一种就是用while循环,在其中设置一个撒谎分支结构,当定义的节点是第一个时,和不是第一个时。
//定义指针变量 struct student* head, * p, * q; //定义结点大小 p = (struct student*)malloc(sizeof(struct student)); //初始化结点 scanf("%d%s%c%d",&p->num,p->name,&p->sex,&p->score); //将第一个结点的地址给head。 head = p; head->next=NULL; //用一个for循环,初始化所有的结点,即整个链表。 for (int i = 0; i < N; i++) { //要重新定义第二个结点,然后初始化 q = (struct student*)malloc(sizeof(struct student)); scanf("%d%s%c%d", &q->num, q->name, &q->sex, &q->score); //让前一个结点的指针域指向下一个结点的地址 p->next = q; //再让p指向已定义好的结点,q指针继续去处理下一个结点。 p = q; } //循环结束后,最后一个结点的指针域为空。 p->next = NULL;
void input() { struct stud_node* p; int x; scanf("%d", &x); while (x) { p = (struct stud_node*)malloc(sizeof(struct stud_node)); p->num = x; scanf("%s%d",p->name, &p->score); p->next = NULL; if (head == NULL) head = p; else tail->next = p; tail = p; scanf("%d", &x); } }
要分清这两种方法该怎样的时候要怎么样。
3.逆序链表
http://hi.csdn.net/attachment/201010/23/0_1287825676duab.gif
http://hi.csdn.net/attachment/201010/23/0_1287825676duab.gif
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <stdlib.h> typedef struct tagListNode { int data; struct tagListNode* next; }ListNode, * List; void PrintList(List head); List ReverseList(List head); int main() { //分配链表头结点 ListNode* head; head = (ListNode*)malloc(sizeof(ListNode)); head->next = NULL; head->data = -1; //将[1,10]加入链表 int i; ListNode* p, * q; p = head; for (int i = 1; i <= 10; i++) { q = (ListNode*)malloc(sizeof(ListNode)); q->data = i; q->next = NULL; p->next = q; p = q; } PrintList(head); /*输出原始链表*/ head = ReverseList(head); /*逆序链表*/ PrintList(head); /*输出逆序后的链表*/ return 0; } List ReverseList(List head) { if (head->next == NULL || head->next->next == NULL) { return head; /*链表为空或只有一个元素则直接返回*/ } ListNode* t = NULL, * p = head->next, * q = head->next->next; while (q != NULL) { t = q->next; q->next = p; p = q; q = t; } /*此时q指向原始链表最后一个元素,也是逆转后的链表的表头元素*/ head->next->next = NULL; /*设置链表尾*/ //这个地方就是一个知识点了,链表的第一个元素是元节点,即头节点的下一个,故,链表逆序时,头节点不变。 head->next = p; /*调整链表头*/ return head; } void PrintList(List head) { ListNode* p = head->next; //或者用个for循环,遍历 while (p != NULL) { printf("%d ", p->data); p = p->next; } printf("/n"); }
4.链表的增删查操作
确实,这些操作都比较简单,直接给个柔和在一起的题,慢慢去磨吧。
#define _CRT_SECURE_NO_WARNINGS 1 //建立链表,查找值为x的结点并删除这个结点,x的值从键盘输入。 #include<stdio.h> #include<stdlib.h> //先定义结构体类型 struct node { int date; struct node* next; }; //函数声明 struct node* create_number(int); struct node* find(struct node *, int); struct node* Delete(struct node *, struct node * ); void out_list(struct node*); int main() { //建议还是初始化一下 struct node* head=NULL, * p=NULL; int n, x; n = x = 0; printf("Create List,Enter n:"); scanf("%d", &n); head = create_number(n); printf("List:"); out_list(head); printf("x:"); scanf("%d", &x); p = find(head, x); head = Delete(head, p); printf("Result:"); out_list(head); return 0; } //创造结点的函数 struct node* create_number(int n) {//n代表准备创造结点的个数 //创造结点时,至少需要三个指针变量,head固定在首结点,p和k用于创造新结点 int i; struct node* head=NULL, * k=NULL, * p=NULL; if (n < 1) { return NULL; } else { k = (struct node*)malloc(sizeof(struct node)); //判断是否申请成功 if (!k) exit(1); k->date = 1;//初始化第一个数据 //第一个结点,也是最后一个,这个操作相当于给指针域初始化 k->next = NULL; head = k; p = k; for (i = 2; i <= n; i++) { k = (struct node*)malloc(sizeof(struct node)); if (!k) exit(1); k->date = i; //初始化 k->next = NULL; p->next = k; p = k; } return head; } } //查找函数 struct node* find(struct node* head, int x) {//头指针和待查找的元素 //另外定义一个指针去遍历,从而保证head指针不被改变 struct node* p = head; while (p && p->date != x) { p = p->next; } if (!p) return NULL; else return p; } //删除函数 struct node* Delete(struct node* head, struct node* p) { struct node* q; if (!p) return head; //如果p不为空,判断p指向结点的位置,从而进行删除操作 if (p = head) head = head->next; else { q = head; while (q->next != p) q = q->next; q->next = p->next; } //别忘了释放空间 free(p); return head; } //输出函数 void out_list(struct node* head) { //另外定义变量去遍历 struct node* p; if (!head) { p = head; while (!p) { printf("%d", p->date); p = p->next; } } else printf("不存在\n"); putchar('\n'); }
其实要写还能写好多,我想了想,还是不写太多了,还不如接着再写一章,希望大家能从中学到东西。