目录
一、结构体
1、结构体的声明与初始化
结构体常见的声明方式有三种,如:
1、
struct stu
{
char name[20];
int age;
char sex[5];
char id[20];
};
//创建结构体变量
struct stu s1;
//初始化结构体
s1 = { {"zhangsan"},18,{"man"},{"123"} };
//当然可以边创建结构体变量边初始化;
struct stu s2={ {"zhangsan"},18,{"man"},{"123"} };
2、
struct stu
{
char name[20];
int age;
char sex[5];
char id[20];
}s1,s2;
//结构体的创建和初始化与上面相同
3、
typedef struct stu
{
char name[20];
int age;
char sex[5];
char id[20];
}Stu;
//结构体变量的创建,这种结构体创建就不用写”struct stu“,而是直接写他们的别名就行。
Stu s1;
//然后初始化与上面类似。
第一种是直接声明一个结构体;第二种是在声明的同时创建了两个全局的结构体变量s1,s2;第三种是给声明的结构体创了一个别名“Stu”。这个别名的作用体现在创建结构体变量的方式:第一种、第二种创建结构体变量是:struct stu s1,这个s1就是结构体变量;而第三种创建结构体变量是:Stu s1,这个s1也是一个结构体变量,别名的作用就体现在这。
结构体还有一种特殊声明:在声明结构体的时候,不用起名字,这种结构体就是匿名结构体。
struct
{
int a;
char b;
float c;
}x;
但是声明这种结构体的同时,要把结构体变量同时创建,在其他地方就不能创建结构体变量了。
2、结构体大小的计算
首先我们先了解结构体的对齐规则:
1.第一个成员在与结构体变量偏移量为0的地址处。
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数=编译器默认的一个对齐数与该成员人小的较小值。
VS中默认的值为8 Linux中的默认值为4
3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
下面我们来做几道题来理解一下
struct S1
{
char c1;
int i;
char c2;
};
c1是char类型大小是1,vs默认是8,对齐数取较小值,所以这个变量的对齐数是1,而其实偏移量是0,是1的倍数,所以可以放下c1。i的大小是4,vs默认是8,所以这个变量的对齐数是4,而此时的偏移量是1,不是对齐数的整数倍,所以不能放,要舍弃3个字节,到偏移量为4的地方放下i。放完i后到了偏移量为8的位置,c2变量的对齐数是1,8是1的倍数可以放c2,放完c2后到了偏移量为9的位置,不是最大对齐数4的倍数,所以我们要舍弃3个字节,到偏移量为12的位置。因此整个结构体的大小为12.
那么为什么要内存对齐呢?
1.平台原因(移植原因)︰不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
⒉性能原因︰数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说︰
结构体的内存对齐是拿空间来换取时间的做法。
3、结构体传参
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;
}
上面那种传参方式好呢?
答案是传地址那种好。因为如果按值传参,我们需要把整个结构体传过去,传参是需要压栈的,如果结构体过大,系统的开销就比较打。而如果我们按址传参,我们只需要传一个地址过去,所需的系统开销就比较小。
二、枚举
1、枚举的定义。
枚举使用关键字 enum
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
其中Mon的默认值是0,Tues的默认值是1,以此类推sun的默认值是6。
但如果这样写:
enum Day//星期
{
Mon,
Tues,
Wed,
Thur=10,
Fri,
Sat,
Sun
};
Mon的是0,Tues是1,Wed是2,Thur是10,Fri是11,Sat是12,sun是13,。
2、枚举的使用
enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
enum Color clr = GREEN;
只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。如果写成clr=1,这样在某些编译器下会报错。
三、联合体
1、联合体的定义
联合体也称共用体
这是一种特殊的自定义类型,这种类型也包含一系列的成员,但这些成员公用一块空间。
联合体的关键字是 union
union Un
{
int i;
char c;
};
union Un un;
下面让我们看看un的空间分布
因为联合体是公用空间的,所以i和c共用一块空间,i占4个字节,c占用一个字节,所以整个联合体是4个字节,且c与i共同使用最低那个地址的空间。
printf("%d\n", &(un.i));
printf("%d\n", &(un.c));
这个结果相同吗?
答案是相同的,因为共用一块空间,所以起始地址都相同。
un.i = 0x11223344;
un.c = 0x55;
printf("%x\n", un.i);
输出结果是11223355
机器按小端方式,u.i在空间上是如图这样显示的,后来un.c=0x55,有因为i和c共用一块空间,且c只有一个字节,就将低地址的44改为了55
所以打印11223355。
2、联合体的大小计算
计算规则:
联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
union Un1
{
char c[5];
int i;
};
数组c的类型是char,对齐数是1,但整个数组占用5个字节,i的对齐数是4,5个字节的空间够i使用,但是5不是最大对齐数4的倍数,所以得浪费3个空间的字节,使联合整个空间大小为最大对齐数的倍数,因此这个联合体占用8个字节的空间。