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
[类]。我们将重点理解 struct
和 class
之间的核心区别——默认的成员访问权限,并探讨 struct
的常见使用场景。
2. 详细解释
让我们一步一步来揭开 struct
的神秘面纱。
2.1 什么是 struct
(结构体)?
在C++中,struct
,全称 structure [结构体],是一种用户定义的复合数据类型。它允许你将不同数据类型的变量组合成一个单一的逻辑单元。想象一下,你要描述一个“点”,一个点有x坐标和y坐标。使用 struct
,你就可以创建一个名为 Point
的类型,它内部包含x和y两个成员。
2.2 struct
与 class
(类) 的关系
在C++中,struct
和 class
的功能几乎是完全相同的。你用 class
能做的事情,用 struct
基本上也都能做:
- 它们都可以包含数据成员 (member variables [成员变量])。
- 它们都可以包含成员函数 (member functions [成员函数],也叫方法 methods [方法])。
- 它们都可以使用继承 (inheritance [继承])、多态 (polymorphism [多态]) 等面向对象的特性。
2.3 核心区别:默认访问权限 (Default Access Specifier)
这是 struct
和 class
之间 唯一 的根本区别:
struct
:如果你不明确指定,其所有成员(包括数据成员和成员函数)默认都是public
[公有] 的。这意味着在结构体外部的代码可以直接访问这些成员。class
:如果你不明确指定,其所有成员默认都是private
[私有] 的。这意味着这些成员只能在类的内部(比如类的成员函数中)被访问,外部代码不能直接访问。
让我们来看个例子:
1 | // 使用 struct 定义 Cat |
在上面的例子中:
- 对于
Cat
结构体,我们可以直接在main
函数中通过myCat.m_name
来访问和修改它的名字,因为m_name
默认是public
。 - 对于
Dog
类,如果我们尝试myDog.m_name = "Buddy";
,编译器会报错,因为m_name
默认是private
。我们必须通过一个public
的成员函数(如setName
)来间接设置名字。
2.4 显式指定访问权限
虽然 struct
和 class
有默认的访问权限,但我们随时可以使用 public:
,private:
,和 protected:
(我们稍后会学习 protected
[保护]) 这些 访问修饰符 (access specifiers) 来明确指定成员的访问级别。
1 | struct ExampleStruct { |
一旦你显式地使用了 public:
或 private:
,那么 struct
和 class
在那一部分的行为就完全一样了。比如,一个所有成员都显式声明为 public
的 class
,和一个所有成员都显式声明为 public
(或者不声明,依赖默认行为) 的 struct
,在使用上是没有区别的。
2.5 何时使用 struct
?(约定俗成的规范)
尽管 struct
和 class
功能上几乎等同(除了默认访问权限),但在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. 代码示例
让我们通过课程中提到的例子来具体看看 struct
和 class
的行为。
1 |
|
代码解释:
Dog
类:m_name
是private
的(因为class
默认是private
,且没有显式声明为public
)。- 我们提供了
public
的构造函数Dog(std::string name)
来初始化m_name
。 - 我们提供了
public
的setName
和getName
函数来间接访问和修改m_name
。 - 直接在
main
中访问dog1.m_name
会导致编译错误。
Cat
结构体:m_name
和m_age
都是public
的(因为struct
默认是public
)。- 我们提供了
public
的构造函数Cat(std::string name, int age)
。 - 可以直接在
main
中通过cat1.m_name
和cat1.m_age
来访问和修改它们。
Point
结构体:x
和y
都是public
的。这是一个典型的数据聚合的例子。printPoint
函数接受一个Point
对象的常量引用 (const Point&
),并打印其坐标。直接访问p.x
和p.y
是允许的。Point p2 = {5.2, 8.7};
这种初始化方式叫做聚合初始化,对于成员都是public
的struct
(或class
) 非常方便。
运行这段代码,你会看到类和结构体成员访问权限的不同以及它们如何被使用。
4. QA 闪卡 (Flash Cards)
卡片1:
Q: C++ 中 struct
和 class
的主要区别是什么?
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;
如何访问 p
的 x
成员?
A: p.x
(因为 struct
成员默认是 public
)。
5. 常见误解或易犯错误
误解:
struct
不能有成员函数或私有成员。- 澄清: 这是一个常见的误解,尤其对于有C语言背景的人。在C++中,
struct
可以拥有成员函数、构造函数、析构函数,也可以有private
和protected
成员,就像class
一样。它们的功能几乎完全相同。
- 澄清: 这是一个常见的误解,尤其对于有C语言背景的人。在C++中,
忘记定义末尾的分号。
- 无论是
class
还是struct
,在定义的大括号}
之后都必须有一个分号;
。 - 错误:
struct Point { double x; double y; }
- 正确:
struct Point { double x; double y; };
- 无论是
混淆默认访问权限。
- 在
class
中直接尝试访问未声明为public
的成员,导致编译错误。 - 或者,以为
struct
的成员需要像class
一样通过公有方法访问,实际上可以直接访问(如果未显式声明为private
)。
- 在
过度使用
struct
。- 虽然
struct
可以做class
能做的大部分事情,但如果一个类型需要复杂的行为、数据隐藏和封装,按照约定俗成的规范,使用class
并将数据成员设为private
会更清晰地表达你的设计意图。
- 虽然
认为
struct
比class
“轻量”或性能更高。- 在C++中,编译后
struct
和class
在内存布局和性能上没有本质区别。编译器对待它们的方式(一旦访问权限被解析)是一样的。选择哪个主要基于语义和设计意图(即默认的公开性 vs. 默认的私有性)。
- 在C++中,编译后
6. 编码练习
现在,让我们来动手练习一下!请你完成下面的代码。我们来定义一个表示矩形 (Rectangle
) 的结构体,并计算它的面积。
1 |
|
请你尝试填写 // 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_width
和m_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);
正确地调用了函数并传入了对象。
总结: 你的代码不仅正确,而且展现了良好的编程习惯(比如添加构造函数和使用常量引用)。为你点赞!
参考答案与解释:
你的答案本身已经非常棒了!这里我再提供一个版本,主要是为了展示满足题目基本要求的最简洁写法,并和你添加了构造函数的版本进行对比说明。
1 |
|
解释关键点:
struct Rectangle
的定义:- 最基本的要求就是包含两个
double
类型的成员m_width
和m_height
。因为是struct
,它们默认就是public
的。 - 你添加的构造函数 (
Rectangle() = default;
和Rectangle(double w, double h)
) 是很好的实践,它们:- 提供了更明确的对象初始化方式。
- 使得类/结构体的意图更清晰。
- 允许更复杂的初始化逻辑(如果需要的话)。
= default
告诉编译器生成一个标准的默认构造函数。
- 最基本的要求就是包含两个
printRectangleArea
函数:- 使用
const Rectangle& rect
是最佳实践,原因之前已经提过(效率和安全)。 - 直接通过
rect.m_width
和rect.m_height
访问成员是合法的,因为它们是public
的。
- 使用
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
及相关概念的知识,这里有一些建议和可以思考的问题:
成员函数 (Member Functions):
- 尝试给
Rectangle
结构体添加一个成员函数,比如double getArea() const;
,这个函数直接在结构体内部计算并返回面积。然后在main
函数中像这样调用它:double area = rect1.getArea(); std::cout << "Area from member function: " << area << std::endl;
- 思考:将计算面积的逻辑放在结构体内部(作为成员函数)和外部(如
printRectangleArea
全局函数)各有什么优缺点?
- 尝试给
构造函数 (Constructors) 的更多思考:
- 你已经添加了构造函数,非常好!思考一下:如果
Rectangle
的宽度和高度必须是正数,你会如何在构造函数中处理无效输入(比如负数或零)?(提示:可以抛出异常 (exception [异常]),或者设置一个默认的有效值并打印警告)。
- 你已经添加了构造函数,非常好!思考一下:如果
struct
vsclass
的选择:- 尝试将
Rectangle
改写成一个class
。你需要做什么改动才能让main
函数中的代码(尤其是成员访问和对象创建)仍然能正常工作?(提示:public
关键字)。 - 反思一下,对于
Rectangle
这个例子,你觉得使用struct
还是class
更能表达其“主要作为数据容器”的意图?
- 尝试将
封装 (Encapsulation) 的初步接触:
- 如果我们想让
Rectangle
的m_width
和m_height
不能被随意修改,而是只能通过特定的成员函数(比如setWidth
,并且这个函数可以检查宽度是否有效),你会怎么修改Rectangle
的定义?这时class
和private
成员会更有用武之地。
- 如果我们想让
返回多个值:
struct
常被用来从一个函数返回多个值。想象一个函数需要返回一个点的 x 和 y 坐标,你就可以定义一个struct Point { int x; int y; };
然后让函数返回一个Point
对象。你能写一个简单的例子吗?
struct
的大小 (Size of struct):- 在下一课中,你会学习到
sizeof
操作符。可以提前思考一下,sizeof(Rectangle)
的结果大概会是多少?(提示:它通常是其成员大小的总和,但有时会有内存对齐 (memory alignment [内存对齐]) 的影响)。
- 在下一课中,你会学习到
这些问题有些可能超出了当前课程的范围,但它们是指向未来学习方向的好路标。不要有压力,按照自己的节奏来探索。
你今天表现非常出色!继续保持这种学习的热情和实践的习惯,你会很快掌握C++的!如果你对任何解释或扩展问题有疑问,随时问我。
🧑💻 User
struct 中是不是完全不能有private关键词
🤖 Assistant
不,这个理解是不正确的。
struct
中完全可以使用 private
(私有) 关键字,也可以使用 protected
(保护) 关键字,就像在 class
(类) 中一样。
struct
和 class
的唯一区别在于它们的默认访问权限 (default access specifier [默认访问修饰符]):
- 在
struct
中,如果你不写public:
、private:
或protected:
,那么它之后定义的成员默认是public
(公有) 的。 - 在
class
中,如果你不写public:
、private:
或protected:
,那么它之后定义的成员默认是private
(私有) 的。
一旦你显式地使用了 public:
、private:
或 protected:
,那么 struct
和 class
在那一部分的行为就完全一样了。
举个例子:
1 |
|
在这个 Student
结构体的例子中:
public_name
因为在任何访问修饰符之前,所以它遵循struct
的默认规则,是public
的。student_id
和gpa
在private:
关键字之后,所以它们是private
的,不能在main
函数中直接访问。- 构造函数
Student(...)
和成员函数displayGPA()
、getStudentID()
在public:
关键字之后(或者在private:
之前,因为struct
默认是public
),所以它们是public
的。这些公有函数可以在内部访问私有成员student_id
和gpa
。
总结一下:
- 你可以自由地在
struct
中使用public:
,private:
和protected:
来控制成员的访问级别。 struct
只是意味着“如果你不指定,我就认为是public
的”。class
只是意味着“如果你不指定,我就认为是private
的”。
尽管语法上允许,但按照 约定俗成 (convention [惯例]),当你的类型主要目的是数据聚合,且大部分成员希望是公有的时候,倾向于使用 struct
。如果你的类型需要更强的封装,有很多私有数据和复杂的公共接口,则倾向于使用 class
。但这更多的是一种风格和意图表达,而不是C++语言的强制限制。