avatar

目录
272 - first class

272 - First Class

https://g.co/gemini/share/0f8339f51e2e

你好!我是你的 C++ 老师。很高兴能引导你进入 C++ 的世界。今天,我们将一起学习 C++ 中一个非常核心且强大的概念 —— 类 (Class)。别担心,我会一步一步地详细讲解,确保你能够轻松理解。

让我们开始吧!

1. 标题和摘要

标题: C++ 入门:创建你的第一个类 (Class) - 打造自定义数据类型

摘要: 本节课将向你介绍 C++ 中的类 (Class),它是一种允许我们创建自定义数据类型(就像 intdouble 一样,但由我们自己设计)的机制。我们将通过创建一个表示“圆柱体”的类作为例子,学习如何定义类的属性(成员变量)和行为(成员函数),以及如何控制对这些成员的访问权限(publicprivate)。最后,你将学会如何使用你创建的类来创建对象 (Object) 并在程序中使用它们。

2. 详细讲解

a. 为什么需要类 (Class)?

到目前为止,我们已经使用过 C++ 的一些内置(或称为基本)数据类型 (Type),比如 int (整数), double (双精度浮点数), bool (布尔值) 等。我们可以这样使用它们:

C++

Code
1
2
int age = 30;
double price = 99.9;

这里,intdouble 是类型,ageprice 是变量名。

但是,如果我们想表示更复杂的事物呢?比如,想在程序里表示一个“人”,一个人可能有名字、年龄、地址等信息。或者像我们今天要做的,表示一个“圆柱体”,它有底面半径 (base radius) 和高 (height)。

C++ 的基本类型无法直接、完整地表示这些复杂概念。这时,我们就需要一种方法来创建我们自己的、能够封装(组合)多个数据和相关操作的类型。这就是 类 (Class) 发挥作用的地方!类是创建自定义类型的蓝图 (blueprint)。

b. 定义一个简单的类:以圆柱体为例

想象一个圆柱体。定义一个圆柱体需要哪些信息?通常是它的 底面半径 (base radius)高 (height)。有了这两个信息,我们就可以做很多事情,比如计算它的底面积(公式:π r²)或体积(公式:底面积 高)。

在 C++ 中,我们可以使用 class 关键字 (keyword) 来定义一个表示圆柱体的类。基本语法如下:

C++

Code
1
2
3
4
// 定义类的语法
class ClassName {
// 成员 (Members)
}; // <-- 注意!这里必须有一个分号

让我们来定义 Cylinder(圆柱体)类:

C++

Code
1
2
3
4
5
6
// 引入常量 PI (圆周率)
const double PI = 3.1415926535; // 实际项目中可能有更精确的方式定义 PI

class Cylinder {
// 类的内容会放在这里
}; // <-- 千万不要忘记这个分号!

c. 类成员:成员变量和成员函数

一个类通常包含两个主要部分:

  1. 成员变量 (Member Variables): 这些变量代表了类的属性或状态。对于 Cylinder 类,我们需要存储底面半径和高。我们可以选择 double 类型来存储它们,因为半径和高可能是小数。

    C++

    Code
    1
    2
    3
    4
    5
    6
    class Cylinder {
    public: // 先暂时设为 public,后面会解释
    // 成员变量 (属性)
    double baseRadius {0.0}; // 使用花括号初始化为 0.0
    double height {0.0}; // 使用花括号初始化为 0.0
    };
    • 我们在这里把 baseRadiusheight 定义为 double 类型。
    • {0.0} 是一种初始化方式,确保创建圆柱体对象时,这些值有一个默认的初始状态。
  2. 成员函数 (Member Functions) / 方法 (Methods): 这些函数定义了类的行为或操作。它们通常会使用类的成员变量来完成某些任务。对于 Cylinder 类,我们可以定义一个函数来计算体积。

    C++

    Code
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Cylinder {
    public: // 稍后解释 public
    // 成员变量
    double baseRadius {1.0}; // 给个默认值
    double height {1.0}; // 给个默认值

    // 成员函数 (行为/方法)
    double volume() {
    // 体积 = PI * 半径 * 半径 * 高
    // 注意:这里可以直接访问同一个类中的成员变量 baseRadius 和 height
    return PI * baseRadius * baseRadius * height;
    }
    };
    • volume() 函数返回一个 double 类型的值(体积)。
    • 它没有参数(括号是空的)。
    • 函数体内,它直接使用了成员变量 baseRadiusheight 来进行计算。这是关键点:成员函数可以自由访问同一个类的其他成员(变量或函数),无论它们是 public 还是 private。

d. 访问控制:public (公有) 与 private (私有)

你可能注意到上面代码中出现了 public: 这个词。这是一个 访问修饰符 (access specifier)。它决定了类的哪些成员可以从类的 外部(比如 main 函数)被访问。

  • public (公有):public: 后面声明的成员(变量或函数)可以被类的外部代码自由访问。
  • private (私有):private: 后面声明的成员只能被 类内部 的成员函数访问。它们对类的外部是隐藏的。

重要规则:默认情况下,类的所有成员都是 private 的!

如果我们不写 public:,像这样:

C++

Code
1
2
3
4
5
6
7
8
9
class Cylinder {
// 没有写 public 或 private
double baseRadius {1.0};
double height {1.0};

double volume() {
return PI * baseRadius * baseRadius * height;
}
}; // 分号不能少

那么 baseRadius, height, 和 volume() 默认都是 private 的。这意味着,如果你在 main 函数中尝试访问它们,编译器会报错!

C++

Code
1
2
3
4
5
6
7
8
int main() {
Cylinder cylinder1; // 创建一个 Cylinder 对象
// 下面的代码会编译失败,因为 volume() 是 private 的 (默认)
// std::cout << "Volume: " << cylinder1.volume() << std::endl;
// 下面的代码也会编译失败,因为 baseRadius 和 height 是 private 的
// cylinder1.baseRadius = 10.0;
return 0;
}

良好的设计实践:

通常,我们会将 成员变量设为 private,以保护类的内部状态不被外部随意修改,这叫做 数据封装 (Data Encapsulation)。然后,提供 public 的成员函数 作为外部与类交互的接口 (interface)。

修改后的 Cylinder 类(更好的设计):

C++

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const double PI = 3.1415926535;

class Cylinder {
private: // 明确声明成员变量为私有 (虽然默认也是 private)
double baseRadius {1.0};
double height {1.0};

public: // 公共接口
// 成员函数来计算体积
double volume() {
// 仍然可以访问 private 成员,因为 volume() 是类的一部分
return PI * baseRadius * baseRadius * height;
}

// 我们可能需要提供 public 函数来安全地设置或获取私有成员的值
// (这些称为 Setters 和 Getters,我们稍后会学)
// 例如,添加一个函数来设置半径和高
void setDimensions(double r, double h) {
if (r > 0 && h > 0) { // 可以加入验证逻辑
baseRadius = r;
height = h;
}
}

// 添加一个函数来获取半径 (Getter)
double getRadius() {
return baseRadius;
}

// 添加一个函数来获取高度 (Getter)
double getHeight() {
return height;
}
};
  • 现在 baseRadiusheightprivate 的,不能从 main 函数直接访问(如 cylinder1.baseRadius = 10; 会失败)。
  • volume(), setDimensions(), getRadius(), getHeight()public 的,可以从 main 函数调用。
  • setDimensions() 提供了一个受控的方式来修改内部的私有数据。

e. 使用类:创建对象 (Object)

类本身只是一个蓝图。要真正使用它,我们需要根据这个蓝图创建具体的实例,这些实例称为 对象 (Object)

创建对象就像声明一个普通变量一样,只是类型是我们自己定义的类名:

C++

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream> // 需要包含 iostream 来使用 std::cout

int main() {
// 创建一个 Cylinder 类的对象,名为 cylinder1
Cylinder cylinder1; // 这会使用默认的 baseRadius=1.0, height=1.0

// 调用 public 成员函数 volume()
std::cout << "Cylinder 1 Volume (default): " << cylinder1.volume() << std::endl; // 输出默认体积

// 使用我们添加的 public setter 函数来修改尺寸
cylinder1.setDimensions(10.0, 3.0); // 设置半径为 10,高为 3

// 再次计算并打印体积
std::cout << "Cylinder 1 Volume (10, 3): " << cylinder1.volume() << std::endl;

// 使用 getter 函数获取值
std::cout << "Cylinder 1 Radius: " << cylinder1.getRadius() << std::endl;
std::cout << "Cylinder 1 Height: " << cylinder1.getHeight() << std::endl;

// 尝试直接访问 private 成员(会失败)
// cylinder1.baseRadius = 5.0; // 编译错误!baseRadius 是 private 的

// 可以创建多个对象
Cylinder cylinder2;
cylinder2.setDimensions(2.0, 5.0);
std::cout << "Cylinder 2 Volume (2, 5): " << cylinder2.volume() << std::endl;

return 0;
}
  • 我们使用 Cylinder cylinder1; 创建了一个 Cylinder 对象。
  • 我们使用 点运算符 (.) 来访问对象的 public 成员(函数)。例如:cylinder1.volume()cylinder1.setDimensions(10.0, 3.0)
  • 每个对象(cylinder1, cylinder2)都有自己的一套成员变量副本。修改 cylinder1 的半径和高不会影响 cylinder2

f. 回顾总结

  • 类是创建自定义类型的蓝图。
  • 类包含成员变量(属性)和成员函数(行为)。
  • public 成员可以从类外部访问,private 成员只能从类内部访问。
  • 默认情况下,类成员是 private 的。
  • 良好的实践是将数据成员设为 private,并提供 public 函数接口。
  • 使用类名可以创建对象(类的实例)。
  • 使用点运算符 (.) 访问对象的 public 成员。

3. 详细代码示例

下面是包含 Cylinder 类定义和 main 函数使用示例的完整代码:

C++

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include <iostream> // 用于输入输出 (std::cout, std::endl)
#include <cmath> // 在某些系统或标准下可能需要包含 cmath 来获取更精确的 PI 或数学函数

// 定义常量 PI
const double PI = 3.141592653589793;

// 定义 Cylinder 类
class Cylinder {
private: // 私有成员:类的内部数据,对外部隐藏
double baseRadius; // 底面半径
double height; // 高

public: // 公有成员:类的外部接口
// 构造函数 (我们会在后续课程学习,这里先提供一个简单的默认设置)
// 构造函数用于初始化对象,这里我们设置默认值
Cylinder() {
baseRadius = 1.0; // 默认半径
height = 1.0; // 默认高度
std::cout << "Cylinder object created with default dimensions (r=1, h=1)" << std::endl;
}

// 带参数的构造函数 (允许在创建对象时指定尺寸)
Cylinder(double r, double h) {
std::cout << "Cylinder object created with dimensions (r=" << r << ", h=" << h << ")" << std::endl;
// 使用 setDimensions 来进行可能的验证
setDimensions(r,h);
}

// 成员函数:计算体积
double volume() {
// volume 函数可以访问 private 成员 baseRadius 和 height
return PI * baseRadius * baseRadius * height;
}

// 成员函数:设置圆柱体的尺寸 (Setter 方法)
void setDimensions(double r, double h) {
// 可以添加检查,确保半径和高度是有效的(例如,大于0)
if (r > 0 && h > 0) {
baseRadius = r;
height = h;
std::cout << "Dimensions set to: radius = " << baseRadius << ", height = " << height << std::endl;
} else {
std::cout << "Error: Radius and height must be positive." << std::endl;
// 可以选择保持原值或设置一个安全的默认值
if (baseRadius <= 0) baseRadius = 1.0;
if (height <= 0) height = 1.0;
}
}

// 成员函数:获取底面半径 (Getter 方法)
double getRadius() {
return baseRadius;
}

// 成员函数:获取高 (Getter 方法)
double getHeight() {
return height;
}
}; // 不要忘记类定义末尾的分号

// 主函数:程序入口
int main() {
std::cout << "--- Creating cylinder1 (using default constructor) ---" << std::endl;
Cylinder cylinder1; // 创建第一个 Cylinder 对象 (使用默认构造函数)

std::cout << "Initial volume of cylinder1: " << cylinder1.volume() << std::endl;
std::cout << "Initial radius of cylinder1: " << cylinder1.getRadius() << std::endl;
std::cout << "Initial height of cylinder1: " << cylinder1.getHeight() << std::endl;
std::cout << std::endl; // 打印空行

std::cout << "--- Modifying cylinder1 dimensions ---" << std::endl;
cylinder1.setDimensions(10.0, 3.0); // 修改 cylinder1 的尺寸
std::cout << "Volume of cylinder1 after modification: " << cylinder1.volume() << std::endl;
std::cout << std::endl;

std::cout << "--- Creating cylinder2 (using parameterized constructor) ---" << std::endl;
Cylinder cylinder2(2.0, 5.0); // 创建第二个 Cylinder 对象,并直接指定尺寸

std::cout << "Volume of cylinder2: " << cylinder2.volume() << std::endl;
std::cout << "Radius of cylinder2: " << cylinder2.getRadius() << std::endl;
std::cout << "Height of cylinder2: " << cylinder2.getHeight() << std::endl;
std::cout << std::endl;

std::cout << "--- Trying to set invalid dimensions for cylinder1 ---" << std::endl;
cylinder1.setDimensions(-5.0, 10.0); // 尝试设置无效的半径
// 检查设置后的实际值(应该被setDimensions的逻辑处理了)
std::cout << "Radius of cylinder1 after trying invalid set: " << cylinder1.getRadius() << std::endl; // 可能还是 10 或者被重置为 1
std::cout << "Height of cylinder1 after trying invalid set: " << cylinder1.getHeight() << std::endl; // 可能还是 3 或者被重置为 1

// 尝试直接访问 private 成员 (这会导致编译错误)
// cylinder1.baseRadius = 20.0; // 取消注释这行会导致编译失败
// std::cout << cylinder1.height; // 取消注释这行也会导致编译失败

return 0; // 程序正常结束
}

4. Q&A 闪卡 (Flash Cards)

卡片 1

问题 (Q): 在 C++ 中,什么是类 (Class)?它有什么用途?

答案 (A): 类是用户定义的数据类型的蓝图或模板。它允许我们将数据(成员变量)和操作这些数据的函数(成员函数)捆绑在一起,用来创建我们自己的复杂数据类型,以模拟现实世界中的概念或实体。

卡片 2

问题 (Q): 什么是成员变量 (Member Variables) 和成员函数 (Member Functions)?

答案 (A): 成员变量是定义在类内部的变量,用于存储对象的状态或属性(例如圆柱体的半径和高)。成员函数是定义在类内部的函数,用于定义对象的行为或操作(例如计算圆柱体的体积)。

卡片 3

问题 (Q): public 和 private 访问修饰符有什么区别?类的成员默认是什么访问权限?

答案 (A): public 成员可以从类的外部(例如 main 函数或其他类)访问。private 成员只能从类的内部(即被同一个类的成员函数)访问。默认情况下,C++ 类的成员是 private 的。

卡片 4

问题 (Q): 如何根据一个类创建一个对象?如何访问对象的公有成员?

答案 (A): 创建对象就像声明一个变量:ClassName objectName;。访问对象的公有成员(变量或函数)使用点运算符 (.): objectName.publicMemberName。

5. 常见误解或错误

  1. 忘记类定义末尾的分号 (;): 这是初学者非常容易犯的错误,会导致奇怪的编译错误。

    C++

    Code
    1
    2
    3
    class MyClass {
    // ... members ...
    } // <-- 错误!缺少分号
  2. 从类外部访问 private 成员: 尝试在 main 函数或类的外部直接读写 private 成员变量会导致编译错误。必须通过 public 的成员函数(如 getters/setters)来间接访问。

    C++

    Code
    1
    2
    Cylinder c;
    // c.baseRadius = 10; // 错误!如果 baseRadius 是 private 的
  3. 混淆类 (Class) 和对象 (Object): 类是蓝图,对象是根据蓝图创建出来的实体。你不能直接对类执行操作(比如计算“Cylinder类”的体积),而是要先创建类的对象,然后对该对象执行操作(计算 cylinder1 对象的体积)。

  4. 调用成员函数时忘记对象名和点运算符: 成员函数必须通过某个对象来调用。

    C++

    Code
    1
    2
    // volume(); // 错误!必须指定是哪个对象的 volume()
    // cylinder1.volume(); // 正确
  5. 不必要地将成员变量设为 public: 虽然这样做可以让外部直接访问,但破坏了封装性,使得类的内部状态容易被意外或恶意修改,通常是不良设计。应尽量保持数据成员 private

  6. 在成员函数内部访问成员变量时使用点运算符: 在类的成员函数内部,可以直接使用成员变量的名字,不需要 this-> (虽然也可以用) 或者对象名。

    C++

    Code
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Example {
    int value;
    public:
    void setValue(int v) {
    // value = v; // 正确且常用
    // this->value = v; // 也正确,有时用于消除歧义
    // Example.value = v; // 错误!
    // objectName.value = v; // 错误!(除非 objectName 是传进来的参数)
    }
    };

6. 编码练习

现在轮到你来实践了!下面是一个稍微不同的类 Box(盒子),它有长 (length)、宽 (width) 和高 (height)。请你补全代码中的 // ??? 部分,让程序能够正确编译和运行。

C++

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>

// 定义常量 (这里不需要 PI)

class Box {
private:
// ??? 定义三个 double 类型的私有成员变量: length, width, height
double length;
double width;
double height;


public:
// 构造函数,用于初始化盒子尺寸
Box(double l, double w, double h) {
// ??? 在这里初始化成员变量 length, width, height
// 可以直接赋值,或者调用一个设置函数(如果需要验证)
length = l;
width = w;
height = h;
std::cout << "Box created with dimensions: l=" << length << ", w=" << width << ", h=" << height << std::endl;
}

// ??? 定义一个名为 'volume' 的公有成员函数
// ??? 它应该返回盒子的体积 (长 * 宽 * 高)
// ??? 返回类型应该是 double
double volume(){
return length*width*height;
}

// ??? 定义一个名为 'printDimensions' 的公有成员函数
// ??? 它不返回任何值 (void)
// ??? 它应该打印出盒子的长、宽、高
void printDimensions(){
std::cout << "Box dimensions: length = " << length << ", width = " << width << ", height = " << height << std::endl;
}

}; // <-- 不要忘记这个分号!

int main() {
// ??? 创建一个 Box 对象,名为 'myBox',长宽高分别为 10.0, 5.0, 2.0
Box myBox(10.0,5.0,2.0);

// ??? 调用 myBox 的 printDimensions 函数来打印它的尺寸
myBox.printDimensions();

// ??? 调用 myBox 的 volume 函数,并将结果打印到控制台
// ??? 打印格式: "The volume of the box is: [体积]"
std::cout << "The volume of the box is: " << myBox.volume() << std::endl;


return 0;
}

请仔细阅读注释,尝试填充 // ??? 标记的代码。完成后,将你的代码发给我,我会帮你检查并给出评分和反馈!加油!


评论