一:结构体
(1)结构体类型的声明
//结构的声明:关键字+标签/结构体名称
struct STU//STU标签可以省,但不建议省,起名应该做到见名知义
{
//聚合数据类型:数组和结构体,c中的结构体成员不能为空,这些成员类型相同或者不同
char name[20];//姓名
int age;//年龄
char sex[5];//性别
char id[20];//学号
}y[20],*z;//分好不能丢
//这个声明创建了y和z两个结构体变量,对于结构体变量建议省,按需所取。y是一个数组,它包含了20个结构,z是一个指针,它指向这个类型的结构。
(2)结构成员
结构的成员可以是标量、数组、指针、甚至是结构体
结构体成员的访问并不是像数组一样通过下表访问的,而是通过操作符(.)访问的,这是属于一般访问的
#include
#include
struct S
{
char a;
int age;
struct simple *sp;
}s;//这个声明创建了一个名叫x的变量,它包含三个成员:一个字符,一个整型数,一个结构体指针
int main()
{
struct S A;
A.a = 'A';
A.age=18;
system("pause");
return 0;
}
结构体成员的间接访问:
#include
#include
struct S
{
char a;
int age;
struct simple *sp;
}s;//这个声明创建了一个名叫x的变量,它包含三个成员:一个字符,一个整型数,一个结构体指针
int main()
{
struct S A;
A.a = 'A';
A.age = 18;
struct S *p;//访问结构体指针变量
p = &A;
//(*p).a = 'a';//点的操作符优先级高于间接访问的操作符
//(*p).age = 20;
p->a = 'a';//箭头操作符,它接受两个操作符,但左操作数必须是一个指向结构的指针
p->age = 20;
system("pause");
return 0;
}
(3)结构的自引用(注意这个是用了typedef的,创建了一种新类型)
#include
#include
typedef struct S
{
char a;
int age;
struct S *next;//结构的自引用必须是*next,必须是指针,它指向的是同一种类型的不同结构
}s;
int main()
{
s n;//这个技巧和声明一个结构标签的效果几乎相同,此时的s现在是一个类型名而不是结构标签,所以后面的声明必须是以下的
s x;
s y[20] , *z;
n.a = '5';
n.age = 22;
printf("%d\n", sizeof(struct S));
system("pause");
return 0;
}
(4)结构的不完整声明(注:struct B;在不声明的时候可以编译运行,只是由于编译器问题而已,但是原则上不允许只这样,必须声明)
#include
#include
struct B;//A的成员列表需要标签B的不完整声明,一旦A被声明之后,B的成员列表也可以被声明
struct A
{
char a;
int b;
struct B *next;
};
struct B
{
char a;
int b;
struct A *next_o;
};
int main()
{
printf("%d\n", sizeof(struct A));
system("pause");
return 0;
}
(5)结构体变量的定义和初始化(可以类比数组,可以整体初始化,不允许整体赋值)
#include
#include
struct B;
struct A
{
char a;
int b;
struct B *next;
int c[5];
}x = { '5', 10, { NULL }, {0} };//方式一
struct B
{
char a;
int b;
struct A *next_o;
};
int main()
{
struct A obj = { '5', 10, { NULL }, { 0 } };//方式二
//obj = { '5', 10, { NULL }, { 0 } };//错误
printf("%d\n", sizeof(struct A));
system("pause");
return 0;
}
(6)结构体内存对齐问题
#include
#include
struct A
{
char a;//对齐数1,因为1<8,偏移量0.结构体第一个成员的地址是对齐的
int b;//对齐数4,起始偏移量1,但是不能整除4,所以偏移量为1+3
int c[5];//对齐数4,总大小:20+4+1+3=28,偏移量8
}x;
//此时最大对齐数为4,而28刚好能整除4,所以对齐数为28
//结构体的总大小为最大对齐数的整数倍。
int main()
{
printf("%p %p\n", &x, &(x.a));//结构体的地址和结构体成员的第一个地址相同
printf("%d\n", sizeof(struct A));//28
system("pause");
return 0;
}
接下来我们看一下将成员顺序一变,看有什么变化,两个程序将会将内存对齐问题很明显的显现
#include
#include
struct A
{
char c;//对齐数:1,1+3
int b;//对齐数:4
char a;//对齐数:1
}x;
//最大对齐数:4,结构总大小:1+3+4+1=9,结构总大小要是4的整数倍,即为12
int main()
{
printf("%p %p\n", &x, &(x.c));
printf("%d\n", sizeof(struct A));//12
system("pause");
return 0;
}
#include
#include
struct A
{
char a;//对齐数:1
char c;//对齐数:1
int b;//对齐数:4
}x;
//最大对齐数:4,结构总大小:1+1+4=6,结构总大小要是4的整数倍,即为8
int main()
{
printf("%p %p\n", &x, &(x.a));
printf("%d\n", sizeof(struct A));//8
system("pause");
return 0;
}
接下来我们讨论结构体嵌套问题
#include
#include
struct B
{
double c;//对齐数8
char d;//对齐数1
float e;//对齐数4
};//最大对齐数:8,总大小:(8+1+3+4+x)%8==0,所以结构的总大小为:16
struct A
{
char a;//对齐数:1
struct B p;//对齐数:16,16+3+1
double b;//对齐数:8
}x;
//最大对齐数:16,结构总大小:(1+16+3+8+x)%16==0,结构总大小要是16的整数倍,即为32
int main()
{
printf("%d\n", sizeof(struct B));//16
printf("%d\n", sizeof(struct A));//32
system("pause");
return 0;
}
关于内存对齐,我们用图来分析(CPU能从4的整数倍处读取数据)
浪费了空间,换取了时间,那么在设计的时候,我们既要满足对齐,又要节省空间,所以我们可以尽可能的让占用空间小的成员尽量集中在一起。
接下来我们修改编辑器默认的对齐数,然后来实践一下,看看我们的结构的大小的变化
#include
#include
#pragma pack(1)//修改编译器的默认对齐数,编译器默认为8,如果是#pragma pack(0)或者空,表示恢复编译器默认值,并且其参数只能是1,2,4.6,8,16这样的偶数
struct B
{
double c;//对齐数1,因为长度8>1对齐数,只能是两者中的较小值
char d;//对齐数1,1=1
float e;//对齐数1,1<4(长度)
};//最大对齐数:1,总大小:(8+1+4+x)%1==0,所以结构的总大小为:13
struct A
{
char a;//对齐数:1,(长度)1=1
struct B p;//对齐数:1,13>1
double b;//对齐数:1,1<长度8,
}x;
//最大对齐数:1,结构总大小:(1+13+8+x)%1==0,结构总大小要是1的整数倍,即为32
int main()
{
printf("%d\n", sizeof(struct B));//13
printf("%d\n", sizeof(struct A));//22
system("pause");
return 0;
}
(7)结构体传参
注:* 数组,指针在传参时会发生降维,而结构体在传参时不会发生降维,但是仍会形成临时变量,只要传参就会形成临时变量
*尽量不要传结构体变量,必须传结构体指针(原因:函数在传参的时候,参数是要压栈的,如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降)
#include
#include
struct B
{
double c;
char d;
int e;
};
struct B s = { 85964587, 'A', 20 };
void print(struct B *ps)
{
printf("%d\n", ps->d);//65
}
int main()
{
print(&s);//传参传结构体地址,不能传结构体
system("pause");
return 0;
}
(8)结构体实现位段
什么是位段:①位段的成员必须是int ,unsigned int或signed int。
②位段的成员名后面有一个冒号和一个数字。
如下,在vs中的位段运行结果:
#include
#include
struct B
{
char a :3;//a占char类型的3个bit
char b :4;//b占char类型的4个bit
char c :5;//c占char类型的5个bit
char d :4;//d占char类型的4个bit
};
int main()
{
struct B s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
printf("%d\n", sizeof(struct B));
system("pause");
return 0;
}
然后我们用图示的方法来看一下它在内存中的分布
位段的内存分配:
1:位段的成员可以是int 、unsigned int、signed int 或者char(属于整型家族)类型;
2:位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的;
3:位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
位段的跨平台问题:
1:int 位段被当成有符号数还是无符号数是不确定的;
2:位段中最大位的数目不确定(16位机器最大16,32位机器最大32,写成27,在16位机器中会出问题);
3:位段中的成员在内存中从左往右分配,还是从右往左分配尚未定义;
4:当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
总结位段:跟结构体相比,位段可以达到相同的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
二:枚举
(1):枚举类型的定义
下面我们用代码来举例说明它的使用
#include
#include
enum DAY
{
MON = 4,
TUES = 5,
SUN = 8,
SAT,//9
FRI//10
};
enum color
{//下面{}里面的内容是枚举类型的可能取值,即枚举常量,这些可能取值都是有值的,默认从0开始,依次递增,当然在定义的时候也可以赋值(enum DAY)
RED,//0
BLACK,//1
YELLOW,//2
GREEN,//3
BLUE//4
};
int main()
{
enum color c;
enum DAY p;
c = RED;
p = MON;
printf("%d\n", MON);
printf("%d\n", SUN);
printf("%d\n", FRI);
printf("%d\n", RED);
printf("%d\n", BLACK);
printf("%d\n", BLUE);
system("pause");
return 0;
}
(2):枚举的优点
1:增加代码的可读性和可维护性
2:和#define定义的标识符比较枚举有类型检查,更加严谨
3:防止了命名污染(封装)
4:便于调试
5:使用方便,一次可以定义多个常量
(3):枚举的使用(看枚举类型的定义下面的代码)
三:联合(共用体)
(1):联合类型的定义(联合变量也可以进行初始化,但初始值必须与联合第一个成员的类型匹配)
下面我们用代码来详细分析联合的特点以及声明
#include
#include
typedef union A
{
int a;//对齐数4
char b;//对齐数1
double c;//对齐数8,
char d[9];//对齐数1,
}un_t;
//最大对齐数;8,联合大小:9,9不能被8整除,所以联合大小为16
int main()
{
un_t x;
x.a = 0x11223344;
x.b = 0x55;
printf("%p ,%p ,%p ,%p ,%p \n", &x, &(x.a), &(x.b), &(x.c), &(x.d));//联合体所有成员的地址相同=总体空间的第一个字节的地址=联合体的地址
printf("%d\n", sizeof(un_t));//联合的大小至少是最大成员的大小(16)
printf("%x\n", x.a);//11223355(大小端问题)此时为小端
system("pause");
return 0;
}
我们估计会好奇x.a的结果,这是大小端问题,此时这台机器为小端
单步调试后
(2):联合的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)
(3):联合大小的计算
①联合的大小至少是最大成员的大小;
②当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
注:如有错误,请各位大佬留言,谢谢