目录
1.结构体类型的声明
结构体是一些值得集合,这些值被称为成员变量,结构体的每一个成员可以是不同类型的变量。
声明:
struct tag
{
member - list;
}variable-list;
那么描述一个学生该怎么描述呢?
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //分号不能丢
声明结构体的时候,还可以声明匿名结构体类型。
struct
{
int a;
double b;
char c[100];
}x;****************************
struct
{
int a;
double b;
char c[100];
}a[20],*p;
上面两个结构体声明的时候都省略掉了结构体标签 。
虽然这两个结构体的成员变量一样,但是编译器还是会把它们当成是两个不同的结构体类型。
2.结构体的自引用
结构体不可以包含一个类型为该结构体本身的成员。
定义一个链表的机构体
struct Node
{
int data;
struct Node n;
} ;
int main()
{
struct Node n;
return 0;
}//这样是不行的,结构体变量n有成员data和n,n又有data和n......不断循环。
正确的自引用方式:
struct Node
{
int data;
struct Node* next;
} ;**********************************************************
typedef struct
{
int data;
Node* next;
}Node;//typedef是类型重命名,不可以这样写,结构体重命名为Node在后。
正确的写法:
typedef struct Node
{
int data;
struct Node* next;
}Node;
3.机构体变量的定义和初始化
定义:
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2**************************************************
初始化:
struct Point p3 = {x, y}; //定义变量的同时赋初值
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
};
struct Stu s = { "zhangsan", 20 };//初始化struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = { 10, {4,5}, NULL }; //结构体嵌套初始化
struct Node n2 = { 20, {5, 6}, NULL };//结构体嵌套初始化
4.结构体内存对齐
怎么计算结构体的大小,是把所有成员大小相加?NO
计算结构体大小之前,我们得先掌握结构体的对齐规则。
1.第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。不同编译器默认对齐数不一样,vs 中默认对齐数为8。
3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
下面来计算几个结构体的大小吧
c1对齐到偏移量为0的地址处,编译器默认对齐数是8,i的大小是四个字节,所以对齐数是4,i要对齐到偏移量为4的地址处,c2为一个字节,所以c2要对齐到偏移量为8的地址处,结构体大小为9个字节,不是最大对齐数(4)的整数倍,所以要增加到12个字节的大小。
d对齐到偏移量为0的地址处,因为double类型数据大小占8个字节,所以d占0~7,c大小为1个字节,所以对齐数为1,c对齐到偏移量为8的地址处,i大小为4个字节,所以对齐数为4,i要对齐到偏移量为9的地址处,占9~12,此时结构体大小为13个字节,不是最大对齐数的整数倍,所以要增加到16个字节。
c1对齐到偏移量为0的地址处,因为s3是一个结构体变量,所以s3要对齐到其成员变量最大对齐数(8)的整数倍,所以s3对齐到偏移量为8的地址处,已知s3大小为16个字节,所以s3占空间为8~23,d大小为8个字节,所以对齐数为8,d要对齐到偏移量为24的地址处,占24~31,故结构体大小为32个字节,满足是所有最大对齐数的整数倍。
为什么要内存对齐?
1.(平台原因)不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些 特定类型的数据,否则抛出硬件异常。
2.(性能原因)访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅需要一次访 问。
总体来说:结构体的内存对齐是拿空间来换取时间的做法。
其实我们在设计结构体的时候我们可以让占用空间小的成员尽量集中在一起,这样会比较节省空间。
5.修改默认对齐数
可以使用#pragma这个预处理指令修改我们的默认对齐数。
用法:
#pragma pack(8) //设置默认对齐数为8
......
#pragma pack() //取消设置的默认对齐数,还原为默认
特殊:#pragma pack(1) //设置默认对齐数为1,就是没有对齐
6.结构体传参
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;
}
传结构体和传地址哪个好呢? 传地址更好
因为函数传参的时候,参数需要压栈,会有时间和空间上的系统开销,如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
tip:
在结构体定义时,用结构体变量.成员名更方便
在结构体传参的时候,一般传的是机构体指针,使用结构体指针->成员名更方便
7.结构体实现位段
位段的声明和结构体类似,但有两个不同:
1.位段的成员必须是int,unsigned int,signed int,char(属于整形家族)类型
2.位段的成员名后边有一个冒号和一个数字
示例:
先开辟一块整形空间,(32位机器)一块整形空间占4字节32个bit位,_a和_b和_c这三个占17个bit位,还剩15个bit位,不够放30个bit位,需要再开辟一块整形空间。这30个bit位到底是全部放在第二块空间,还是第一块和第二块空间分摊保存取决于编译器。
1. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
2. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段.
举个例子:
结构体变量s是三个字节大小,可知在vs环境下,系统先开辟了一个字节空间存放成员变量a和b,剩下1个bit的空间不够放c,所有又开辟了一个字节空间存放c,剩下三个bit位又不够放d,又开辟了一个字节大小的空间存放d。
那下面那个赋值后结果是多少? 我也不知道......
我们可以先假设在vs环境下,开辟的内存空间是从后往前使用的,不够用了在开辟一块空间。
10的二进制是1010,但是a只有三个bit位大小,只能放010,12的二进制是1100,b刚好放得下,3的二进制是0011,因为c是5个bit大小,所有前面加个0,4的二进制是0100,d刚好放得下。
如果按照我的假设的话,结果就是620304 。
下面我们来看一下结果吧,和我猜测的一样。
位段跨平台问题:
1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16(一个int是两个字节),32位机器最大32(一个int是4个字节),写成27,在16位机
器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。VS是从右向左分配的。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
舍弃剩余的位还是利用,这是不确定的。