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.pswp.cn/bicheng/91462.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/91462.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

化学结构式解读指南:从基础认知到InDraw智能识别

中文名称&#xff1a;3-[2-(二甲基氨基)乙基]-1H-吲哚英文名称&#xff1a;3-[2-(dimethylamino)ethyl]-1H-indole分子式: C12H16N2分子量: 188.2740这是什么结构式&#xff1f;怎么绘制呢&#xff1f;可以用InDraw里的AI图像识别这个结构式&#xff0c;也可以手动绘图&#xf…

如何使用一台电脑adb调试多个Android设备

目录 一、临时断开其中一个设备连接 二、指定调试设备 总结 当我们使用Android调试工具调试多个设备&#xff0c;例如一开始使用adb连接了一台Android真机进行调试&#xff0c;此时又在Android studio中打开了一个模拟机&#xff0c;此时我们在adb命令窗口中使用adb命令的…

ChatGPT的下一站:从“答案引擎”到“思维教练”

摘要&#xff1a;我们正处在一个“万物皆可ChatGPT”的时代&#xff0c;但当它沦为最高效的“代码搬运工”和“作业速成器”时&#xff0c;我们得到的究竟是效率的提升还是思维的退化&#xff1f;本文深入探讨一个引人深思的概念——“导师模式”的AI。它不再直接提供答案&…

SpringBoot集成Flyway

SpringBoot集成Flyway_springboot flyway-CSDN博客 Flyway 本质上是一个开源的数据库迁移工具&#xff0c;它能够以自动化、可重复且可靠的方式管理数据库的变更。无论是小型项目还是大型企业级应用&#xff0c;Flyway 都能助力开发者轻松应对数据库架构的演进。它支持多种数据…

【实时Linux实战系列】实时图像处理应用开发

在当今快速发展的技术领域&#xff0c;实时图像处理应用在众多领域发挥着至关重要的作用。从自动驾驶汽车、工业自动化检测到医疗影像诊断&#xff0c;实时图像处理技术的应用场景无处不在。通过在实时Linux系统中开发图像处理应用&#xff0c;开发者能够充分利用Linux的稳定性…

Caterpillar Fungus Optimizer, CFO

核心算法解析1. 算法框架与初始化class EnhancedCFO: def __init__(self, objective_func, dim10, pop_size30, max_iter200, lb-10, ub10):​​改进点​​&#xff1a;针对传统优化算法后期易停滞的问题&#xff0c;结合了精英策略、多样性控制和自适应参数​​关键特性​​&a…

c++设计模式编程练习

一、运用观察者模式原理编写鸟类模型运行结果&#xff1a;二、运用简单工厂模式编写打怪掉装备模型运行结果

FastMCP本地构建Server和Clinet交互

1. MCP Server介绍 MCP Server 是实现模型上下文协议&#xff08;MCP&#xff09;的服务器&#xff0c;旨在为 AI 模型提供一个标准化接口&#xff0c;连接外部数据源和工具&#xff0c;例如文件系统、数据库或 API。 相比之下&#xff0c;在MCP出现前&#xff0c;AI调用工具…

工业企业与清洁生产匹配数据库(1998-2015年)

1484工业企业与清洁生产匹配数据库&#xff08;1998-2015年&#xff09;“清洁生产”近年发文趋势及主题分布数据来源中华人民共和国生态环境部以及中国工业企业数据库&#xff0c;由数据皮皮侠团队整理时间跨度1998-2015年数据范围各工业企业数据指标参考文献孙博文,郑世林.环…

第13届蓝桥杯C++青少组中/高级组选拔赛2022年1月22日真题

第13届蓝桥杯C青少组中/高级组选拔赛2022年1月22日真题 更多内容请查看网站&#xff1a;【试卷中心 -----> 蓝桥杯----> C ----> 选拔赛】 网站链接 青少年软件编程历年真题模拟题实时更新 编程题 第 1 题 比大小 题目描述&#xff1a; 给出两个不同的整数&#…

从0到1学PHP(七):PHP 与 HTML 表单:实现数据交互

目录一、表单的创建与提交方式1.1 HTML 表单的基本结构1.2 GET 和 POST 提交方式的区别及适用场景二、表单数据的接收与处理2.1 使用\$_GET、\$_POST 超全局变量获取表单数据2.2 对接收的数据进行验证三、表单安全处理3.1 防止 XSS 攻击的方法3.2 防止 CSRF 攻击的措施一、表单…

Docker compose和Docker-compose的区别

Docker Compose 的两个命令形式 docker compose&#xff08;空格连接&#xff09;与 docker-compose&#xff08;短横线连接&#xff09;核心区别如下&#xff1a;一、技术本质docker-compose&#xff08;短横线&#xff09;独立可执行文件&#xff1a;早期实现方式&#xff0c…

自定心深凹槽参数检测装置及检测方法 - 激光频率梳 3D 轮廓检测

一、引言在机械零件深凹槽检测中&#xff0c;传统方法常因定心不准导致检测误差。如平台推表检测时零件基准面与测量平台难以精准对齐&#xff0c;三坐标测量需人工找正&#xff0c;效率低且误差大。激光频率梳 3D 轮廓检测虽精度高&#xff0c;但缺乏自定心机制会影响深凹槽轴…

C语言---结构体(格式、用法、嵌套、初始化)、共用体、枚举类型、typedef类型

目录 结构体与共用体 1、结构体(struct) (1) 格式与用法 (2) 结构体允许嵌套 (3) 结构体成员初始化 (4) 指针替换变量 (5) 求结构体在内存空间所占字节 2、共用体(union) (1) 格式与概念 (2) 应用 3、枚举类型(enum) (1) 格式与概念 (2) 应用 4、typedef 类型 结构体与共用…

辐射源定位方法简述

文章目录 前言 一、按照信息建模分类 1.1.时间参数 1.1.1.到达时间&#xff08;TOA, Time of Arrival&#xff09;定位 1.1.2.到达时间差&#xff08;TDOA, Time Difference of Arrival&#xff09;定位 1.2.角度参数 1.2.1.到达角度&#xff08;AOA, Angle of Arrival&a…

CamX-设置SceneMode:CONTROL_SCENE_MODE_FACE_PRIORITY不生效问题解决

应用设置CONTROL_SCENE_MODE_FACE_PRIORITY设置不生效 问题&#xff1a;app 代码 CaptureRequest.Builder captureRequestBuilder mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);builder.set(CaptureRequest.CONTROL_SCENE_MODE, CameraMetadata.CONTRO…

MFC CChartCtrl编程

重点&#xff1a;创建CChartCtrl控件有2种方式1、直接创建CChartCtrl2、窗体上添加Custom Control&#xff08;切记一定不能是Static Text或者Picture Control&#xff0c;否则无法响应鼠标消息&#xff09;&#xff0c;然后根据ID关联CChartCtrl控件&#xff0c;初始化代码如下…

从0到1了解热部署

热部署&#xff08;Hot Deployment&#xff09;是软件开发中一种提高开发效率的技术&#xff0c;指的是在应用程序不停止运行的情况下&#xff0c;动态更新代码、配置或资源&#xff08;如页面、图片等&#xff09;&#xff0c;并让这些修改立即生效的过程。热部署主要用于开发…

[12月考试] E

[12月考试] E 题目描述 给定 nnn 个正整数 a1,a2,…,ana_1,a_2,\ldots,a_na1​,a2​,…,an​&#xff0c;小 E 可以进行若干次交换&#xff0c;每一次可以交换两个相邻的整数。 求小 E 至少要交换多少次&#xff0c;才可以让 a1a_1a1​ 是 nnn 个数里的最小值&#xff0c;ana_n…

kmp复习,需要多看多练

151. 反转字符串中的单词 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:string reverseWords(string s) {int n s.size();int i n - 1;string res;while(i > 0){while(i > 0 && s[i] ) i--;if(i < 0) break;int j i;while(i > …