【C初阶】自定义类型--结构体

目录

[1. 结构体类型的声明](#1. 结构体类型的声明)

[1.1 结构体回顾](#1.1 结构体回顾)

[1.2 结构的特殊声明](#1.2 结构的特殊声明)

[1.3 结构的自引用](#1.3 结构的自引用)

[2. 结构体的内存对齐](#2. 结构体的内存对齐)

[2.1 对齐规则](#2.1 对齐规则)

[2.2 为什么要存在内存对齐?](#2.2 为什么要存在内存对齐?)

[3. 结构体传参](#3. 结构体传参)

[4. 结构体实现位段](#4. 结构体实现位段)

[4.1 什么是位段](#4.1 什么是位段)

[4.2 位段的内存分配](#4.2 位段的内存分配)

[4.3 位段的跨平台问题](#4.3 位段的跨平台问题)

[4.4 位段的应用](#4.4 位段的应用)


1. 结构体类型的声明

1.1 结构体回顾

我们之前就或多或少的学习了结构体的相关知识。现在,我们先来回顾一下:

结构体的声明

代码块

struct tag

{

member - list;

}variable-list;

比如说描述一个学生:

复制代码
// 描述一个学生
struct stu
{
	char name[40]; // 名字
	int age;       // 年龄
	char sex[10];  // 性别
	char id[11];   // 学号
}; // 这里的分号不能丢

结构体变量的初始化:

复制代码
// 描述一个学生
struct stu
{
	char name[40]; // 名字
	int age;       // 年龄
	char sex[10];  // 性别
	char id[11];   // 学号
}; 
int main()
{
	struct stu s1 = { "zhangsan",21,"man","23405010211" };
	struct stu s2 = { .name = "lisi",.age = 18, .id = "23405010101", .sex = "man" };
	return 0;
}

1.2 结构的特殊声明

在声明结构体的时候,可以不完全声明。

例如:

复制代码
// 匿名结构体 -- 只能使用一次
struct
{
	int a;
	char b;
	float c;
}x,y,z;
struct
{
	int a;
	char b;
	float c;
}*p;
int main()
{
	p = &x;
	return 0;
}

这个代码运行的时候是会报警告的,因为匿名结构体只能使用一次。

1.3 结构的自引用

在一个结构体中包含一个类型为该结构体本身的成员是否可以呢?可以的。

复制代码
struct Node
{
	int data;
	struct Node;
};

你们看段代码对不对。答案是不对的,这样子写,我们无法确定这个结构体占据多少字节,而且它会一直递归下去是死递归。那么,如何写才是对的,没错就是使用指针。

复制代码
struct Node
{
	int data;
	struct Node* count;
};

这样子就是正确的。

我们在书中会看到结构体的重命名。

复制代码
typedef struct Node
{
	int data;
	struct Node* count;
}Node;

这段代码相当于把结构体重命名为Node,使用起来更加方便。

2. 结构体的内存对齐

我们已经初步了解了结构体,接下来我们计算一下结构体的大小。

一个示例:

从代码码运行的结果来看,和我们的预算值不一样,这就要学习一下结构体的内存对齐。

2.1 对齐规则

首先得掌握结构体的对齐规则

  1. 结构体的第1个成员对齐到和结构体变量起始位置偏移量为0的地址处。
  2. 从第2个成员变量开始,都要对齐到某个对齐数的整数倍地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员变量大小的较小值。 VS的默认值为8 Linux中gcc没有默认对齐数,对齐数就是成员自身的大小
  3. 结构体总大小为最大对齐数的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所以最大对齐数的整数倍。

我们在举例子之前先了解一个计算偏移量的宏:offsetof

我们就计算一下上面代码中S1的各成员偏移量。

接下来我们说说这是怎么来的,根据对齐规则。

我们就可以得到结构体S1在内存中如何存储,中间的空白就相当于浪费了。

现在,我们明白了如何储存,根据对齐规则趁热打铁对S2分析作为练习。

中间空白空间仍然是被浪费了。

2.2 为什么要存在内存对齐?

  1. 平台原因(移植原因)

不是所有的硬件平台都能访问任意地址上的任意数据的;某些平台只能在某些地址处取某些特定类型的数据,否则会出现异常。

  1. 性能原因

数据结构(尤其是栈)应该尽可能的在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

总而言之:结构体的内存对齐是拿空间来换取时间的做法。

那么,在设计结构体的时候,我们急要满足对齐,又要节省空间,该怎么做?让占用空间小的成员尽量集中在一起。

3. 结构体传参

我们了解了结构体在内存中的存储和结构体的取出,现在我们来使用一下结构体。

复制代码
// 结构体传参
struct S
{
	int data[1000];
	int num;
};

struct S s = { {1,2,3,4},1000 };

// 结构体传参
void print1(struct S t)
{
	printf("print1: %d\n", t.num);
}

// 结构体地址传参
void print2(struct S* ps)
{
	printf("print2: %d\n", ps->num);
}

int main()
{
	print1(s); // 传结构体 传值调用
	print2(&s);// 传地址 传址调用
}

我们在上面的代码中使用了两种传参方式,哪一种更好呢?第二种。

因为:函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,会导致性能的下降。

总而言之:结构体传参的时候,要传结构体的地址。

4. 结构体实现位段

结构体的知识了解完之后,我们就可以通过结构体实现位段了。

4.1 什么是位段

位段和结构体的声明类似,有两个不同:

1.位段成员必须是int、unsigned int或signed int。

2.位段的成员名后边有一个冒号和一个数字。

代码演示

复制代码
struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

A就是一个位段类型。那么它占据多大的空间呢?

会和结构体一样吗? -- 答案是不一样。

4.2 位段的内存分配

  1. 位段的成员可以是int、unsigned int、signedint或者char类型

  2. 位段的空间上是按照需要以4个字节或者1个字节的方式来开辟的

  3. 位段设计很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

这里我就不过多介绍了。

4.3 位段的跨平台问题

  1. int位段当成有符号还是无符号是不确定的

2.位段中最大数目不确定。

  1. 位段中的成员在内存中从左向右还是从右向左分配不确定,标准是未定义的。

  2. 空间的利用是不稳定的。

位段可以节省空间,但是不稳定因素有点多。

4.4 位段的应用

我们在知晓每个成员占据多少位的时候,我们就可以设计位段。就比如网络传输,有的传输的内容大,有的小,特别小的我们就可以使用位段。

相关推荐
口袋物联17 小时前
设计模式之工厂模式在 C 语言中的应用(含 Linux 内核实例)
linux·c语言·设计模式·简单工厂模式
Want59519 小时前
C/C++跳动的爱心①
c语言·开发语言·c++
lingggggaaaa19 小时前
免杀对抗——C2远控篇&C&C++&DLL注入&过内存核晶&镂空新增&白加黑链&签名程序劫持
c语言·c++·学习·安全·网络安全·免杀对抗
gfdhy20 小时前
【c++】哈希算法深度解析:实现、核心作用与工业级应用
c语言·开发语言·c++·算法·密码学·哈希算法·哈希
我不会插花弄玉20 小时前
vs2022调试基础篇【由浅入深-C语言】
c语言
福尔摩斯张21 小时前
《C 语言指针从入门到精通:全面笔记 + 实战习题深度解析》(超详细)
linux·运维·服务器·c语言·开发语言·c++·算法
fashion 道格21 小时前
数据结构实战:深入理解队列的链式结构与实现
c语言·数据结构
铁手飞鹰1 天前
二叉树(C语言,手撕)
c语言·数据结构·算法·二叉树·深度优先·广度优先
[J] 一坚1 天前
深入浅出理解冒泡、插入排序和归并、快速排序递归调用过程
c语言·数据结构·算法·排序算法
散峰而望1 天前
C++数组(一)(算法竞赛)
c语言·开发语言·c++·算法·github