结构与联合体

在C++中,结构体(struct)、联合体(union)和类(class)都是用于组织数据的复合数据类型。它们允许我们将不同类型的变量组合在一起,从而更有效地管理数据。

结构体

结构体(struct)是由用户自定义的数据类型,它允许你将 不同类型 的数据项 组合 在一起,形成一个有机的整体,用来描述一个复杂的实体。

想象一下,如果你要描述一个“学生”实体。一个学生有很多属性:姓名、学号、年龄、成绩等等。这些属性是相关的,它们共同描述了一个学生。如果用单独的变量来存储这些属性,会非常零散,难以管理。

这时我们就可以用结构体来描述“学生”这个实体。

定义结构体

我们可以使用 struct 关键字来定义结构体:

struct 结构体类型名称 {
  // 结构体成员
  数据类型1 成员变量名1;
  数据类型2 成员变量名2;
  ...
};

需要注意的是,结构体定义的末尾有一个分号 ;,别漏掉了。

例如,我们要描述“学生”这个实体,可以定义下面这样一个结构体:

// 定义一个名为 Student 的结构体
struct Student {
  // 结构体成员
  int id; // 学号
  string name; // 姓名
  int age; // 年龄
  char gender; // 性别
};

使用结构体

现在有了一个名为 Student 的结构体类型,我们就可以像使用基本数据类型(如 int)一样,声明结构体类型的变量。

struct Student stu1; // 定义学生1
stu1.id = 202310001;
stu1.name = "Jordan";
stu1.age = 23;
stu1.gender = 'Male';

下面是完整的代码:

#include <iostream>
using namespace std;

// 结构体类型声明
struct Student {
  int id; // 学号
  string name; // 姓名
  int age; // 年龄
  char gender; // 性别
};

int main() {
  struct Student s1; // 定义学生对象1
  s1.id = 202310001;
  s1.name = "Jordan";
  s1.age = 23;
  s1.gender = 'M';
  
  struct Student s2; // 定义学生对象2
  s2.id = 202310002;
  s2.name = "Alice";
  s2.age = 26;
  s2.gender = 'F';
  
  cout << "学生1: " << s1.name << " " << s1.age << " " << s1.gender << endl;
  cout << "学生2: " << s2.name << " " << s2.age << " " << s2.gender << endl;
  
  return 0;
}

运行程序,结果如下:

学生1: Jordan 23 M 学生2: Alice 26 F

上面是先定义结构体(Student),再单独创建具体的对象(s1s2);也可以直接紧跟在结构体的类型声明之后创建对象,代码如下所示:

// 结构体类型声明与创建
struct Student {
  int id; // 学号
  string name; // 姓名
  int age; // 年龄
  char gender; // 性别
} s1, s2, s[100];

上面代码在声明结构体类型的同时,创建了三个具体的结构体对象:

  • s1,学生对象1
  • s2,学生对象2
  • s[100],含有 100 个 Student 对象的数组 s

对于结构体类型的数组,访问方式与普通的数组差不多,只是通过数组直接拿到的数组元素是结构体,还需要多一层结构才能访问到具体的数据项。

下面是一个简单的示例,演示了结构体数组的访问。

#include <iostream>
using namespace std;

struct Student {
  int id; // 学号
  string name; // 姓名
  int age; // 年龄
  char gender; // 性别
} s[100];

int main() {
  int n;
  cin >> n;
  // 生成 n 个 Student 并赋值
  for (int i = 0; i < n; i++) {
    s[i].id = i + 1;
    s[i].name = "name" + to_string(i + 1);
    s[i].age = 10;
    if (i % 2 == 0) s[i].gender = 'M';
    else s[i].gender = 'F';
  }
  
  for (int i = 0; i < n; i++) {
    cout << s[i].id << " " << s[i].name << " " << s[i].age <<  " " << s[i].gender << endl;
  }
  
  return 0;
}

当输入 n=5n = 5 时,程序运行结果如下:

1 name1 10 M 2 name2 10 F 3 name3 10 M 4 name4 10 F 5 name5 10 M

结构体指针

我们也可以使用结构体指针来访问结构体的元素,有下面两种方式定义结构体指针。

定义结构体指针

你可以用一个结构体对象的地址来创建一个指针变量(也就是结构体指针变量)。

Student s;
s.id = 1;
s.name = "Jordan";
s.age = 23;
s.gender = 'M';

Student* p = &s; // 定义了一个指向 Student 结构的指针

使用指针访问结构体成员

通过指针可以访问结构体的成员,有两种常见的方式:

  1. 通过解引用操作符(*) 访问

通过解引用操作符*,我们可以访问指针所指向的结构体实例,并使用点操作符.访问其成员:

Student* p = &s; // 定义了一个指向 Student 结构的指针

cout << "Id: " << (*p).id << ", Name: " << (*p).name << endl;
  1. 通过箭头操作符(->)访问

C++提供了一种更简便的方式,即使用箭头操作符 -> 来访问成员,它结合了解引用和访问成员的操作:

Student* p = &s; // 定义了一个指向 Student 结构的指针

cout << "Id: " << p->id << ", Name: " << p->name << endl;

联合体

联合体(union)也是一种将多个不同类型的数据组合在一起的用户定义的数据类型,但与结构体不同的是,联合体的所有成员共享相同的内存空间。这意味着在同一时间只能有一个成员占用存储空间(存储有效值)。

定义联合体

使用 union 关键字来定义联合体,语法如下:

// 定义一个名为 Data 的联合体
union Data {
  // 联合体成员
  int intValue;
  float floatValue;
  char charValue;
};

使用联合体

我们可以通过以下方式创建和使用联合体:

// 声明一个 Data 类型的联合体变量
Data data;

data.intValue = 10;
cout << data.intValue << endl;

data.floatValue = 3.14;
cout << data.floatValue << endl;

下面是一个完整的示例,展示了如何定义和使用联合体:

#include <iostream>
using namespace std;

union Data {
  int intValue;
  float floatValue;
  char charValue;
};

int main() {
  Data data;
  data.intValue = 10;
  cout << data.intValue << endl;

  data.floatValue = 3.14;
  cout << data.floatValue << endl;
  
  // 如果这时再去访问之前的 intValue,结果是不可预测的!!!
  cout << data.intValue << endl;

  data.charValue = 'A';
  cout << data.charValue << endl;

  return 0;
}

结构体与联合体看起来很相似,但它们在内存使用和应用场景上有着显著的不同。

  • 内存分配:结构体的每个成员都有自己独立的内存空间,而联合体的所有成员共享同一块内存。
  • 使用场景:结构体适合用于需要同时存储和访问多个不同类型的数据的情况;联合体适合用于节省内存的场景,尤其是在同一时间只需要一个数据成员的情况下。

重要: 使用联合体时,你需要自己负责跟踪当前联合体中存储的是哪个成员的值。如果你向一个成员赋值后,又去读取另一个成员的值,这是未定义行为 (Undefined Behavior),结果是不可预测的。

类是 C++ 中 面向对象编程 (Object-Oriented Programming, OOP) 的核心概念。它比结构体更强大,不仅可以组合数据,还可以定义操作这些数据的行为

你可以把类看作一个更完善的“模板”或“蓝图”,它不仅描述了对象的属性(数据成员),还描述了对象能做什么(成员函数或方法)。

如何定义类

使用 class 关键字来定义类,下面是一个完整的示例代码:

#include <bits/stdc++.h>
using namespace std;

// 定义一个名为 Dog 的类
class Dog {
private: // 私有成员,只能在类内部访问
  string name;
  int age;

public: // 公有成员,可以在类外部访问
  // 成员函数 (方法)
  // 构造函数 (用于创建对象时初始化成员)
  Dog(string n, int a) {
	name = n;
	age = a;
	cout << "一只名为 " << name << " 的狗被创建了。" << endl;
  }

  // 成员函数:获取名字
  string getName() {
	return name;
  }

  // 成员函数:获取年龄
  int getAge() {
        return age;
  }

  // 成员函数:执行行为
  void bark() {
    cout << name << " 汪汪叫!" << endl;
  }

  // 析构函数 (在对象销毁时执行)
  ~Dog() {
    cout << "一只名为 " << name << " 的狗被销毁了。" << endl;
  }
}; // 注意类定义结束后的分号 ;

int main() {
  // 这里是使用类的地方,稍后会讲
  return 0;
}

下面是关于这段代码的解释:

  • class Dog:定义了一个名为 Dog 的类类型。
  • 类可以包含数据成员(如 nameage)和成员函数(如 Dog()getName()bark()~Dog())。
  • 访问修饰符 (publicprivateprotected): 用来控制类成员的访问权限。
    • public:公有成员,可以在类的内部和外部任何地方访问。
    • private:私有成员,只能在类的内部访问。这是实现封装 (Encapsulation) 的重要手段,可以保护数据不被随意修改。
    • protected:保护成员,可以在类的内部和其派生类(继承类)中访问。
  • 构造函数: 与类名同名,没有返回类型。在创建类的对象时自动调用,用于初始化对象的状态。
  • 析构函数: 函数名前面加 ~,与类名同名,没有返回类型和参数。在对象生命周期结束时自动调用,用于清理资源。

如何使用类

定义了类类型后,你就可以创建类的对象 (Object),也称为实例化 (Instantiation)。对象是类的具体实例。

#include <bits/stdc++.h>
using namespace std;

class Dog {
private:
  string name;
  int age;
public:
  Dog(string n, int a) { // 构造函数
    name = n;
    age = a;
    cout << "一只名为 " << name << " 的狗被创建了。" << endl;
  }
  string getName() { return name; }
  int getAge() { return age; }
  void bark() { cout << name << " 汪汪叫!" << endl; }
  ~Dog() { cout << "一只名为 " << name << " 的狗被销毁了。" << endl; } // 析构函数
};

int main() {
  // 创建一个 Dog 类的对象
  // 调用了构造函数 Dog("旺财", 3)
  Dog myDog("旺财", 3);

  // 访问公有成员函数:使用点运算符 '.'
  cout << "我的狗叫: " << myDog.getName() << endl;
  cout << "它的年龄是: " << myDog.getAge() << endl;
  myDog.bark(); // 调用 bark 方法

  // 尝试访问私有成员 (会编译错误)
  // cout << myDog.age << endl; // 错误!age 是 private 的

  // 对象在 main 函数结束时会被销毁,自动调用析构函数 ~Dog()

  return 0;
}

小结

  • 结构体 (struct): 主要用于将不同类型的数据打包成一个单一的单元。在 C++ 中,它也可以包含成员函数,但默认成员是 public的,常用于表示纯粹的数据结构。
  • 联合体 (union): 是一种特殊的结构,其成员共享同一块内存空间,用于在不同类型的数据之间节省内存。使用时需要手动管理当前哪个成员是有效的。
  • 类 (class): 是面向对象编程的基础,将数据和操作数据的方法封装在一起。通过访问修饰符实现封装,默认成员是 private 的,更强调数据保护和行为定义。

在实际编程中:

  • 如果你只是想简单地组合一些数据,并且希望这些数据可以方便地在外部直接访问,可以使用结构体
  • 如果你需要在不同时间存储不同类型的数据,并且希望最大程度地节省内存,可以考虑使用联合体(但要注意使用时的安全性)。
  • 如果你想定义一个具有特定属性和行为的对象类型,实现封装、继承、多态等面向对象特性,那么应该使用

在 C++ 中,struct 和 class 在功能上非常相似,主要区别在于默认访问权限。习惯上,struct 更常用于那些主要由数据组成的类型(Plain Old Data, POD),而 class 更常用于那些包含复杂行为和需要封装的类型。