结构体类型的创建
结构是一些值的集合,这些值称为成员变量,结构的每个成员可以是不同类型的变量。
例如描述一个学生:
1.声明类型的同时对结构体变量进行定义
struct Stu
{
char name[20];
int age;
char sex[5];
char id[20];
}stu;
2.先声明结构体类型,再对变量进行定义
struct Stu
{
char name[20];
int age;
char sex[5];
char id[20];
};
strcut Stu stu;
3.匿名声明结构体类型(这种结构体只可以用一次)
struct
{
char name[20];
int age;
char sex[5];
char id[20];
}stu;
结构体成员的访问
1.用(.)访问
适用于非结构体指针变量
struct Stu
{
char name[20];
int age;
}stu;
strcpy(stu.name, "zhangsan");
stu.age = 20;
2.用(->)访问
适用于结构体指针变量
struct Stu
{
char name[20];
int age;
}*p;
strcpy((*p).name, "zhangsan");
strcpy(p->name, "zhangsan");
(*p).age = 20;
p->age = 20;
结构体初始化
1.先定义再初始化
struct Stu
{
char name[20];
int age;
}stu;
struct Stu stu = {"zhangsan", 20};
2.定义的同时初始化
struct Stu
{
char name[20];
int age;
}stu = {"zhangsan", 20};
结构体内存对齐
对齐规则:
- 第一个成员始终在0偏移量的地址处;
- 其他成员变量对齐到对齐数(编译器默认对齐数与该成员大小的较小值,Linux下为4,VS下为8)的整数倍的地址处;
- 结构体总大小为最大对齐数的整数倍;
- 若嵌套了结构体,嵌套的结构体对齐到自己最大对齐数的整数倍处,结构体的总大小为最大对齐数的整数倍,包括所嵌套结构体的对齐数
为什么存在内存对齐:
- 不是所有平台都能访问任意地址上的任意数据
- 应尽可能在自然边界上对齐,原因在于,为了访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存访问仅需要一次
内存对齐是用空间换取时间的做法,为了尽可能少的浪费空间,所以要把占用空间小的成员集中在一起
#pragma pack(4)—–修改默认对齐数为4
#pragma pack()——-取消修改默认对齐数
结构体传参
#include <stdio.h>
#include <Windows.h>
struct S
{
char name[100];
int age;
};
void print1(struct S s)
{
printf("name = %s age = %d\n", s.name, s.age);
}
void print2(const struct S* ps)
{
printf("name = %s age = %d\n", ps->name, ps->age);
}
int main()
{
struct S s = {"zhangsan", 25};
int i = 0;
int start1 = 0;
int end1 = 0;
int start2 = 0;
int end2 = 0;
start1 = GetTickCount();
//GetTickCount()函数计算的是系统运行到此时的差值,引用头文件<Windows.h>
for (i = 0; i < 1000; i++)
{
print1(s);
}
end1 = GetTickCount();
start2 = GetTickCount();
for (i = 0; i < 1000; i++)
{
print2(&s);
}
end2 = GetTickCount();
printf("%d\n", end1-start1);//两数值差为执行1000次打印函数用的时间
printf("%d\n", end2-start2);
return 0;
}
从上面代码的结果可以看出,结构体传参时要传结构体的地址,因为传地址时只是传过去4个字节,更加节省时间
位段
位段的成员名后面有一个冒号和数字,数字表示的是占用几个二进制位(比特位)
例如:
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
上图为在VS编译器下的位段内存分配,可以看出总共占用了8个字节,浪费了17个字节,相比于结构体来说,已经节省了很多
位段的一些缺点:
- 有符号数还是无符号数是不确定的
- 位段中的最大数不能确定,在16位机器中为16,32位机器中为32,
- 内存中从左向右分配还是从右向左(VS)分配尚未定义
- 第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃(VS)还是利用尚未定义
跟结构体相比,位段可以达到同样的效果,但可以更好地节省空间,不过有跨平台的问题存在
枚举
枚举的定义,例如:
enum Color
{
RED,
GREEN,
BULE
};
当不赋初值时,默认从0开始,一次递增1,当然也可以赋初值
enum Color
{
RED = 1,
GREEN = 2,
BULE = 4
};
枚举的优点:
- 增加代码的可读性和可维护性
- 和#define定义的标识符相比,枚举有类型检查,更加严谨
- 防止命名污染
- 便于调试
- 使用方便,一次可定义多个常量
enum Color
{
RED = 1,
GREEN = 2,
BULE = 4
};
enum Color c = GREEN;//正确,枚举常量给枚举变量赋值
enum Color clr = 2;//错误,左右两边的类型不同,不能这样赋值
联合(共用体)
联合的变量包含一系列成员,特征是这些成员公用同一块空间
例如,用联合来写个函数,判断当前计算机的大小端:
#include <stdio.h>
int sys()
{
union Un
{
char c;
int i;
}un;
un.i = 1;
return un.c;
}
int main()
{
int ret = sys();
if (ret == 1)
{
printf("该计算机是小端字节序存储模式");
}
else
{
printf("该计算机是大端字节序存储模式");
}
return 0;
}
联合体大小的计算:
- 至少为最大成员的大小
- 当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍
union Un1
{
char c[5];//对齐数为1,大小为5个字节
int i;//对齐数为4,大小4个字节
};//大小为8个字节
union Un2
{
short c[7];//对齐数为2,大小为14个字节
int i;;//对齐数为4,大小4个字节
};//大小为16个字节
将联合与结构体巧妙地使用:
//将long类型的IP地址转换为点分十进制的表示形式:
union ip_add
{
unsigned long add;
struct
{
unsigned char c1;
unsigned char c2;
unsigned char c3;
unsigned char c4;
}ip;
}my_ip;
my_ip.add = 176238749;
printf("%d.%d.%d.%d\n", my_ip.ip.c4, my_ip.ip.c3,
my_ip.ip.c2, my_ip.ip.c1);