目录
一、结构体声明
我们来定义一个学生类,有名字、年龄、性别、学号。
//结构体声明,定义一个学生类
struct stu
{
char name[20];
int age;
char sex[5];
char id[20];
};
int main()
{
return 0;
}
二、结构体变量
接着上面的学生类,我们来给结构体成员初始化
需要注意需按照结构体成员的顺序初始化。
int main()
{
//按照结构体成员的顺序进行初始化。
struct Stu s = { "张三",20,"男",2023000102 };
printf("name : &s\n", s.age);
printf("age : %d\n", s.age);
printf("sex : %s\n", s.sex);
printf("id : %s\n", s.id);
//按照指定的顺序初始化
struct Stu s2 = { .age = 18, .name = "lisi", .id = "202332" };
printf("name : &s\n", s.age);
printf("age : %d\n", s.age);
printf("sex : %s\n", s.sex);
printf("id : %s\n", s.id);
return 0;
}
2.1结构体的特殊声明
说白了就是不完全的声明。
把结构体类型S省略。
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;
警告:
编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。
匿名的结构体类型,如果没有对结构体类型重命名的话,只能用一次。
我们可以重命名来使用
typedef struct
{
int a;
char b;
float c;
}S;
int main()
{
S s1 = { 0 }, s2 = { 0 };//初始化。
return 0;
}
2.2结构体的自引用
说白了就是自己包含自己。
比如,定义一个链表的结点:
struct Node
{
int data;//数据存储
struct Node* next;//下一个节点
};
上述代码正确吗?
如果正确,那sizeof(struct Node)是多少呢?
仔细分析是不对的,因为一个结构体中再包含一个同类型的结构体变量,这样结构体大小就会无穷的大,是不合理的。
正确的自引用方式:
struct Node
{
int data;//数据存储
struct Node* next;//下一个节点的地址
};
另外:
结构体自引用中夹杂了tyoedef对匿名结构体类型重命名,也容易引入问题,如。
typedef struct
{
int data;
Node* next;
}Node;
这段代码正不正确呢?
仔细分析是不对的。为什么?如果对Node重命名的话,再结构体内部就提前使用了Node,所以是不正确的,这就是先有鸡还是先有蛋的问题了。如何改正呢?
定义结构体就不要使用匿名结构体了。
typedef struct Node
{
int data;
struct Node* next;
}Node;
三、结构体大小
为了计算结构体的大小就需要了解结构体对齐。
对齐规则:
1.结构体第一个成员对齐到偏移量为0的地址处。
2.第二个成员变量对齐到第二个成员变量对齐数的整倍数的地址处。
(对齐数=编译器默认的对齐数 和 该成员变量大小的较小值)
VS 默认值为8,Linux中gcc没有默认对齐数,对齐数就是成员自身的大小。
3.所以成员完成了对齐并不是整个结构体的大小,结构体的总大小为所有成员变量最大对齐数的倍数。
4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整倍数处,
结构体的整体大小同3.
了解了对齐规则,练习一下。
先看结构体S1 和 S2.
//练习1
struct S1
{
char c1;
int i;
char c2;
};
//练习2
struct S2
{
char c1;
char c2;
int i;
};
结构体S1,S2的大小为多少呢?是否相等呢?
分析一下:
由分析可得处:
s1 -- 12, s2 -- 8
仅仅因为顺序不同,结构体大小就不同。
再看两个例子:把s3嵌套再s4中。
了解到这,那为什么会存在内存对齐呢?
其实就是:结构体的内存对⻬是拿空间来换取时间的做法。
3.1修改默认对齐数
我们可以通过预处理指令#pragma,修改编译器对齐数。
四、结构体传参
结构体变量.成员名
结构体指针->成员名
如下例子:
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
原因:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销就比较大,所以导致性能下降。
结论:结构体传参的时候,要传结构体的地址。
五、结构体实现位段
位段是基于结构体的。
先看一下结构体的代码:
struct A
{
int _a;
int _b;
int _c;
int _d;
};
int main()
{
printf("%d\n", sizeof(struct A));
return 0;
}
结构体实现位段
位段的成员必须是 int , singed int ,unsigned int , char 类型的。(整形家族里的)
位段后由冒号和一个数字。
再结构体的基础上实现位段:
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
![](https://img-blog.csdnimg.cn/direct/cbb2b3a4f34640c3a1c19bbb9954ed36.png)
![](https://img-blog.csdnimg.cn/direct/708db370907c43fcb290e83de768831e.png)
![](https://img-blog.csdnimg.cn/direct/f54c5137196d4883a9b2a89b9cff616e.png)