一、概述
结构体和联合体用于描述事物的属性,如一只鸟的信息,可能包括它的品种,体重,颜色,年龄等。
二、结构体
用户根据自己的需求构造的数据类型,但必须“先定义,后使用”
用户必须先构造一个结构体类型,然后才能使用这个结构体类型来定义变量或数组
2.1、结构体类型
结构体是构造数据类型数据,使用关键字struct进行定义,它由若干个“组成”,每一个成员可以是相同、也可以部分相同,或者是完全不同
struct 结构体类型名{ // 结构体类型名取名要“见名之其意” 如学生stu 书的信息bookcard
数据类型1 成员1;
数据类型2 成员2;
数据类型3 成员3;
}
// 描述学生信息
struct student{
char no[8]; // 学号 字符数组
char name[8];// 姓名 字符数组
float eng; // 英语成绩
float math; // 数学成绩
float ave; // 平均成绩
}
一个结构体内部成员数据类型面可以是一个已经定义过的结构体类型(嵌套)。
struct date
{
int year;
int month;
int day;
};
struct student
{
char name[10];
char sex
struct date birthday;// birthday 成员被声明为struct data结构类型
int age;
float score;
};
结构体的成员名可以与程序中其他定义为基本类型的变量名同名,同一个程序中不同结构体的成员名也可以相同,它们代表的是不同的对象,不会发现冲突
如果结构体类型的定义在函数内部,则这个类型名的作用域仅为该函数;如果是定义在所有函数的外部,则可以在整个程序中使用
2.2、定义结构体变量的几种方式
2.2.1、方式一:先定义结构体,后定义变量
struct stu
{
char no[8];
char name[8];
float eng;
float math;
float ave;
};
// 用定义好的结构体类型来定义变量,该变量就可以用来存储一个学生的信息
struct student stu[20];//定义了一个包含20个元素的数组,每个元素都是一个结构体类型数据,可以存储20个学生的信息
当一个程序中多个函数内部需要定义同一个类型变量时,采用此方法。且结构体类型定义为全局类型
2.2.2、方式二:定义结构体同时定义变量
struct 结构体标识符
{
数据类型1 成员1;
数据类型2 成员2;
......
数据类型n 成员n
}变量1,变量2,......,变量n;
/*变量1,变量2,...,变量n为变量列表,遵循定义规则,彼此之间通过逗号分割。或结构体类型变量,例如在一个文件内部或函数内部*/
2.2.3、方式三:直接定义结构体类型变量
struct // 匿名结构体
{
数据类型1 成员名1;
数据类型2 成员名2;
....
数据类型n 成员名n;
}
2.3、初始化结构体变量
定义结构体变量的同时就是对其成员赋初值的操作(结构体变量初始化)。结构体初始化与数组初始化方式类似。在定义结构体变量的同时,把赋给各个成员的初值用“{}”括起来,称为初值表,各个数据用逗号分割。
struct 结构体标识符
{
数据类型1 成员名1;
数据类型2 成员名2;
......
数据类型n 成员名n;
}struct 结构体标识符 变量名={初始值化1,初始化值2,....,初始化值n};
struct stu
{
char name[10];
char sex;
int age;
float score;
}stu0[30],stu1={"zhangsi",1,20,88.8},stu2;
// 定义结构体类型struct stu的同时定义了结构体数组和两个结构体变量
2.4、结构体变量的引用
2.4.1、结构体变量成员的引用
结构体变量包括一个或多个结构体成员变量,应用其成员变量的语法格式如下结构体变量名.成员名其中“.”是专门的结构体成员运算符,用于连接结构体变量名和成员名,属于最高级运算符,结构体成员的引用表达式在任何地方出现都是一个整体,如
stu1.age,stu1.score
struct date
{
int year;
int month;
int day;
};
struct student
{
char name[10];
char sex;
struct date birthday;
int age;
float score;
}stu1;
结构体变量stu1成员birthday也是一个结构体类型的变量,这是嵌套的结构体定义。对该成员的引用,要用结构体成员运算符进行分级运算。对成员birthday的引用,stu.birthday.year,stu.birthday.month,stu.birthday.day
// 结构体成员变量和普通变量一样使用,需要对结构体变量进行赋值操作
scanf("%s",stu1.name);
stu1.sex=1;
stu1.age=20;
stu.birthday.year=1990;
2.4.2、结构体变量本身作为操作对象的引用
struct student
{
char name[10];
char sex;
int age;
float score;
};
struct student stu1 ={"zhangsan",1,20,88.8},stu2;
同类型的结构体变量之间可以进行赋值运算stu2=stu1;系统将按成员一一对应赋值,赋值语句执行后,stu2中的4个成员变量分别等到数值zhangsan、1、20、88.8c规定,不允许将一个结构体变量作为一个整体进行输入或输出操作
scanf("%s,%d,%d,%f",&stu1);
printf("%s,%d,%d,%f",stu1);
将结构体变量作为操作对象时
(1)用sizeof运算符计算结构体变量所占内存空间
定义结构体变量时,编译系统会为该变量分配内存空间,结构体变量所占内存空间的大小等于其各个成员所占内存空间之和。c中提供有sizeof运算符来计算结构体变量所占内存空间的大小sizeof(结构体变量名) 或 sizeof(结构体类型名)
(2)用&运算符对结构体变量进行取址运算
三、结构体数组
3.1、定义结构体数组的三种方式
3.1.1、先定义结构体类型,再定义结构体数组
struct 结构体标识符
{
数据类型1 成员名1;
数据类型2 成员名2;
.........
数据类型n 成员名n
}; struct 结构体标识符 数组名[数组长度];
3.1.2、定义结构体类型的同时,定义结构体数组
sturct 结构体标识符
{
数据类型1 成员名1;
数据类型2 成员名2;
.......
数据类型n 成员名n;
}数组名[数组长度];
3.1.3、不给出结构体类型名,直接定义结构体数组
struct
{
数据类型1 成员名1;
数据类型2 成员名2;
.......
数组名n 成员名n
}数组名[数组长度];
其中“数组名”为数组名称,遵循变量的命名规则:"数组长度"为数组的长度要求为大于零的整型常量。如:定义长度位10的struct student 类型数组 stu[10]的方法有如下三种形式。
// 第一种方式
struct student
{
char name[10];
char sex;
int age;
float score;
}stu;
struct student sut[10];
// 第二种方式
struct student
{
char name[10];
char sex;
int age;
float score;
}stu[10];
// 第三种方式
struct
{
char name[10];
char sex;
int age;
float score;
}stu[10];
结构体数组定义好后,系统即为其分配相应的内存空间,数组中个元素在内存中连续存放,每个数组元素读是结构体类型,分配相应大小的存储空间,例子中sut在内存中存放顺序如图所示
3.2、结构体数组的初始化
//1、 定义结构体的同时初始化,结构体的每一个元素
/*定义结构体类型的同时,定义长度为2的结构体数组stu[2],并分别对每个元素进行初始化
*/
struct student //定义结构体truct student
{
char Name[20];
float Math;
float English;
float Physical;
}stu[2]={{"zhang",78,89,95},{"wang",87,79,85}};
// 2、定义数组并同时进行初始化时,可省略数组长度,系统会根据初始化数据的多少来确定数组长度
truct key
{
char name[20];
int count;
}key1[]={{"break",0},{"case",0},{"void",0}}; // 系统会自动确认结构体数组key1的长度为3
3.3、结构体数组元素的引用
结构体数组元素引用语法格式:
数组名[数组下标];
#include "stdio.h"
void main()
{
struct student {
char name[10];
char sex; // m 表示男 f表示女
int age;
float score;
} stu[5]; // 5组数据
int i;
printf("请输入数据:姓名 性别 年龄 分数\n");
// 输入结构体数组个元素的成员值
for(i=0; i<5; i++)
scanf("%s %c %d %f",stu[i].name,&stu[i].sex,&stu[i].age,&stu[i].score);
printf("输出数据:姓名 年龄 分数\n");
for(i=0; i<5; i++)
if(stu[i].sex=='f')
printf("%s %d %4.1f\n",stu[i].name,stu[i].age,stu[i].score);
}
四、结构体指针
4.1、定义结构体指针
指针变量应用灵活可以指向任一类型的变量
整型指针指向一个整型变量,字符指针指向一个字符型的变量
同样,也可以指向一个结构体类型的指针,使他指向结构体类型的变量
相应的结构体变量的指针就是该变量所占用内存空间的首地址
//定义结构体指针变量的一般形式
struct 结构体名 *指针变量名;
struct student *p,stu1;
其中,struct student是一个已经定义过的结构体类型,这里定义的指针变量p是struct student结构体类型的指针变量,
它可以指向一个struct student结构体类型 的变量,例如:p=stu
4.2、初始化结构体指针
/*
使用结构体指针变量前必须进行初始化
初始化方式与基本数据类型指针变量的初始化一致
在定义的同时赋予其中一个结构体变量的首地址,即让结构体指针指向一个确定的地址值
*/
struct student
{
char name[10];
char sex;
struct date birthday;
int age;
float score;
}stu,*p=&stu;
/*
定义的结构体类型的变量stu和一个结构体类型的指针变量p,定义的时候编译系统会为stu分配该结构体类型所占字节数大小的存储空间
通过*p=&stu,使指针p指向结构体变量stu存储区域的首地址。这样指针变量p就有了确定的值,即结构体变量stu的首地址
*/
4.3、使用指针访问成员
/*
定义的结构体类型的变量stu和一个结构体类型的指针变量p,定义的时候编译系统会为stu分配该结构体类型所占字节数大小的存储空间
通过*p=&stu,使指针p指向结构体变量stu存储区域的首地址。这样指针变量p就有了确定的值,即结构体变量stu的首地址
*/
struct // 定义匿名结构体并初始化
{
int a;
char b;
}m,*p;
p=&m; // 指针变量指向p
/*使用指针p访问变量m中的成员有以下3种方法
1、使用运算符".",如m.a,m.b
2、使用"."运算符,通过指针变量访问目标变量,如(*p).a、(*p).b
由于运算符"."的优先级高于"*",因此必须使用圆括号把*p括起来即把(*p)作为一个整体
3、使用"->"运算符,通过指针变量访问目标变量,如p->a、p->b
说明:结构体指针在程序使用很频繁,为了简化引用形式,c提供结构成员运算符"->",利用它可以简化用指针引用结构成员的形式。
并且结构成员运算符"->"和"."的优先级相同,在c中属于高级运算符
*/
#include "stdio.h"
void main()
{
struct UE // 声明结构类型
{
char u1;
int u2;
}a={'c',90},*p=&a; // 声明结构体类型指针变量p并初始化
printf("%c %d\n",(*p).u1,(*p).u2); // 输出结构体成员变量a的值
// printf("%c %d\n",p->u1,p->u2); 等价于 printf("%c %d\n",(*p).u1,(*p).u2);
// printf("%c %d\n",a.u1,a.u2); 等价于 printf("%c %d\n",(*p).u1,(*p).u2);
}
4.4、指向结构体数组的指针
/*
指向结构体数组的结构体指针变量加1的结果是指向结构体数组的下一个元素
那么指向结构体指针变量的地址值的增量大小就是“szieof(结构体类型)”的字节数
*/
struct UE
{
char u1;
int u2;
}tt[4],*p=tt;
/*
定义一个结构体类型的指针p指向结构体数组tt的首地址
即初始时是指向数组的第一个元素,那么(*p).u1等价于tt[0].u1,(*p).u2等价与tt[1].u2
如果对p进行加1运算则指针p指向数组的第二个元素,即tt[1],那么(*p).u1等价与tt[1].u1,(*p)u2等价于tt[1].u2
指向结构体类型数组的结构体指针使用并不复杂,但要区分以下几种情况
p->u1++ // 等价于(p->u1)++,先取成员u1的值,再使用u1自增1
++p->u1 // 等价于++(p->u1),先对成员u1进行自增1,在取u1的值
(p++)->u1 // 等价于先取u1的值,用完后在使指针p加1
(++p)->u1 // 等价于先使用指针p加1,然后再取成员u1的值
*/
#include "stdio.h"
void main()
{
struct UE
{
char u1;
int u2;
}tt[4]={{'a',97},{'b',98},{'d',100}}; // 声明结构体类型的数组并初始化
struct UE *p =tt;
printf("%c %d\n",p->u1,p->u2); // 输出语句
printf("%c\n",(p++)->u1); // 输出语句
printf("%c %d\n",p->u1,p->u2++); // 输出语句
printf("%d\n",p->u2); // 输出语句
printf("%c %d\n",(++p)->u1,p->u2); // 输出语句
p++; // 结构体指针变量自增1
printf("%c %d\n",++p->u1,p->u2); // 输出语句
}
五、结构体与函数
5.1、结构体作为函数的参数,有两种形式
5.1.1、在函数之间直接传递结构体类型的数据 ---- 传值调用方式
#include "stdio.h"
#include "math.h"
/*结构体变量之间可以赋值
所以可以把结构体变量作为函数的参数使用
把函数的形参定义为结构体变量,函数调用时,将主调函数的实参传递给被掉函数的形参。
*/
struct triangle // 全局变量
{
float a,b,c;
};
// 自定义函数,功能是利用海伦公式计算三角形的面积
float area(struct triangle side1) // 结构体变量作为形参
{
float l,s;
l=(side1.a+side1.b+side1.c)/2; // 计算三角形的周长
s=sqrt(l*(l-side1.a)*(l-side1.b)*(l-side1.c));// 计算三角形的面积
return s;
}
void main()
{
float s;
struct triangle side; // 实参
printf("输入三角形的3条边长:\n");
scanf("%f %f %f",&side.a,&side.b,&side.c); // 键盘输入三角形的三条边长
s=area(side); // 调用自定义函数
printf("面积是:%f\n",s);
}
5.1.2、在函数之间传递结构指针 ---- 传址调用方式
#include "stdio.h"
#include "math.h"
/*
运用指向结构体类型的指针变量作为函数的参数,将主调函数的结构体变量的指针(实参)
传递给被掉函数的结构体指针(形参),利用作为形参的结构体指针操作主调函数中的结构体变量
及其成员,达到数据传递的目的
*/
struct triangle
{
float a,b,c;
};
float area (struct triangle *p)
{
float l,s;
l=(p->a+p->b+p->c)/2; // 三角形周长
s=sqrt(l*(l-p->a)*(l-p->b)*(l-p->c));
return s;
}
void main()
{
float s;
struct triangle side;
printf("请输入三角形的3条边长:\n");
scanf("%f %f %f",&side.a,&side.b,&side.c); // 从键盘输入三角形的三条边长
s=area(&side);
printf("面积是:%f\n",s);
}
5.2、结构体函数作返回值
#include "stdio.h"
#include "math.h"
/*
通常情况下,一个函数只有一个返回值
如果一个函数确实需要带多个返回值,可以利用全局变量或指针来解决
使用结构体,可以在被调函数中利用return语句将一个结构体的数据结果返回到主调函数中,从而得到多个返回值,有利于解决一个函数有多个返回值的情况
*/
struct area1
{
float l,s;
};
struct area1 area2 (float a,float b,float c)
{
struct area1 result;
result.l=(a+b+c)/2;
result.s = sqrt(result.l*(result.l-a)*(result.l-b)*(result.l-c));
return result;
}
void main()
{
float a,b,c;
struct area1 triangle;
printf("输入三角形的三条边长");
scanf("%f %f %f",&a,&b,&c); // 从键盘输入三角形的三条边长
triangle = area2(a,b,c);
printf("半周长是:%f\n的面积是:%f\n",triangle.l,triangle.s);
}
六、联合体
c语言中,可以定义不同数据类型的数据共占同一段内存空间,以满足某些特殊的数据处理要求,这种数据类型就是联合体。
6.1、联合体类型
联合体是一种构造数据类型,它是由各种不同类型的数据组成,这些数据叫联合体成员。
联合体中,c语言编译系统使用覆盖技术,使联合体的所有成员在内存中具有相同的首地址,占用同一段内存空间,这些数据可以相互覆盖,因此联合体常常被称之为共用体,在不同的时间保存不同的数据类型和不同的成员的值,即在某一时刻,只有最新存储的数据是有效的。
联合体的优点:节省内存空间
union 联合体名 // union 联合体声明的关键字 联合体名必须是合法的c语言标识符
{
数据类型1 成员名1; // 联合体类型成员可以是c语言中的任何一个数据类型
数据类型2 成员名2;
....
数据类型n 成员名n;
}; // 此处的分号表示定义的联合体结束
/*
用户构造一个联合体后,可以像c语言提供的基本数据类型一样使用
可以用它来定义变量,数组等
注意:定义联合体时,系统不会为其分配存储空间,而是由该联合体类型定义的变量、数组等分配存储空间
*/
union ucode
{
char u1;
int u2;
long u3;
};
6.2、联合体变量的定义的三种方式
// 1、定义联合体类型后定义变量
union 联合体名
{
数据类型1 成员名1;
数据类型2 成员名2;
.....
数据类型3 成员名3;
};
union 联合体名 变量名,变量名2... 变量名n;
// 2、定义联合体类型同时定义变量
union 联合体名
{
数据类型1 成员名1;
数据类型2 成员名2;
.....
数据类型3 成员名3;
}变量名1,变量名2.....变量名n;
// 3、直接定义联合体变量
union // 不指定具体的联合体名
{
数据类型1 成员名1;
数据类型2 成员名2;
.....
数据类型3 成员名3;
}变量名1,变量名2.....变量名n;
实质为
union // 不指定具体的联合体名
{
数据类型1 成员名1;
数据类型2 成员名2;
.....
数据类型3 成员名3;
};
/* 匿名联合体充当临时定义局部使用的联合体类型变量时注意事项
1、当一个联合体变量被定义,编译程序会自动给变量分配存储空间,其长度为联合体的数据成员中所占内存空间最大的成员长度
2、联合体可以嵌套定义,即一个联合体的成员可以是另外一个联合体类型的变量;另外联合体和结构体也可以相互嵌套
*/
6.3、联合体变量的初始化
6.4、联合体变量的引用
/*联合体不能整体引用,对联合体变量的赋值,使用都只能对变量的成员进行,联合体变量引用其成员的方法与访问
结构体变量成员的方法相同*/
union ucode
{
char u1;
int u2;
long u3;
};
union ucode a,*p=&a;
//对联合体成员的引用方法
//1、使用运算符".",访问联合体成员
a.u1,a.u2
// 2、使用指针变量访问联合的成员
(*p).u1,(*p).u2,p->u1,p->u2
七、结构体和联合体的区别
结构体和联合体都是由多个不同的数据类型组成,结构体用来描述同一事物的不同属性,所以任意使用结构体的成员都存在,对于结构体的不同成员赋值是互不影响的。而联合体虽然也有多个成员,但在任何同一时刻,对于联合体的不同成员赋值,将会对其他成员重写,原来成员的值将会被覆盖,即联合体中任一时刻只能存放一个被赋值的成员。
实际开发中,结构体类型应用比较多,而联合体主要是节约内存。
八、应用
#include "stdio.h"
#include "math.h"
#define N 5 // 宏定义字符常量N
struct student // 定义结构体类型
{
char num[8]; // 学号
char name[10]; // 姓名
float chinese; // 语文成绩
float english; // 英语成绩
float math; // 数学成绩
float total; // 总分
}stu[N]; // 定义结构体的同时声明一个N个元素的结构体数组
// 学生信息函数
void input()
{
int i;
printf("输入%d名学生的:学号 姓名 语文 英语 数学\n",N);
for(i=1;i<=N;i++)
{
printf("%d",i);
scanf("%s %s %f %f",stu[i].num,&stu[i].name,&stu[i].chinese,&stu[i].english,&stu[i].math);
}
}
// 定义计算总分的函数
float sum(struct student *p,int i)
{
stu[i].total=p->chinese+p->english+p->math;
return stu[i].total;
}
void main()
{
int i;
float stotal;
input();
printf("输入数据:学号 姓名 总分\n");
for(i=1;i<=N;i++)
{
stotal=sum(&stu[i],i);
printf("%s %s %5.1f\n",stu[i].num,stu[i].name,stotal);
}
}