C语言——8-结构体
0 一个问题
数组 解决多个相同类型的变量的定义问题, 解决多个相同类型的对象.
假如说我们想要描述一个人 , 要把人抽象成计算机中的数据 .
人有哪些属性 , 把这些属性转换成数据, 这些数据数据都是描述人的,
不能把他单独分开,
人 : 你要关注人的哪些属性(参数)
学号 --> int num
姓名 --> char name[32];
性别 --> char gender; 'M' /' F '
年龄 --> int age
地址 --> char addr[100]
C语言允许程序员自定义组合的数据类型.
结构体 联合体 枚举
1 结构体
结构体是C程序员自定义的一种组合的数据类型.
1.1 定义语法
- (1) 先定义结构体,在定义结构体变量
struct 结构体名 //定义结构体类型 新的类型 { 类型 成员1; 类型 成员2; 类型 成员3; ... }; 结构体 ,成员名 : 标识符 struct 结构体名 ---> 你定义的 新类型的名字(相当于 int 类型) 成员的类型 : 任意合法的类型(基本类型,自定义的类型,指针类型..)都可以例子 :
(1) 构造一个类型 来描述我们的日期 (年/月/日) struct date //--> struct date 就是我们定义的用来描述"日期"的类型 { //这个类型有三个成员 ; year ,month,day int year;//年 int month;//月 int day;//日 }; or struct date { int year,month,day; }; struct date day1;//struct date 是一个类型名 day1, day2 是一个变量名 struct date day2;(2) 构造一个类型来描述学生 "学号" "姓名" "生日" struct student { int num;//学号 char name[32];//姓名 struct date birthday;//生日 };
- (2) 定义结构体类型时,同时定义变量.
struct 结构体名 { 类型 成员1; 类型 成员2; 类型 成员3; ... }变量名列表; 结构体 ,成员名 : 标识符 struct 结构体名 ---> 你定义的 新类型的名字(相当于 int 类型) 成员的类型 : 任意合法的类型(基本类型,自定义的类型,指针类型..)都可以 变量名列表 : 同时定义了 多个变量例子 :
struct date //--> struct date 就是我们定义的用来描述"日期"的类型 { //这个类型有三个成员 ; year ,month,day int year;//年 int month;//月 int day;//日 }day1,day2; int a,b,c;//和int 定义变量的方式是一样的 struct date day3;
- (3) 直接定义结构体类型变量
struct { 类型 成员1; 类型 成员2; 类型 成员3; ... }变量名列表; 如果定义这种结构体类型的变量,后续不能再定义结构体变量了,因为没有结构体名. //一次性 struct { int year;//年 int month;//月 int day;//日 }day1,day2; struct day3;//error 不能定义 day3 这个变量,因为没有结构体名.
1.2 "实例化" : 构造某某类型的对象
struct student s;//s 是类型 struct student 的一个变量 s 中会有一个学号 num , int s 中会有一个姓名 name , char[32] s 中会有一个日期 birthday , struct date birthday 中 会有 year,month,day
1.3 结构成员变量的内存布局
结果体变量所占内存大小,是各成员变量所占内存大小之和,
结果体变量所占的内存空间,是一块连续的空间.
各成员变量是按定义的顺序依次分配空间.
struct student s; sizeof(s) = sizeof(num) + sizeof(name) + sizeof(birthday)
1.4 结构体成员变量如何引用呢?
struct student s;s 里面有num , name, birthday
(1) 域运算符(分量运算符)
. 语法 : 组合类型变量名.成员变量 "整个表达式的值" 是最后那个"成员变量" 的 "值"如 :
s.num s.name s.birthday.year s.birthday.month s.birthday.day引用组合类型的成员变量 和 引用普通变量是一样的, 也有左值,也有右值
s.num = 1; int b = s.num;如果取 成员变量的地址,如何操作呢?
&(s.num) ==> &s.num练习 :
1.定义一个学生信息结构体, 从键盘上输入一个学生的各种信息,然后输出,
//先定义类型 ,后定义变量 scanf("%d",&s.num); printf("学号 : %d\n",s.num);#include <stdio.h> struct date //日期这个类型 { int year;//年 int month;//月 int day;//日 }; struct student //学生这个类型 { int num;//学号 char name[32];//姓名 struct date birthday;//生日 }; int main() { struct student s; //将键盘上输入给学生的各项信息存储到 s 中去 //学号 姓名 年 月 日 printf("请输入学生的信息 : 学号 姓名 年 月 日\n"); scanf("%d%s%d%d%d",&s.num,s.name,&s.birthday.year,&s.birthday.month,&s.birthday.day); printf("学号 : %d\n",s.num); printf("姓名 : %s\n",s.name); printf("生日 : %d/%d/%d\n",s.birthday.year,s.birthday.month,s.birthday.day); return 0; }
(2) (*结构体指针).成员变量
struct student s;//定义了一个结构体变量 struct student *p; p = &s; p的右值是 &s *p ==> *&s ==> s (*p).num ==> s.num
(3) -> 域运算符 分量运算符 (结构体指针)
用来访问组合类型(结构体,共用体)的成员变量的
组合类型的指针->成员变量
表达式的值 就是 最右边的那个 成员变量的值
p->num p->name p->birthday.year p->birthday.month p->birthday.day ...练习 :
- 用动态创建内存的方式保存学生的各项信息, 从键盘上输入一个学生的各种信息, 然后输出.
int main() { struct student *p = malloc(); scanf(); printf(); return 0; }#include <stdio.h> #include <stdlib.h> struct date //日期这个类型 { int year;//年 int month;//月 int day;//日 }; struct student //学生这个类型 { int num;//学号 char name[32];//姓名 struct date birthday;//生日 }; int main() { struct student *p = (struct student *)malloc(sizeof(struct student)); //将键盘上输入给学生的各项信息存储到 s 中去 //学号 姓名 年 月 日 printf("请输入学生的信息 : 学号 姓名 年 月 日\n"); scanf("%d%s%d%d%d",&(p->num),p->name,&p->birthday.year,&p->birthday.month,&p->birthday.day); printf("学号 : %d\n",p->num); printf("姓名 : %s\n",p->name); //printf("生日 : %d/%d/%d\n",p->birthday.year,p->birthday.month,p->birthday.day); printf("生日 : %d/%d/%d\n",(*p).birthday.year,(*p).birthday.month,(*p).birthday.day); free(p); return 0; }
1.5 定义并初始化结构体变量的方法
在C语言中, 构造类型(数组,结构体,共用体) 的初始化用 { }
struct date { int year;//年 int month;//月 int day;//日 }; struct student { int num;//学号 char name[32];//姓名 struct date birthday;//生日 };
(1) 按定义的顺序依次初始化各成员变量,个成员变量之间用逗号隔开
如 :
struct student s = { 1,//学号 {"zhangsan"},//姓名 可以不用{} { 2023,7,14 } };
(2) 不按顺序,用.成员变量名 = 值
struct student s = { .name = {"zhangsan"}, .birthday = { .year = 2000 } };
(3) 结构体数组的初始化
- <1> 按数组元素的顺序依次初始化
struct student s[2] = { { .num = 1, .name = {"lisi"}, .birthday = { 2023,7,14 } },//s[0] { }//s[1] };
- <2> 不按数组元素的顺序, [下标] =
struct student s[5] = { [0] = { .num = 1, .name = {"lisi"}, .birthday = { 2023,7,14 } },//s[0] [2] = { }//s[2] };
2 共用体
2.1 共用体(联合体 union)
共用体也是一种C程序员自定义的组合类型.
union 共用体名 { 类型 成员变量1; 类型 成员变量2; ... };共用体在语法形式上 和 使用上 ,和结构体是一样的.
引用共用体的对象的成员也是 :
. ->共用体和结构体的区别在哪里呢?
(1) 共用体的所有的成员变量 共用一块内存
共用体对象所占内存的大小是其最大的那个成员所占的大小
共用体对象的所有成员变量共用一块内存,共用体所有成员变量的地址都是一样的
"共用体的成员变量一般不能同时使用",共用体是在资源比较紧张的时候提出来的.
(2) 结构体对象所占内存大小是各成员变量所占内存大小之和.
2.2 大端模式 和 小端模式
从存储器开始讲起 :
所有运算的操作数, 必须在CPU的内部寄存器,才能参与运算.
对于CPU来说,寄存器才是他真正的存储空间.但是CPU寄存器的个数以及
容量非常有限.所以下设计CPU时,必须要有个他的存储器 Memory.
把CPU寄存器中的数据 存储到 存储器memory中,同时把存储器memory中数据
加载到 CPU寄存器中来.
CPU寄存器 <---> 存储器memory之间 的数据交换.
但是 : 寄存器 和 存储器
寄存器是按bit位存储, "高字节 低字节" b31 b30 b29 ...b1 b0
存储器memory ,是按字节来连续编址.
R0 32bits b31 b31 ... b1 b0 (4) (3) (2) (1) 高字节 低字节 memory,按字节连续编址 |--------|--------|--------|--------|... 0x3000 0x3001 0x3002 0x30003 0x3004 ....考虑 寄存器 与 存储器 之间的数据交换.
高字节 低字节 R : (4) (3) (2) (1) M : ___ ___ ___ ___ 低地址 高地址 小端模式 : 小存小 存储中的低地址 存储 寄存器中 低字节 (1) (2) (3) (4) ___ ___ ___ ___ 低地址 高地址 大端模式 : 存储器中的低地址 存储 寄存器中的 高字节 (4) (3) (2) (1) ___ ___ ___ ___ 低地址 高地址练习 :
1.写一个程序 ,测试一下运行这个程序的机器, 是大端模式 ,还是小端模式?
union test { int a; char b; }; union test t; t.a = 1;//0x 00 00 00 01 ==> 01 00 00 00 内存 ___ ___ ___ ___ 小端 00 00 00 01 内存 ___ ___ ___ ___ 大端 &t.b 判断是大端还是小端模式 ,只需要判断内存中的低地址的内容是 01 还是 00 if(t.b) //t.b != 0 { //小端 } else { //大端 }
3 枚举
0 一个问题
假如在你的C程序中,需要弄一个变量,来表示星期几? int weekday;// 1 2 3 4 5 6 7 weekday = 6; weekday = 7; ... weekday = 250;?
1 枚举
把该类型变量所有可能的值都列出来, 并且每个值,还可以有一个名字("字面意思")
枚举也是C程序员自定义的一种类型.
枚举值 就是一个 整数值 , 默认第一个值是 0 ,后面自动增加.
定义格式如下:
enum 枚举名 { 枚举值的名(标识符) };如 :
enum weekday { MON, TUE, WED, THU, FRI, SAT, SUN }; enum weekday w;//w就是一个 类型 enum weekday 的变量 w = MON; if(w == SUN) { happy day; }//构造一个类型来表示几种常用的颜色 enum color { RED, GREEN, BLUE, YELLOW, BLACK, WHITE }; enum color c = YELLOW;枚举类型在实现时, 枚举的值 ,是用一个整数值来表示, 如 上面的例子 enum weekday
MON 0 TUE 1 WED 2 ...我们在定义枚举类型时,也可以指定枚举的值,如 :
enum weekday //新的类型名 { //该类型变量所有可能的值,都在这个{}内 MON = 250, TUE, WED, THU, FRI = 600, SAT, SUN };因为枚举类型的实现是通过整数来实现,所有枚举类型的变量都可以 ++ ,-- 等等整数可以参与的运算.
enum weekday c = MON; c++;//TUE
4 typedef用法
0 一个问题
int a[10];//不仅定义了一个数组a, 同时也声明了一个新的类型 "像a这样的类型"
//int[10]
定义一个与a类型一样的变量 b;
//int b[10]
typeof(a) b;
有没有办法声明一个 int[10] 这种类型呢?
1 typedef
typedef 用来声明(定义) 一个新的类型名
这个类型已经有的 ,只不过我去一个新的名字而已.
语法 :
typedef 已经有的类型名 新的类型名 ; 新的类型名 就等同于 已经有的类型名例子 :
typedef int zhengshu;//zhengshu 就是一个新的类型名 , 他等同与 int int a; zhengshu a; typedef unsigned char uint8_t; typedef char u8; typedef short s16; ...struct student { int num; char name[32]; //... }; typedef struct student stu_t; stu_t s; ==> typedef struct student { int num; char name[32]; //... }stu_t; struct student { int num; char name[32]; //... }stu; //上面定义一个类型 struct student 同时定义了一个这种类型的 变量stutypedef struct { int a; int b; }TEST; //TEST 也是一个新类型名 typedef struct node { //... }Node,Node2; //Node ,Node2 都是新类型名 , 代表的是 struct node typodef struct node { //... }Node ,*pNode; ==> typedef struct node Node; typedef struct node* pNode; //Node ,pNode都是新类型名,但是: //Node <==> struct node //pNode <==> struct node* pNode是结构体指针类型名
2 typedef 一些比较高级的用法
int b;//b是一个变量名 typedef int b;//b是一个类型名 int NUM[10];//NUM是一个数组名 typedef int NUM[10];//NUM是一个类型名 ,typedef int[10] NUM; NUM a;//a就是一个数组名,里面含有 10个int类型的元素 void (*p)(int , float);//p是一个函数指针变量 typedef void (*p)(int , float);//p一个类型名 , 函数指针类型
5 字节对齐
1 什么是字节对齐
从内存访问的效率出发, CPU底层或编译器一般会要求 ,所有对象的地址按某种方式对齐.
这种对齐,我们称之为"字节对齐"
为了提升内存的访问效率 ?
一般来说,字节地址就是要求对象的地址是 n 的倍数.
2 对齐方式
- (1) n-字节对齐 : 对象地址和大小必须是 n 的倍数(n一般为2的x次幂)
如 :
4-字节对齐 : 所有对象的地址必须为4的倍数
8-字节对齐 : 所有对象的地址必须为8的倍数
16-字节对齐 :
...
(2) 自然对齐
编译器默认的一种对齐方式.
对象的地址是对象长度的倍数
在32bits x86机器,编译器: Microsoft Visual C++ Borland/Code Gear(c++ builder) Digital Mars(DMC) GNU(gcc) 对 A char(1byte) 一字节对齐 a short(2bytes) 二字节对齐 an int(4bytes) 四字节对齐 a long(4bytes) 四字节对齐 a float(4bytes) 四字节对齐 a double(8bytes) 8字节对齐 On windows 4字节对齐 On linux(除非 -malign-double 8字节对齐) a long long(8bytes) 4字节对齐 a long doulbe(10bytes) ?? 具体的对齐方式和编译器、系统有关 any pointer (4bytes) 4字节对齐 在64bits x86机器下,编译器 Microsoft Visual C++ Borland/Code Gear(c++ builder) Digital Mars(DMC) GNU(Gcc) 和32bits机子,只有以下不同: A long (8 bytes) 8字节对齐. A double (8 bytes) 8字节对齐. A long long (8 bytes) 8字节对齐. A long double (8 bytes with Visual C++, 8字节对齐在Visual C++ 16 bytes with GCC, 16字节对齐在GCC Any pointer (8 bytes) 8字节对齐.例子 :
sizeof(int) = 4; int a: &a 必须为4的倍数 if &a 不是4的倍数,则说明 a不是自然对齐 每个变量(包括结构中的成员变量)都会有一个默认的对齐方式 : 自然对齐对结构体的对齐方式?
两个重要的要求
(1) 结构体变量按其最大的自然类型的成员变量的对齐方式对齐;
(2) 结构体的大小必须为其对齐方式的整数倍(一般向上取整).
例子 : 假设机器 32bits
struct test { char a;//a按 1-字节对齐 int b;//b按 4-字节对齐 short c;//从按 2-字节对齐 }; struct test 按其最大成员(int b)的对齐方式来对齐 struct test 按4字节对齐.
#include <stdio.h> struct test { char a;//a按 1-字节对齐 int b;//b按 4-字节对齐 short c;//c按 2-字节对齐 }; struct test2 { char a; short c; int b; }; struct test3 { short c; int b; char a; }; int main() { struct test2 t2; struct test3 t3; printf("sizeof(struct test) = %ld\n",sizeof(struct test));//12 printf("sizeof(t2) = %ld\n",sizeof(t2));//8 printf("sizeof(t3) = %ld\n",sizeof(t3));//12 return 0; }
3 练习
分析如下程序, 假设机器是 32bits
(1)
struct MixedData { char Data1; short Data2; char Data4; int Data3; }; struct MixedData 按4-字节对齐 sizeof(struct MixedData) = ? 12(2)
struct FinalPad { float x; char n[1]; }; struct FinalPad 按4-字节对齐 sizeof(struct FinalPad) = ? 8 //数组的对齐方式是按 数组元素类型的对齐方式 对齐(3)
struct FinalPadhort { short x; char n[3]; }; struct FinalPadhort 按2-字节对齐 sizeof(struct FinalPadhort) = ? 6 struct test { short x;// int n[3]; }; struct test 按4-字节对齐 sizeof(struct test) = ? 16(4)
struct MixedData { char Data1; short Data2; int Data3; char Data4; }; struct test { char s; struct MixedData m; }; sizeof(struct test) = ?
#include <stdio.h> struct MixedData { char Data1; short Data2; int Data3; char Data4; }; struct test { char s; struct MixedData m; }; int main() { struct test t; printf("sizeof(t) = %ld\n",sizeof(t));//16 return 0; }






浙公网安备 33010602011771号