C/C++ 入门:结构体

变量只能保存一个值,结构体可以把一组相关数据打包成一个整体。

📚 基本概念速读

名称 定义 省流
结构体 把多个字段组合成一个自定义类型 数据打包
字段 结构体里的成员变量 对象的一部分
成员访问 通过 .-> 访问字段 拿里面的数据
结构体变量 按结构体类型创建出的对象 一个具体数据包
结构体数组 元素类型是结构体的数组 一组数据包
结构体指针 保存结构体对象地址的指针 指向数据包
结构体引用 结构体对象的别名 不拷贝地操作对象
默认访问权限 不写 publicprivate 时的默认规则 struct 默认公开

🧩 结构体的基本模型

结构体的核心作用是:把属于同一个概念的数据放在一起。

比如一个学生有姓名、年龄和分数。单独写变量会变散:

1
2
3
string name = "Alice";
int age = 18;
double score = 92.5;

写成结构体后,这些字段就属于同一个对象:

1
2
3
4
5
struct Student {
string name;
int age;
double score;
};

可以这样理解:

1
2
3
4
5
6
Student
+----------------+
| name = Alice |
| age = 18 |
| score = 92.5 |
+----------------+

结构体让代码从“散落的变量”变成“有名字的数据模型”。



flowchart LR
    A[多个相关变量] --> B[struct 类型]
    B --> C[结构体变量]
    C --> D[通过成员访问读写字段]

🧱 结构体定义

字段

结构体定义的一般形式:

1
2
3
4
struct 类型名 {
字段类型 字段名;
字段类型 字段名;
};

示例:

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <string>
using namespace std;

struct Student {
string name;
int age;
double score;
};

Student 是一个新类型,里面有三个字段:nameagescore

结构体定义末尾的分号不能省略。

初始化

定义好结构体类型后,就可以创建结构体变量。

1
Student s1 = {"Alice", 18, 92.5};

这叫聚合初始化。初始化值会按字段定义顺序依次填进去:

字段 初始化值
name "Alice"
age 18
score 92.5

C++ 里也可以使用 {}

1
Student s2{"Bob", 19, 88.0};

如果想先创建,再逐个赋值,也可以:

1
2
3
4
Student s3;
s3.name = "Carol";
s3.age = 20;
s3.score = 95.0;

初学阶段建议优先使用初始化写法,让对象一创建就处于完整状态。

成员访问

普通结构体变量使用 . 访问成员。

1
2
3
4
5
6
7
8
Student s{"Alice", 18, 92.5};

cout << s.name << endl;
cout << s.age << endl;
cout << s.score << endl;

s.score = 96.0;
cout << s.score << endl;

s.score = 96.0; 的意思是:修改 s 这个学生对象里的 score 字段。

📦 结构体变量与结构体数组

结构体变量

结构体变量和普通变量一样,有类型、有名字、有自己的生命周期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Point {
int x;
int y;
};

int main()
{
Point p{3, 4};

cout << p.x << endl; // 3
cout << p.y << endl; // 4

return 0;
}

这里的 p 是一个 Point 类型变量,它内部有两个整数成员。

结构体之间可以赋值

同类型结构体变量之间可以直接赋值。

1
2
3
4
5
6
7
Point a{1, 2};
Point b{3, 4};

b = a;

cout << b.x << endl; // 1
cout << b.y << endl; // 2

这会把 a 的字段值复制给 b。赋值之后,ab 仍然是两个独立对象。

结构体数组

如果要保存多个同类型对象,可以使用结构体数组。

1
2
3
4
5
6
7
8
9
10
Student students[3] = {
{"Alice", 18, 92.5},
{"Bob", 19, 88.0},
{"Carol", 20, 95.0}
};

for (int i = 0; i < 3; i++) {
cout << students[i].name << " "
<< students[i].score << endl;
}

students[i] 是数组里的一个 Student 对象,所以继续用 . 访问它的字段。

也可以修改数组中某个结构体的成员:

1
students[1].score = 90.0;

这只会修改数组下标为 1 的那个学生。

📍 结构体指针

用指针保存结构体地址

结构体变量也有地址,可以用指针保存。

1
2
Student s{"Alice", 18, 92.5};
Student* p = &s;

这里的 p 保存的是 s 的地址。

-> 成员访问

通过结构体指针访问成员,常用 ->

1
2
3
4
5
cout << p->name << endl;
cout << p->age << endl;

p->score = 96.0;
cout << s.score << endl; // 96

p->score 等价于 (*p).score

1
(*p).score = 97.0;

因为 . 的优先级比 * 高,所以必须写成 (*p).score,不能写成 *p.score

写法 含义
s.score 通过结构体变量访问字段
p->score 通过结构体指针访问字段
(*p).score 先解引用指针,再访问字段

指针传参

结构体指针可以作为函数参数,让函数修改外部结构体对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void updateScore(Student* student, double newScore)
{
if (student != nullptr) {
student->score = newScore;
}
}

int main()
{
Student s{"Alice", 18, 92.5};
updateScore(&s, 96.0);

cout << s.score << endl; // 96
return 0;
}

调用时写 &s,表示把 s 的地址传进去。

空指针风险

结构体指针可以是 nullptr,所以使用前要考虑空指针。

1
2
3
Student* p = nullptr;

// cout << p->name << endl; // 危险:空指针不能访问成员

如果函数参数允许传空指针,就必须检查:

1
2
3
4
5
6
7
8
void printStudent(const Student* student)
{
if (student == nullptr) {
return;
}

cout << student->name << endl;
}

如果函数要求一定传入有效对象,结构体引用通常更直接。

🔗 结构体引用

避免拷贝

结构体可能包含多个字段,值传递会复制整个对象。

1
2
3
4
5
6
void printStudent(Student student)
{
cout << student.name << " "
<< student.age << " "
<< student.score << endl;
}

如果只是读取,建议使用 const 引用:

1
2
3
4
5
6
void printStudent(const Student& student)
{
cout << student.name << " "
<< student.age << " "
<< student.score << endl;
}

const Student& 表示:不拷贝整个结构体,也不会通过这个参数修改实参。

修改成员

如果函数需要修改结构体成员,可以使用普通引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
void updateScore(Student& student, double newScore)
{
student.score = newScore;
}

int main()
{
Student s{"Alice", 18, 92.5};
updateScore(s, 96.0);

cout << s.score << endl; // 96
return 0;
}

引用版本调用时直接传 s,不需要写 &s,也不需要在函数内部检查 nullptr

指针参数和引用参数怎么选

可以先按语义选择:

需求 推荐写法 原因
只读结构体 const Student& 避免拷贝,表达不会修改
必须修改结构体 Student& 直接修改实参,语义清楚
参数可能为空 Student* 可以用 nullptr 表达没有对象
需要和 C 接口交互 Student* C 语言没有引用

如果不存在“没有对象”的情况,引用通常比指针更适合做函数参数。

🆚 struct 与 class

默认访问权限

C++ 里的 structclass 很像,都可以有字段和函数。最重要的默认差异是访问权限。

1
2
3
4
5
6
7
struct StudentStruct {
string name; // 默认 public
};

class StudentClass {
string name; // 默认 private
};

struct 成员默认是 public,外部可以直接访问:

1
2
StudentStruct a;
a.name = "Alice"; // 正确

class 成员默认是 private,外部不能直接访问:

1
2
StudentClass b;
// b.name = "Bob"; // 错误:name 默认是 private

如果显式写访问权限,两者可以写出很接近的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct StudentA {
private:
string name;

public:
void setName(string value)
{
name = value;
}
};

class StudentB {
public:
string name;
};

使用习惯

实际写 C++ 时,一般习惯这样区分:

类型 常见用途
struct 更偏简单数据聚合,字段通常直接公开
class 更偏封装行为和不变量,成员通常受访问控制保护

这不是语法强制要求,而是代码风格和设计习惯。

⚠️ 常见误区

误区 正解
结构体只是 C 语言里的东西 C++ 也有 struct,而且可以有成员函数
结构体变量和字段是同一个东西 结构体变量是整体,字段是整体的一部分
结构体指针可以直接用 . 指针访问成员通常用 ->
p->x*p.x 一样 p->x 等价于 (*p).x
指针传参一定比引用传参好 如果对象必定存在,引用更清楚
structclass 完全不同 C++ 中主要默认访问权限不同

✅ 总结

结构体把一组相关字段组织成一个自定义类型,普通对象用 . 访问成员,指针用 ->,函数传参优先按“是否可空、是否修改、是否避免拷贝”选择指针或引用。

学完结构体后,变量、数组、指针、引用和函数参数就能组合起来表达更真实的数据模型。

Happy Hacking! 🎉