一、定义和使用结构体
1.1概述
前面我们见过的数据类型,比如int,float,char等是在程序中简单的使用,如果我们要根据自己的需求来建立一些复杂的数据,就需要用到结构体。
例如,一个学生的学号,姓名,性别,年龄等是同属于一个学生的,但是这些变量又属于不同的类型,如果只是利用不同的变量分别进行简单的定义,那很难反应出来它们之间内在的联系。那么我们创建一个变量,能够将这些数据进行组合后放在变量中,这样使用起来就简单的多,这就是引出来结构体的学习;
结构体类型:struct Student{int num;//学号为整形;char name[20];//姓名为字符串;char sex;//性别为字符型;int age;//年龄为整形float score;//成绩为浮点型char addr[30];//地址为字符型;}
其中:
struct Student 为结构体类型;
struct是声明结构体类型的关键字。
Student是结构体名,为了同其他结构进行区分;
花括号中是该结构体的成员,组在一起称为成员列表,其命名与变量的命名是一致的;
注:结构体类型是可以有多中的;如:struct Student;struct Teacher;struct Worker;
结构体的成员也可以是另一个结构体的类型;
struct Date
{
int month;
int day;
int year;
};
struct Student
{
int num;
char name[20];
char sex;
int age;
struct Date birthday;
char addr[30];
}
1.2结构体变量的定义
前面我们只定义了一个结构体类型,相当于一个模型,并没有定义变量,下面我们定义结构体变量,并在其中存放具体的数据,3种方法;
方法1:先声明结构体类型,在定义结构变量
在前面结构体类型的基础上 struct Student,定义结构体变量;
struct Student student1,student2;
这种方式类似于int a,b;只是前提是已经有了结构体类型,在类型的基础上定义结构体变量。
方法2:在声明类型的同时定义变量
形式:
struct 结构体名
{
成员列表;
}变量名列表;
举例:
struct Student
{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
}student1,student2;
方法3:不指定类型名而直接定义结构体类型变量
形式:
struct
{
成员变量;
}变量名列表;
其为一个无名的结构体类型,不经常使用;
注:1、结构体变量中的成员名可以与程序中的变量名相同;
2、结构体变量中的成员可以单独使用;他的地位和作用相当于普通变量;
1.3结构体变量的初始化和引用
在定义结构体变量的时候顺便对其初始化;
举例:把一个学生的信息(包括学号、姓名、性别、住址)放在一个结构体变量中,然后输出该学生的信息;
#include <stdio.h>int main()
{struct Student{int num;char name[20];char sex;char addr[20];}student1 = {101,"Li li",'M',"123XiAn"};printf("NO:%d,\nname:%s\nssex:%c\naddress:%s\n",student1.num,student1.name,student1.sex,student1.addr);return 0;
结果分析:
在定义结构体变量的时候顺便对其进行成员初始化,初始化列表是用花括号括起来的一些常量,这些常量是一次赋给结构体变量的成员,注意:是对结构体变量初始化,不是对结构体类型初始化。
结构体变量中成员的值,引用方式为:
结构体变量名.成员名
student1.name
对于结构体成员本是又属于一个结构体类型,则要一级一级地找到最低一级的成员,例如上面所述的struct Student中包含 struct Date birthday;则引用的方式为:
Student birthday.month
同类型的结构体可以相互赋值: student1= student2;
举例:输入两个学生的学号、姓名和成绩,输出成绩较高的学生的学号、姓名和成绩;
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>int main()
{struct Student{int num;char name[20];float score;}student1,student2;scanf("%d%s%f", &student1.num, student1.name, &student1.score);scanf("%d%s%f", &student2.num, student2.name, &student2.score);printf("The higher score is:\n");if (student1.score > student2.score){printf("%d %s %6.2f\n", student1.num, student1.name, student1.score);}else if (student1.score < student2.score){printf("%d %s %6.2f\n", student2.num, student2.name, student2.score);}else{printf("%d %s %6.2f\n", student1.num, student1.name, student1.score);printf("%d %s %6.2f\n", student2.num, student2.name, student2.score);}
}
用scanf函数输入结构体变量的时候,必须分别输入,不能在scanf函数中使用结构体变量名一次性输入全部成员的值,注意,scanf函数在student1.name前面没有&,因为数组名本来就代表了地址。
二、使用结构体数组
2.1定义结构体数组
我们前面在定义结构体变量的时候,是一个一个进行定义的,但是,如果是一组有关联得数据需要参与运算,显然就需要用到数据,例如10名同学的信息,这就是结构体数组,结构体数组的每一个数组元素都是一个结构体。
举例:有3名候选人,每一个选民只能投票选一个人,要求编写一个统计票数的程序,先输入备选人的姓名,最后显示各人得票的结果。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>struct Person
{char name[20];int count;
}leader[3] = { "wang", 0, "zhang", 0, "li", 0 };
int main()
{int i, j;char lead_name[20];for (i = 0; i < 10; i++){scanf("%s", lead_name);for (j = 0; j < 3; j++){if (strcmp(lead_name, leader[j].name) == 0)leader[j].count++;}}printf("\nResult\n");for (i = 0; i < 3; i++){printf("name:%s,count:%d\n", leader[i].name, leader[i].count);}return 0;}
定义结构体数组的一般形式:
struct 结构体名
{
变量列表
} 数组名[数组长度];
或
先声明一个结构体类型,如:struct student,然后在定义结构体数组;
结构体类型 数组名[数组长度];
对于结构体数组初始化的形式是在定义后加:
={初值列表};
2.2使用结构体数组
举例:有n个学生的信息(包括学号、姓名、成绩),要求按照成绩的高低顺序输出各个学生的信息;
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>struct Student
{int num;char name[20];float score;
};int main()
{struct Student stu[5] = { 1001, "Wangwei", 98.25, 1002, "Liuliu", 91, 1003, "Zhangli", 98, 1004, "Xiaozhao", 85, 1005, "Baibai", 94 };struct Student temp;printf("The order id:\n");int i, j,k;for (i = 0; i < 4; i++){k = i;for (j = i + 1; j < 5; j++){if (stu[j].score>stu[k].score){k = j;}}temp = stu[k]; stu[k] = stu[i]; stu[i] = temp; }for (i = 0; i < 5; i++){printf("%d %s %5.2f", stu[i].num, stu[i].name, stu[i].score);printf("\n");}return 0;
}
三、结构体指针
3.1指向结构体变量的指针
所谓的结构体指针就是指向结构体变量的指针,一个结构体变量的起始地址就是这个结构体变量的指针。
指向结构体对象的指针变量既可以指向结构体变量,也可以指向结构体数组的元素,指针变量的基类型必须与结构体变量的类型相同。
如:struct Student * pt;
举例:通过指向结构体变量的指针变量输出结构体变量中成员的信息;
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>struct Student
{long num; char name[20];char sex;float score;
};int main()
{struct Student stu1;struct Student *pt;pt = &stu1;stu1.num = 10001;strcpy(stu1.name, "Lili");stu1.sex = 'M';stu1.score = 96.5;printf("No:%d\nname:%s\nsex:%c\nscore:%5.1f\n", stu1.num, stu1.name, stu1.sex, stu1.score);printf("\n");printf("No:%d\nname:%s\nsex:%c\nscore:%5.1f\n", (*pt).num, (*pt).name, (*pt).sex, (*pt).score);printf("\n");printf("No:%d\nname:%s\nsex:%c\nscore:%5.1f\n", pt->num, pt->name, pt->sex, pt->score);return 0;
}
结果分析:
在函数中,第一个printf函数是通过结构体变量名stu1进行成员的访问;
第二个printf函数是通过指向结构体变量的指针变量访问他的成员的,(* pt)表示指向的结构体变量,(*pt).num表示指向的结构体成员。
此外,在c语言中,允许将(*pt).num用pt->num代替;
3.2指向结构体数组的指针
可以用指针变量指向结构体数组的元素。
举例:有三个学生的信息,放在结构体数组中,要求输出学生的信息;
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>struct Student
{long num; char name[20];char sex;float score;
};int main()
{struct Student stu[3] = { 1001, "wangle", 'M', 95, 1002, "chengcai", 'M', 99.9, 1003, "shangmin", 'F', 85.2 };struct Student *pt;printf("No. name sex score\n");for (pt = stu; pt < stu + 3; pt++){printf("%d %s %c %5.2f\n", pt->num, pt->name, pt->sex, pt->score);}return 0;
}
程序中pt是一个指向struct Student类型结构体变量的指针变,它用来指向的是结构体变量,不是用来指向结构体中某一个成员,pt=stu[1].name是不合法的,因为一个是结构体变量,一个是结构体成员变量。p++,p的值增加的是结构体的长度。
3.3用结构体变量和结构体变量的指针作为函数参数
将一个结构体变量的值传递给函数,共有三种方法;
1、将结构体变量的成员作为参数,这种方法就是相当于传递了普通的变量,应该注意形参与实参(结构体成员)的类型相同;
2、用结构体变量作为实参。用结构体变量作为实参时,也是值传递,将结构体变量所占的内存单元的内容全部传递给形参,形参必须也是结构体变量。
3、利用指向结构体变量(数组)的指针作为实参,将结构体变量(数组)的地址传递给形参。
举例:
有n个结构体,包括学生学号,姓名和3门课程的成绩,要求输出平均成绩最高学生的信息(包括学号、姓名、3门课程成绩、平均成绩)。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
void print(struct Student stud);
struct Student Max(struct Student stu[]);
void input(struct Student stu[]);
struct Student
{int num; char name[20];float score[3];float aver;
};int main()
{struct Student stu[3], *pt;pt = stu;input(pt);print(Max(pt));return 0;
}
void input(struct Student stu[])
{int i;printf("请输入各学生的信息:学号、姓名、3门成绩:\n");for (i = 0; i < 3;i++){scanf("%d%s%f%f%f", &stu[i].num, stu[i].name, &stu[i].score[0], &stu[i].score[1], &stu[i].score[2]);stu[i].aver = (stu[i].score[0] + stu[i].score[1] + stu[i].score[2]) / 3.0; }
}struct Student Max(struct Student stu[])
{int i, m = 0;for (i = 0; i < 3; i++){if (stu[i].aver > stu[m].aver)m = i;}return stu[m];
}
void print(struct Student stud)
{printf("成绩最好的学生是:\n");printf("学号:%d 姓名:%s 三门课程:%5.2f %5.2f %5.2f平均成绩:%5.2f\n", stud.num, stud.name, stud.score[0], stud.score[1], stud.score[2], stud.aver);
}
1、调用input函数时,实参是指针变量pt,形参是结构体数组,传递的是结构体元素的起始地址,函数无返回值;
2、调用Max函数时,实参是指针变量pt,形参是结构体数组,传递的是结构体元素的起始地址,函数返回值为结构体类型数据。
3、调用print函数时,实参是结构体变量(结构体数组元素),形参是结构体变量,传递的是结构体变量中各成员的值,函数无返回值。
四、链表
4.1概述
链表是一种常见的数据结构,它是动态进行存储分配的一种结构。
数组在存放数据的时候,必须事先定义好数组长度(多个数组),如果有的班级有100人,有的班级有30人,若用同一个数组存放不同的班级学生的数据,则必须定义长度为100的数组,但是这样往往会存在资源的浪费,链表没有这样的缺点,他根据需要开辟内存单元。
链表有一个“头指针”变量,图中用head表示,它存放一个地址,该地址指向一个元素,链表中每一个元素称为“结点”,每个节点应该包括两个部分:
1、用户需要的实际数据;
2、下一个元素的地址;
可以看见链表中各元素在内存中的地址可以是不连续的,要找到某一元素,必须先找到前一个元素,根据前一个元素的地址才能找到下一个元素。如果不提供“头指针”,则整个链表都无法访问。
那这样来说,用结构体建立链表是最合适的,一个结构体包含若干个成员,用指针类型成员来存放下一个节点的地址。
struct Student
{
int num;
int score;
struct Student *next;
}
其中,num,score用来存放用户数据,next用于存放下一个节点的地址。
4.2静态链表
案例:
建立一个静态链表,由3个学生的数据的节点组成,要求输出各个节点的数据;
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>struct Student
{int num; float score;struct Student * next;
};int main()
{struct Student a, b, c, *head, *p;a.num = 1001; a.score = 85.5;b.num = 1002; b.score = 95.5;c.num = 1003; c.score = 84.5;head = &a;a.next = &b;b.next = &c;c.next = NULL;p = head;do{printf("%d %5.2f\n", p->num, p->score);p = p->next;} while (p != NULL);return 0;
}
为例建立链表,使head指向a结点,a.next指向b结点,b.next指向c结点,c.next指向null,这就构成了链表关系。
在输出链表的时候,要先借助p,先使p指向a,然后输出a中的数据,p=p->next是输出位下一个结点做准备。
4.3动态链表
4.3.1 动态内存分配
在说动态链表之前我们先给大家介绍,动态内存分配。
在前面我们说变量分为全局变量与局部变量,全局变量分配在内存的静态存储区,非静态存储的局部变量是分配在内存中的动态存储区,这个存储区被称为栈。
此外,C语言还允许建立内存动态分配区域,以存放一些临时用的数据,这些数据需要时开辟,不需要时释放,这些数据是临时存放在一个特别的自由存储区,这个存储区被称为堆。
对于内存的动态分配,是通过系统提供的函数来实现的:malloc、calloc、free以及realloc函数。
1、用malloc函数开辟动态存储区
函数: void *malloc (unsigned int size);
功能:
在内存的动态存储区分配一个长度为size的连续空间(单位:字节)。
返回值:
所分配的第一个字节的地址;此地址没有类型,只是一个纯地址;
2、用calloc函数开辟动态存储区
函数:void * calloc(unsigned n,unsigned size);
功能:
在内存的动态存储区分配n个字节长度为size的连续空间。这个空间比较大足以保存一个数组;
用calloc函数可以为一维数组开辟动态存储空间,n为数组元素个数,size为元素长度。
返回值:
所分配的第一个字节的地址;此地址没有类型,只是一个纯地址;
3、用realloc函数重新分配动态存储区
函数:void * realloc(void *p ,unsigned int size);
功能:
对已经分配的动态内存进行重新分配。
用recalloc函数将p所指向的动态空间的大小改为size。p的值保持不变;
返回值:
更新后动态内存的第一个字节的地址,实质上也就是p所指向的地址。
4、用free函数释放动态存储区
函数: void free(void *p);
功能:
释放指针变量p所指向的动态空间,使这部分空间能被其他变量使用,
p是最近一次调用malloc、calloc函数所得的返回值;
返回值:
无;
注:以上4个函数的声明都在stdlib.h头文件中。
void指针类型:
在上面的函数malloc函数与calloc函数的返回值都是void *类型,它表示不指向任何类型的数据,不要把其理解为指向任何的类型,而是指向空类型或者不指向确定类型的数据。
在调用动态内存的时候,程序只是利用了该函数带回来的纯地址,并没有用到指向哪一个数据类型的属性,如果要使用这个地址,必须将其进行转化。
例如:
int *p;
p=(int*)malloc(100);
案例:
建立一个动态数据,输入5个学生的成绩,另外用一个函数检车其中有无低于60分的,输出不合格的成绩;
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>int main()
{void check(int * p);int *p1, i;p1 = (int *)malloc(5 * sizeof(int));for (i = 0; i < 5; i++){scanf("%d", p1 + i);}check(p1);return 0;
}
void check(int * p)
{int i;printf("they are fail:\n");for (i = 0; i < 5; i++){if (*(p+i) < 60)printf("%d ", *(p + i));}
}
运行结果:
结果分析:
在程序中并没有定义数组,只是开辟了一段的动态自由分配区,作为数组使用。
在malloc函数中,并没有直接传递具体的数值用来分配动态空间,而是利用sizeof计算此系统整型的字节数,然后创建5个元素。利用p指向第一个字节地址,并将其转化为int类型,p+1指向下一个元素。
4.3.2动态链表建立
写一函数建立一个有4名学生数据的动态链表。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>#define LEN sizeof(struct Student)struct Student
{long num;float score;struct Student * next;
};int n;
struct Student * creat(void)
{struct Student * head, *p1, *p2;n = 0; p1 = p2 = malloc(LEN);scanf("%ld%f", &p1->num, &p1->score);head = NULL;while (p1->num != 0){n = n + 1;if (n == 1)head = p1;else p2->next = p1;p2 = p1;p1 = (struct Student *)malloc(LEN);scanf("%ld%f", &p1->num, &p1->score);}p2->next = NULL;return (head);}int main()
{struct Student * pt;pt = creat();printf("\n num:%ld score:%5.2f\n", pt->num, pt->score);return 0;
}
运行结果:
结果分析:动态链表的创建先指定了三个结构体指针,然后利用malloc函数先创建了一个节点,将三个结构体指针都指向这个结点,然后p1再去利用malloc函数进行创建节点,并将p2的next指向创建的结点,指向后p2=p1,p1再去创建p2的next指向创建的结点,p2=p1....直到p1中元素的值为0,p2的next不指向新创建的结点,指向NULL,这样一个动态链表就创建完毕,head指向最开始的结点,自己就是头结点。
动态链表的输入与输出:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>#define LEN sizeof(struct Student)struct Student
{long num;float score;struct Student * next;
};
int n;
struct Student * creat(void)
{struct Student * head, *p1, *p2;head = NULL;n = 0;p2 = p1 = malloc(LEN);scanf("%ld%f", &p1->num, &p1->score);while (p1->num != 0){n = n + 1;if (n == 1)head = p1;elsep2->next = p1;p2 = p1;p1 = malloc(LEN);scanf("%ld %f", &p1->num, &p1->score);}p2->next = NULL;return head;
}void print(struct Student * p)
{do{printf("%ld %5.2f\n", p->num ,p->score);p=p->next;} while (p->num != NULL);
}int main(void)
{struct Student * pt;pt = creat();print(pt);return 0;
}
运行结果:
五、共用体类型
5.1概述
在有些时候,希望再通一段内存单元中存放不同类型的变量,例如,将整型变量,字符型变量以及浮点型变量放在同一个地址开始的内存单元中,这使得几个变量共享同一段内存结构,称为“共用体”。
定义共用体的一般形式:
union 共用体名
{
成员列表;
}变量列表;
例如:
union Data
{
int i;
char ch;
float f;
}a,b,c;
结构体变量所占的内存长度是各成员占的内存长度之和;而共用体所占的内存长度是最长成员的长度。
5.2引用
只有事先定义好共用体变量,才能引用,注意,我们引用的不是共用体变量而是引用的共用体变量中的成员。
a.i 引用共用体变量中的整形变量;
a.ch 引用共用体变量中的字符变量;
a.f 引用共用体变量中的实型变量;
5.3特点
1、在内存段中可以用来存放几种不同类型的成员,但是在每一瞬间只能存放其中的一个成员,而不是同时存放几个。
2、可以对共用体变量初始化,但是初始化表中只能有一个常量。
3、共用体变量中其作用的成员,是最后一次被赋值的成员,前面原有的变量被覆盖取代。
4、共用体变量的地址和它的各成员的地址都是一样的。
5、不能对共用体变量名称赋值,也不能企图引用变量名来得到一个值。
六、枚举类型
如果有的变量的取值只有几种可能性,则可以定义为枚举类型;所谓的枚举就是将可能出现的值一一进行列举。
声明枚举类型用enum开头;例如
eunm Weekday{sun,mon,tue,wed,thu,fri,sat};
以上声明了一个枚举类型enum Weekday。然后可以用此类型来定义变量。
enum Weekday workday,weekend;
其中,workday与weekend被定义为枚举变量,花括号中称为枚举元素或枚举常量。
声明枚举类型的一般形式:
enum [枚举名] {枚举元素列表};
举例:
口袋中有红黄蓝白黑5种小球,每次从口袋中先后取出3个,问得到3种不同颜色的球的可能取法并进行排列;
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>int main()
{enum Color {red,yellow,bule,white,black};enum Color i, j, k, pri;int n, loop;n = 0;for (i = red; i <= black; i++)for (j = red; j <= black; j++)if (i != j){for (k = red; k <= black; k++)if ((k != i) && (k != j)){n++;printf("%d ", n);for (loop = 1; loop <= 3; loop++){switch (loop){case 1:pri = i; break;case 2:pri = j; break;case 3:pri = k; break;default:break;}switch (pri){case red:printf("%s ", "red"); break;case yellow:printf("%s ", "yellow"); break;case bule:printf("%s ", "bule"); break;case white:printf("%s ", "white"); break;case black:printf("%s ", "black"); break;default:break;}}printf("\n");}}printf("\n total:%d\n", n);return 0;
}
运行结果:
七、用typedef声明新类型名
利用typedef指定新的类型名代替已有的类型名;
1、简单地用一个新的类型名代替原有的类型名
例如:
typedef int Integer;
typedef float Real;
这样,下面两行是等价的:
int i,j;——— Integer i,j;
2、命名一个简单的类型名代替复杂的类型表示方法
命名新的类型名代表结构体类型:
typedef struct
{
int mun;
......
} Data;
则用新的类型名Data去定义变量;
Data brithday;
Data * p;
命名一个新的类型名代替数组类型:
typedef int Num[100];
Num a;
命名一个新的类型名代表指针类型:
typedef char * String;
String p,a[10];
命名一个新的类型名代表指向函数的指针;
typedef int (* Pointer)();
Pointer p1,p2;
归纳:按照定义变量的方式,把变量名换成新的类型名,并且在最前面加typedef,就声明了新类型名代表原来的类型。
typetef只是对已经存在的类型指定一个新的类型名,而并没有创造新的类型。
typetef和#define表面上有相似之处。
typedef int Count
#define Count int
他们的作用都是用Count代替int,但是事实上他们两者是不同的,#define是在预编译是处理的,他只能作为简单的字符串替换,而typedef是在编译阶段处理。实际上并不是简单的替换。而是生成了一个新的类型名,再去定义变量。