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
-
给它的
width
和height
赋值
✅现在 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 个字节的“空白” -
所以实际内存布局是:
字节偏移 | 内容 |
---|---|
0 | a |
1~3 | 填充 |
4~7 | b |
如何减少结构体大小?
把小的字段放在一起,有时可以减少对齐浪费:
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
的由来:
-
p
是一个结构体指针,指向r1
-
*p
就是解引用,表示r1
本体 -
(*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);