目录
结构体
什么是结构体
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
为什么需要结构体,因为生活中有各种各样的对象,拥有不同的属性,用结构体来描述这些对象
结构体的声明
#include<stdio.h> struct A { char c; short s; double d; }; struct stu { struct A S; char name[20];//名字 int age;//年龄 char id[20];//学号 }s1,s2;//s1和s2也是结构体变量,但是s1,s2是全局变量
结构成员的类型
结构的成员可以是变量、数组、指针,甚至是其他结构体。
结构体变量的初始化和定义
#include<stdio.h> struct A { char c; short s; double d; }; struct stu { struct A S; char name[20];//名字 int age;//年龄 char id[20];//学号 }s1,s2;//s1和s2也是结构体变量,但是s1,s2是全局变量 struct Node { int data; struct Point p; struct Node* next; }n1 = {10, {4,5}, NULL}; //结构体嵌套初始化 int main() { struct stu s = { {'w',20,3.14},"张三",30,"20200534" }; return 0; }
特殊声明
//匿名结构体类型 struct { int a; char b; float c; }x;//直接创建一个变量s struct { int a; char b; float c; }a[20], * p; //匿名结构体变量只能用一次
结构的自引用
//代码1 struct Node { int data; struct Node next;//会陷入死循环,无限调用 }; //怎么实现结构体自引用呢,数据结构的链表 struct NODE { int a;//数据域 struct NODE* next;//指针域 }; typedef struct Node //把struct重命名为Node c程序顺序执行 { int data; struct Node* next; }Node;
结构体成员的访问
结构体变量访问成员
结构变量的成员是通过点操作符(.)访问的。点操作符接受两个操作数。
#include<stdio.h> struct A { char c; short s; double d; }; struct stu { struct A S; char name[20];//名字 int age;//年龄 char id[20];//学号 }s1,s2;//s1和s2也是结构体变量,但是s1,s2是全局变量 int main() { struct stu s = { {'w',20,3.14},"张三",30,"20200534" }; printf("%c\n", s.S.c);//w printf("%s\n", s.id);//20200534 struct stu* ps = &s; printf("%c\n", (*ps).S.c); printf("%c\n", ps->S.c); return 0; }
结构体内存对齐
#include<stdio.h> struct S { int i; char c; }; struct S1 { char c; int i; char d; }; struct S3 { double d; char c; int i; }; struct S4 { char c1; struct S3 s3; double d; }; int main() { struct S s = { 0 }; printf("%d\n", sizeof(s));//8 struct S1 s1 = { 0 }; printf("%d\n", sizeof(s1));//12 struct S3 s3 = { 0 }; printf("%d\n", sizeof(struct S3));//16 struct S4 s4 = { 0 }; printf("%d\n", sizeof(struct S4));//32 }
如何计算?
首先得掌握结构体的对齐规则:
1结构体的第一个成员变量放在结构体变量在内存纯粹位置0偏移处开始
2从第二个成员变量往后的所有成员,都放在一个对齐数(成员的大小和默认对齐数的较小值)的整数倍的地址处 vs的默认值是8
3结构体的总大小是结构体所有成员的对齐数中最大的那个对齐数的整数倍
4如果嵌套了结构体的情况,嵌套的结构体对齐自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
为什么存在对齐数
1平台原因
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常。
2性能原因
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访 问。
结构体的内存对齐是拿空间来换取时间的做法
对于i来说,不对齐需要访问两次
怎么在提高效率的同时怎么省空间
让空间小的成员尽量集合在一起
struct S1 { char c1; int i; char c2; };//内存大小是9 struct S2 { char c1; char c2; int i; };//内存大小是8
修改默认对齐数
#include<stdio.h> struct S1 { char c;//0 对齐数1 int i;//4到7 对齐数4 char d;//8 对齐数1 //9到11直接浪费 //默认对齐数是8 }; #pragma pack(2) //将默认对齐数改为2 struct S2 { char c;//0 对齐数1 int i;//2到5 对齐数2 char d;//6 对齐数1 //7直接浪费 //默认对齐数是2 }; int main() { struct S1 s1 = { 0 }; printf("%d\n", sizeof(s1));//12 struct S2 s2= { 0 }; printf("%d\n", sizeof(s2));//8 return 0; }
结构体传参
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> struct B { char c; short s; double d; }; struct Stu { //成员变量 struct B sb; char name[20];//名字 int age;//年龄 char id[20]; }; void print1(struct Stu t)//传值 { printf("%c %d %lf %s %d %s\n", t.sb.c, t.sb.s, t.sb.d, t.name, t.age, t.id); } void print2(struct Stu* ps)//传址 { printf("%c %d %lf %s %d %s\n", ps->sb.c, ps->sb.s, ps->sb.d, ps->name, ps->age, ps->id); } int main() { //s是局部变量 struct Stu s = { {'w', 20, 3.14}, "张三", 30, "202005034" };//对象 //写一个函数打印s的内容 print1(s);//传值调用 print2(&s);//传址调用 return 0; }
上面的 print1 和 print2 函数哪个好些?
答案是:首选print2函数。
原因:
函数传参的时候,参数是需要压栈的。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
传值时,需要把整个结构体的数据都传过去,需要开辟一个新的内存空间,但是传址的话,只需要传输一个地址的内存
结论:
结构体传参的时候,要传结构体的地址。
位段 (依附于结构体)
位段的声明与结构体相似,但是不完全一样
1位段的成员必须是int ,unsigned int ,或者signed int
2位段的成员名后面有一个冒号和一个数字
struct A { int _a:2; int _b:5; int _c:10; int _d:30; }; struct S { char a:3; char b:4; char c:5; char d:4; };
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> struct A { int _a : 2;//_a占2个比特位 int _b : 5;//_b占5个比特位 int _c : 10;//_c占10个比特位 int _d : 30;//_d占30个比特位 };//为什么使用位段,因为实际需求是可能需要的选择比较少,所以给的位数少,节省空间 int main() { printf("%d\n ", sizeof(struct A));//8 return 0; }
位段的内存分配
1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
位段的内存空间是如何开辟的
struct S { char a:3; char b:4; char c:5; char d:4; }; struct S s = {0}; s.a = 10; s.b = 12; s.c = 3; s.d = 4;
位段先使用低地址的比特位,如果不够,就开辟新的字节
0 1100 010 000 00011 0000 0100
b a c d
位段的跨平台问题
1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机 器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是 舍弃剩余的位还是利用,这是不确定的
枚举类型
枚举顾名思义就是列举
把一切可能的值一一列举起来
比如一周有7天 性别有男女 月份有12个
枚举的定义
enum Day//星期 { Mon, Tues, Wed, Thur, Fri, Sat, Sun }; enum Color { RED, GREEN, BLUE };
枚举的使用
#include<stdio.h> enum Color { RED, GREEN, BLUE //这里面放的是这个枚举类型的可能取值 并且这些可能值都是常量 }; enum sex { man = 2, woman }; int main() { enum Color c = BLUE; //enum Color c = 3;在c++的编译环境下不行 //,不能这样赋值,因为c是一个枚举类型,而3是int类型,类型不匹配 // RED=6; 错误,因为类型不匹配 printf("%d\n", RED);//0 printf("%d\n", GREEN);//1 printf("%d\n",BLUE);//2 enum sex s = man; printf("%d\n", man);//2 printf("%d\n", woman);//3 return 0; }
这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。
枚举的优点
我们可以使用define来定义常量,但是为什么用枚举呢
1. 增加代码的可读性和可维护性 比如switch这个中可以用枚举常量来表示选项,更加具有可读性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试 因为调试是在test.exe这个阶段调试的,而define在编译的时候就被替换了,所以在调试期间,并不知道这个你用了define来定义常量,枚举并不是替换的
5. 使用方便,一次可以定义多个常量
枚举就是一种类型,整型就是整型,枚举不是整型
联合(共用体)
联合也是一种特殊的自定义类型,这些类型定义的变量包含一系列的成员,特征 这些成员共用一块空间
#include<stdio.h> union Un { char c; int i; //成员可能会共用一块空间 }; int main() { union Un u; printf("%d\n", sizeof(u));//4 printf("%p\n", &u); printf("%p\n", &(u.c)); printf("%p\n", &(u.i)); union Un u1 = { 10 }; printf("%d\n", (u1.c));//10 printf("%d\n", (u1.i));//10 说明共用了一块空间 return 0; }
联合体大小计算方法
1联合体的大小最少是最大成员的大小
2当最大成员的大小不是最大对齐数的整数倍的时候,就要对齐最大对齐数的整数倍
#include<stdio.h> union Un1 { char c[5];//对齐数1 int i;//对齐数4 }; union Un2 { short c[7];//对齐数是2 int i;//对齐数是4 }; int main() { printf("%d\n", sizeof(union Un1));//8 printf("%d\n", sizeof(union Un2));//16 return 0; }