结构体: 具有相同或者不同类型元素的集合。
结构体也是一种类型。
结构体只能在初始化时定义内容,而不能以赋值的形式进行,若想操作,可用·
操作符。
数组也是同样,若想赋值可以用循环。
struct stu {
char name[20];
char sex[5];
int age;
char number[10];
};
不允许结构体内为空(C标准要求)。为空的话没有任何实际意义。
结构体的成员可以是标量、数组、指针、还可以是其它结构体。
1.结构体中的成员访问:
- 结构体变量 访问成员,结构体变量的成员通过
·
操作符访问。如:
struct stu {
char name[20];
char sex[5];
int age;
char number[10];
};
int main()
{
struct stu s;
s.age = 20;
return 0;
}
- 结构体指针访问指向变量的成员,指向结构体的指针。
struct stu {
char name[20];
char sex[5];
int age;
char number[10];
};
int main()
{
struct stu s = {"小花","女",18,"192001"};
struct stu *p = &s;
printf("姓名:%s\n性别:%s\n年龄:%d\n学号:%s\n", p->name, p->sex, p->age, p->number);
return 0;
}
用->
操作符进行访问。
2.结构体传参
结构体传参的时候,参数需要压栈(形参实例化),结构体没有“退化,降维”之类的问题,要发生对应的硬拷贝问题。故而导致了效率很低(系统开销大,性能下降)。一般建议用结构体指针。
即使结构体中有数组,也不会发生降维,而全是硬拷贝。
结构体的类型是否一致,取决于是否是同一个结构体变量。哪怕是两个结构体成员变量完全一致,也是两个结构体。
例如:
struct {
char name[20];
char sex[5];
int age;
char number[10];
}x;
struct {
char name[20];
char sex[5];
int age;
char number[10];
}*p;
int main()
{
p = &x;
return 0;
}
上述代码,在编译的时候是可以运行的,但会有告警。
3.结构体自引用:结构体中包含一个类型为该结构本身的成员。
struct Node
{
int data;
//struct Node next;//这是一种错误的自引用
struct Node *next;
};
再看另一个代码:
typedef struct Node //①
{
int data;
struct Node *next;//②
}node; //③
这struct Node
进行重定义。其中①和②的Node意思是相同的,③和另外两个是不一样的。
比如这样的重定义就是有错误的。
typedef struct
{
int data;
Node *next;
}Node;
程序会报错:
因为Node还没有完成重定义,故在结构体内不能先使用。
4.结构体内存对齐
结构体内部是存在内存对齐情况的,内存对齐会影响结构体整体的大小。
计算机访问内存的基本单位是字节,故计算机可以任意读取内存中的任意一个字节。但是,计算机因为硬件设计的原因,计算机读取数据的时候,必须从特定要求的起始位置开始读取。
①为什么要内存对齐?
因为硬件设计的原因导致效率变低。若访问未进行内存对齐的内存,处理器就会进行多次访存。
②内存对齐是什么?
牺牲内存空间,来满足cpu访存硬件的限制,提高效率的方式,就叫做内存对齐。
内存对齐的本质:空间换取时间。
③如何内存对齐:
内存对齐的规则:
1.第一个成员与结构体变量偏移量为0的地址处。
2.其它成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
3.结构体总大小为最大对齐数(每一个成员变量都有对齐数)的整数倍。
4.嵌套了结构体的情况,嵌套的结构体对齐到字节的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
举例:
struct stu{ //42 +2 4
char name[20];//20
char sex[5];//5
int age;// 3 + 4
char number[10];//10
};
int main()
{
printf("%d", sizeof(struct stu));
return 0;
}
分析:
char name[20]; 大小为20 不需要对齐
char sex[5]; 每一个元素大小为1字节,对齐数为1,可以整除 故大小为5
int age; age为int型,对齐数为4,前面的字节数为25,不能整除,故有偏移量,偏移量为3
char number[10];每一个元素大小为1字节,对齐数为1,可以整除 故大小为10
上述总大小为:42。最大对齐数为:4.
不能整除4,故需进行扩大。为44
结果:
struct list //32 最大对齐数:8 32
{
char num[5];//5
int add;// 3 + 4
char *p;//4
double prt;//8
char opp;//1
int div;// 3 + 4
};
int main()
{
printf("%d\n", sizeof(struct list));
return 0;
}
嵌套了结构体的情况:
struct stu{ //50 最大对齐数:8 = 56
char name[20];//20
char sex[5];//5
int age;//3 + 4
double address;//8
char number[10];//10
};
struct list //81 最大对齐数:8 = 88 //这里的最大对齐数为什么是8?
{ //因为嵌套的结构体,最大对齐数为8,故,外部结构体最大对齐数也为8
char num[5];//5
int add;// 3 + 4
char p;//1
struct stu s;//3 + 56
char opp;//1
int div;// 3 + 4
char mul;//1
};
int main()
{
printf("%d\n", sizeof(struct stu));
printf("%d\n", sizeof(struct list));
return 0;
}
结果:
当然对齐数也可以进行修改。
#pragma pack()
可以对默认对齐数进行修改。
注意:()内的数值,必须为1,2,4,8,16。
如上述代码:我们将对齐数修改为2:
#pragma pack(2)
struct list
{
char num[5];
int add;
char *p;
double prt;
char opp;
int div;
};
int main()
{
printf("%d\n", sizeof(struct list));
return 0;
}
字节数就发生了改变:28。
我们将对齐数修改为1:
#pragma pack(1)
字节数就发生了改变:26。
5.位段: 位段的声明和结构体比较类似,但有不同。
1.位段的成员必须是int、unsigned int 和signed int
2.位段的成员名后面有一个冒号和一个数字
struct A
{
unsigned char a : 2;
unsigned char b : 3;
unsigned char c: 6;
unsigned char d;
unsigned char e : 4;
};
int main()
{
printf("%d\n", sizeof(struct A));
return 0;
}
所以这个代码的字节大小为4个字节。
我们对这个进行赋值试试。
int main()
{
struct A s;
s.a = 4;
s.b = 5;
s.c = 6;
s.d = 7;
s.e = 8;
return 0;
}
分析:
当为有符号数时,一定要注意最高位的符号位是什么。
struct A
{
char a : 2;
char b : 3;
char c: 6;
char d;
char e : 4;
};
int main()
{
struct A s;
s.a = 4;//100 发生截断00
s.b = 5;//101 最高位为1,最后会原反补转换 结果为-3
s.c = 6;//1100
s.d = 7;//1101
s.e = 8;//1000 原反补转换 结果为-8
printf("%d\n%d\n%d\n%d\n%d\n", s.a, s.b, s.c, s.d, s.e);
return 0;
}
位段可以达到很好的节省空间的效果,但是跨平台存在问题。
位域整体可以取地址,但单独无法进行取地址。
枚举:枚举类型本质是int,而且默认情况(可以改,正负皆可)是从0开始,依次递增的。
枚举是一个类型,在编译的时候,会进行类型检查。宏是基于替换原则的,在这个过程中,并不进行任何检查工作。
enum Day
{
MON,
TUES,
WED,
THUR,
FRI,
SAT,
SUN
};//一般都习惯于大写。(默认为常量)
枚举的优点:
- 增加代码的可读性和可维护性
- 和宏定义的标识符相比,枚举有类型检查,更加严谨。
联合:联合也是一种特殊的自定义类型,该类型中也包含一系列的成员,但是这些成员共用一块空间。
联合体的大小并不是把所有类型加起来的大小。
一般而言,联合体的大小是由联合体最大元素的大小决定的。决定之后,所有元素共享空间。
在使用union的时候,一次只会访问一个元素。
联合体也要考虑内存对齐问题,联合体的最终大小,要能整除联合体内的最大对齐数。
union un
{
int i;
char c[6];
};//该联合体字节大小为8
联合的成员是公用一块内存空间的,改变一个成员的值,就会影响后续的值。
有一个比较实用的例子:判断当前计算机的大小端?
union un
{
int i;
char c;
};
union un temp;
temp.i = 1;
printf("%d\n", temp.c);
输出0,大端存储,输出1,小端存储。