结构体声明
tag是结构体标签,利用成员列表里已经写好的变量类型来定义变量列表(s1 s2 s3)
特殊声明
将结构体标签tag省略,为匿名结构体类型
struct
{
int a;
char b;
float c; }x;
struct
{
int a;
char b;
float c; }a[20], *p; // 匿名结构体加上*,变为结构体指针
// p = &x 是非法的,&x是指针,p是个指针变量,两个类型相同,在编译器看来不同
匿名结构体只能用一次,无法创造新的变量
匿名结构体类型如果一样,成员变量一样,在编译器看来是不同类型的结构体
结构体自引用
结构体能找到与自身结构类型相同的变量
代码实现如下
struct Node
{
int data; //数据域
struct Node* next; //指针域
};
类型重命名 typedef
typedef struct Node
{
int data;
struct Node* next; }Node;
int main()
{
Node n1;
Node n2; //typedef重命名后可以不写struct
}
typedef struct Node
{
int data;//数据
struct Node* next;//指针
} Node, * pNode;
//pNode --> struct Node* 对结构体指针Struct Node*进行重命名为pNode
重命名匿名结构体时,下面写法是错误的。将结构体重命名为Node在变量列表里,在此之前成员列表里已经使用了Node* 这一未定义的变量。
结构体变量的定义和初始化
struct Book
{
char name[20];
float price;
char id[12];
}s = {"鹏哥C语言", 55.5f, "PGC001"};
struct Node
{
struct Book b;
struct Node* next;
};
int main()
{
struct Book s2 = { "杭哥数据结构", 66.6f, "HG001" };
struct Node n = { {"汤神Java", 66.8, "TG001"}, NULL};
return 0;
}
结构体内存对齐
首先了解结构体内存对齐的规则:
struct S1{char c1 ;int i ;char c2 ;};printf ( "%d\n" , sizeof ( struct S1 ));c1放在偏移量为0的地址处,占一个字节:0
i的对齐数是4,故1—3的地址被浪费,从4(倍数原因)开始占4个字节 :4—7
c2的对齐数是1,故紧接着8开始占一个字节。
c1、i、c2的最大对齐数是4,所以结构体总大小是4的倍数,之前从0—8一共为9个字节,所以继续往后浪费地址空间,到11,即从0—11一共12个字节
相同结构体,当成员列表顺序不同时,占的字节数也可能不同。VS环境下默认对齐数是8,Linux环境下没有默认对齐数,当没有默认对齐数时,自身大小就是默认对齐数
尽可能将所占字节数小的变量放在前面,这样可以减少浪费的地址。
结构体嵌套问题
struct S4{char c1 ;struct S3 s3 ;double d ;};printf ( "%d\n" , sizeof ( struct S4 )0:被c1占用
这时,就开始到了struct S3 s3;
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
之前我们计算的s3是16个字节所以:
1~7:就被浪费为了对齐嵌套的S3的最大对齐数
8~24:被嵌套的S3占用
25~32:为了让double对齐就只能浪费了
32:正好是最大的成员对齐数的倍数
内存对齐的原因:
1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
2. 性能原因:
offsetof 宏
引用结构体名称和成员名称,返回类型是无符号整型(%u) 所需要引用的头文件是<stddef.h> 。用来计算结构体成员相对于起始位置的偏移量
修改默认对齐数
结构体传参
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 ;}上面的 print1 和 print2 函数哪个好些?
首选print2函数:
传值是结构体的时候我们优先考虑传地址,这样我们可以防止浪费空间,减少系统开销。
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降
结论:
结构体传参的时候,要传结构体的地址