目录
结构体
1【结构体类型的声明】
2【结构体类型的声明】
3【结构体变量的定义和初始化】
4【结构体内存对齐】
5【结构体传参】
6【结构体实现位段的能力 】
- 生活中对象是复杂的,比如书、书名、作者、出版社、定价、书号等。怎么描述这一类对象呢?于是就出现了结构体。
- 结构是一些值的集合,这些值称为成员变量,结构的每个成员可以是不同类型的变量。
struct tag
{member-list;
}variable-list;
【举例理解一下:】
//相当于自己创建了一个类型Book
struct Book //struct 是结构体的关键字,Book是结构体的标签名
{char name[20]; // {}里面放的是描述这个对象的成员变量列表int price;char id[12];
}b3,b4,b5; // b3,b4,b5这里创建的实体是全局的,而b1,b2在{}里面所以是局部的
int main()
{struct Book b1; //拿自己创建的类型Book创建了个b1,b1就是一个实体struct Book b2;return 0;
}
【不完全声明】
//匿名结构体类型,只能用一次,用完就不能用了
struct
{char c;int i;char ch;
}s;
结构体类型的声明:就是结构体包含结构体
【举例理解一下】
struct A
{int ret;char w;
};struct B
{char t;struct A aa; //结构体B包含了结构体Adouble f;
}
int main()
{return 0;
}
【结构体的自引用】
struct Node
{int data;struct Node next;
};
//这种是错误的引用
//结构体不能引用同一结构体的成员变量,而是引用同一结构体的指针struct Node
{int data;struct Node* next;
};
//这种引用同一结构体的指针是正确的
【举例理解一下】
struct A
{int x;char y;
}p1; // 声明类型的同时定义变量p1struct B
{double b;struct A s;
};
int main()
{struct A p2; // 定义结构体变量p2struct A P3 = {0,'c'};//初始化:定义变量的同时赋初值struct B p4 = {3.14,{0,'y'}};//. 是针对结构体变量的//->是针对结构体指针的printf("%lf %d %c\n",p4.b,p4.s.x,p4.s.y);return 0;
}
先看下面代码,结构体的大小是多少
struct S
{char c1;int i;char c2;
};int main()
{struct S s = {0};printf("%d\n",sizeof(s));return 0;
}
//输出为12
输出为:12,为什么是12呢,然后引出结构体内存对齐
- 1.第一个成员在与结构体变量偏移量为0的地址处。
- 2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处对齐数 = 编译器默认的一个对齐数 与该成员大小的较小值(VS中默认的值为8)。
- 3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
- 4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数 )的整数倍。
下图:
C1 | |||
i | |||
C2 | |||
为什么存在内存对齐?
大部分的参考资料都是如是说的:
- 1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅需要一次访问。
总体来说
结构体的内存对齐是拿空间来换取时间的做法
【指定对齐值】
- #pragma pack (n),C 编译器将按照 n 个字节对齐。
- #pragma pack (),取消自定义字节对齐方式。
#pragma pack(2)
struct S
{char c1;int i;char c2;
};
#pragma pack()
int main()
{struct S s = {0};printf("%d\n",sizeof(s));return 0;
}//输出为8
【offsetof宏】
#include
struct S
{char c1;int i;char c2;
};int main()
{printf("%d\n",offsetof(struct S,c1));printf("%d\n",offsetof(struct S,i));printf("%d\n",offsetof(struct S,c2));return 0;
}//输出为://0//4//8//请按任意键继续. .
下面的print1和print2函数哪个好些?
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 signed int 或者是char (属于整形家族)类型。
- 2.位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
- 3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
位段的跨平台问题
- 1.int位段被当成有符号数还是无符号数是不确定的
- 2.位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
- 器会出问题。
- 3位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。4.当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。