8. 结构体、共用体、枚举
8.1 结构体的概念和定义
8.1.1 基本概述
构造类型:
不是基本类型的数据结构也不是指针,它是若干个相同或不同类型的数据构成的集合。常用的构造类型有数组、结构体、共用体。
- 数组用于保存多个相同类型的数据
- 结构体用于保存多个不同类型的数据
8.1.2 概念
结构体是一种构造类型的数据结构,是一种或多种基本类型或构造类型的数据的集合。
8.1.3 定义
struct 结构体类型名{
成员列表
}
// 第一种定义方法
struct stu{ // 定义三个struct stu成员
int num;
char name[20];
char sex;
};
// 有了结构体之后,可以用类型定义变量
struct stu lucy, bob , meimei; // 每个变量都有三个成员 num,name,sex
// 第二种定义方法
struct 结构体类型名{
结构体变量
} 结构体变量1,结构体变量2;
struct stu{
int num;
char name[20];
char sex;
} lucy,bob,lilei; // 定义结构体的同时定义结构体变量
struct stu xiaohong,xiaoming; // 这种就和第一种一样,定义完类型后再定义变量
注意:
- 一般结构体类型都会定义在全局,也就是main函数的外面。所以在定义结构体类型的同时定义变量,这些变量一般都是全局变量
- 定义完类型之后,再定义的结构体变量,内存分配要看定义的位置
// 第三种定义方法,无名结构体
struct {
成员列表
} 变量1,变量2;
// 注意:无名结构体由于没有结构体名,所以定义完之后是无法在定义结构体变量的,只能在定义类型的同时定义结构体变量
// 第四种方法,最常用的方法
typedef struct 结构体名{
成员列表
} 重新定义的结构体类型名A;
// 1. 先用结构体定义变量
typedef struct stu{
int num;
char name[20];
char sex;
} STU;
// 注意:typedef主要用于给一个类型取别名,此时相当于给当前结构体重新起了一个类型名为A相当于 struct 结构体名,所以如果结构体要取别名,一般不需要先给结构体定义名字,定义结构体变量时,直接使用A即可,不用加struct
// 2. 使用
STU lucy; // 这个与struct stu lucy是等价的
8.2 结构体变量的初始化
#include <stdio.h>
struct stu
{
int id;
char name[32];
char sex;
int age;
} zhangsan, lisi;
// 使用typedef对结构体取别名
typedef struct
{
int a;
int b;
} MSG;
int main(int argc, char const *argv[])
{
struct stu wangwu;
struct stu zhaoliu = {1001,"老刘", "b",20};
// typedef 定义变量不需要加struct
MSG msg1,msg2;
return 0;
}
8.3 结构体变量的使用
8.3.1 基本调用
结构体变量对成员的调用方式:
结构体变量.结构体成员
注意:这地方的结构体变量针对的是普通结构体变量
#include <stdio.h>
#include <string.h>
struct stu
{
int id;
char name[32];
char sex;
int age;
} zhangsan, lisi;
// 使用typedef对结构体取别名
typedef struct
{
int a;
int b;
} MSG;
int main(int argc, char const *argv[])
{
struct stu wangwu;
struct stu zhaoliu = {1001, "老刘", "b", 20};
// typedef 定义变量不需要加struct
MSG msg1, msg2;
// 结构体变量使用
zhangsan.id = 1000;
strcpy(zhangsan.name, "zhangsan"); // 字符串赋值
zhangsan.sex = 'A';
zhangsan.age = 18;
printf("%d - %s -%c -%d\n", zhangsan.id, zhangsan.name, zhangsan.sex, zhangsan.age);
return 0;
}
输出结果
1000 - zhangsan -A -18
8.3.2 结构体嵌套
#include <stdio.h>
#include <string.h>
typedef struct
{
int year;
int month;
int day;
} BD;
typedef struct stu
{
int id;
char name[32];
BD birthday;
} STU;
int main(int argc, char const *argv[])
{
STU xiaoming;
xiaoming.id = 1000;
strcpy(xiaoming.name, "xiaoming");
xiaoming.birthday.year = 2022;
xiaoming.birthday.month = 12;
xiaoming.birthday.day = 10;
printf("%d - %s - %d - %d -%d", xiaoming.id, xiaoming.name, xiaoming.birthday.year, xiaoming.birthday.month, xiaoming.birthday.day);
return 0;
}
输出结果
1000 - xiaoming - 2022 - 12 -10
8.3.3 结构体相互赋值
相同类型的结构体变量可以相互赋值
#include <stdio.h>
#include <string.h>
struct stu
{
int id;
char name[32];
char sex;
int age;
} zhangsan;
int main(int argc, char const *argv[])
{
zhangsan.id = 1000;
strcpy(zhangsan.name, "zhangsan");
zhangsan.sex = 'B';
zhangsan.age = 18;
printf("%d - %s -%c -%d\n", zhangsan.id, zhangsan.name, zhangsan.sex, zhangsan.age);
// 相同类型的结构体之间可以相互赋值
struct stu lisi;
lisi = zhangsan;
strcpy(lisi.name, "lisi");
printf("%d - %s -%c -%d\n", lisi.id, lisi.name, lisi.sex, lisi.age);
return 0;
}
输出结果
1000 - zhangsan -B -18
1000 - lisi -B -18
8.4 结构体数组
结构体数组是一个数组,由若干个相同类型的结构体变量构成的集合。
8.4.1 结构体数组的定义方法
struct 结构体类型名 数组名[元素个数];
struct stu edu[3]; // 定义了一个struct stu类型的结构体数组stu
8.4.2 引用
// 使用下标引用
// 数组名[下标]
edu[1]
8.4.3 结构体数组元素对成员的使用
数组名[下标].成员
#include <stdio.h>
typedef struct
{
/* data */
int num;
char name[20];
float score;
} STU;
int main(int argc, char const *argv[])
{
/* 定义一个结构体数组 */
STU edu[3] = {{101, "Lucy", 78},
{102, "Bob", 59.5},
{103, "Tom", 85}};
int i;
float sum = 0;
for (i = 0; i < 3; i++)
{
sum += edu[i].score;
}
printf("平均成绩为%.2f", sum / 3);
return 0;
}
输出结果
平均成绩为74.17
8.5 结构体指针
结构体指针即结构体的地址,结构体变量存放内存中,也有起始地址。
定义一个变量来存放这个地址,那这个变量就是结构体指针变量。
struct 结构体类型名 * 结构体指针变量名;
// 结构体指针变量对成员的引用
(*结构体指针变量名).成员;
结构体指针变量名 -> 成员; // 这种方式最常用
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct stu
{
/* data */
int id;
char name[32];
char sex;
int age;
};
int main(int argc, char const *argv[])
{
// 定义一个结构体指针变量
struct stu *s;
// 在堆区开辟结构体空间并将其地址保存在结构体指针变量中
s = (struct stu *)malloc(sizeof(struct stu));
s->id = 1001;
strcpy(s->name, "zhangsan");
s->sex = 'B';
s->age = 20;
printf("%d - %s - %c -%d\n", s->id, s->name, s->sex, s->age);
return 0;
}
输出结果
1001 - zhangsan - B -20
8.6 结构体内存分配
8.6.1 结构体内存分配
? 结构体变量大小是它所有成员之和。因为结构体变量是所有成员的集合。
貌似这两句话没啥问题,实际上不是这样的,C语言中开辟内存是有规则的。
8.6.2 规则 1: 以多少个字节为单位开辟内存
在结构体变量分配内存的时候,会在结构体变量中找占字节数多的基本类型成员,就以它大大小为单位开辟内存。(在 gcc 中出现了 double 类型的例外)
-
成员中只有
char
型数据 ,以1字节
为单位开辟内存。 -
成员中出现了
short int
类型数据,没有更大字节数的基本类型数据,以2字节
为单位开辟内存 -
成员中出现了
int float
且 没有更大字节的基本类型数据的时候以4字节
为单位开辟内存 -
成员中出现了
double
类型的数据,以8字节
为单位开辟内存在vc环境,double以8字节开辟内存空间; 在gcc环境,double以4字节开辟内存空间; //注意 :上述是针对结构体而言的,对于变量,无论哪种环境都是8字节
-
如果在结构体中出现了数组,数组可以看成多个变量的集合。
如果出现指针的话,没有占字节数更大的类型的,以4字节
为单位开辟内存。
8.6.3 规则 2: 字节对齐
char
1字节对齐 ,即存放 char 型的变量,内存单元的编号是1的倍数即可。short int
2 字节对齐 ,即存放 short int 型的变量,起始内存单元的编号是2的倍数即可。int
4 字节对齐 ,即存放 int 型的变量,起始内存单元的编号是4的倍数即可long int
在 32 位平台下,4 字节对齐 ,即存放long int
型的变量,起始内存单元的编号是4的倍数即可float
4字节对齐,即存放foat 型的变量,起始内存单元的编号是4的倍数即可double
a. vc环境下
8字节
对齐,即存放double型变量的起始地址,必须是8的倍数;double变量占8字节
b.gcc环境下
4字节
对齐,即存放double型变量的起始地址,必须是4的倍数;double变量占8字节
struct stu{
char sex;
int age;
} lucy;
// lucy 的内存空间是 4字节的倍数
- 如果在结构体中出现了数组,可以看成多个变量的集合
- 开辟内存空间的时候,从上向下依次按成员在结构体中的位置顺序开辟空间
#include <stdio.h>
struct stu
{
char a;
short b;
int c;
}temp;
int main(int argc, char const *argv[])
{
printf("%d \n",sizeof(temp));
printf("%p \n",&(temp.a));
printf("%p \n",&(temp.b));
printf("%p \n",&(temp.c));
return 0;
}
输出结果
// 最大成员是int类型,那么结构体空间是4字节的倍数
8
00007ff6c824a030
00007ff6c824a032
00007ff6c824a034
struct stu{
char buf[10]; // 占用12字节
int a; // 占用4个字节
}temp;
// 最大成员是int类型,那么结构体空间是4字节的倍数,temp占用16个字节
// 含double 变量的结构体
#include <stdio.h>
struct stu
{
char a;
double b;
}temp;
int main(int argc, char const *argv[])
{
printf("%d \n",sizeof(temp));
printf("%p \n",&(temp.a));
printf("%p \n",&(temp.b));
return 0;
}
输出结果
// 在vc中占 16 个字节 a和b的地址差8个字节
// 在gcc中占 12个字节 a和b的地址差4个字节
16
00007ff6c1a6a030
00007ff6c1a6a038
8.6.4 为什么需要字节对齐?
用空间换时间,提高cpu读取数据的效率
8.7 位段
在结构体中,以位为单位的成员,称之为位段(或者位域)。
struct packed data
{
unsigned int a:2;
unsigned int b:6;
unsigned int c:4;
unsigned int d;4;
unsigned int i;
} data;
注意:不能对位段成员取地址
-
位段引用
data.a = 2; //赋值不要超过位段定义的范围
-
位段成员的类型必须指定为整型或者字符型
-
一个位段必须存放在一个存储单元中,不能跨两个单元
第一个单元空间不能容纳下一个位段,则该空间不可再用,而是从下一个单位开始存储。
-
位段存储单元
位段的存储单元: (1):char 型位段 存储单元是1个字节 (2):short int 型的位段存储单元是2个字节 (3):int 的位段存储单元是4字节 (4):1ong int 的位段,存储单元是4字节
#include <stdio.h> struct stu{ char a:7; char b:7; char c:2; }temp; // 占用三个字节,b不能跨其存储单元存储 int main(){ printf("%d\n",sizeof(temp)); return 0 }
-
位段长度不能大于存储单元的长度
(1):char 型位段不能大于 8 位; (2):short int 型位段不能大于 16 位; (3):int 的位段不能大于 32 位; (4):long int 的位段,位段不能大于 32 位
#include<stdio.h> struct stu{ char a:9; // char存储单元的大小为8,这儿定义时是9,会出错 char b:7; char c:2; }temp; int main{ printf("%d\n",sizeof(temp)); // 编译出错,位段a不能大于其存储单元大小 return 0; }
-
如果一个位段需要从另一个存储单元开始,可以定义
unsigned char a:1; unsigned char b:2; unsigned char :0; // 由于用了长度为 0 的位段,其作用是使下一个位段从下一个存储单元开始存放 unsigned char c:3; // (另一个单元)
-
可以定义无意义的位段
unsigned a:1; unsigned :2; // 无意义的位段 unsigned b:3;
8.8 共用体 - union
共用体和结构体类似,也是一种构造类型的数据结构。
定义共用体类型的方法和结构体非常相似,把 stuct 改成 union 就可以了。
// 在进行某些算法的时候,需要使几种不同类型的变量存到同一段内存单元中,几个变量所使用空间相互重叠,这种几个不同的变量共同占用一段内存的结构,在C语言中,被称作“共用体”类型结构
- 共用体所有成员占有同一段地址空间
- 共用体的大小是 其占内存长度最大的成员的大小
typedef struct data{
short int i;
char ch;
float f;
}DATA;
DATA temp1; // 结构体变量中,temp1最小占用7个字节(2+1+4)
typedef union data{
short int i;
char ch;
float f;
}DATA;
DATA temp2; // 共有体变量中,temp1最小占用4个字节(float最大,为4),这个空间 i,ch,f共同使用
共用体的特点:
- 同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用
- 共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖
- 共用体变量的地址和它的各成员的地址都是同一地址
#include <stdio.h>
union un
{
int a;
int b;
int c;
};
int main(int argc, char const *argv[])
{
union un myun;
myun.a = 100;
myun.b = 200;
myun.c = 300;
printf("a = %d ,b = %d, c= %d", myun.a, myun.b, myun.c);
return 0;
}
输出结果
a = 300 ,b = 300, c= 300
8.9 枚举 - enum
将变量的值一一列举出来,变量的值只限于列举出来的值的范围内
枚举类型也是个构造类型的
8.9.1 枚举定义
enum 枚举类型名{
枚举列举值; // 在枚举值列表中列出所有可用值,也称为枚举元素
}
// 枚举变量仅能去枚举值所列的元素
enum week{
Mon,Tue,Wed,Thur,Fri,Sat,Sun
}
enum week workday,weekday; // workday ,weekday只能去Mon ... Sun中间的一个
workday = Mon; // 正确
weekday = Tue; // 正确
weekday = abc; // 错误
8.9.2 枚举特点
-
枚举值是常量,不能在程序中用赋值语句再对它赋值
例如:sun=5; mon=2; sun=mon; 都是错误的
-
枚举元素本身由系统定义了一个表示序号的数值,默认是从0开始顺序定义为0,1,2..
如在week中,mon值为0,tue值为1,...,sun值为6
-
可以改变枚举值的默认值
enum week{ //枚举类型 mon=3 , tue , wed , thu , fri=4 , sat,sun } // mon=3 tue=4,以此类推 // fri=4 sat=5 sun=6,以此类推
注意:在定义枚举类型的时候枚举元素可以用等号给它赋值,用来代表元素从几开始编号
在程序中,不能再次对枚举元素赋值,因为枚举元素是常量。