结构是一些值的集合,这些值成为成员变量。有些复杂的数据类型,不能单纯的由一个基本数据类型描述,可以用结构体描述。
如描述一个学生
变量名 | 基本数据类型 |
---|---|
姓名 | char |
年龄 | int |
性别 | char |
成绩 | float |
struct tag//结构体类型名
{
//member
//这里定义成员变量
};//分号千万不能丢
可以紧跟在分号后面创建变量,创建的为全局变量,也可以直接在此进行初始化
struct tag//结构体类型名
{
//member
}s1,s2;
如描述一个学生
struct Stu//
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};
声明的时候,可以省略结构体的名字
struct//这里的tag被省略了
{
int a;
int b;
}x;
这里我们主要讨论一个问题:如何计算结构体类型的大小
struct S1
{
char a;
char b;
int c;
};
struct S2
{
char a;
int b;
char c;
};
int main()
{
printf("%d,%d\n",sizeof(struct S1),sizeof(struct S2));
return 0;
}
2个char1个字节,1个int4个字节,大小是占6个字节吗?
那如何计算? 首先得掌握结构体的对齐规则:
1.第一个成员在与结构体变量偏移量为0的地址处。
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8
Linux没有默认对齐数
3.结构体总大小为各个成员最大对齐数的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
在s1中,char a变量大小为1,默认对齐数为8,对齐数取较小值,为1,
char b对齐数也为1。但int c变量大小为4,默认对齐数为8,取最小值则为4。但此时内存的使用偏移量为2,不为4的倍数,要到对齐到4的倍数(4)开始使用内存。最后得出大小为8。
如何理解第3点呢?
#include
struct S1
{
char c1;
double d1;
int i;
};
int main()
{
printf("%d\n", sizeof(struct S1));
return 0;
}
double类型对齐数为8,所以char类型后需要在偏移量为8的位置写下double类型,在偏移量为16下写下int类型,int占4字节,大小并不为20,而是24,因为需要为个成员最大对齐数(double类型)8的整数倍24。
如何理解第4点呢?
#include
struct S1
{
char c1;
double d1;
int i;
};
struct S2
{
char c2;
struct S1 s1;
int j;
};
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
S2中,因为嵌套了S1,S1中最大对齐数为8,char c2之后的偏移量为1,要在偏移量为8的地方写下,S1的大小为24,在偏移量为32的地方写下int,占4个字节,此时S2大小为36,不为最大对齐数8的倍数,所以要到40。
struct S
{
int data[1000];
int num;
};
struct S s = { {1,2,3,4},1000 };
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps) {
printf("%d\n", ps->num);
}
int main()
{
print1(s);//传结构体
print2(&s); //传地址
return 0;
}
print2比1好,传参的时候尽量使用指针,因为传值调用需要压栈,会重新复制一份结构体,导致性能的下降,而在函数内修改结构体的内容也必须用到指针。
这里的位,指的是二进制的位
位段可以同样使用结构体实现
位段的成员必须是int,unsigned int(标准的定义没有char类型,但经过实验,char类型也可实现位段)
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
位段后面的数字,表示这个变量需要占用的比特位
如果剩下的空间不够下一个变量开辟,将直接使用下一个字节,剩下的比特位会被浪费掉。
位段设计出来的作用,是为了节省空间
比如,我们的学生类型中的分数变量,我们发现分数永远不会超过100分,所以我们只需要7个比特位存储,而不需要占用4字节,也就是32比特位了。
但是,位段有跨平台的问题,因为位段的设计并没有详细标准,各个编译器和系统可能不一样。
枚举类型就是把可能的取值,一一列举出来
//enum枚举关键字
enum name//枚举类型名
{
//member list枚举成员名
};
比如定义一个星期
//enum为枚举类型关键字
enum Day
{
MON,
TUES,
WED,
THUR,
FRI,
SAT,
SUN
};
默认第一个变量为0,一次递增1,当然可以在定义中赋予初值,后面的变量会继续以被赋予的值继续加一。
enum Day
{
MON=1,
TUES,
WED,
THUR,
FRI,
SAT,
SUN
};
比如我们要实现一个计算器
写一个计算器的菜单
1.add 2.sub
3.mul 4.div
我们在代码的选项中可以这样写
switch (option)
case 1:
case 2:
case 3:
case 4:
用数字来代替选项,会带来许多不必要的麻烦,我们考虑可以使用枚举变量。
enum Option
{
ADD=1,
SUB,
MUL,
DIV
};
将菜单改为以下这样,就方便我们进行对照了
switch (option)
case ADD:
case SUB:
case MUL:
case DIV:
它的特征是成员变量共用一块存储空间
union Test
{
char c;
int i;
}
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。
union U
{
int i;
char c;
}u;
int main()
{
u.i = 0x11223344;
u.c = 0x55;
printf("%x\n",u.i);
return 0;
}
联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
union Un1
{
char c[2];
int i;
};
union Un2
{
char c[5];
int i;
};
int main()
{
printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));
return 0;
}
Un1最大成员为i,大小为4个字节。Un2最大成员为数组c,大小为5个字节,但不是4的倍数,所以要占下一个整数倍(8)的大小。