引言:
北京时间:2023/2/7/21:03,昨天熬夜写完博客,导致早上没起来,然后下午有课,只能到现在开始写博客了,刚刚玩了一下斗地主,发现斗地主这个游戏,玩起来还是挺舒服的,想当初我也是一个至尊70多个星的小佬佬,并且手持200多万的豆豆(大概两年前),当时真的是叱咤风云,谁与争锋,哈哈哈!很久很久没玩了,刚刚玩了一下,广告看了10几个,哈哈哈!所以各位假如无聊了,不知道干嘛的时候,不要打开腾讯视频、爱奇艺、优酷,更不要打开抖音、快手之类的,虽然我很喜欢打开腾讯视频,哈哈哈!因为你总会在这些东西里面,发现好看的东西,然后不能自拔,我就是典型案例,哈哈哈!所以我们以后无聊了,就去斗地主,斗着斗着,你就把豆子输光了,很好的就能抵制不能自拔,哈哈哈!好了,多的不说,今天我们继续学习我们的类和对象中的相关知识,并且我意识到再有两篇博客我们的类和对象就可以学完了,就可以算是正式的步入了C++门槛,So Let’s go !
规范实现日期类
在我们学习新的知识之前,我们先来把我们以前学习的有关类和对象的知识像什么拷贝构造,构造函数,析构函数,运算符重载、赋值重载、和一系列有关类内部的知识复习一下,我们就通过日期类的实现来完成这个工作吧!
实现日期类代码:(一图带你搞懂日期类)
上图就是有关日期类的完整代码,细节方面都有标注,如有某些地方写的不好,欢迎点评,此时我们把类和对象的知识和相关知识差不多都给复习了一遍,此时我们就继续学习一下别的有关类和对象的知识吧!
浅浅的摸一下io流
此时搞定了上述知识,我们会发现,除了上篇文章说的那5个运算符不能重载之外,其它的运算符是可以重载的,所以我们的流插入(<<) 运算符也是允许重载的,接下来我们就学习一下流插入运算符的重载方式和具体原理。
首先,我们搞清楚流插入的具体使用原理,如上图,此时我们可以发现,流插入运算符是在我们C++中的 < ios >库中的< ostream >库中的,在我们的<iostream>
这个头文件中,所以我们如果想使用cout函数就必须要引入头文件#include<iostream>
,但此时就算我们引入了头文件之后,我们还会发现一个问题,就是如图的这个问题,此时我们打印的时候,只能对内置类型打印,却不能对自定义类型进行打印,这是为什么呢?此时就涉及到一个新的知识点,叫自定义类型除了我们的(赋值运算符)可以直接使用之外,别的运算符都不允许直接给自定义类型使用,这又是为什么呢?原因就是:赋值运算符是一个默认成员函数,无论是自定义类型还是内置类型,都可以直接使用该运算符,所以得出结论:我的自定义类型,如果想要使用除了赋值运算符之外的任何一个运算符(当然这里特指的就是<<),都一定要去把该运算符进行重载(就是自我实现),否则自定义类型,永远用不了除了赋值运算符以外的运算符。但是此时又有一个问题来了,为什么我的内置类型可以直接使用流插入运算符呢?,此时原因就是: 在我们的<iostream>
库中,库中的<ostream>
已经把有关内置类型的所有的流插入运算符进行了重载,所以我们可以在包含了头文件的情况下,是可以直接使用流插入(<<) 打印内置类型的,所以此时第一个问题的答案就来了,就是因为<iostream>
库中,并没有自定义类型的流插入运算符重载,所以此时库中没有,自然不能直接调用,就要通过自己实现了,并且有人会问,为什么<iostream>
库没有自定义类型的流插入重载呢?这个问题非常好回答,自然就是因为:我们的编译器并不能控制我们的自定义对象,因为自定义对象是我们自己实现出来的,编译器中并没有这个设定,此时就只能通过自己来完成这个自定义类型的流插入重载。
总:所以本质上根本没有什么自动实现,都是通过调用已经实现了的重载而已,只是看这个重载需不需要你自己去实现而已(是否已经存在)。
例:下图中就是我们的<ostream>
库中的内置类型的流插入(<<) 函数的重载,不仅涉及运算符重载,还涉及函数重载:
自定义类型流插入的实现
弄懂了这些,我们知道了自定义类型除了赋值运算符之外是没有其它运算符重载函数可以使用的,所以此时我们为了自定义类型可以直接使用流插入(<<) 运算符,我们是必须要去自己实现流插入(<<) 运算符的重载的,因为不重载,我们就不能打印自定义类型,所以运算符重载的意义就是可以支持自定义类型的使用,如果C++中没有运算符重载,那么此时流插入(<<) 使用不了,那么自定义类型就无法被打印,原因:因为在C++中,我的类是受到访问限定符的限制的,此时如果没有重载流插入(<<),单单只是用C语言中的printf
,此时就会因为printf
只能直接访问类中的公有成员变量,不能访问私有的成员变量,就会变得较为不好处理,所以此时就有了运算符重载这个概念,特指我们的流插入(<<) 此时就可以很好的使用运算符的重载对无论是内置类型,还是自定义类型都可以很好的进行打印了。所以此时我们就自己实现一下流插入(<<) 这个运算符吧!
此时的实现方法有两种,一种是把流插入(<<) 运算符函数写在我们的类中,一种是把它写在全局中(也就是类外),此时我们就先来看一下将其写在类中的场景:
此时通过上图,我们就可以发现,在流插入(<<) 运算符中,左操作数是第一个参数,右操作数是第二个参数,所以现在发现不是d1流入cout,而是cout流入d1中,所以我们猜想应该是在传递参数的时候出现了问题,但是我们又会发现,我们并不能传递cout作为第一个参数,d1作为第二个参数,因为d1参数的位置,在我们的类中,就被默认成了this指针,而我们并不能对this指针进行任何的修改,所有如果我们把该函数写在类中的话,就会导致流插入(<<) 运算符的左边永远都是this指针,也就是我们创建的对象,永远不可能是我们的cout函数,此时就可以发现,我们如果把该函数写在类中是不能完成我们的预期的,所以我们将流插入(<<) 运算符函数写在我们的类中是不成立的,所以我们使用第二个场景。
写在类外的场景:
此时按照这样子写,有了返回值之后,我们的流插入(<<) 运算符就算是真正的自己实现好了,并且记住想要重载这个运算符,首先就一定要去库里面调用该运算符所在的那个类,也就是<ostream>
类中,这是我们实现这个流插入(<<) 运算符的首要条件,没有这个类就不能实现该运算符的自定义,也就不能使自定义对象得到很好的打印了。
流提取的实现
搞定了流插入,此时我们肯定是接着搞定我们的流提取,大致原理差不多,此时我们就不做过详细的介绍了,如下图:
const成员函数
搞明白了上述的知识,我们把运算符的重载就算是基本上搞清楚得差不多了,此时我们就可以进入到新的知识的学习,所以我们现在就来看一看什么是const成员函数,总而言之看到成员函数,我们就应该要想到是在我们的类中的,所以const成员函数,最大的前提就是,该函数是在类中,并且该函数是被const修饰的,是具有常属性的,当的对象就行调用该函数的时候,是不会发生权利放大的,只有可能是权利缩小,所以加上了const的成员函数是非常的安全的,当然前提是合适的情况之下,如图:我们通过一个小问题,正式的来看一看什么是const成员函数。
问题:
此时问题的原因就在于其涉及了一个权利放大的问题,因为从图中,我们可以看出,此时的a是被const修饰了的,并且a直接去调用了Print函数,且因为Print函数是在类中的,所以a代表的就是就是我们的this指针,且a是具有常属性的,所以不能改变,所以此时的this指针也就更不可能可以改变,所以此时是不可能可以把a传给this指针的,因为如果可以传给this指针的话,那么this指针就可以拿着这块地址,把a给改变,此时就会造成权利放大的问题,编译器是不可能允许的,所以此时想要让a可以调用Print的话,就一定要让this指针也具有const(常属性),这样才可以形成权利的保持,a才可以调用,但此时问题就来了,this指针怎样才可以被const修饰呢? this指针是一个隐藏起来的变量,要怎样才可以使它有常属性了,面对这个问题,此时我们的const成员函数就可以很好的解决,直接让整个成员函数具有常属性,这样this自然而然的就也具有常属性了。
所以解决方法就是在该函数的后面,跟上一个const就行了(编译器规定好了的)
如图:
并且因为平时我们为了减少拷贝构造,一般会在接收参数的使用,使用传引用的方法传参,并且我们传引用传参,一般都会加上const,此时加上了const,在进行对象调用成员函数的时候,就非常的容易导致对象因为权限放大的问题而报错,而不可以去调用相应的函数,所以此时不管怎样,我们都最好是给我们的成员函数加上一个const,这样不管是什么情况,我都可以很好的去调用该函数了,因为这样就不可能出现权利放大的问题,函数调用就不会因为该问题而出问题了,所以按照这个原理,此时我们的类中的函数都可以适当的加上一个const,像我们昨天实现的日期类中的比较大小的、判断相不相等的、反正只要是内部变量不会被修改的,就都可以加上const,这样就可以使我们的代码变得更加的规范和安全。
但是注意:我们的+和-其实本质上是可以加上const的,只有像+=和-=和++和–这种才不可以(前提:在类中,并且属于是成员函数,像友元并不算成员函数的)并且声明和定义都要加。
取地址和const取地址操作符重载
搞定了上述的const成员函数,此时我们就最后来看一看我们的6大默认成员函数中最后两个取地址和const取地址操作符重载,首先这两个默认成员函数是满足默认成员函数的特性的。我们不自己实现,这个函数编译器也是会自己去实现的。
取地址重载函数的自己实现
取地址函数的使用
回顾运算符重载的使用
弄懂了上述的知识,我们可以发现取地址符号的重载对我们来说是没有什么意义的,所以不需要过于在意,只要知道有这个东西,并且知道这个东西怎么使用就行了,此时我们就在取地址符号的基础上,我们再来回顾一下运算符重载是如何具体使用的,如下代码所示:
#include<iostream>
#include<assert.h>
using namespace std;
class Array
{
public:
int& operator[](int i)//直接重载这个括号,然后使用这个括号就是在执行我们这个函数
{
assert(i < 10);
return _a[i];
}
const int& operator[](int i) const//所以此时可以提供一个重载版本给下面const修饰的使用
{
//所以一个函数有可能是普通的函数,也有可能是const函数,所以一个运算符可以写多种版本的函数重载
assert(i < 10); //并且此时只要后面加const就是构成了我们的const成员函数了,前面加的那个const只是为了确定返回值的类型而已
return _a[i];
}
private:
int _a[10];
int _size;
int _capacity;
};
void Function(const Array&a)
{
for (int i = 0; i < 10; ++i)
{
cout << a[i] << " ";//此时这个就是经典的权利放大,调不动,所以要有const成员函数
}
}
int main()
{
Array a;
for (int i = 0; i < 10; ++i)
{
a[i] = i;//此时我的i是访问不到我的私有成员变量的,但是此时上面的括号函数已经把_a[],作为返回值返回给我了,所以这整句代码就是一个函数调用的意思
}
for (int i = 0; i < 10; ++i)
{
cout << a[i] << " ";
}
cout << endl;
Function(a);
return 0;
}
从代码中,我们可以发现,我们的运算符重载int& operator[](int i)
其实本质上就是一个函数,在使用该重载运算符的过程就是在调用一个函数的过程而已,并且我们的运算符重载是可以写成多样的形式的,如:const int& operator[](int i) const
,可以写成具有常属性的const成员函数,也可以写成不具有const的成员函数,怎么写具体就是看你想要怎么使用,如果你传参是时候使用的是带有常属性的参数,那么你写的成员函数也就应该带有常属性,这叫做防止权利的放大,做到权利的保持,当然如果你写成const成员函数,那么缺点就是你不能进行修改,别的地方基本是没有缺点的,优点居多,再次强调,使用重载运算符就是在使用函数调用,如上述代码中的a[i] = i;
和cout << a[i] << " ";
其中使用的[],就是在调用我们的int& operator[](int i)
运算符重载函数,并且像上述Function
函数,我们可以看到,它的参数是一个具有常属性的参数,所以此时普通的运算符重载函数,它是调用不了的,原因就是会导致权利放大,所以此时必须要在int& operator[](int i)
该重载运算符函数的基础之上,再通过函数重载的方式再重载一个const成员函数const int& operator[](int i) const
,这样才可以同时满足我的各种需求,所以代码具体怎么写,细节怎么处理,大部分都是由我们自己决定的。
再谈构造函数(初始化列表的使用)
当初我们学习了构造函数的使用,但是我们只学了70%的样子,所以现在我们继续把有关构造函数的知识给学习一下,从而彻底搞定小小的构造函数,此时我们就来学一下什么初始化列表;初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式。如下图所示:
所以如图所示,我们可以发现,当我们想要使用const修饰的变量时,就必须要给其初始化,然而,我们又发现一个问题,就是我们的类中只有声明的地方,并没有给变量定义的地方,所以此时就引出了我们的初始化成员列表的概念,并且哪个对象调用构造函数,初始化列表就是它所有成员变量定义的位置,不管是否对初始化列表就行定义,编译器此时都会默认每个变量是在初始化列表初始化的,如下图就是初始化成员列表的具体使用方法和细节处理:
所以此时我们可以发现初始化列表的一些的好处,它可以帮助我们进行成员变量的定义,和处理const成员变量,引用成员变量和没有默认构造函数的自定义成员变量的初始化定义,并且每一个成员变量都只能在初始化列表初始化一次,所以此时我们就大致学会了在构造函数中的初始化列表的使用。此时我们通过一个有关初始化列表的题目来看一下初始化的一些小细节的处理。如下代码:
#include<iostream>
using namespace std;
class A
{
public:
A(int a)
:_a1(a),_a2(_a1)
{
cout << "证明调用过该函数" << endl;
}
void Print()
{
cout << _a1 << endl << _a2 << endl;
}
private:
int _a2;
int _a1;
};
int main()
{
A a(1);
a.Print();
return 0;
}
运行结果如下:
此时就可以说明一个问题,**成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。**所以按照这个原理,此时得到的答案就是1和随机值,不是1 和1 ,这点要注意一下。
什么是explicit
此时搞懂了上述的知识,我们现在就来看一下什么是explicit,和explicit的基本使用方法是什么 ;此时针对于单参数的构造函数,A a1(1);
这种,此时我们也可以写成这样A a1 = 1;
但当我们写成这样时,此时就涉及了隐式类型转换,此时我们整形(int)先隐式转换成类(A)类型,再讲我们的数据赋值给a1这个对象,例如:我们在C语言中的double i = d;
,所以当我们了解了什么是隐式转换,此时我们就来聊一聊,构造和拷贝构造的那点事,如:此时的A a1(1);
因为是直接进行单个参数的传递,所以此时就可以直接调用构造函数进行a1对象的初始化,但是如果是:A a1 = 1;
这样的,没有进行直接传值的,此时就会涉及到临时变量的问题,因为要隐式转换,所以它此时按照正常来说,会调用一次构造函数,去构造出一个A类型的临时变量,然后再通过A类型的临时变量将我们的数据拷贝构造给aa2,从而实现a1的初始化。但正常来说是这样的,此时由于编译器会对这种类型进行一定的优化,所以此时一次构造和一次拷贝构造会被优化成就一次构造,就会直接就用1去构造a1,不需要用1 去构造一个A类型的变量,再同过A类型的变量拷贝构造给aa2了。如下图:就可以很好的证明这个特点
#include<iostream>
using namespace std;
class A
{
public:
A(int a)
:_a1(a)
{
cout << "A(int a)" << endl;
}
A(const A& aa)
:_a1(aa._a1)//这个的意思就是用一个已经初始话的成员变量去初始化另一个变量(名字相同,但是却是两个不同的变量)
{
cout << "const A& aa" << endl;
}
private:
int _a1;
};
int main()
{
A aa1(1);//此时按照我们以前学的有关,构造和拷贝构造的知识,此时这里应该是要调用一次构造就行了
A aa2 = 1;//一次构造和一次拷贝构造=>编译器会进行优化,直接就用1去拷贝构造aa2,不需要用1 去构造一个A类型的变量,再同过A类型的变量拷贝构造给aa2
//所以答案是两次构造就行来了
//const A& ref = 10;//注意此时用的是引用,所以在进行拷贝构造的时候,经过类型转换生成了那个临时变量是具有常属性的,所以只有加了常属性的ref此时临时变量才可以拷贝构造给ref,不然就是一个权利放大
return 0;
}
并没有调用拷贝构造,而是优化成了一次构造,并且此时还涉及一个问题,就是关于引用的问题, const A& ref = 10;
注意此时用的是引用,所以在进行拷贝构造的时候,经过类型转换生成了那个临时变量是具有常属性的,所以只有加了常属性的ref此时临时变量才可以拷贝构造给ref,不然就是一个权利放大问题,所以要注意好是否产生临时变量和const的合理使用
所以针对隐式转换讲了这么多,此时我们就可以来看看关键字(explicit) 的使用了,explicit的作用就是可以防止你进行类型的转换,起着不允许自定义类型直接变成内置类型的作用,使用场景:explict A(int a);
此时对我们的构造函数加了这个新的关键字之后,我们的A类型就不允许被转换成内置类型了,也就是不能用int等内置类型就行对A类型对象的初始化。当然这个知识针对于单参数的构造函数而已
针对多参数的构造函数
多参数的想要进行隐式类型转换,此时就用我们的大括号就可以支持了,如: A a1 ={1,1};
这样就支持类型转换,当然前提没有加explicit修饰构造函数。并且此时const A& ret = {2,2};
这种代码和上述所说一样是涉及到了临时变量的常属性问题,这里不多加描述了。
什么是友元
友元我们其实在上述的知识中,我们已经学习过了,就是把一个全局变量的函数(或者称为该类外部的函数),在某个类中进行声明的,用关键字(friend) 进行声明,此时这样我的该函数就可以去使用类的成员变量或者成员函数了,就相当于该函数就是类的朋友,可以使用朋友内部的东西,如下图:就是一个友元的使用方式
友元函数的说明:
友元函数可访问类的私有和保护成员,但不是类的成员函数 |
---|
友元函数不能使用const修饰 |
友元函数可以在类定义的任何地方声明,不受类访问限定符限制 |
一个函数可以是多个类的友元函数 (注意一下) |
友元函数的调用与普通函数的调用原理是相同的 |
友元类
就是表示不仅函数可以是类的友元函数,类也可以是类的友元,这个就叫友元类,无相互关系,只看谁的类声明了谁的函数或者声明了谁的类,谁就可以访问,还是以声明为主。你的是我的,我的还是我的,这句话很形象说明。
总:友元函数可以让我们直接访问类的私有成员变量或者函数,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时加上friend关键字就行了,但由于友元容易破坏类的封装,所以我们应该尽量少使用友元函数。
什么是static
概念:声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数,静态成员变量一定要在类外进行初始化。
实现一个类,计算程序中创建出了多少个类对象的代码如下:
原理:使用构造函数,因为不管怎样,只要有使用我们的类创建一个对象出来,哪么此时编译器就会优先去调用我们构造函数来对这个对象就行初始化,所以只要在构造函数中使用一个计数器(前提这个计数器是创建在全局的),此时就可以很好的使用这个计数器来统计我们创建了几个对象,但当我们一写完,却发现报错了,这是什么原因呢?原因就是:count
在我们的C语言库中是一个已经被实现了的函数,此时如果我们使用的话,就会和库冲突,所以此时就有了两个解决的方法,一个是把库的展开using namespace std;
给取消掉,还有一个就是用我们接下来要学习的static静态变量来处理,此时我们就直接把定义在全局的count计数器给放到类中,这样就可以避免命名冲突的问题,但是此时又有一个问题,就是把计算器放入了类中,此时这个计数器就不再是整个程序的了,而是变成某一个类独有的了,所以此时不符合我们的预期,所以此时我们就可以使用我们的static静态成员变量,让它去修饰该类中的那个计数器,这样就可以使这个计数器还是属于整个程序,而不是单独的某个类,这样,我们就可以真正的利用这个计数器去统计整个程序当中创建了几个类对象了。
注意点:1.此时的该静态成员变量不可以在类中进行初始化,只能是在全局进行初始化,因为此时的这个计数器是属于整个程序的,并不属于该类,类中只能初始化该类自己的成员变量 。2.访问静态成员变量一般会用到静态成员函数,这样在写法上更好。3.初始化一个数组时,数组的大小决定初始化的次数,也就是决定调用构造函数的次数。4.区分好类域,指明好函数调用的是那一个类,并且我们是可以直接使用类对象进行对类域的使用的。
#include<iostream>
using namespace std;
class A
{
public:
A(int a = 0)//构造函数
{
++_count;
}
A(const A& aa)//拷贝构造函数
{
++_count;
}
static int GetCount()//下面那个叫静态成员变量,这个叫静态成员函数,特性:在类中没有this指针,因为其根本不属于该类,是属于整个程序的。
{
//_a++;//此时没有this指针,所以就不可以去访问外部的非静态成员变量
return _count;
}
//一个自定义对象不是调用构造函数就一定是调用拷贝构造函数
//private:
static int _count;// 把这个_count搞成静态的,此时它就是属于所有的对象,属于整个程序,这样就可以计算每个类创建了的对象了
//此处是声明,不可以给缺省值,想初始化只能下面这样,因为类中只能初始化自己的成员变量,有静态成员变量就一定要有静态成员函数,这样才可以正常使用
int _a = 0;
};
int A::_count = 0;//静态成员的初始化
void Function(A a)
{
A aa1;
A aa2;
A aa3;
A aa4[10];//直接会调用10次构造函数(初始化10次)这个联想到调式过程就很好理解
cout << A::_count << endl;
}
int main()
{
A a1;
A a2;
A a3;
A a4;
A aa;
A* ptr = nullptr;
//Function(a1);
cout << A::_count << endl;//想要访问,就一定要声明类的作用域在哪里
cout << a1._count << endl;
cout << ptr->_count << endl;//公有通过直接调用成员变量使用,ptr只是为了声明类域,并不是解引用,没有进行访问
cout << a3.GetCount() << endl;//私有通过函数调用
cout << aa.GetCount() - 1 << endl;//这种写法就是为了可以去访问类中的函数,但是导致我们多了一个对象,所以要-1,但是此时这种写法非常的傻
cout << A::GetCount << endl;//所以此时我们就可以实现一个静态成员函数,这样我们就可以直接访问类中的那个静态成员函数了,因为此时这个函数是属于整个程序的,所以不需要对象,只需要声明一下类域就行了
return 0;
}
搞定完这些,此时我们来总结一下它的特性: |
---|
静态成员变量是为所有类对象共同使用的,不属于某个具体的对象,存放在静态区 |
静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明 |
类静态成员即可用类名::静态成员 或者对象.静态成员 来访问 |
静态成员函数没有隐藏的this指针,不能访问任何非静态成员 |
静态成员也是类的成员,受public、protected、private访问限定符的限制 |
总:静态成员函数由于没有this指针无法访问非静态成员函数,而非静态成员函数由于具有this指针,所以可以访问静态成员函数。
什么是匿名对象
此时搞定了上述的一系列的问题,此时我们学习一个新的语法,叫匿名对象,例:此时我有一个类叫Sum,此时用这个类定义对象,我们就可以直接使用Sum();
这个就是一个匿名对象的定义,此时的特性有:1.没有名字,2.生命周期就只有一行。所以此时凭借着生命周期只有一行的特性,我们对匿名对象的使用,就是在当我们要返回一个值和调用类中的某个函数或者成员变量的时候,我们经常就会直接使用匿名对象的调用,例:经常直接使用 cout << Sum().Solution(10) < endl;
而不是先用类去创建一个对象,然后使用对象去访问类,例:先Sum a;
然后再cout << a.Solution(10) << endl;
所以这就是匿名对象的好处,在有的情况下非常的方便。举例:一个一次性的杯子和一个经常用的水杯的区别。
编译器内部优化
这个知识点,我们在上述的构造函数和拷贝构造的时候大致讲过了,这里就不多加描述了,因为我要去睡觉了,北京时间:2023/2/9/15:22,感谢下午每课,不让这篇文章要到晚上才能发。午觉是人生中最快乐的事情。See you everyone.
总结:C++中类和对象的基本知识我们差不多是学完了,所以此时我们要开始学习C++后面的内容了,但我们并不可以和类和对象说拜拜,无论是以后我们会经常的使用类和对象,还是我们需要去把之前有关类和对象的知识给复习一下,都使我们离不开类和对象,类和对象可以说是C++中非常重要的一块知识了。So,lest’go.
转载:https://blog.csdn.net/weixin_74004489/article/details/128925934