跳到主要内容

结构体

在本课程中,您将学习如何创建由许多较小元素组成的数据类型——在 C++ 中我们称之为结构体。

动机

Goblin enemy presentation
小妖精图片由LuizMelo提供

例如,在创建游戏🎮时,如果我们想在程序中加入对手,我们通常需要记录关于每个对手的一些信息。

思考一下游戏中关于敌人哪些数据可能有用?例如,可以是:

  • 名称 👾
  • 生命值 💚
  • 力量 💪

等等。

利用我们目前所学的知识,如果我们要编写一个存储这些信息的程序,我们可以这样做:

#include <string>

int main() {
std::string enemy_name = "Goblin";
float enemy_health = 50;
float enemy_strength = 12;
// ...
}

当我们想在游戏中有更多的对手时,我们会遇到一个问题,或者说不便。

如果我们为此目的使用多个数组

std::vector< std::string >	enemy_names;
std::vector< float > enemy_health;
std::vector< float > enemy_strength;

那么每个对手将在这些表中由相同的索引描述

  • enemy_names[ index ] 描述名称
  • enemy_health[ index ] 描述生命值
  • enemy_strength[ index ] 描述力量值
重要

这种方法涉及将单个对手的信息“分散”到多个表格中。

在这样的程序中,向一个集合添加一个敌人会是这样:

enemy_names.push_back("Goblin");
enemy_health.push_back(50);
enemy_strength.push_back(10);

我们想要存储的关于对手的信息越多,就会越麻烦。幸运的是,**结构体**在这里为我们提供了帮助。

创建结构体

让我们回顾一下我们需要存储哪些数据:

  • 名称 👾
  • 生命值 💚
  • 力量 💪

我们即将添加一个允许我们创建一个包含这三项内容的对象的结构体。

#include <string>

struct Enemy
{
std::string name;
float health;
float strength;
};

int main()
{
// We leave it empty for now
}

上面的代码引入了一个新的**结构体**——`Enemy`。

记住

一个**结构体**是一种描述,一种模式,一个创建对象(在本例中为敌人)的配方。

要创建一个结构体,我们在 `struct` 关键字后面写上它的名称,然后将其内容放在花括号 `{` 和 `}` 之间。

内容可以是变量。

分号

请注意花括号后**必须**有分号来结束结构体定义

struct Enemy
{
std::string name;
float health;
float strength;
};

对象

下面是如何创建一个使用 `Enemy` 结构体(被视为创建对象的公式)的对象:

// prism-push-types:Enemy
int main()
{
Enemy boss;
}

因此,我们将这三个字段(`name`、`health` 和 `strength`)都包含在一个变量 `boss` 中。

命名

从现在开始,我们将说 `boss` 是 `Enemy` 类型的一个对象。这意味着它是使用 `Enemy` 结构体作为公式创建的。

访问字段

如上所述,`boss` 包含 3 个事物(字段),即它由三个变量组成。要访问此对象的特定成员,我们需要使用以下表示法:

将boss的名称设置为“Ogre”
boss.name = "Ogre";

我们使用点 `.` 来引用对象的字段。同样,我们也可以修改敌人的**力量**。

修改对象的字段
boss.strength	= 50; // I set bosses strength to 50

// Boss enables "fury" skill - strength increases
// Health decreases by a half
boss.strength += 25;
boss.health *= 0.5f;

...或显示其信息

使用对象字段
#include <iostream>
#include <string>

struct Enemy
{
std::string name;
float health;
float strength;
};

int main()
{
// I create boss object
Enemy boss;
// I assign values to its fields
boss.name = "Ogre";
boss.health = 250;
boss.strength = 50;

std::cout << boss.name << " has "
<< boss.health << " hp and "
<< boss.strength << " strength."
<< std::endl;
}

将对象传递给函数

没有任何限制阻止您创建一个将特定结构体的对象作为参数的函数。一个很好的例子就是显示敌人信息:

一个显示对手信息的函数
void print_enemy_info(Enemy enemy)
{
std::cout << enemy.name << " has "
<< enemy.health << " hp and "
<< enemy.strength << " strength."
<< std::endl;
}
顺序

print_enemy_info 要求类型 `Enemy` 在函数本身定义**之前**存在。这意味着我们需要将函数放在结构体创建的下方(参见下面的示例)。

利用以上信息,我们将创建一个包含两个对手的“游戏”:

  • 普通敌人 👹
    **哥布林战士**,生命值 `60`,力量 `14`

  • boss 💀
    **食人魔**,生命值 `250`,力量 `50`

包含食人魔和哥布林的游戏代码片段
#include <iostream>
#include <string>

// Creating struct
struct Enemy
{
std::string name;
float health;
float strength;
};

// Function that display information about an enemy
void print_enemy_info(Enemy enemy)
{
std::cout << enemy.name << " has "
<< enemy.health << " hp and "
<< enemy.strength << " strength."
<< std::endl;
}

// The main function
int main()
{
// I create boss and goblin objects
Enemy boss;
Enemy goblin;

// I setup goblin object fields
goblin.name = "Goblin warrior";
goblin.health = 60;
goblin.strength = 14;

// I setup boss object fields
boss.name = "Ogre";
boss.health = 250;
boss.strength = 50;

// and print information about them
print_enemy_info(goblin);
print_enemy_info(boss);
}

数组中的对象

我们可以像普通变量一样将对象放入数组中

敌人向量
std::vector< Enemy > enemies;

下面是如何向这样的数组添加元素的示例

在向量中添加对象
// ...

int main()
{
std::vector< Enemy > enemies;

// (optional)
// A code block to limit the scope
// of local variables inside
{
// I create goblin variable 👉 locally 👈
Enemy goblin;
// I setup the fields
goblin.name = "Goblin warrior";
goblin.health = 60;
goblin.strength = 14;

// I add the object to the vector
enemies.push_back( goblin );
}
// 👈 from this moment, goblin exists ONLY inside the vector

// Print every enemy in the vector
for (Enemy enemy : enemies)
print_enemy_info(enemy);
}
示例

阅读本课后,请回顾此示例程序:👾 战斗竞技场及其概述。在那里您将看到数组和结构体在实际中是如何使用的。

字段默认值

我们可以给结构体元素设定默认值,这样就不必每次都去填充它们了。

一个很好的使用默认值的例子是存储对手造成的总伤害量的变量。首先,对于每个敌人,这个值都必须等于 `0`。

字段值

如果你将结构体字段留空,没有默认值,例如:

struct Car
{
int number_of_wheels;
};

这并不意味着 `number_of_wheels` 在对象创建时会被设置为 `0`,你必须**手动**完成!

要为结构体字段赋值,我们使用与创建变量时相同的常规初始化方法:

“total_damage”的默认值 ⚔
// Creating the structure
struct Enemy
{
std::string name;
float health;
float strength;

float total_damage = 0;
};

现在当我们创建一些敌人时

Enemy snake; // snake as an example

那么它的 `total_damage` 字段的值

snake.total_damage

将自动设置为 `0`。

您可以通过打印它来确认这一点,例如:

int main() {
Enemy snake;
snake.name = "Snake";
// 🟡 Note, I'm not setting "total_damage" manually

std::cout << snake.name
<< " has dealt "
<< snake.total_damage
<< " total damage";
}
结果
Snake has dealt 0 total damage

潜在错误

重要

本节需要改进。您可以通过编辑此文档页面来帮助我们。

以下是与本课相关的常见错误列表:

定义后没有分号

仅供参考。

无效顺序

确保结构体在首次使用**之前**定义。

**错误**代码示例

🔴 顺序不正确
// ❌ Error: usage of type "Enemy" before its definition
void print_enemy_info(Enemy enemy)
{
std::cout << enemy.name << " has "
<< enemy.health << " hp and "
<< enemy.strength << " strength."
<< std::endl;
}

// Creating the structure
struct Enemy
{
std::string name;
float health;
float strength;
};
提示

这个问题可以通过另一种更方便的方式解决,而不是将 `print_enemy_info` 函数移动到结构体定义下方,即所谓的*前向声明*。我们将在课程后面提到它。

在结构体定义内部修改

变量**不能**在结构体定义内部修改。只允许赋值初始值。

🔴 尝试在错误的位置修改字段
struct Enemy
{
std::string name;
float health;
float strength;

int total_damage = 0; // OK ✅

// ❌ Error: Attempt to modify field in a wrong place
health = 250;
};

结构体

在本课程中,您将学习如何创建由许多较小元素组成的数据类型——在 C++ 中我们称之为结构体。

动机

Goblin enemy presentation
小妖精图片由LuizMelo提供

例如,在创建游戏🎮时,如果我们想在程序中加入对手,我们通常需要记录关于每个对手的一些信息。

思考一下游戏中关于敌人哪些数据可能有用?例如,可以是:

  • 名称 👾
  • 生命值 💚
  • 力量 💪

等等。

利用我们目前所学的知识,如果我们要编写一个存储这些信息的程序,我们可以这样做:

#include <string>

int main() {
std::string enemy_name = "Goblin";
float enemy_health = 50;
float enemy_strength = 12;
// ...
}

当我们想在游戏中有更多的对手时,我们会遇到一个问题,或者说不便。

如果我们为此目的使用多个数组

std::vector< std::string >	enemy_names;
std::vector< float > enemy_health;
std::vector< float > enemy_strength;

那么每个对手将在这些表中由相同的索引描述

  • enemy_names[ index ] 描述名称
  • enemy_health[ index ] 描述生命值
  • enemy_strength[ index ] 描述力量值
重要

这种方法涉及将单个对手的信息“分散”到多个表格中。

在这样的程序中,向一个集合添加一个敌人会是这样:

enemy_names.push_back("Goblin");
enemy_health.push_back(50);
enemy_strength.push_back(10);

我们想要存储的关于对手的信息越多,就会越麻烦。幸运的是,**结构体**在这里为我们提供了帮助。

创建结构体

让我们回顾一下我们需要存储哪些数据:

  • 名称 👾
  • 生命值 💚
  • 力量 💪

我们即将添加一个允许我们创建一个包含这三项内容的对象的结构体。

#include <string>

struct Enemy
{
std::string name;
float health;
float strength;
};

int main()
{
// We leave it empty for now
}

上面的代码引入了一个新的**结构体**——`Enemy`。

记住

一个**结构体**是一种描述,一种模式,一个创建对象(在本例中为敌人)的配方。

要创建一个结构体,我们在 `struct` 关键字后面写上它的名称,然后将其内容放在花括号 `{` 和 `}` 之间。

内容可以是变量。

分号

请注意花括号后**必须**有分号来结束结构体定义

struct Enemy
{
std::string name;
float health;
float strength;
};

对象

下面是如何创建一个使用 `Enemy` 结构体(被视为创建对象的公式)的对象:

// prism-push-types:Enemy
int main()
{
Enemy boss;
}

因此,我们将这三个字段(`name`、`health` 和 `strength`)都包含在一个变量 `boss` 中。

命名

从现在开始,我们将说 `boss` 是 `Enemy` 类型的一个对象。这意味着它是使用 `Enemy` 结构体作为公式创建的。

访问字段

如上所述,`boss` 包含 3 个事物(字段),即它由三个变量组成。要访问此对象的特定成员,我们需要使用以下表示法:

将boss的名称设置为“Ogre”
boss.name = "Ogre";

我们使用点 `.` 来引用对象的字段。同样,我们也可以修改敌人的**力量**。

修改对象的字段
boss.strength	= 50; // I set bosses strength to 50

// Boss enables "fury" skill - strength increases
// Health decreases by a half
boss.strength += 25;
boss.health *= 0.5f;

...或显示其信息

使用对象字段
#include <iostream>
#include <string>

struct Enemy
{
std::string name;
float health;
float strength;
};

int main()
{
// I create boss object
Enemy boss;
// I assign values to its fields
boss.name = "Ogre";
boss.health = 250;
boss.strength = 50;

std::cout << boss.name << " has "
<< boss.health << " hp and "
<< boss.strength << " strength."
<< std::endl;
}

将对象传递给函数

没有任何限制阻止您创建一个将特定结构体的对象作为参数的函数。一个很好的例子就是显示敌人信息:

一个显示对手信息的函数
void print_enemy_info(Enemy enemy)
{
std::cout << enemy.name << " has "
<< enemy.health << " hp and "
<< enemy.strength << " strength."
<< std::endl;
}
顺序

print_enemy_info 要求类型 `Enemy` 在函数本身定义**之前**存在。这意味着我们需要将函数放在结构体创建的下方(参见下面的示例)。

利用以上信息,我们将创建一个包含两个对手的“游戏”:

  • 普通敌人 👹
    **哥布林战士**,生命值 `60`,力量 `14`

  • boss 💀
    **食人魔**,生命值 `250`,力量 `50`

包含食人魔和哥布林的游戏代码片段
#include <iostream>
#include <string>

// Creating struct
struct Enemy
{
std::string name;
float health;
float strength;
};

// Function that display information about an enemy
void print_enemy_info(Enemy enemy)
{
std::cout << enemy.name << " has "
<< enemy.health << " hp and "
<< enemy.strength << " strength."
<< std::endl;
}

// The main function
int main()
{
// I create boss and goblin objects
Enemy boss;
Enemy goblin;

// I setup goblin object fields
goblin.name = "Goblin warrior";
goblin.health = 60;
goblin.strength = 14;

// I setup boss object fields
boss.name = "Ogre";
boss.health = 250;
boss.strength = 50;

// and print information about them
print_enemy_info(goblin);
print_enemy_info(boss);
}

数组中的对象

我们可以像普通变量一样将对象放入数组中

敌人向量
std::vector< Enemy > enemies;

下面是如何向这样的数组添加元素的示例

在向量中添加对象
// ...

int main()
{
std::vector< Enemy > enemies;

// (optional)
// A code block to limit the scope
// of local variables inside
{
// I create goblin variable 👉 locally 👈
Enemy goblin;
// I setup the fields
goblin.name = "Goblin warrior";
goblin.health = 60;
goblin.strength = 14;

// I add the object to the vector
enemies.push_back( goblin );
}
// 👈 from this moment, goblin exists ONLY inside the vector

// Print every enemy in the vector
for (Enemy enemy : enemies)
print_enemy_info(enemy);
}
示例

阅读本课后,请回顾此示例程序:👾 战斗竞技场及其概述。在那里您将看到数组和结构体在实际中是如何使用的。

字段默认值

我们可以给结构体元素设定默认值,这样就不必每次都去填充它们了。

一个很好的使用默认值的例子是存储对手造成的总伤害量的变量。首先,对于每个敌人,这个值都必须等于 `0`。

字段值

如果你将结构体字段留空,没有默认值,例如:

struct Car
{
int number_of_wheels;
};

这并不意味着 `number_of_wheels` 在对象创建时会被设置为 `0`,你必须**手动**完成!

要为结构体字段赋值,我们使用与创建变量时相同的常规初始化方法:

“total_damage”的默认值 ⚔
// Creating the structure
struct Enemy
{
std::string name;
float health;
float strength;

float total_damage = 0;
};

现在当我们创建一些敌人时

Enemy snake; // snake as an example

那么它的 `total_damage` 字段的值

snake.total_damage

将自动设置为 `0`。

您可以通过打印它来确认这一点,例如:

int main() {
Enemy snake;
snake.name = "Snake";
// 🟡 Note, I'm not setting "total_damage" manually

std::cout << snake.name
<< " has dealt "
<< snake.total_damage
<< " total damage";
}
结果
Snake has dealt 0 total damage

潜在错误

重要

本节需要改进。您可以通过编辑此文档页面来帮助我们。

以下是与本课相关的常见错误列表:

定义后没有分号

仅供参考。

无效顺序

确保结构体在首次使用**之前**定义。

**错误**代码示例

🔴 顺序不正确
// ❌ Error: usage of type "Enemy" before its definition
void print_enemy_info(Enemy enemy)
{
std::cout << enemy.name << " has "
<< enemy.health << " hp and "
<< enemy.strength << " strength."
<< std::endl;
}

// Creating the structure
struct Enemy
{
std::string name;
float health;
float strength;
};
提示

这个问题可以通过另一种更方便的方式解决,而不是将 `print_enemy_info` 函数移动到结构体定义下方,即所谓的*前向声明*。我们将在课程后面提到它。

在结构体定义内部修改

变量**不能**在结构体定义内部修改。只允许赋值初始值。

🔴 尝试在错误的位置修改字段
struct Enemy
{
std::string name;
float health;
float strength;

int total_damage = 0; // OK ✅

// ❌ Error: Attempt to modify field in a wrong place
health = 250;
};