320 likes | 447 Views
第十一章 结构体. 11.1 概述 11.2 定义结构体变量的方法 11.3 结构体变量的引用 11.4 结构体变量的初始化 11.5 结构体数组 11.6 指向结构体数据的指针 11.7 用指针处理链表. 结构体类型定义形式 :. struct 结构体类型名 { 数据类型 成员名 1; 数据类型 成员名 2; : : 数据类型 成员名 n; } ;. 称成员表列. 11.1 概述. 在实际问题中我们常需要把不同类型的几个数据组合起来 , 构成
E N D
第十一章 结构体 • 11.1 概述 • 11.2 定义结构体变量的方法 • 11.3 结构体变量的引用 • 11.4 结构体变量的初始化 • 11.5 结构体数组 • 11.6 指向结构体数据的指针 • 11.7 用指针处理链表
结构体类型定义形式: struct 结构体类型名 { 数据类型 成员名1; 数据类型 成员名2; : : 数据类型 成员名n; } ; 称成员表列 11.1 概述 在实际问题中我们常需要把不同类型的几个数据组合起来, 构成 一个整体。如一个公司职员的个人信息, 或学校中教师和学生的信息。 以学生信息为例, 它可能包括学生的学号、班级、姓名、性别、年龄、 成绩等。这时原有的那些数据类型就显的有点无能为力了,所以引入 一种新的数据类型---- 结构体。 结构体是由一些逻辑相关, 但数据类型不同的分量组成的一组数据。 用户定义 的标识符 关键字 注意: 用户需要先定义 结构体类型, 之后才能 定义结构体变量 注意不要忘了分号
st1 st2 11.2 定义结构体变量的方法 name age s1 s2 一、 定义结构体变量 1. 先定义结构体类型, 再定义变量 struct student { char name[10] ; int age ; float s1 , s2 ; } ; struct student st1 , st2 ; name age s1 s2 结构体变量st1和st2各自都 需要20个字节的存储空间
3. 直接定义结构体变量 struct { char name[10] ; int age ; float s1 , s2 ; }st1 , st2 ; 2. 定义结构体类型同时定义变量 struct student { char name[10] ; int age ; float s1 , s2 ; }st1 , st2 ; 4. 说明: (1) 结构体变量具有结构体类型的一切特征 在内存中结构体变量占有一片连续的存储单元 存储单元的字节数可用sizeof 运算符算出 printf(“%d\n” , sizeof(struct student) ) ; printf(“%d\n” , sizeof(st1) ) ;
(2) 结构体类型可以嵌套定义 例: struct date { int year ; int month ; int day ; } ; struct stud { char name[10] ; struct date birthday ; float s1 , s2 ; } ; 或 : struct stud { char name[10] ; struct date { int year ; int month ; int day ; }birthday ; float s1 , s2 ; } ;
11.3 结构体变量的引用 1. 引用结构体变量中的成员 格式:结构体变量名. 成员名 struct student st1 ; st1. name = “Mary” ; st1. age = 21 ; st1. s1 = 78 ; st1. s2 = 86 ; struct student { char name[10] ; int age ; float s1 , s2 ; } ; 注意: 一般是对结构体变量的各个成员分别进行赋值 st1 = { “Mary”, 21 , 78 , 86 } ; 这样的赋值是不允许的
struct stud st2 ; int age , year ; st2. name = “John” ; st2. age = 20 ; st2. birthday. year = 1980 ; st2. birthday. month = 11 ; st2. birthday. day = 23 ; st2. s1 = 89 ; st2. s2 = 95 ; age = 24 ; year = 2000 ; struct date { int year ; int month ; int day ; } ; struct stud { char name[10] ; int age ; struct date birthday; float s1 , s2 ; } ; 可以定义与结构体变量 成员名相同名字的变量 它们之间不会发生混乱
注意要正确赋值的条件 是变量st1已经有了数据 2. 相同类型的结构体变量可以进行整体赋值 struct stud st1, st2, st3; st1. name = “John” ; st1. age = 20 ; st1. birthday.year = 1980 ; st1. birthday.month = 11 ; st1. birthday.day = 23 ; st1. s1 = 89 ; st1. s2 = 95 ; st2=st1; st3. name=“Mary”; st3. age=20; st3. birthday=st1. birthday; st3. s1 = 76; st3. s2 = 85; struct date { int year ; int month ; int day ; } ; struct stud { char name[10] ; int age ; struct date birthday; float s1 , s2 ; } ;
3. 结构体变量的输入 输出 C语言不允许结构体变量整体进行输入和输出, 只能对结构体变量的成员进行输入和输出 gets( st1. name ) ; scanf( “%d%d%d”, &st1. birthday. year , &st1. birthday. month , &st1. birthday. day ) ; scanf ( “%d%f%f”, &st1. age , &st1. s1 , &st1. s2 ) ; puts( s1. name ) ; printf( “%4d”, st1. age ); printf( “%d .%d .%d”, st1. birthday. year , st1. birthday. month , st1. birthday. day ) ; printf(“%5.2f %5.2f\n”, st1. s1 , s1t. s2 ) ;
11.4 结构体变量的初始化 struct student { char name[10] ; int age ; float score1 , score2 ; } ; struct student st1; st1={“ Mary”, 21, 78, 86} ; struct student { char name[10] ; int age ; float score1 , score2 ; } st1={“ Mary”, 21, 78, 86} ; 这是初始化, 正确 struct stud { char name[10] ; struct date birthday ; float score1 , score2 ; } ; struct stud st2={ “John” , 1980 , 11 , 23 , 89 , 95 } ; 这是赋值, 错误 C不允许这么做
11.5 结构体数组 一、 结构体数组的定义 1. 先定义结构体类型 再定义结构体数组 struct student { char name[10] ; int age ; float s1 , s2 ; } ; struct student st[6] ; 2. 定义结构体类型的同时定义数组 struct student { char name[10] ; int age ; float s1 , s2 ; } st[6] ; 3. 直接定义结构体数组 struct { char name[10] ; int age ; float s1 , s2 ; } st[6] ;
st[0] st[1] st[2] 二、结构体数组的初始化 将每个数组元素的数据用花括号{ } 括起来 struct student { char name[10] ; int age ; float s1 , s2 ; } ; struct student st[3]={ {“Mary”, 21, 78, 86}, {“Alex”, 20, 90, 80} , {“Mike”,19, 75, 68} };
都是结构体变量的整体赋值 三、 结构体数组的引用 1. 引用某个数组元素的成员 例:puts( st[0]. name ) ; printf(“%d, %d”, st[1]. age , st[1]. s1 ) ; 2. 数组元素之间可以整体赋值 也可以将一个元素赋给一个相同类型的结构体变量 struct student x , st[3]={ {“Mary”,21,78,86}, {“Alex”, …} } ; st[2] = st[0] ; x = st[1] ; 3. 只能对数组元素的成员进行输入和输出 gets( st[2]. name ) ; scanf(“%d” , &st[2]. age) ; scanf(“%f%f ”, &st[2]. s1 , &st[2]. s2 ); puts( st[0]. name ); printf(“%4d%5.2f %5.2f\n”, st[0]. age , st[0]. s1 , st[0]. s2) ;
例: 有30名学生, 每个学生包括学号、姓名、成绩, 要求找出成绩最高者,并输出他的信息 #include <stdio.h> #define N 30 void main( ) { struct student { int n ; char name[10] ; int score ; }; struct studentst[N]; int i , m ; int max ; for ( i=0 ; i<N ; i++) scanf(“%d%s%d”, &st[i].n , st[i].name, &st[i].score) ; max=st[0].score; for( i=1 ; i<N ; i++ ) if ( st[i].score > max ) { max=st[i].score; m=i; } printf(“%4d”, st[m].n ) ; printf(“%10s ”, st[m].name ); printf(“%5d ”, st[m].score); }
例: 按成绩对学生信息进行从高到底的排序 void sort(struct stud a[ ] ) { int i , j ; struct stud temp; for ( i=0 ; i<N-1 ; i++) for ( j=i+1 ; j<N ; j++) if ( a[i].s<a[j].s ) { temp=a[i] ; a[i]=a[j] ; a[j]=temp ; } } #include <stdio.h> #define N 30 struct stud { int n ; char name[10] ; int s ; }; void input(struct stud a[ ]) { int i ; for ( i=0 ; i<N ; i++) scanf(“%d%s%d”, &a[i].n, a[i].name, &a[i].s) ; } void main( ) { struct studst[N]; input(st); sort(st); output(st); } void output(struct stud a[ ]) { int i ; for ( i=0 ; i<N ; i++) printf(“%4d%10s%4d”, a[i].n, a[i].name, a[i].s) ; }
11.6 指向结构体数据的指针 一、 指向结构体变量的指针 1. 定义 struct student { char name[20] ; int age ; float s1 , s2 ; } ; struct student stu , *p ; p = &stu ; 2. 成员的引用格式 (1)结构体变量名. 成员名 stu . name (2) (*指针变量名) . 成员名 (*p) . age (3) 指针变量名 -> 成员名 p -> s1 gets( stu. name ); (*p). age = 21 ; p -> s1 = 87 ; p -> s2 = 90 ;
二、 指向结构体数组的指针 1. 定义 struct student a[3] , *p ; 2. 使用 for ( p=a ; p<a+3 ; p++ ) { gets( p->name ) ; scanf( “%d%d %d ”,&p->age,&p->s1,&p->s2) ; } 三、 结构体变量作为函数参数 1. 函数的实参和形参都用结构体变量 , 参数之间为值传递 即:实参 结构体变量各成员的值依次传给形参结构体变量 注意 结构体类型是已经定义好的 2. 返回结构体类型值的函数 函数定义格式 : 结构体类型名 函数名 ( 形参表列) { 函数体 ; } 例 : struct student funct ( int x , float y ) { 函数体 ; }
例: 求学生成绩的总分和平均分 #include <stdio.h> #define N 5 struct stud { char name[10] ; int s[3] ; float sum , ave ; } ; struct stud count (struct stud x) /*结构体变量做形参,函数返回值为结构体类型*/ { int j ; x.sum = 0 ; for ( j = 0; j<3 ; j++ ) x.sum = x.sum + x.s[j] ; x.ave = x.sum / 3 ; return(x); } void main ( ) { struct stud a[N] ; int j ; for ( j=0; j<N; j++ ) { scanf(“%s%d ”, a[j].name, &a[j].s[0] ); scanf(“%d %d ”, &a[j].s[1] , &a[j].s[2] ); } for ( j=0; j<N; j++) a[j]=count ( a[j] ) ;/*结构体数组元素名作实参*/ for ( j=0; j<N; j++) { printf(“%10s%4d”, a[j].name, a[j].s[0] ); printf(“%4d%4d”, a[j].s[1], a[j].s[2] ); printf(“%8.2f %6.2f\n”, a[j].sum , a[j].ave ); } }
数据成员 指针成员 11.7 用指针处理链表 一.、基本概念 1. 动态存储分配: 根据需要临时分配内存单元用以存放数据, 当数据不用时可以随时释放内存单元 2. 链表: 是可以动态地进行存储分配的一种数据结构 它是由一组动态数据链接而成的序列 3. 结点: 链表中的每一个动态数据称为一个结点 4. 结点类型: 是一个包含指针项的结构体类型 一般由两部分组成: (1) 数据成员: 存放数据 (2) 指针成员: 存放下一个结点的地址 struct sd { int num; int score; struct sd *next ; } ;
head 2010 1428 1570 1 2 3 2010 95 86 82 1428 1570 NULL 二、简单链表 头指针 表头结点 表尾结点 NULL为空地址, 表示链表到此结束
head a c b p #include <stdio.h> struct sd { int num; int score; struct sd *next ; } ; void main( ) { struct sd a,b,c,*head,*p; head=&a; a.num=1; a.score=95; a.next=&b; b.num=2; b.score=86; b.next=&c; c.num=3; c.score=82; c.next=NULL; p=head; while(p!=NULL) { printf(“%3d%4d\n”,p->num,p->score); p=p->next; } } 2010 1428 1570 2010 2 86 1570 3 82 NULL 1 95 1428 NULL 1428 1570 2010 说明: 该程序虽然建立了一个链表, 但这个链表是静态的, 因为它 的结点个数是固定的, 不能按需要增加新的结点,也不能按需要删 除结点. 这样的链表并不实用, 我们需要的是“动态链表”
三、 处理动态链表所需的函数 ( 需用头文件<stdlib.h> ) 1. malloc 函数 原型 : void *malloc( unsigned int size ) 作用 : 在内存中开辟一个长度为 size 的连续存储空间 , 并将此存储 空间的起始地址带回 注意 : (1) 函数返回值是指针, 但该指针是指向void类型的 , 因此在 使用时希望这个指针指向其他类型需要用强制类型转换 (2) 如果内存缺少足够大的空间进行分配 , 则 malloc 函数 返回值为“空指针” (即NULL) 结构体类型占用的字节长度 例: struct sd *p; p = (struct sd * ) malloc ( sizeof(struct sd) ) ; 强制类型转换
p 2. free函数 原型 : void free( void *ptr ) 作用 : 将指针变量ptr指向的存储空间释放 注意: (1) ptr的值不是任意的地址, 必须是程序中执行malloc或 calloc函数所返回的地址 (2) 模型中ptr是void型, 但调用free函数时, 参数可能是 其他类型, 计算机系统会自动进行转换 例: struct sd *p; p=(struct sd *) malloc (sizeof(struct sd)) ; free(p); 4200 4200
四、 链表应用举例 1. 建立链表(表尾添加法) #include <stdio.h> #include <stdlib.h> #define ST struct student ST { int num ; int score ; ST *next ; }; #define LEN sizeof(ST) int n ; 宏定义ST , 以后书写简单 求出结构体类型占用的字节数 全局量 , n 表示结点的个数
3264 head p1 p2 n 0 2010 1428 1570 1 95 2 86 3 82 0 0 NULL 2010 1 2 3 1428 1570 NULL 1570 3264 1428 1570 1428 创建一个由head指向的链表 ST * creat(void) { ST *head , *p1 , *p2 ; n=0 ; head=NULL ; printf(“\ninput num and score:”); scanf( “%d %d ”, &x, &y ) ;/*输入某个学生的学号和成绩*/ while ( x!=0 ) /*当学号不为0时循环执行*/ { n=n+1; p1 = (ST *) malloc (LEN) ; /*生成新结点p*/ p->num=x;p->score=y; if (n= =1) head=p1 ; /*新结点为链表中第一个结点*/ else p2->next=p1 ; /*新结点为链表中中间结点*/ p2 = p1 ; printf(“\ninput num and score:”); scanf( “%d %d ”, &x, &y ) ; } p2->next = NULL ; return( head ) ;/*返回新建链表的头指针*/ } 2010 2010
head p 2010 1428 1570 2010 2 86 1570 3 82 NULL 1 95 1428 2. 输出链表 (将head指向的链表从头到尾输出一遍) void list ( ST *head ) { ST *p ; p = head ; /*从表头结点开始对链表进行访问*/ while ( p!=NULL ) /*当访问完表尾结点结束循环*/ { printf(“%3d %4d\n”, p->num, p->score ) ; p = p->next ; /*找下一个结点*/ } } 1428 1570 NULL 2010
2010 1428 1570 head 2010 2 86 1570 3 82 NULL 1 95 1428 num 2 p1 p2 n 3 2 1570 3. 链表的删除 (在head指向的链表中,删除数据域为num的结点) ST *del ( ST *head , int num ) { ST *p1 , *p2 ; p1 = head ; while ( ( num!=p1->num) && (p1->next!=NULL) ) { p2 = p1 ; p1 = p1->next ; }/*p1指向被删除结点p2指向 被删除结点的前面的结点*/ if ( num = = p1->num ) { if ( p1= =head ) head=p1->next ; /*被删除结点为表头结点*/ else p2->next=p1->next ; /*被删除结点是中间结点*/ free( p1 ) ; n=n-1 ; /*释放被删除结点,链表长度减1*/ printf( “deleted ! \n”) ; /*删除成功*/ } else printf( “can not delete!\n”) ; /*删除不成功*/ return( head ) ; /*返回被删除结点链表的头结点*/ } 1428 2010 2010
1428 1570 2010 head 2010 5 82 NULL 1 95 1428 3 86 1570 2680 p1 p0 p2 n 3 4 2680 4. 链表的插入 ST *insert ( ST *head ) { ST *p0 , *p1 , *p2 ; p1=head ; p0=( ST *) malloc ( LEN ) ; scanf (“%d%d”, &p0->num, &p0->score) ; if ( head= =NULL ) /*链表为空*/ { head = p0 ; p0->next = NULL ; } else { while ( ( p0->num > p1->num ) && ( p1->next!=NULL) ) { p2 = p1 ; p1 = p1->next ; }/*寻找结点插入位置*/ if ( p0->num<p1->num ) /*插入结点位置在链表中间*/ { if ( head= =p1) head = p0 ; else p2->next =p0 ; p0->next = p1 ; /*将结点插入到链表中*/ } else { p1->next = p0 ; p0->next = NULL; } /*插入结点位置在链表尾部*/ } n++ ;/*链表长度加1*/ return( head ) ;/*返回处理后链表的头指针*/ } 2010 1428 1570 1428 2010 4 75 1570 2680
1428 1570 2010 head 2010 5 82 NULL 2 95 1428 3 86 1570 2680 p0 p1 p2 n 3 插入的结点作为表头 4 2680 1 75 2680 2010 2010
1428 1570 2010 head 2010 5 82 NULL 2 95 1428 3 86 1570 2680 p0 p1 p2 n 3 插入的结点作为表尾 4 2680 8 75 1570 1428 2010 1428 2010 2680 NULL
5. main 函数 void main ( ) { ST *h ; int delnum ; h = creat( ) ; list ( h ) ; scanf(“%d”, &delnum ) ; h = del ( h , delnum ) ; list ( h ) ; h = insert ( h ) ; list ( h ) ; } /* 建立一个链表 */ /* 输出链表 */ /* 输入要删除的学号 */ /* 删除学号为delnum的结点 */ /* 在链表中插入一个结点 */
课后作业:P297 11.5 11.8 11.10