这里主要讲一些结构体得进阶知识,如果想了解得更多可以结合这篇博客一起学习——初始结构体
结构是一些值得集合,这些值称为成员变量。结构得每个成员可以是不同类型得变量。
在声明结构得时候,可以不完全得声明。
例如:
//匿名结构体类型
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20],*p;
想要探究这个问题,我们只需执行下面的操作即可:
//使用结构体指针变量p接收结构体变量x的地址,若能接收说明它们的类型是相同的
p = &x;
执行后,VS编译器给出如下警告:
"="两端的类型是不兼容的,证明虽然它们的结构体是相同的,但它们是不同的类型
警告:
编译器会把上面的两个声明当成完全不同的两个类型
所以是非法的。
在结构体中包含一个类型为该结构体本身的成员是否可以呢?
struct Node
{
int data;
struct Node next;
};
//这样写是否可以?
这么写明显是不对的,一个结构体类型中包含着一个该结构体类型的成员变量,这么一直下去该类型创建的成员变量的大小将无法确定,这明显是不符合C语言语法的。
struct Node
{
int data;
struct Node* next;
};
在前段时间我学习数据结构时,写了一个非常经典的错误写法,这里写出来共大家参考。
typedef struct Node
{
int data;
Nd* next;
}Nd;
//方法1
typedef struct Node Nd;
typedef struct Node
{
int data;
Nd* next;
}Node;
//方法2
typedef struct Node
{
int data;
struct Node* next;
}Node;
结构体的自引用是在特殊情况使用的如:数据结构中的链表、二叉树等
如果对这方面的应用感兴趣可以点击链接了解一下线性表之单链表
当我们明白了结构体的基本使用后,下一个问题就是:计算结构体的大小。
这是一个特别热门的考点:结构体对齐
通过下面一个例子你就能明白:
struct text
{
int a;
char b;
}text;
我们创建了结构体类型变量text,方便求成员变量a和b的偏移量。
- a是第一个成员变量,所以它的偏移量就是0
- b是第二个成员变量,在它之前的变量a为int类型占4个字节,所以b的偏移量就是4.
我们就能得到该结构体的大小为5
offsetof(type,member)
struct S1
{
char a;
int b;
char c;
};
int main()
{
struct S1 s1;
printf("%d\n", offsetof(struct S1, a));
printf("%d\n", offsetof(struct S1, b));
printf("%d\n", offsetof(struct S1, c));
return 0;
}
a时第一个元素,偏移量为0,而b和c的偏移量为什么时这样呢?
着源于结构体的对齐规则
看了这些规则后,让我们接着上面的问题继续分析
a:是第一个成员,char类型只占一个字节
b:不是第一个成员,int类型占4个字节。按上边的规则,b的偏移量是某个数字(对齐数)的整数倍的地址处,4小于8(编译器默认对齐数),所以对齐数是4,要偏移到4的整数倍处,此时的偏移量为4的地址是空的,该位置又符合4的整数倍这一要求。
c:b从偏移量为4的地址处开始,一直到7,而c的类型为char占1个字节,所有数字都是1的整数倍,所以偏移量是8.
从0开始,此时一共使用9字节空间。
而结构体的大小又是最大对齐数的整数倍,a——1 , b——4 ,c——1,
最大对齐数为4,而9不是4的整数倍,需要继续偏移,最终,该结构体的大小为12.
//查看struct S1类型的大小
printf("%d\n", sizeof(struct S1));
//练习1
struct S2
{
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S2));
//练习2
struct S3
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));
//练习3-结构体嵌套问题
struct S4
{
char c1;
struct S3 s3;
double d;
};
printf("%d\n", sizeof(struct S4));
这里官方没有给出原因,大部分参考书给出的原因如下
比如因为硬件的原因当我们要定义一个整型的变量时,要求它必须存储在偏移量为4的倍数的位置,这样它的对齐数就只能是4.(基于一些特殊的平台)
由于CPU和数据存储方式的原因,当一个整数,放在偏移量为1的位置上时,CPU需要访问两次,才能完全取出数据。大家要是对这个原理感兴趣,可以看一下我总结的这篇博客 探索未对齐内存CPU的访问逻辑
总体来说:
结构体的内存就是拿空间换取时间的做法
使用#pragma预处理指令改变默认对齐数。
#include
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
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;
}
上述代码哪一种传参方式更好?
函数传参是,参会需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体变量值时,如果该结构体变量过大,参数压栈的系统开销比较大,所以会导致性能的下降。
简单的说,传结构体变量过去,形参会复制一份实参的数据,当数据过大时,计算机性能就会下降。
而传地址,只是传递过去一个地址,在32位平台下,地址的大小时固定的,占4个字节。