C语言结构体和联合
一、结构体的介绍
结构体的概念
- 在实际问题处理中,一组数据往往具有不同的数据类型。
- 例如在学生登记表中,姓名-字符型,学号-整型或字符型,年龄-整型,性别-字符型,成绩-整型或实型
- 数组由于其中各元素类型长度须一致所以无法存储这组类型不同的数据
- C语言引入了另一种__构造数据类型__称为结构(structure)或叫结构体。
- 结构体是一种构造类型,它是由若干成员组成的,每一个成员可以是一个基本数据类型或者又是一个构造类型。
结构体的声明
- 结构体在使用之前需要先声明结构体
- 结构体的声明语法
struct 结构体名
{
类型说明符 成员名;//成员列表
...
};
-
成员列表由若干个成员组成,每个成员都是该结构的一个组成部分,对每个成员也必须作类型声明。
-
成员可以是基本数据类型或者另一个构造类型
-
"{}"不表示复合语句,其后需要加上分号。
-
结构体的声明可以放置在程序的开始部分,位于头文件声明之后,也可以声明在头文件中。
-
结构体名与其成员可以重名,结构体成员和其他变量可以重名。
-
结构体类型名称是“struct 结构体名”,注意struct关键字不能省略。
-
结构体可以用来定义变量的类型,和其他标准数据类型不同的是,结构体需要用户自己指定。
-
结构体的使用必须要定义对应的__结构体变量__
-
结构体类型相当于一个模型,系统对其不分配实际内存,只有定义相应的变量才实际分配内存。
结构体变量的定义
- 预先已声明结构体
struct 结构体名 变量名1,…,变量名n; - 声明结构体的同时定义结构体变量
struct 结构体名 {成员列表;}变量名1,…,变量名n; - 直接定义结构体变量
struct {成员列表;}变量名1,…,变量名n; - 结构体 可以对应 java中的“类”,结构体变量 对应 “对象”
结构体变量成员的引用
- 在程序中使用结构体变量时,不能将结构体变量作为一个整体进行输入输出。
- 在C语言中一般对结构体变量的使用包括赋值、输入、输出、运算等都是通过结构体变量的成员来实现的。
- 结构体变量成员的引用方式
结构体变量.成员名;- 结构体变量成员通过使用运算符“.”来使用
- 如果成员本身又属于一个结构体类型,则要用若干个点运算符,一级一级地找到最低一级的成员。
- 结构体变量通过成员可以像普通变量一样进行各种相应的运算
- 可以引用结构体变量成员的地址,也可以引用结构体变量的地址(结构体指针)
结构体变量的赋值
结构体变量的赋值就是给结构体变量的各成员进行赋值
student.xh=100;
student.name="zhangsan";
可用输入语句来完成对成员的赋值
scanf("%c %f",&student.sex,&student.score);
允许具有相同类型的结构体变量相互赋值
student1=student2;
结构体变量的初始化
- 预先已声明结构体再定义结构体变量并初始化
struct 结构体名 变量名1={成员值列表},...,变量名n={成员值列表};
例
struct student stu1 =
{1,"zhangsan","male",20,"shanghai"};
struct student stu2 =
{2,"lisi","female",21,"beijing"};
- 声明结构体的同时定义结构体变量并初始化
struct 结构体名
{
成员列表;
}变量名1={成员值列表;},...,变量名n={成员值列表;};
- 直接定义结构体变量并初始化
struct //省略结构名
{
成员列表;
}变量名1={成员值列表;},...,变量名n={成员值列表;};
结构体变量的嵌套
- 可以将一个结构体放入另一个结构体内,但结构体不能嵌套它自身(除非是结构体指针类型)。
struct student
{
int xh;
char name[20];
struct address addr;
}stu;
- 要访问结构体address中的成员,而address是另一个结构体student的成员,可通过点运算符的链式方式访问。
- 例:访问成员country: stu.addr.country;
- 例:访问成员city: stu.addr.city;
例
/*文件:student.c*/
struct address
{
char *country;
char *city;
char *street;
};
struct student{
/*成员列表*/
int xh;
char *name;
char *gender;
//char name[30];
//char gender[20];
int age;
struct address addr; //嵌套结构体
};
/*文件:out_student.c*/
#include<stdio.h>
#include<string.h>
#include"student.h"
void out_student(struct student stu);
int main(void){
/*定义结构体变量并初始化*/
struct address addr =
{"china","shanghai","fanghua road"}
struct student stu1 =
{1,"zhangsan","male",20,addr};
struct student stu2 =
{2,"lisi","female",21,{"china","beijing","gugong road"}};
/*
/*定义结构体变量*/
struct student stu1,stu2;
stu1.xh=1;
stu1.name="zhangsan";//对应char *name,字符指针可以用字符串赋值
stu1.gender="male";
//strcpy(stu1.name,"zhangsan");//当结构中为数组时使用
//strcpy(stu1.gender,"male");
stu1.age=20;
/*结构体变量的赋值*/
//stu2 = stu1;
scanf("%d %s %s %d",&stu2.xh,stu2.name,stu2.gender,&stu2.age);
*/
out_student(stu1);
printf("------------\n");
out_student(stu2);
}
void out_student(struct student stu){
printf("xh:%d\n",stu.xh);
printf("name:%s\n",stu.name);
printf("gender:%s\n",stu.gender);
printf("age:%d\n",stu.age);
printf("country:%s\n",stu.addr.country);
printf("city:%s\n",stu.addr.city);
printf("street:%s\n",stu.addr.street);
}
二、结构体数组
结构体数组的概念
-
元素为结构体类型的数组称为结构体数组
-
在实际应用中,经常用结构体数组来表示具有_相同数据结构_的一个群体。例如一个班的学员档案,公司员工档案等。
- 定义了一个结构体数组stu,共有30个元素,stu[0]到stu[29]。
- 买个数组的元素都是struct student结构体类型的结构体变量。
结构体数组的定义和初始化
- 先声明结构体后定义结构体数组
struct 结构体名{成员列表;};
struct 结构体名 数组名[长度]={{成员值列表},...,{成员值列表}}
- 声明同时定义(结构体名可省略)
struct [结构体名]{成员列表;}
数组名[长度]={{成员值列表},...,{成员值列表}};
例
struct student
{
int xh;
char name[20];
char sex;
float score;
}stus[3];
strcpy(stus[0].name,"jack");//设置第一个学生的名字
stus[1].age++;//增加第二个学生的年龄。
结构体数组的引用
结构体数组名[下标].成员名
void out_students(struct student stus[],int n)
{
int i;
for(i=0;i<n;i++){
out_student(stus[i]);//逐个输出学员的全部信息
printf("======\n");
}
}
案例 学生信息的增删改查
案例介绍:使用结构体数组完成学生信息的增删改查
设计流程:
- 使用结构体类型来定义一名学生
- 使用结构体数组来存放一组学生并完成学生信息的增加,删除,修改和查找功能
/*文件名:student.h*/
struct address
{
char country[20];
char city[20];
char street[30];
};
struct student{
int xh;
char name[20];
char gender[10];
int age;
struct address addr;
};
//-----------------------------------------
/*文件名:stumis.h*/
#include "student.h"
#define MAX_SIZE 100
/*增加学生*/
extern void add_student(struct student stu);
/*删除学生*/
extern void del_student(int xh);
/*更新学生*/
extern void update_student(int xh,struct student newstu);
/*查找学生*/
extern struct student find_student(int xh);
/*查找所有学生*/
extern void findall();
//-----------------------------------------
/*文件名:stumis.c*/
#include "stumis.h"
#include <stdio.h>
#include <string.h>
static struct student stus[MAX_SIZE];
static int counter;
/*查找学生所在数组的位置(下标)*/
static int find_position(int xh){
int position;
for(position=0;position<MAX_SIZE;position++){
if(stus[position].xh == xh)return position;
}
return -1;
}
/*输出学生信息*/
static void out_student(struct student stu){
printf("xh:%d\n",stu.xh);
printf("name:%s\n",stu.name);
printf("gender:%s\n",stu.gender);
printf("age:%d\n",stu.age);
printf("country:%s\n",stu.addr.country);
printf("city:%s\n",stu.addr.city);
printf("street:%s\n",stu.addr.street);
}
/*增加学生*/
extern void add_student(struct student stu){
if(counter>=MAX_SIZE)return;
stus[counter]=stu;
counter++;
}
/*删除学生*/
extern void del_student(int xh){
int position=find_position(xh);
if(position==-1){
printf("delete student is not exist!\n");
return;
}
int i;
for(i=position;i<counter-1;i++){
stus[i]=stus[i+1];
}//删除就是用后面的数据前移覆盖掉该需要删除的数据
counter--;
}
/*更新学生*/
extern void update_student(int xh,struct student newstu){
int pos=find_position(xh);
if(pos==-1){
printf("update student is not exist!\n");
return;
}
strcpy(stus[pos].name,newstu.name);
stus[pos].age=newstu.age;
strcpy(stus[pos].addr.country,newstu.addr.country);
strcpy(stus[pos].addr.city,newstu.addr.city);
strcpy(stus[pos].addr.street,newstu.addr.street);
}
/*查找学生*/
extern struct student find_student(int xh){
int pos=find_position(xh);
if(pos==-1){
printf("find student is not exist!\n");
return;
}
return stus[pos];
}
/*查找所有学生*/
extern void findall(){
int i;
for(i=0;i<counter;i++){
out_student(stus[i]);
printf("---------------\n");
}
}
//-----------------------------------------
/*文件名:stumis_test.c*/
#include <stdio.h>
#include "stumis.h"
int main(void){
struct address addr1={"china","shanghai","fanghua road"};
struct student stu1={10,"zhangsan",20,"male",addr1};
struct address addr2={"china","beijing","changan road"};
struct student stu2={11,"lisi",22,"female",addr2};
printf("**********add student**********\n");
add_student(stu1);
add_student(stu2);
printf("**********find all student**********\n");
findall();
printf("**********find student**********\n");
struct student stu = find_student(10);
printf("%d %s %d %s\n",stu.xh,stu.name,stu.age,stu.addr.city);
printf("**********update student**********\n");
struct address newaddr={"china","wudangshan","zhongsan road"};
struct student newstu={10,"zhangsanfeng",100,"male",newaddr};
update_student(10,newstu);
stu=find_student(10);
printf("%d %s %d %s\n",stu.xh,stu.name,stu.age,stu.addr.city);
printf("**********delete student**********\n");
del_student(11);
printf("**********find all student**********\n");
findall();
return 0;
}
三、结构体指针
- 结构体指针的概念
- 结构体变量的地址(即结构体变量的第一个成员的地址)称为结构体指针,结构体指针指向一个结构体变量
- 结构体指针变量中的值是所指向的结构体变量的地址
- 结构体指针的定义
struct 结构体名 *结构体指针变量名; - 结构体指针的初始化
结构体指针变量名=&结构体变量;- 结构体指针必须要先初始化后才能使用,否则野指针
- 结构体变量成员的访问方式
-
通过结构体指针访问结构体变量的成员
结构体指针->成员名;
或者
(*结构体指针).成员名;- 如:stup->name; (*stup).name
- 第二种方式括号不能少,因为运算符“.”的优先级高于"*"
-
访问结构体变量的三种方式
- 方式一:结构体指针->成员名;
- 方式二:(*结构体指针).成员名;
- 方式一:结构体变量.成员名;
- 结构体指针作为函数参数
- 在C语言中允许用结构体变量作为函数参数进行整体传送。但这种传送要将全部成员逐个传送,特别是成员为数组时将会使传送的时间和空间开销很大,严重地降低了程序的效率。
- 最好的方法就是使用结构体指针,即用结构体指针变量作为函数参数进行传送,这时由实参传向形参的只是地址,从而减少了时间和空间开销。
void out_student(struct student *stup)
{
printf("xh:%d\n",stup->xh);
printf("name:%s\n",stup->name);
}
- 指向结构体数组的指针
- 结构体指针可以指向一个结构体数组,结构体指针变量的值是整个结构体数组的首地址。
- 结构体指针也可以指向结构体数组中的一个元素即结构体变量,这时结构体指针变量的值就是该结构体数组元素的地址。
- 一个结构体指针虽然可以用来访问结构体变量或结构体数组元素的成员,但是,不能使它指向一个成员,也就是说不允许取一个成员的地址来赋予它。
- 结构体的自引用和不完整声明
结构体的自引用
-
在一个结构体内部包含指向该结构体本身的指针(事实上指向的是同一类型的不同结构体变量)
struct SELF_REF1{ int a; struct SELF_REF1 *b; int c; }
结构体的不完整声明
- 不同结构体相互依赖
- 解决方案
- 声明一个作为结构体标签的标识符
- 用该标识符声明指向该结构体的指针
四、类型别名
- 类型定义符typedef
-
C语言允许用户自己定义类型说明符,允许用户自己为数据类型去别名
-
语法:
typedef 原类型名 新类型名(类型别名);-
用typedef定义数组、指针、结构体等类型别名将带来很大的方便。
-
易于理解,容易修改,可移植性好。
-
示例:
typedef float Dollars; Dollars cash_in,cash_out;
-
- #define定义符类型别名
-
typedef定义类型别名也可以用预处理指令#define即宏定义来代替
-
语法:
#define 新类型名 原类型名
-
结尾没有分号
-
示例
#define INTEGER int INTEGER a=100;
-
- typedef 和 #define的区别
-
#define指令是在预处理阶段完成的,而typedef则是在编译时完成的,后者更为灵活方便。
-
#define无法正确处理指针类型,typedef更为合适。
typedef char* ptr_char; ptr_char a,b,c; //a,b,c都为指针 #define PTR_CHAR char* PTR_CHAR a,b,c; //a为指针,b和c为char类型
五、字节对齐
1.字节对齐的概念
-
现代计算机中内存空间都是按照byte划分的,在访问特定类型变量的时候经常在特定的内存地址访问。
-
字节对齐的定义:
- 各种类型的数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是字节对齐。比如原来是1个字节char+4个字节int顺序存储,字节对齐后则char和int两个都为4个字节
-
字节对齐的原因和作用
- 各个硬件平台对存储空间的处理上有很大的不同。比如char+int,不对齐情况下读到int需要读两个周期再拼起来得到int,但对齐后,读取到int只需要一个周期
char a;int b;
不对齐:abbbb
对齐:a000bbbb - 利用字节对齐可提高存储效率,但是牺牲了空间
- 各个硬件平台对存储空间的处理上有很大的不同。比如char+int,不对齐情况下读到int需要读两个周期再拼起来得到int,但对齐后,读取到int只需要一个周期
-
字节对齐示例(假定运行在32位系统)
struct A{int a; char b; short c;}; sizeof(struct A);//8--aaaa bcc0 struct B{char b; int a; short c;}; sizeof(struct B);//12--b000 aaaa cc00
2.#pragma指令设置字节对齐
/*指定按2字节对齐*/
#pragma pack(2)
struct C{char b; int a; short c;};
#pragma pack()
sizeof(struct C);//6--b0 aa aa
/*指定按1字节对齐*/
#pragma pack(1)
struct D{char b; int a; short c;};
#pragma pack()
sizeof(struct D);//7--b a a a a c c
4.编译器的字节对齐原则
基本概念
- 数据类型自身的对齐值
- char型数据,其自身对齐值为1
- short类型为2
- int,float,double类型,其自身类型值为4
- 结构体自身对齐值:其成员中自身对齐值最大的那个值
- 指定对齐值:#pragma pack(value)时的指定对齐值value
- 数据成员、结构体的__有效对齐值__:自身对齐值和指定对齐值中小的那个值
重要概念
- 有效对齐N:存放起始地址%N=0
- 对齐值圆整:结构体成员变量占用总长度需要是对结构体有效对齐值的整数倍
5.字节对齐的编程设置
空间换取时间
-
结构体中的成员按类型大小从小到大定义
struct A{char a;char reserved[3];int b;}; sizeof(A); //8
六、位段
1.位段的概念
- C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”。利用位段能够用较少的位数存储数据。
2.位段的定义
-
语法:
类型说明符 [位段名]:位长- 位段名可选,位长表示该位段所占的二进制位数,不能超过该数据类型的上限
- 位段成员的类型说明符必须为int\signed int和unsigned int
- 无名位段不能被访问,但会占据空间
- 不能对位段进行取地址操作
struct node{ unsigned int a:4; unsigned int :0; unsigned int b:4; int c:32; int :6; };
七、联合
联合的概念
- 将几种不同类型的变量存放到__同一段内存中__,这种数据结构称为联合或者共用体。
- 联合的声明语法:
union 联合名 {类型说明符 成员名;…};- 所有成员使用的是内存中的相同位置
- 联合的长度是它最长成员的长度
- 联合变量的定义方式和结构体相同,只要将关键字换成union
- 联合变量初始化必须是联合的第一个成员类型值
八、学生信息录入
要求:
- 利用结构体和联合完成教师和学生信息的录入和输出
- 使用结构体来保存教师和学生相同的字段信息,如:号码、姓名、性别、职业。
- 使用联合并根据职业来设置教师和学生的类别(班级或职务)
/*文件名:student_teacher.c*/
#include<stdio.h>
struct{
char name[10]; char sex;
char job; int num; //job分为"s"和"t"
union{
int class; //班级号
char position[10];//职位名称
}category;
}person[2];
//-----------------------------
int main(void){
int n,i;
for(i=0;i<2;i++){
printf("Please enter num,name,sex,job\n");
scanf("%d %s %c %c",
&person[i].num,&person[i].name,
&person[i].sex,&person[i].job);
if(person[i].job=='s'){
printf("Please enter Class:");
scanf("%d",&person[i].category.class);
}else if(person[i].job=='t'){
printf("Please enter Position:");
scanf("%s",&person[i].category.position);
}else printf("input error!");
}
printf("\n");
printf("No.\tName\tsex\tjob\tclass/position\n");
for(i=0;i<2;i++){
if(person[i].job=='s'){
printf("%-6d %-10s %-3c %-3c %-6d\n",
person[i].num,person[i].name,
person[i].sex,person[i].job,person[i].category.class);
}else{
printf("%-6d %-10s %-3c %-3c %-6s\n",
person[i].num,person[i].name,
person[i].sex,person[i].job,
person[i].category.position);
}
}
}