结构体
结构体的声明
结构体是一些值的集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量。
声明方式如下:
struct tag
{
member-list; //成员变量
}variable-list; //变量列表,即可定义多个变量。
//举个例子
struct stu
{
char name[20];
int age;
char sex;
char id[10];
}stu1,*p; //定义了两个不同类型的变量
尽量不要匿名声明,即使不报错(因为c是弱类型语言),但不能复制,因此不建议这样做。
结构体的自引用
错误示例
struct Node
//要求sizeof()大小,就要先求出sizeof(next),而next是struct Node此类型的,因此不能求出
{
int data;
struct Node next; //next变量是未定义的,大小也未知
}
正确示例
struct Node
{
int data;
struct Node*next; //此时定义指针指向此类型,sizeof(next)大小是4字节(32位平台下)
}
结构体的定义和初始化
//结构体类型声明
struct stu
{
char name[15];
int age;
}
//声明的同时定义结构体变量
struct Point
{
int x;
int y;
}p1;
//定义结构体变量
struct Point p2;
//定义的同时初始化结构体变量,整体初始化只能在定义时进行
struct Point p3={2,3};
p3->x=4;
//结构体嵌套初始化
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1={10,{4,5},NULL};
struct Node n2={20,{5,6},NULL};
结构体内存对齐
内存对齐主要应用在求结构体的大小
- 对其规则如下:
-
第一个成员在与结构体变量偏移量为0的地址处。
-
其他成员变量要对齐到对齐数的整数倍地址处。
对齐数=编译器默认的对齐数与该成员大小的较小值(vs默认8)。 -
结构体总大小为成员变量的最大对齐数的整数倍。
-
如果嵌套了结构体,同理,子结构体与变量情况相同。
- 为什么存在内存对齐
- 平台原因(移植原因):不是所有硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因:数据结构尤其是栈,应该尽可能地自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要两次访问内存,而对齐的内存仅需访问一次。
总体来说,结构体的内存对齐是拿空间来换取时间的做法。那么在设计结构体的时候,就既要满足对齐,又要节省空间,尽量让占用空间小的成员集中在一起。
//两个结构体成员变量相同,但排列顺序不同,造成结构体大小不同。
struct s1
{
char c1; //默认8,本身1,取最小为1 ,偏移量0
int i; //默认8,本身4,取最小为4,偏移量4-7(满足4的整数倍)
char c2; //默认8,本身1, 取最小为1,偏移8
/*成员最大对齐数为4,整体大小为4的倍数,0-8大小为9,
最接近4的倍数为12,因此该结构体大小为12字节。*/
}
struct s2
{
char c1; //默认8,本身1,取最小为1 ,偏移量0
char c2; //默认8,本身1, 取最小为1,偏移量1
int i; //默认8,本身4,取最小为4,偏移量4-7(满足4的整数倍)
/*成员最大对齐数为4,整体大小为4的倍数,0-7大小为8,
刚好是4的倍数为,因此该结构体大小为8字节。*/
}
结构体传参
struct S
{
int data[1000];
int num;
};
struct S s1={{1,2,3,4},1000};
//结构体传参
void printf(struct S s)
{
printf("%d",s.num);
}
//结构体地址传参
void printf2(struct S* s)
{
printf("%d",s->sum);
}
int main()
{
printf1(s1); //传结构体
printf2(&s1); //传地址
return 0;
}
首选地址传参,因为函数传参时,参数需要压栈,会有时间上和空间上的系统开销。如果传递一个结构体对象,结构体过大,会导致系统性能下降。
位段
位段的声明和结构体是类似的,有两个不同:
- 位段的成员必须是int、unsigned int、signed int、char
- 位段的成员名后边有一个冒号和一个数字。
struct S
{
int a:2; //开辟4个字节,占一个字节的其中两位
int b:5; //在a的后边占5位,仍然只占了1个字节
int c:10; //在b的后边占第二个字节的8位,再占第三个字节的2位
int d:30; ///再开辟4个字节,共32位,够放下30位
}; //因此sizeof(struct S)为8,S就是一个位段类型
枚举
enum Day
{
Mon;
Tues;
Weds;
Thur;
Fri;
Sat;
Sun;
};
enum Sex
{
MALE;
FAMALE;
};
enum Color
{
RED;
GREEN;
BLACK;
};
以上定义的enum Day,enum Sex,enum Color都是枚举类型,{}中的内容是枚举类型的可能取值,叫枚举常量,这些的取值从0开始,依次递增1,当然也可以在定义的时候赋初值。
联合体
联合体也是一种特殊的自定义类型,这种类型定义的变量也包含一系列的成员,特征就是这些成员共用一块内存空间。这样一个联合体的大小,至少是最大成员的大小。
union Un //联合体的声明
{
int i;
char c;
}; //sizeof(union Un)为4
union Un un; //联合体的定义
printf("%d",&(un.i));
printf("%d",&(un.c)); //输出内容相同
un.i=0x11223344;
un.c=0x55;
printf("%d",un.i); //输出0x11223355,体现了计算机的小端存储模式,这个也可以应用在判断大小端存储上
联合体大小的计算和结构体类似,只是共用内存
union Un1
{
char c[5]; //0-4,大小为5,对齐数1
int i; //0-3,对齐数4
}; //整体大小为4的倍数,因此大小为8
union Un2
{
short c[7]; //0-13,大小为14,对齐数2
int i; //0-3,对齐数4
}; //整体大小为4的倍数,因此大小为16