当前位置: 首页 > news >正文

11.结构体

typedef

typedef 可以让你为现有的类型创建一个别名。例如,如果你想简化指针类型的声明:

typedef unsigned long ulong;

这样,你可以使用 ulong 来代替 unsigned long

对于复杂的类型,如函数指针,typedef 也能大大简化声明:

假设我们有一个函数,它接受两个 int 参数并返回一个 int

int add(int a, int b) {return a + b;
}

我们可以使用 typedef 来定义一个指向这种类型函数的指针类型:

// 定义一个名为 FuncPtr 的类型,它是指向函数的指针
// 该函数接受两个 int 参数,返回一个 int
typedef int (*FuncPtr)(int, int);// 现在可以像使用普通类型一样声明函数指针变量
FuncPtr ptr = add;  // ptr 指向 add 函数// 调用函数
int result = ptr(3, 4);  // result = 7

这使得声明比较函数指针更加直观。

结构体 (struct)

结构体是一种用户自定义的数据类型,它可以包含多个不同类型的变量。每个变量称为结构体的一个成员。

定义和声明

struct Person {char name[50];int age;float height;
};// 声明结构体变量
struct Person person1;

为了简化结构体变量的声明,可以使用 typedef

typedef struct {char name[50];int age;float height;
} Person;Person person1;

访问结构体成员

使用点运算符 . 来访问结构体成员:

strcpy(person1.name, "John");
person1.age = 30;
person1.height = 1.75f;

如果是指向结构体的指针,则使用箭头运算符 ->

Person *ptr = &person1;
ptr->age = 31; // 修改年龄

结构体成员的内存布局

  1. 字节对齐(Alignment):大多数处理器要求数据按特定的字节边界对齐,这意味着结构体中的每个成员都会被放置在一个地址上,这个地址是其自身大小或处理器架构规定的最小对齐值的倍数。例如,一个4字节的整数可能需要4字节对齐。

  2. 填充(Padding):为了满足上述对齐要求,在结构体成员之间或末尾可能会插入一些未使用的字节,这被称为填充。这些填充字节确保了后续成员能够正确地对齐。

  3. 总大小:结构体的总大小不仅取决于其成员的大小,还包括由于对齐要求而添加的任何填充字节。

示例

考虑以下结构体定义:

struct Example {char a;     // 1 byteint b;      // 4 bytes (假设为32位系统)short c;    // 2 bytes
};printf("%d\n", sizeof(Example)  ); // 输出12
  • 内存布局:
    1. a 放在偏移 0 处,占用 1 字节。
    2. b 需要 4 字节对齐。下一个 4 字节对齐的位置是偏移 4。因此,在 a (偏移0) 和 b (偏移4) 之间需要 3 字节填充
    3. b 占用偏移 4, 5, 6, 7。
    4. c 需要 2 字节对齐。偏移 8 是 2 字节对齐的,所以 c 可以放在偏移 8 处,占用 2 字节 (偏移 8, 9)。
    5. 结构体总大小需要是其最大成员对齐要求(4 字节)的倍数。当前总大小是 10 字节。下一个 4 字节对齐的大小是 12。因此,在末尾添加 2 字节填充
  • 总大小: 1 (a) + 3 (填充) + 4 (b) + 2 (c) + 2 (末尾填充) = 12 字节

情况二:重新排序 (b, c, a)

struct Example {int b;      // 4 bytesshort c;    // 2 byteschar a;     // 1 byte
};
  • 内存布局:
    1. b 放在偏移 0 处(4 字节对齐),占用 4 字节 (0-3)。
    2. c 需要 2 字节对齐。偏移 4 是 2 字节对齐的,所以 c 放在偏移 4 处,占用 2 字节 (4-5)。
    3. a 只需要 1 字节对齐。偏移 6 是 1 字节对齐的,所以 a 放在偏移 6 处,占用 1 字节 (6)。
    4. 当前总大小是 7 字节。需要是最大成员对齐要求(4 字节)的倍数。下一个 4 字节对齐的大小是 8。因此,在末尾添加 1 字节填充
  • 总大小: 4 (b) + 2 (c) + 1 (a) + 1 (末尾填充) = 8 字节

结论: 通过将 int b (4字节) 放在最前面,可以避免在 char aint b 之间产生大的填充。无论 short cchar a 的顺序如何,只要 int b 在最前面,最终大小都可以优化到 8 字节。这比原始的 12 字节节省了 4 字节(33%)的空间。

最佳实践:在定义结构体时,尽量将成员按大小从大到小排序(int, short, char),这样可以最大限度地减少填充字节,节省内存。

 扩展

标准 C 语言中,结构体(struct)内部不能直接定义函数

C 语言的结构体(struct)是一种数据聚合类型,它的主要作用是将不同类型的数据(如 int, char, float 等)组合在一起,形成一个复合的数据结构。它本身不包含行为(即函数)。


在结构体中存储函数指针来模拟现在对象调用方法

在结构体中存储函数指针,将函数的地址赋给指针,从而实现类似“结构体有函数”的效果。这是一种在 C 语言中模拟“对象”和“方法”的常用技巧。

示例:
#include <stdio.h>// 定义一个结构体
struct Calculator {int value;// 在结构体中声明函数指针void (*add)(struct Calculator*, int);void (*print)(struct Calculator*);
};// 定义函数
void calc_add(struct Calculator* calc, int num) {calc->value += num;
}void calc_print(struct Calculator* calc) {printf("Current value: %d\n", calc->value);
}int main() {struct Calculator calc = {0, calc_add, calc_print};calc.add(&calc, 10);calc.print(&calc);  // 输出: Current value: 10calc.add(&calc, 5);calc.print(&calc);  // 输出: Current value: 15return 0;
}

在这个例子中:

  • struct Calculator 包含一个数据成员 value 和两个函数指针成员 add 和 print
  • 我们将实际函数的地址赋给了这些指针。
  • 通过结构体变量调用 calc.add(&calc, 10),看起来就像是在调用一个方法(虽然需要手动传入 this 指针)。

 为什么不能直接写函数?

C 语言的设计哲学是过程式和面向过程的。函数是独立于数据的。结构体纯粹用于组织数据。这与 C++ 等面向对象语言有本质区别。


 注意:C++ 中的结构体

如果你使用的是 C++,那么情况完全不同!在 C++ 中:

  • struct 和 class 非常相似。
  • 结构体(struct)内部可以定义函数(成员函数)、构造函数、析构函数等
  • C++ 的 struct 默认成员是 public 的。
// 这是 C++ 代码!
struct Calculator {int value;// C++ 中允许在 struct 内定义函数void add(int num) {value += num;}void print() {printf("Current value: %d\n", value);}
};

 

语言结构体能否包含函数
C 语言❌ 不行。但可以用函数指针成员来模拟。
C++ 语言✅ 可以。struct 可以包含成员函数,用法几乎和 class 一样。

所以,在纯 C 语言环境下,记住:结构体里不能写函数,但可以写指向函数的指针

http://www.lryc.cn/news/605858.html

相关文章:

  • 项目中如何定义项目范围
  • Python:如何从地球大数据科学服务中心批量下载VPM-GPP?
  • 《Java 程序设计》第 17 章 - 并发编程基础
  • Ceph、K8s、CSI、PVC、PV 深入详解
  • ros2 tf2详解
  • 从 0 到 1:PHP 基础到就业教程指南(附教程资料)
  • ceph sc 设置文件系统格式化参数
  • Python 程序设计讲义(48):组合数据类型——字典类型:字典的常用操作
  • 商旅平台怎么选?如何规避商旅流程中的违规风险?
  • 云原生技术创新中的安全和合规问题有哪些解决方案?
  • Java客户端连接Redis
  • 《计算机“十万个为什么”》之 [特殊字符] 字符集:数字世界的文字密码本 [特殊字符]️
  • OpenCV 中的「通道」(Channel)详解
  • Windows 安全中心是什么?如何关闭 Windows 11 的安全中心
  • centos下安装anaconda
  • Traccar:开源GPS追踪系统的核心价值与技术全景
  • VuePress 使用详解
  • 【Coze Studio代码分析】开源多智能体应用开发平台原理与实践
  • 技术分享 | 悬镜亮相于“2025开放原子开源生态大会软件物料清单(SBOM)”分论坛
  • 「源力觉醒 创作者计划」开源大模型重构数智文明新范式
  • 前端 vue 第三方工具包详解-小白版
  • 「源力觉醒 创作者计划」破局与重构:文心大模型开源的产业变革密码
  • 前端开发(HTML,CSS,VUE,JS)从入门到精通!第一天(HTML5)
  • [论文阅读] 人工智能 + 软件工程 | KnowledgeMind:基于MCTS的微服务故障定位新方案——告别LLM幻觉,提升根因分析准确率
  • MLIR TableGen
  • SpringAI:AI工程应用框架新选择
  • 第三十篇:AI的“思考引擎”:神经网络、损失与优化器的核心机制【总结前面2】
  • 嵌入式系统常用架构
  • 使用iptables封禁恶意ip异常请求
  • Kubernetes架构概览