avatar

目录
282 - cpp结构体与类的区别和用法

C++结构体与类的区别和用法

🧑‍💻 User

In this lecture we’re going to learn about the struct keyword, and this is another syntax you can useto create classes in your C plus plus program.We have seen that we can already use the class keyword to create our classes.I have to break it to you that we can also use struct to do the same.So the syntax here is going to create a class called Cat and it’s going to have a member variable calledstring name.The difference between struct and class is that for classes, members are going to be private by default.And when you do something like this and create class objects, you want to be able to access the namehere directly.For example, from the main function.If you do that, you’re going to get a compiler error because class members are private by default.But if you use struct members are going to be public by default and you will have direct access fromthe outside.This is the only difference between class and struct.Otherwise you can use them interchangeably and they are going to do the same things we have been seeingfor classes.They really are the same things.The only difference is that classes are going to have private member variables by default and structwill have public member variables by default.But we can change this default by putting in our own public and private sections inside our class.So for example, here you see that we have changed our dog class to have a public member variable throughthe public keyword here and we can do the same for cat.For example, we can put in a public section and a private section and it’s going to behave like classeswe have been building so far.One common use we have for struct is when we have to set up classes that only have public member variables.For example, you can need to set up a point model in your C plus plus program and you can set up aclass like this.You can say struct point, you’re going to put in the member variables and they are going to be directlyaccessible from the outside.And this is something you will see a lot out there.So this is really all about struct.We’re going to head over to Visual Studio code and play with the studio more.Okay.Here we are in our working folder.The current project is struct.We’re going to grab our template files and we’re going to put them in place.Struct, that’s our project here and we’re going to open this in Visual Studio code.Let’s grab a struct and drag and drop on top of Visual Studio code here.This is going to open our folder.We can open the main.cpp file and clean things up a little bit.Okay.So we’re going to set up a simple class called Dog.We’re going to say class dog, and inside we’re going to have a member variable called Name.It’s going to be an stdstring, let’s say dog name or M name to mean that it is a member variable.This is a syntax you will see a lot in C plus plus out there.So let’s use this.From now on we’re going to say M name and we’re going to have another class.Let’s put a semicolon.We’re going to use a struct to set up another one and it’s going to be called dog.Let’s call this cat and we are going to go inside and set up our member variable.We’re going to say stdstring M name and it’s not going to have anything in.So if we create objects of these classes and try to access them, we’re going to see the fundamentaldifference between struct and dog.Let’s create a dog and we’re going to say dog one and we’re going to create a cat and we’re going tosay cat one.And if we do dog one and try to access the M name member variable, maybe set it to something else.Let’s say Fluffy.We’re going to have a compiler error because members of a class are private by default and we can’treally do this.So we are trying to access something that is private from the outside.We’re going to get a compiler error.Okay, So we have a squiggly line here, but if we try to do the same for Cat, it’s going to go throughbecause cat is a struct and members of struct are public by default.Let’s do that and really show you this.We’re going to say cat one and we’re going to say M name and we’re going to give it a name.And let’s say Johnny, I am making this up.I don’t know if there is any cat named Johnny, but we’re going to use this and you’re going to seethat we don’t have any squiggly line.We’re going to build and see if GCC is going to take this.It’s going to take it.The build is good.We can bring up a terminal and run the program and we’re not going to see anything because we’re notprinting anything from the main function here.But if we want, we can print this name of our cat.We’re going to say Stdcout and say cat one name.And if we build, we’re going to see that.So the build should be good.It is good.We can clear and run Rooster and we have Johnny printed out and this is the name of our cat and thisis really the difference between class and struct.Members of classes are going to be private by.Members of struct are going to be public by default, but we can change this defaults.For example, let’s make everything public.We can do that.No big deal here and we can move this a little bit to the right so that things are aligned nicely andwe can do the same for struct.Okay, so let’s align this.Now, if we do this, we will be able to go in the main function and say dog name and change that andwe’re going to be able to see that if we print that out.So let’s go down and say Stdcout and say dog one name and print that out.And if we build now, we’re going to see the name of our dog printed out.Okay, build is good.We can bring this up and clear a little bit and run Rooster.We’re going to see Fluffy, our dog name and Johnny our cat name.So this is really all about classes and struct.They are the same things, but the only difference is the defaults that come with these things.But the common use we have for struct is when we want to set up a class that only has direct membervariables and we don’t intend to put methods into that class.A good example of this is the struct of points.So suppose we want to model points in our C plus plus program so we can do something like this.And a point is going to have two coordinates x and y.We’re going to say double y, and from this point on we will be able to create point objects and printthem out however we want.So for example, we can go down and say .0.1, and from this point we can go in and say 0.1 and sayX and assign a value to X, Let’s put in a ten and we’re going to go down and say point one Y and we’regoing to give it a value.Let’s put in 55.5.Why not?And we can do whatever we want with this point.For example, we can set up a function which is going to print a point.We can do that, let’s say void print point and we’re going to pass the point by value.Or we can even use references because this is going to be an object const point reference.I think this is going to do and we’re going to go inside and print the information.We’re going to say Stdcout point and we’re going to put the coordinates and we’re going to say X isgoing to be point X and Y is going to be zero point y, y not.And we’re going to put in our closing square bracket and we’re going to say Stdendl to put a newlinecharacter after our print here.Now we can go down and say print point and pass in 0.1.And if we do that, we’re going to build and we’re going to do that through GCC.You see, the build is good.Now if we print, we should see the information about our point and we see point X one, Y 55.5 andwe see point X is ten, Y is 55.5.We can go down and change the information about our points.For example, we can copy this and go down and change X to 40.4 and Y to 2.7.Why not?And if we print the information about our point, we’re going to see that we have new information andwe’re going to print 0.1.And if we build, we’re going to see the new information.So the build is good.We can clear and run.Rooster Now you see new information in our point.So this is really the difference between class and struct.The only difference is going to be in the default and struct is mostly useful if you want to set upclasses that only have public member variables and you don’t really want to use functions or methodsin that class.And we have an example of that here.And if you have a case like that, you will use struct.Your code is going to be much easier to read and understand from the perspective of other C plus plusdevelopers.We are going to stop here in this lecture and the next one we’re going to learn about the size of objects.Go ahead and finish up here and meet me there.


🤖 Assistant

好的,同学你好!很高兴能成为你的C++启蒙老师。今天我们要学习C++中一个非常有用的关键字:struct。别担心,我会一步一步带你了解它,让你轻松掌握!


1. 标题与概要

标题: C++ 结构体 (Struct) 完全指南:轻松理解与类 (Class) 的区别与应用

概要:
本节课我们将学习C++中的 struct 关键字,它是创建自定义数据类型的一种方式,非常类似于我们之前可能接触过的 class [类]。我们将重点理解 structclass 之间的核心区别——默认的成员访问权限,并探讨 struct 的常见使用场景。


2. 详细解释

让我们一步一步来揭开 struct 的神秘面纱。

2.1 什么是 struct (结构体)?

在C++中,struct,全称 structure [结构体],是一种用户定义的复合数据类型。它允许你将不同数据类型的变量组合成一个单一的逻辑单元。想象一下,你要描述一个“点”,一个点有x坐标和y坐标。使用 struct,你就可以创建一个名为 Point 的类型,它内部包含x和y两个成员。

2.2 structclass (类) 的关系

在C++中,structclass 的功能几乎是完全相同的。你用 class 能做的事情,用 struct 基本上也都能做:

  • 它们都可以包含数据成员 (member variables [成员变量])。
  • 它们都可以包含成员函数 (member functions [成员函数],也叫方法 methods [方法])。
  • 它们都可以使用继承 (inheritance [继承])、多态 (polymorphism [多态]) 等面向对象的特性。

2.3 核心区别:默认访问权限 (Default Access Specifier)

这是 structclass 之间 唯一 的根本区别:

  • struct:如果你不明确指定,其所有成员(包括数据成员和成员函数)默认都是 public [公有] 的。这意味着在结构体外部的代码可以直接访问这些成员。
  • class:如果你不明确指定,其所有成员默认都是 private [私有] 的。这意味着这些成员只能在类的内部(比如类的成员函数中)被访问,外部代码不能直接访问。

让我们来看个例子:

cpp
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
// 使用 struct 定义 Cat
struct Cat {
std::string m_name; // 默认是 public
int m_age; // 默认是 public

void meow() { // 默认是 public
std::cout << m_name << " says: Meow!" << std::endl;
}
};

// 使用 class 定义 Dog
class Dog {
std::string m_name; // 默认是 private
int m_age; // 默认是 private

public: // 需要显式声明 public 区域
void setName(std::string name) {
m_name = name; // 可以在内部访问 private 成员
}

void bark() {
std::cout << m_name << " says: Woof!" << std::endl;
}
};

int main() {
Cat myCat;
myCat.m_name = "Whiskers"; // 正确!Cat 的 m_name 默认是 public
myCat.meow(); // 正确!

Dog myDog;
// myDog.m_name = "Buddy"; // 编译错误!Dog 的 m_name 默认是 private
myDog.setName("Buddy"); // 正确!通过 public 的成员函数访问
myDog.bark(); // 正确!
return 0;
}

在上面的例子中:

  • 对于 Cat 结构体,我们可以直接在 main 函数中通过 myCat.m_name 来访问和修改它的名字,因为 m_name 默认是 public
  • 对于 Dog 类,如果我们尝试 myDog.m_name = "Buddy";,编译器会报错,因为 m_name 默认是 private。我们必须通过一个 public 的成员函数(如 setName)来间接设置名字。

2.4 显式指定访问权限

虽然 structclass 有默认的访问权限,但我们随时可以使用 public:private:,和 protected: (我们稍后会学习 protected [保护]) 这些 访问修饰符 (access specifiers) 来明确指定成员的访问级别。

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct ExampleStruct {
private: // 我想让这个成员是私有的
int secretValue;

public: // 这些是公有的
int publicValue;
void printSecret() {
std::cout << "Secret: " << secretValue << std::endl; // 内部可以访问 private 成员
}
ExampleStruct() : secretValue(42), publicValue(10) {} // 构造函数
};

class ExampleClass {
public: // 我想让这些成员是公有的
int publicValue;
std::string name;
ExampleClass() : publicValue(20), name("Test") {} // 构造函数
};

一旦你显式地使用了 public:private:,那么 structclass 在那一部分的行为就完全一样了。比如,一个所有成员都显式声明为 publicclass,和一个所有成员都显式声明为 public (或者不声明,依赖默认行为) 的 struct,在使用上是没有区别的。

2.5 何时使用 struct?(约定俗成的规范)

尽管 structclass 功能上几乎等同(除了默认访问权限),但在C++社区中,有一个普遍的约定:

  • 使用 struct:当你定义一个主要目的是聚合数据 (aggregate data) 的类型时,特别是当这些数据成员大部分或全部都希望是公有的,并且这个类型主要作为数据的载体,可能没有什么复杂的行为(成员函数很少或很简单)。这种类型通常被称为 POD (Plain Old Data) 类型,或者类似POD的类型。

    • 例如:一个二维点 Point { double x; double y; },一个颜色 Color { int r; int g; int b; }。这些类型的核心在于它们所包含的数据,我们通常希望直接访问这些数据。
  • 使用 class:当你希望实现更复杂的对象,需要 封装 (encapsulation [封装]) 时。封装意味着将数据(通常设为 private)和操作这些数据的方法(通常设为 public)捆绑在一起。这有助于数据隐藏,保护数据不被随意修改,确保对象状态的有效性。

    • 例如:一个 BankAccount 类,它的余额 balance 应该是 private 的,只能通过 deposit()withdraw() 这样的 public 方法来修改。

遵循这个约定,可以让其他C++开发者更容易理解你的代码意图。当他们看到 struct,会倾向于认为这是一个简单的数据容器;看到 class,则会预期它有更复杂的行为和封装。

2.6 历史渊源 (可选了解)

在C语言中,struct 只能包含数据成员,不能有成员函数。C++ 继承了C语言的 struct 并对其进行了扩展,使其具备了和 class 几乎一样的能力,包括成员函数、继承等。引入 class 关键字,并使其默认成员为 private,是为了更好地支持面向对象编程中的封装思想。


3. 代码示例

让我们通过课程中提到的例子来具体看看 structclass 的行为。

cpp
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
#include <iostream>
#include <string>

// 使用 class 定义 Dog
class Dog {
// 默认是 private
std::string m_name; // 成员变量,m_ 前缀是一种常见的命名约定,表示它是成员(member)

public:
// 构造函数 (Constructor [构造函数]),用于初始化对象
Dog(std::string name) : m_name(name) {
std::cout << "Dog " << m_name << " created." << std::endl;
}

// 公有成员函数来设置名字 (通常称为 setter)
void setName(std::string name) {
m_name = name;
}

// 公有成员函数来获取名字 (通常称为 getter)
std::string getName() const { // const 表示这个函数不会修改对象的状态
return m_name;
}

void bark() {
std::cout << m_name << " says: Woof!" << std::endl;
}
};

// 使用 struct 定义 Cat
struct Cat {
// 默认是 public
std::string m_name;
int m_age;

// 构造函数
Cat(std::string name, int age) : m_name(name), m_age(age) {
std::cout << "Cat " << m_name << " aged " << m_age << " created." << std::endl;
}

void meow() {
std::cout << m_name << " says: Meow!" << std::endl;
}
};

// 一个常用于 struct 的例子:表示一个二维点
struct Point {
double x; // 默认 public
double y; // 默认 public
};

// 一个打印 Point 信息的函数
// 我们通过 const 引用传递 Point 对象,避免不必要的拷贝,同时保证函数不会修改它
void printPoint(const Point& p) {
std::cout << "Point(x: " << p.x << ", y: " << p.y << ")" << std::endl;
}


int main() {
// Dog 类的演示
Dog dog1("Buddy");
// dog1.m_name = "Lucy"; // 编译错误! m_name 是 private
std::cout << "Dog's name: " << dog1.getName() << std::endl;
dog1.bark();

dog1.setName("Lucy"); // 通过公有方法修改
std::cout << "Dog's new name: " << dog1.getName() << std::endl;
dog1.bark();

std::cout << "\n--- Struct Demo ---\n" << std::endl;

// Cat 结构体的演示
Cat cat1("Whiskers", 3);
cat1.m_name = "Smokey"; // 正确! m_name 是 public
cat1.m_age = 4; // 正确! m_age 是 public
std::cout << "Cat's name: " << cat1.m_name << ", Age: " << cat1.m_age << std::endl;
cat1.meow();

std::cout << "\n--- Point Struct Demo ---\n" << std::endl;

// Point 结构体的演示
Point p1;
p1.x = 10.5; // 直接访问 public 成员
p1.y = 20.3;
printPoint(p1);

Point p2 = {5.2, 8.7}; // 使用聚合初始化 (aggregate initialization)
printPoint(p2);

return 0;
}

代码解释:

  1. Dog

    • m_nameprivate 的(因为 class 默认是 private,且没有显式声明为 public)。
    • 我们提供了 public 的构造函数 Dog(std::string name) 来初始化 m_name
    • 我们提供了 publicsetNamegetName 函数来间接访问和修改 m_name
    • 直接在 main 中访问 dog1.m_name 会导致编译错误。
  2. Cat 结构体

    • m_namem_age 都是 public 的(因为 struct 默认是 public)。
    • 我们提供了 public 的构造函数 Cat(std::string name, int age)
    • 可以直接在 main 中通过 cat1.m_namecat1.m_age 来访问和修改它们。
  3. Point 结构体

    • xy 都是 public 的。这是一个典型的数据聚合的例子。
    • printPoint 函数接受一个 Point 对象的常量引用 (const Point&),并打印其坐标。直接访问 p.xp.y 是允许的。
    • Point p2 = {5.2, 8.7}; 这种初始化方式叫做聚合初始化,对于成员都是 publicstruct (或 class) 非常方便。

运行这段代码,你会看到类和结构体成员访问权限的不同以及它们如何被使用。


4. QA 闪卡 (Flash Cards)

卡片1:
Q: C++ 中 structclass 的主要区别是什么?
A: 它们之间唯一的根本区别是默认的成员访问权限。struct 的成员默认是 public [公有],而 class 的成员默认是 private [私有]。

卡片2:
Q: struct 成员的默认访问权限是什么?
A: public [公有]。

卡片3:
Q: class 成员的默认访问权限是什么?
A: private [私有]。

卡片4:
Q: 我们可以在 struct 中使用 private: 关键字吗?
A: 可以。同样,也可以在 class 中使用 public: 关键字。使用这些关键字可以覆盖默认的访问权限。

卡片5:
Q: 按照惯例,什么时候更倾向于使用 struct
A: 当定义主要用于数据聚合、成员基本都是公有、行为(方法)较少或较简单的类型时,例如 Point {double x, y;}

卡片6:
Q: struct Point { int x; int y; }; Point p; 如何访问 px 成员?
A: p.x (因为 struct 成员默认是 public)。


5. 常见误解或易犯错误

  1. 误解:struct 不能有成员函数或私有成员。

    • 澄清: 这是一个常见的误解,尤其对于有C语言背景的人。在C++中,struct 可以拥有成员函数、构造函数、析构函数,也可以有 privateprotected 成员,就像 class 一样。它们的功能几乎完全相同。
  2. 忘记定义末尾的分号。

    • 无论是 class 还是 struct,在定义的大括号 } 之后都必须有一个分号 ;
    • 错误:struct Point { double x; double y; }
    • 正确:struct Point { double x; double y; };
  3. 混淆默认访问权限。

    • class 中直接尝试访问未声明为 public 的成员,导致编译错误。
    • 或者,以为 struct 的成员需要像 class 一样通过公有方法访问,实际上可以直接访问(如果未显式声明为 private)。
  4. 过度使用 struct

    • 虽然 struct 可以做 class 能做的大部分事情,但如果一个类型需要复杂的行为、数据隐藏和封装,按照约定俗成的规范,使用 class 并将数据成员设为 private 会更清晰地表达你的设计意图。
  5. 认为 structclass “轻量”或性能更高。

    • 在C++中,编译后 structclass 在内存布局和性能上没有本质区别。编译器对待它们的方式(一旦访问权限被解析)是一样的。选择哪个主要基于语义和设计意图(即默认的公开性 vs. 默认的私有性)。

6. 编码练习

现在,让我们来动手练习一下!请你完成下面的代码。我们来定义一个表示矩形 (Rectangle) 的结构体,并计算它的面积。

cpp
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
#include <iostream>

// TODO 1: 定义一个名为 Rectangle 的 struct
// 它应该有两个 double 类型的公有成员变量: m_width 和 m_height
// YOUR CODE HERE

// TODO 2: 实现一个名为 printRectangleArea 的函数
// 这个函数接收一个 const Rectangle& (常量引用) 作为参数
// 它应该计算并打印出矩形的面积 (面积 = 宽度 * 高度)
// 例如: "Rectangle Area: [面积值]"
// YOUR CODE HERE

int main() {
// TODO 3: 创建一个 Rectangle 对象,名为 rect1
// YOUR CODE HERE

// TODO 4: 设置 rect1 的宽度为 5.0,高度为 8.0
// YOUR CODE HERE

// TODO 5: 调用 printRectangleArea 函数,并传入 rect1
// YOUR CODE HERE


Rectangle rect2 = {3.0, 7.0}; // 使用聚合初始化
printRectangleArea(rect2);

return 0;
}

请你尝试填写 // YOUR CODE HERE 部分。完成后,把你的代码发给我,我会帮你检查并给出反馈!加油!


🧑‍💻 User

include

// TODO 1: 定义一个名为 Rectangle 的 struct
// 它应该有两个 double 类型的公有成员变量: m_width 和 m_height
// YOUR CODE HERE
struct Rectangle {
double m_width;
double m_height;
Rectangle() = default;
Rectangle(double m_width_param, double m_height_param): m_width(m_width_param), m_height(m_height_param) {}
};
// TODO 2: 实现一个名为 printRectangleArea 的函数
// 这个函数接收一个 const Rectangle& (常量引用) 作为参数
// 它应该计算并打印出矩形的面积 (面积 = 宽度 高度)
// 例如: “Rectangle Area: [面积值]”
// YOUR CODE HERE
void printRectangleArea(const Rectangle& rect) {
std::cout << “Rectangle Area: “ << rect.m_width
rect.m_height << std::endl;
}

int main() {
// TODO 3: 创建一个 Rectangle 对象,名为 rect1
// YOUR CODE HERE
Rectangle rect1;

// TODO 4: 设置 rect1 的宽度为 5.0,高度为 8.0
// YOUR CODE HERE
rect1.m_width = 5.0;
rect1.m_height = 8.0;

// TODO 5: 调用 printRectangleArea 函数,并传入 rect1
// YOUR CODE HERE
printRectangleArea(rect1);

Rectangle rect2 = {3.0, 7.0}; // 使用聚合初始化
printRectangleArea(rect2);

return 0;

}


🤖 Assistant

太棒了!你很认真地完成了练习,代码写得非常好!让我们一起来看看。


7. 用户解答评估、参考答案及解释

你的解答评估:

评分: ★★★★★ (5/5 - 非常优秀!)

整体评价:
你的代码非常出色!你准确地完成了所有的 TODO 任务。

  • TODO 1 (定义 Rectangle 结构体): 你正确地定义了 Rectangle 结构体,包含了 double 类型的 m_widthm_height 成员。这些成员因为在 struct 中且没有显式访问修饰符,所以默认为 public,这完全符合题目要求。

    • 加分点: 你还主动添加了默认构造函数 Rectangle() = default; 和一个带参数的构造函数 Rectangle(double m_width_param, double m_height_param)。这非常好!这使得 Rectangle 结构体更加灵活和健壮。
      • Rectangle() = default; 确保了我们可以像 Rectangle rect1; 这样创建对象而无需立即提供初始值。
      • 带参数的构造函数则允许我们像 Rectangle rect3(10.0, 2.0); 这样在创建对象时就初始化其成员。
  • TODO 2 (实现 printRectangleArea 函数): 函数签名 void printRectangleArea(const Rectangle& rect) 非常好,使用了常量引用 (const Rectangle&),既避免了不必要的对象复制开销,也保证了函数不会意外修改传入的 Rectangle 对象。函数体内的面积计算 rect.m_width * rect.m_height 和输出格式也完全正确。

  • TODO 3 (创建 Rectangle 对象 rect1): Rectangle rect1; 正确地创建了一个 Rectangle 对象。因为你定义了默认构造函数,所以这行代码工作得很好。

  • TODO 4 (设置 rect1 的成员): rect1.m_width = 5.0;rect1.m_height = 8.0; 正确地通过直接成员访问设置了矩形的宽度和高度,这是因为 struct 成员默认是 public 的。

  • TODO 5 (调用 printRectangleArea): printRectangleArea(rect1); 正确地调用了函数并传入了对象。

总结: 你的代码不仅正确,而且展现了良好的编程习惯(比如添加构造函数和使用常量引用)。为你点赞!


参考答案与解释:

你的答案本身已经非常棒了!这里我再提供一个版本,主要是为了展示满足题目基本要求的最简洁写法,并和你添加了构造函数的版本进行对比说明。

cpp
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
#include <iostream>

// TODO 1: 定义一个名为 Rectangle 的 struct
// 它应该有两个 double 类型的公有成员变量: m_width 和 m_height
struct Rectangle {
double m_width;
double m_height;

// 你的版本中添加的构造函数非常好,让结构体更完整:
// Rectangle() = default;
// Rectangle(double w, double h) : m_width(w), m_height(h) {}
};

// TODO 2: 实现一个名为 printRectangleArea 的函数
// 这个函数接收一个 const Rectangle& (常量引用) 作为参数
// 它应该计算并打印出矩形的面积 (面积 = 宽度 * 高度)
// 例如: "Rectangle Area: [面积值]"
void printRectangleArea(const Rectangle& rect) {
double area = rect.m_width * rect.m_height; // 直接访问公有成员
std::cout << "Rectangle Area: " << area << std::endl;
}

int main() {
// TODO 3: 创建一个 Rectangle 对象,名为 rect1
Rectangle rect1; // 如果没有用户定义的构造函数,这会进行默认初始化(POD类型成员不确定值,但随后被赋值)
// 在你的版本中,因为有 Rectangle() = default; 所以这是明确的默认构造

// TODO 4: 设置 rect1 的宽度为 5.0,高度为 8.0
rect1.m_width = 5.0; // 直接访问公有成员
rect1.m_height = 8.0; // 直接访问公有成员

// TODO 5: 调用 printRectangleArea 函数,并传入 rect1
printRectangleArea(rect1);


Rectangle rect2 = {3.0, 7.0}; // 使用聚合初始化 (aggregate initialization [聚合初始化])
// 这种初始化方式对于成员都是 public 的 struct 或 class 非常方便
// 即使你定义了构造函数,如果它们符合一定规则,聚合初始化仍然可能有效(C++17+)
printRectangleArea(rect2);

// 如果你有带参数的构造函数,也可以这样创建:
// Rectangle rect3(10.0, 2.0); // 需要你定义的 Rectangle(double w, double h)
// printRectangleArea(rect3);

return 0;
}

解释关键点:

  1. struct Rectangle 的定义

    • 最基本的要求就是包含两个 double 类型的成员 m_widthm_height。因为是 struct,它们默认就是 public 的。
    • 你添加的构造函数 (Rectangle() = default;Rectangle(double w, double h)) 是很好的实践,它们:
      • 提供了更明确的对象初始化方式。
      • 使得类/结构体的意图更清晰。
      • 允许更复杂的初始化逻辑(如果需要的话)。
      • = default 告诉编译器生成一个标准的默认构造函数。
  2. printRectangleArea 函数

    • 使用 const Rectangle& rect 是最佳实践,原因之前已经提过(效率和安全)。
    • 直接通过 rect.m_widthrect.m_height 访问成员是合法的,因为它们是 public 的。
  3. main 函数中的对象创建与使用

    • Rectangle rect1;
      • 如果 Rectangle 没有任何用户定义的构造函数(就像我上面参考答案中注释掉构造函数的情况),rect1 会被默认构造。对于像 double 这样的内置类型成员,它们的值在此时是未定义的(除非 rect1 是全局或静态对象)。但紧接着你对它们进行了赋值,所以没问题。
      • 在你的代码中,因为有 Rectangle() = default;rect1 会被明确地默认构造。
    • rect1.m_width = 5.0; rect1.m_height = 8.0;:直接赋值,简单明了。
    • Rectangle rect2 = {3.0, 7.0};:这是 聚合初始化 (aggregate initialization)。它适用于没有用户声明的构造函数(或者在某些情况下,即使有,只要它们符合特定规则)、没有私有或保护的非静态数据成员、没有基类、没有虚函数的类型。你的 Rectangle 定义(即使有构造函数)通常也支持这种初始化,尤其是如果你的构造函数被认为是“非限制性的”。这是初始化简单数据结构体的一种非常简洁的方式。

你已经做得很棒了,这些解释主要是为了巩固你对这些概念的理解,并展示一些细微的变化和原因。


8. 建议与扩展问题

做得非常好!为了帮助你进一步巩固和扩展 struct 及相关概念的知识,这里有一些建议和可以思考的问题:

  1. 成员函数 (Member Functions)

    • 尝试给 Rectangle 结构体添加一个成员函数,比如 double getArea() const;,这个函数直接在结构体内部计算并返回面积。然后在 main 函数中像这样调用它:double area = rect1.getArea(); std::cout << "Area from member function: " << area << std::endl;
    • 思考:将计算面积的逻辑放在结构体内部(作为成员函数)和外部(如 printRectangleArea 全局函数)各有什么优缺点?
  2. 构造函数 (Constructors) 的更多思考

    • 你已经添加了构造函数,非常好!思考一下:如果 Rectangle 的宽度和高度必须是正数,你会如何在构造函数中处理无效输入(比如负数或零)?(提示:可以抛出异常 (exception [异常]),或者设置一个默认的有效值并打印警告)。
  3. struct vs class 的选择

    • 尝试将 Rectangle 改写成一个 class。你需要做什么改动才能让 main 函数中的代码(尤其是成员访问和对象创建)仍然能正常工作?(提示:public 关键字)。
    • 反思一下,对于 Rectangle 这个例子,你觉得使用 struct 还是 class 更能表达其“主要作为数据容器”的意图?
  4. 封装 (Encapsulation) 的初步接触

    • 如果我们想让 Rectanglem_widthm_height 不能被随意修改,而是只能通过特定的成员函数(比如 setWidth,并且这个函数可以检查宽度是否有效),你会怎么修改 Rectangle 的定义?这时 classprivate 成员会更有用武之地。
  5. 返回多个值

    • struct 常被用来从一个函数返回多个值。想象一个函数需要返回一个点的 x 和 y 坐标,你就可以定义一个 struct Point { int x; int y; }; 然后让函数返回一个 Point 对象。你能写一个简单的例子吗?
  6. struct 的大小 (Size of struct)

    • 在下一课中,你会学习到 sizeof 操作符。可以提前思考一下,sizeof(Rectangle) 的结果大概会是多少?(提示:它通常是其成员大小的总和,但有时会有内存对齐 (memory alignment [内存对齐]) 的影响)。

这些问题有些可能超出了当前课程的范围,但它们是指向未来学习方向的好路标。不要有压力,按照自己的节奏来探索。

你今天表现非常出色!继续保持这种学习的热情和实践的习惯,你会很快掌握C++的!如果你对任何解释或扩展问题有疑问,随时问我。


🧑‍💻 User

struct 中是不是完全不能有private关键词


🤖 Assistant

不,这个理解是不正确的。

struct 中完全可以使用 private (私有) 关键字,也可以使用 protected (保护) 关键字,就像在 class (类) 中一样。

structclass唯一区别在于它们的默认访问权限 (default access specifier [默认访问修饰符])

  • struct 中,如果你不写 public:private:protected:,那么它之后定义的成员默认是 public (公有) 的。
  • class 中,如果你不写 public:private:protected:,那么它之后定义的成员默认是 private (私有) 的。

一旦你显式地使用了 public:private:protected:,那么 structclass 在那一部分的行为就完全一样了。

举个例子:

cpp
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
#include <iostream>
#include <string>

struct Student {
// 这个成员默认是 public
std::string public_name;

private: // 从这里开始,下面的成员是 private
int student_id;
double gpa;

public: // 从这里开始,下面的成员又是 public
Student(std::string name, int id, double gpa_val) {
public_name = name;
student_id = id; // 构造函数可以访问 private 成员
gpa = gpa_val; // 构造函数可以访问 private 成员
std::cout << "Student " << public_name << " created with ID: " << student_id << std::endl;
}

void displayGPA() {
// 成员函数可以访问 private 成员
std::cout << public_name << "'s GPA: " << gpa << std::endl;
}

// 一个公有方法来获取通常不希望直接修改的私有数据
int getStudentID() const {
return student_id;
}
};

int main() {
Student s1("Alice", 101, 3.8);
s1.public_name = "Alicia"; // 正确,public_name 是公有的
std::cout << "Student's public name: " << s1.public_name << std::endl;

// s1.student_id = 102; // 编译错误! student_id 是 private 的
// std::cout << s1.gpa; // 编译错误! gpa 是 private 的

s1.displayGPA(); // 正确,displayGPA 是公有方法
std::cout << "Student ID (via getter): " << s1.getStudentID() << std::endl; // 通过公有方法访问私有数据

return 0;
}

在这个 Student 结构体的例子中:

  • public_name 因为在任何访问修饰符之前,所以它遵循 struct 的默认规则,是 public 的。
  • student_idgpaprivate: 关键字之后,所以它们是 private 的,不能在 main 函数中直接访问。
  • 构造函数 Student(...) 和成员函数 displayGPA()getStudentID()public: 关键字之后(或者在 private: 之前,因为 struct 默认是 public),所以它们是 public 的。这些公有函数可以在内部访问私有成员 student_idgpa

总结一下:

  • 你可以自由地在 struct 中使用 public:private:protected: 来控制成员的访问级别。
  • struct 只是意味着“如果你不指定,我就认为是 public 的”。
  • class 只是意味着“如果你不指定,我就认为是 private 的”。

尽管语法上允许,但按照 约定俗成 (convention [惯例]),当你的类型主要目的是数据聚合,且大部分成员希望是公有的时候,倾向于使用 struct。如果你的类型需要更强的封装,有很多私有数据和复杂的公共接口,则倾向于使用 class。但这更多的是一种风格和意图表达,而不是C++语言的强制限制。


评论