自定类型结构体主要包括以下三类:
结构是一些值的集合,这些值被称为成员变量。结构的每个成员可以是不同类型的变量。
区别于数组,数组是一组相同类型变量的集合。
struct student
{
char name[20];
int age;
char sex[5];//“保密”+“\0”最多占5个字节,一个汉字占两个字节
float score;
}s1,s2,s3;//s1,s2,s3是三个结构体变量--->是全局变量
int main()
{
struct student s4,s5,s6;//s4,s5,s6是三个结构体变量------->是局部变量
return 0;
}
还有一种特殊的声明方法----->匿名声明
struct
{
char name[20];
int age;
char sex[5];
float score;
}b1,b2;//匿名结构体只能在这里创建变量
int main()
{
return 0;
}
在结构体内包含一个类型为该结构本身的成员是否可以呢?
举例:表示链表
struct note
{
int date;//数据
struct note n;//下一个节点
}
这个代码并不正确,无法计算sizeof(struct note)
可以把下一个节点改成下一个节点的指针,最后一个节点存为NULL即可
正确的自引用
struct note
{
int date;//存放数据--->数据域
struct note* n;//存放下一个节点的地址--->指针域
}
int main()
{
printf("%d\n",sizeof(struct note));//16
return 0;
}
注意:自引用不能匿名结构体
struct
{
int date;//存放数据--->数据域
struct note* n;//存放下一个节点的地址--->指针域
}//err
这样也不行
typedef struct
{
int date;//存放数据--->数据域
struct note* n;//存放下一个节点的地址--->指针域
}note;//这里在创建note时就要使用note,所以也不行
struct Point
{
int x;
int y;
}p1={1,2};//全局变量,定义变量并初始化
struct Point p3={4,5};//全局变量
int main()
{
struct Point p2;//-->局部变量
return 0;
}
打印结构体成员默认按照顺序,也可以自己规定乱序,用变量名.
struct stu
{
char name[15];//名字
int age;
};
int main()
{
struct stu s = {"zhangsan",20};
struct stu s2 = {.age=18,.name="wangwu"};
printf("%s %d\n",s.name,s.age);
printf("%s %d\n",s2.name,s2.age);
return 0;
}
嵌套结构体的打印方式
#include
struct Point
{
int x;
int y;
}p1 = {1,2};
struct Node
{
int data;
struct Point p;
struct Node* next;
};
int main()
{
struct Node n = { 100, {20, 21}, NULL };
printf("%d x=%d y=%d\n", n.data, n.p.x, n.p.y);
return 0;
}
现在我们深入讨论一个问题:如何计算结构体的大小?
例子:
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
printf("%d\n",sizeof(struct S1));//12
printf("%d\n",sizeof(struct S2));//8
return 0;
}
为什么成员的顺序会影响结构体的大小呢?
先说明一个宏:
offsetof是一个宏
可以直接使用
可以计算结构体成员相较于起始位置的偏移量
S1如下
struct S3
{
double d;
char c;
int i;
};
int main()
{
printf("%d\n",sizeof(struct S3));//16
return 0;
}
struct S3
{
double d;
char c;
int i;
};
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("%d\n",sizeof(struct S4));//32
return 0;
}
1.平台原因:不是所有硬件平台都能访问任意地址的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐,原因在于为了访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅需要一次访问
总体来说:
结构体的内存对齐是拿空间换取时间的做法
在32位机器上,读取数据一次拿取32位的数据
如果不考虑内存对齐的话
读取如下的结构体s中的 i 要读两次才可以读完
考虑内存对齐,读取 i 只需要一次,效率更高
在设计结构体时,既要满足内存对齐,又要节省空间,就要让占用空间小的成员尽量集中在一起。
#pragma pack(想要设置的对齐数)
一般不设置奇数,设置2的次方的数
例
#pragma pack(1)
struct S2
{
char c1;
char c2;
int i;
};//结构体大小6
通常有两种
1.传值
2.传地址
一般选用传地址,这样压栈空间小,效率更高
函数传参的时候,参数是需要压栈的,会有时间和空间上的系统开销,如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销过大,会导致性能下降。如下函数,首选print2函数。
struct S
{
int data[1000];
int num;
};
void print1(struct S t)
{
printf("%d %d %d %d\n", t.data[0], t.data[1], t.data[2], t.num);
}
void print2(const struct S * ps)
{
printf("%d %d %d %d\n", ps->data[0], ps->data[1], ps->data[2], ps->num);
}
int main()
{
struct S s = { {1,2,3}, 100 };
print1(s);//传值调用
print2(&s);//传址调用
return 0;
}
位段的出现就是为了节省空间
结构体说完就要探讨一下结构体实现位段的能力
- 位段的成员必须是int ,unsigned int ,char(整型家族)
- 位段的成员名后边有一个冒号和一个数字。
位段的位是二进制的位
struct A
{
int _a:2;//表示_a占用2个bit位的空间,而int本来要32个bit位,所以这样可以节约空间
int _b:5;
int _c:10;
int _d:30;
};
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
int main()
{
printf("%d\n",sizeof(struct A));//占用8个字节,相较于结构体节省了一半的空间!
return 0;
}
注意:
位段的空间上是按照四个字节(int)或者一个字节(char)的方式来开辟的
位段涉及很多不确定因素,位段是不跨平台的,注重可移植性的程序应该尽量避免使用位段 但是即使有再多的不确定性,我们也可以探究一下在VS上位段是如何使用的
struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
int main()
{
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
printf("%d\n", sizeof(struct S));
return 0;
}
假设上述代码存储的的方式是从低位向高位存储
存完a,b如下图,第一个字节内剩余空间不够存c,所以再开辟一个字节,假设第一个字节中剩余的空间不再利用,则c存储在新开辟的字节当中
对于a而言,丢失了数据
a二进制是00001010,只存进了010
存储完如下:
这是我们假设的情况,怎样验证呢?只需要换算成16进制,在内存窗口上观察即可
以四个bit位划分,计算的16进制为620304
结果如下,验证成功,说明我们假设成立
int位段被当做有符号位或者无符号位是不确定的
位段中的最大位的数目是不确定的(16位的机器上最大是16,32位机器上最大是32,写成30,在16位机器上会出问题)
位段中的成员在内存中从左向右分配还是从右向左分配是不确定的
当一个结构中包含两个位段时,第二个位段成员比较大时,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这也是不确定的
跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台问题存在
定义的变量只有有限个可能
把可能的取值一一列举就是枚举。
比如月份,性别等都可以枚举
例一:
enum sex
{
man;//枚举常量
woman;
secret;
};
int main()
{
printf("%d",man);//0
printf("%d",woman);//1
printf("%d",secret);//2
return 0;
}
1.枚举的默认取值是从0开始依次向下递增1的
2.也可以在声明变量的时候给变量赋值
3.枚举变量的大小相当于一个整型,占用4个字节
用枚举常量给变量赋值,才不会出现类型的差异
联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间(所以联合也叫共用体)
//union是联合体的关键字
union Un
{
char c;
int i;
};
int main()
{
union Un un;
printf("%d\n",sizeof(un));//4
return 0;
}
- 联合体的大小至少是最大成员的大小
2.当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
3 联合体的成员在同一时间内只能使用一个
同时使用会相互影响
union Un
{
char c[5];//5
int i;//4
};
int main()
{
printf("%zd\n", sizeof(union Un));//8
//本来是5,对齐为4的倍数,结果是8
return 0;
}
union Un
{
short c[7];//14
int i;//4
};
int main()
{
printf("%zd\n", sizeof(union Un));//16
//short类型占2个字节,7个元素就是14个字节,最大对齐数是int,4个字节,结果对齐到16
return 0;
}
常规方法:利用char*指针访问一个字节判断
int check_sys()
{
int a = 1;
if (*(char*)&a == 1)//int*
return 1;
else
return 0;
}
int main()
{
int ret = check_sys();
if (ret == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
int check_sys()
{
union
{
char c;
int i;
}u;
u.i = 1;
return u.c;//返回1表示小端,返回0表示大端
}
int main()
{
int ret = check_sys();
if (ret == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
像这样不同商品有公共属性,也有特殊属性时,可以考虑联合体
不使用联合体,会创建很多不用的变量,浪费内存空间
改造成联合体可节省空间如下