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

C++:结构体(Structure)

目录

第一性原理出发:我们要解决什么问题?

定义结构体(Defining Structures)

问题:名字太长怎么办?

如何定义结构体变量?

结构体的大小(Size of Structures)

初始化结构体(Initialize Structures)

访问结构体成员(Access Structures)

结构体的指针(Pointers to Structures)

如何通过指针访问结构体成员?

👣 分析 (*p).width 的由来:

结构体作为函数参数(Structures as Parameters)

为什么结构体很重要?


第一性原理出发:我们要解决什么问题?

第一性原理是从根本出发,不依赖已有抽象来理解一个问题。

问题:我们如何表达复杂的数据?

在 C 语言里,我们已经知道可以用变量存储数据,比如:

int width = 10;
int height = 5;

这两个变量表示一个矩形的宽和高。但如果我们有多个矩形,就可能会有一堆变量:

int r1_width = 10, r1_height = 5;
int r2_width = 20, r2_height = 10;

 这会让代码变得混乱、不好维护。

💡第一性答案:我们要把相关数据打包成一个“新类型”

我们想要的是:把一个矩形的宽和高“绑定”在一起,像一个单元一样使用它。

这就引出了结构体(Structure)的概念:

结构体是 C 语言提供的一种机制,它允许我们把多个不同或相同类型的数据组合在一起,形成一个“新的数据类型”。


定义结构体(Defining Structures)

我们可以用 struct 关键字定义一个矩形结构体:

struct Rectangle {int width;int height;
};

这段代码做了什么?

  • 它定义了一个新的类型,叫做 struct Rectangle,可以用它创建变量

  • 这个类型有两个成员:

    • width:整型,表示矩形的宽

    • height:整型,表示矩形的高

可以把它理解为一个“结构体工厂”,以后我们可以用它制造一个一个“矩形对象”。

问题:名字太长怎么办?

每次都写 struct Rectangle 很麻烦,所以我们可以使用 typedef定义一个别名,简化使用:

typedef struct Rectangle {int width;int height;
} Rectangle;
  • struct Rectangle 是原始名字

  • Rectangle 是你定义的新别名

这样你以后可以直接写:

Rectangle r1;
//等价于
struct Rectangle r1;

💡更进一步:定义和别名可以合并写成匿名结构体

如果你不打算用 struct Rectangle 这种原始名,可以这样简写:

typedef struct {int width;int height;
} Rectangle;

这也是合法的结构体定义方式,结构体没有“名”,但有了一个别名 Rectangle。 


如何定义结构体变量?

1️⃣ 先定义结构体,再定义变量:

struct Rectangle {int width;int height;
};struct Rectangle r1, r2;

2️⃣ 定义时直接创建变量:

struct Rectangle {int width;int height;
} r1, r2;

 3️⃣ 使用 typedef 简化后:

typedef struct {int width;int height;
} Rectangle;Rectangle r1, r2;

我们可以这样用这个结构体定义实际的矩形:

struct Rectangle r1;
r1.width = 10;
r1.height = 5;

这段代码:

  • 创建了一个结构体变量 r1

  • 给它的 widthheight 赋值

✅现在 r1 是一个“真正的矩形”,有自己的宽和高!

我们可以像这样使用它:

int area = r1.width * r1.height;
printf("Area: %d\n", area);

结构体的大小(Size of Structures)

🔎 问题:一个结构体到底占用多少内存?

我们可以用 sizeof() 运算符来查看:

#include <stdio.h>typedef struct {int width;int height;
} Rectangle;int main() {printf("Size of Rectangle: %lu\n", sizeof(Rectangle));return 0;
}

分析:它的大小是所有成员的大小之和吗?

直觉上你可能以为是:

  • int width:4 字节

  • int height:4 字节

  • 总共:8 字节

 这个答案大多数时候是对的,但是……

🧠现实中会有对齐(Padding)

有时结构体会包含“填充字节”(padding),为了让内存对齐(alignment),提高访问效率。

比如下面这个结构体:

struct Example {char a;int b;
};

你可能以为大小是 1 + 4 = 5 字节,但其实 sizeof(struct Example) 很可能是 8

原因是:

  • char 占 1 字节

  • 为了让 int 对齐到 4 字节位置,会插入 3 个字节的“空白”

  • 所以实际内存布局是:

字节偏移内容
0a
1~3填充
4~7b

如何减少结构体大小?

把小的字段放在一起,有时可以减少对齐浪费:

struct Compact {char a;char b;int c;
};

这样会比 char + int 的组合更紧凑一些。 


初始化结构体(Initialize Structures)

有几种方式可以给结构体变量赋初值。

 方法一:逐个成员赋值

r1.width = 10;
r1.height = 5;

方法二:使用初始化列表

可以直接一次性赋值:

Rectangle r2 = {20, 15};

它会按顺序对应结构体中的成员:第一个是 width,第二个是 height。 

⚠️结构体不能直接比较 

if (r1 == r2) {  // 错误,结构体不能用 == 比较// ...
}

要比较结构体,得自己比较各个字段:

if (r1.width == r2.width && r1.height == r2.height) {// ...
}

访问结构体成员(Access Structures)

访问结构体成员有两种主要方式:

1. 用点运算符 .(用于结构体变量)

Rectangle r1 = {10, 5};
printf("Width: %d\n", r1.width);
printf("Height: %d\n", r1.height);

. 是成员访问运算符,用于访问结构体变量的成员。 

可以理解为:

  • 你有一个“复合数据类型”(结构体)

  • . 告诉编译器:“我要访问这个结构里的某一个字段”

2. 用箭头运算符 ->(用于结构体指针) 

Rectangle *p = &r1;
printf("Width: %d\n", p->width);
printf("Height: %d\n", p->height);

这两个方式的差别在于:

  • . 用于结构体变量本身

  • -> 用于结构体指针

等价的写法也可以是:

(*p).width  // 与 p->width 等价,但不常用

结构体的指针(Pointers to Structures)

为什么我们需要结构体指针?

结构体通常存储多个数据,传递或处理时如果结构体很大(有很多字段),传结构体副本效率低、内存开销大。

所以就像数组、字符串一样,我们经常用“结构体指针”来:

  • 节省内存

  • 修改原始结构体的数据

  • 在函数之间高效传递结构体

如何声明和使用结构体指针?

假设我们有这个结构体:

typedef struct {int width;int height;
} Rectangle;Rectangle r1 = {10, 5};

声明结构体指针,并指向变量 r1:

Rectangle *p = &r1;  // p 是一个指向 Rectangle 的指针
  • p 存储的是 r1 的地址

  • *p 表示通过指针访问 r1


如何通过指针访问结构体成员?

有两种方式:

方式一:箭头运算符 ->

p->width    // 访问 r1 的 width
p->height   // 访问 r1 的 height

这是最常用的方式,代码简洁可读性好。 

方式二:使用 (*p).member 

👣 分析 (*p).width 的由来:

  1. p 是一个结构体指针,指向 r1

  2. *p 就是解引用,表示 r1 本体

  3. (*p).width 的意思是:

  • 先从指针 p 拿到结构体 r1

  • 再取 r1.width

💡 但注意括号一定要加!

如果写成 *p.width 是错误的,解释如下:

  • 运算符优先级中 . 的优先级高于 *

  • 所以 *p.width 实际上是 *(p.width),这表示的是 “p 的成员 width 的值,再解引用”,根本不是我们要的意思!

🔁 所以:正确写法是 (*p).member,等价于:p->member

你可以理解为:p->width   ≡   (*p).width

-> 是语法糖,更方便。


结构体作为函数参数(Structures as Parameters)

在 C/C++ 语言中,结构体可以作为函数的参数传递进去。根据不同的传递方式,它的行为会有非常大的区别。主要有三种方式:

按值传递(Pass by Value)

结构体按值传递时,函数接收到的是结构体的一份副本(copy),修改这个副本不会影响原始结构体。

#include <stdio.h>typedef struct {int width;int height;
} Rectangle;void change(Rectangle r) {r.width = 999;
}int main() {Rectangle r1 = {10, 5};change(r1);printf("r1.width = %d\n", r1.width);  // 输出 10,而不是 999return 0;
}
  • change(r1);r1 拷贝了一份,传给了函数

  • 函数里 r.width = 999; 修改的是副本,不影响 r1 本体

 按指针传递(Pass by Pointer)

把结构体变量的地址传递给函数。函数可以通过这个地址访问并修改结构体的内容。

#include <stdio.h>typedef struct {int width;int height;
} Rectangle;void change(Rectangle *p) {p->width = 999;  // 或 (*p).width = 999;
}int main() {Rectangle r1 = {10, 5};change(&r1);  // 注意传的是地址printf("r1.width = %d\n", r1.width);  //  输出 999return 0;
}
  • change(&r1) 传递结构体的地址

  • p->width = 999; 直接修改原始结构体变量 r1

  • 指针传递效率高,尤其是结构体比较大时非常有用

按引用传递( Pass by Reference)

使用 &,函数接收的是真实结构体的“别名”,不会产生拷贝,函数内部的修改会作用于原始变量

#include <iostream>
using namespace std;struct Rectangle {int width;int height;
};void change(Rectangle &r) {r.width = 999;
}int main() {Rectangle r1 = {10, 5};change(r1);cout << "r1.width: " << r1.width << endl;  // 输出 999 return 0;
}
  • 省去了拷贝,性能高

  • 可以直接修改原始数据

也可以升级为按常量引用传递 (Pass by const Reference)

这是 C++ 最推荐的方式之一,特别是只读访问结构体时

void printArea(const Rectangle &r) {cout << "Area: " << r.width * r.height << endl;
}

为什么结构体很重要?

结构体带来的核心好处是:

  • 组织数据:相关的数据(比如矩形的宽和高)可以组合在一起

  • 可读性强:代码语义更清晰

  • 可扩展性强:以后如果要添加更多属性(比如颜色、边框样式),直接在结构体中加字段就行

例如,我们要扩展 Rectangle 加一个颜色:

struct Rectangle {int width;int height;char color[20];
};

结构体 + 函数 = 更强大!

你甚至可以定义一个函数来计算矩形面积,结构体作为参数传入:

int getArea(struct Rectangle r) {return r.width * r.height;
}int area = getArea(r1);
http://www.lryc.cn/news/605763.html

相关文章:

  • 2025虚幻5光明之魂开发思考1——借鉴软件工程
  • React Filber及核心原理
  • 以AI大模型重构教育新生态,打造“教-学-练-辅-评”一体化智能平台
  • 澳交所技术重构窗口开启,中资科技企业如何破局?——从ASX清算系统转型看跨境金融基础设施的赋能路径
  • matlab - 算4个数的加减法
  • [mind-elixir]Mind-Elixir 的交互增强:单击、双击与鼠标 Hover 功能实现
  • 协同测试总结(电台/WIFI/ID/固定端口设置和开机自启)
  • CentOS 6.10 上安装 GCC 7+
  • PHP 与 MySQL 详解实战入门(1)
  • PHP 5.5 Action Management with Parameters (English Version)
  • 通义千问Qwen3-30B-A3B-Thinking-2507技术解析:推理模型的工程实践突破
  • 常见的中间件漏洞如tomcat,weblogic,jboss,apache靶场攻略
  • 基于瑞芯微SoC的产品开发流程详解
  • 18650圆柱电池自动面垫机:自动化生产的效率革命
  • 人工智能之数学基础:频率和概率之间的关系
  • Java项目:基于SSM框架实现的小区物业管理系统【ssm+B/S架构+源码+数据库+毕业论文+开题报告+任务书+远程部署】
  • JS常见问题
  • BatchNorm 一般放在哪里?
  • InfluxDB 与 Python 框架结合:Django 应用案例(二)
  • DoRA详解:从LoRA到权重分解的进化
  • 小杰数据结构(three day)——静以修身,俭以养德。
  • 【Linux系统】库的制作与原理
  • 【数据结构】算法代码
  • 渗透RCE
  • TS 常用类型与语法
  • Cesium 快速入门(六)实体类型介绍
  • Jmeter 性能测试常用图表、服务器资源监控
  • C语言指针(三):数组传参本质、冒泡排序与二级指针详解
  • FISCO BCOS Gin调用WeBASE-Front接口发请求
  • [硬件电路-111]:滤波的分类:模拟滤波与数字滤波; 无源滤波与有源滤波;低通、带通、带阻、高通滤波;时域滤波与频域滤波;低价滤波与高阶滤波。