Learning C++ No.5【类和对象No.4】

简介: 北京时间:2023/2/6/10:45,今天起床时间10:00,睡迟了,可能是因为昨天睡的有点晚,但是庆幸的是昨天没人叫我玩鹅鸭杀,我们把博客给更新了,哈哈哈!所以今天我们趁热打铁,一鼓作气把类和对象给搞定。但是伴随着开学的钟声快要响起,考试即将来临,说不慌是假,但是我就是没有复习的动力,可能是上课太过于摆烂,连复习什么我都不知道,也可能是我觉得小小的考试,60分并难不倒我,也可能是我对C++的学习比较上头。反正综合现在来看,C++对我的吸引比较强。所以车道山前必有路,开学干的事情,开学再说,现在是我自己的快乐时间,我们今天就来把有关类和对象中的重点知识,运算符重载以及相关知识学习一下。

引言:

北京时间:2023/2/6/10:45,今天起床时间10:00,睡迟了,可能是因为昨天睡的有点晚,但是庆幸的是昨天没人叫我玩鹅鸭杀,我们把博客给更新了,哈哈哈!所以今天我们趁热打铁,一鼓作气把类和对象给搞定。但是伴随着开学的钟声快要响起,考试即将来临,说不慌是假,但是我就是没有复习的动力,可能是上课太过于摆烂,连复习什么我都不知道,也可能是我觉得小小的考试,60分并难不倒我,也可能是我对C++的学习比较上头。反正综合现在来看,C++对我的吸引比较强。所以车道山前必有路,开学干的事情,开学再说,现在是我自己的快乐时间,我们今天就来把有关类和对象中的重点知识,运算符重载以及相关知识学习一下。


我们在前面学习了构造函数,析构函数,拷贝构造函数以及相关的知识之后,此时我们就利用这些知识来实现一个计算日期的日期类代码。意思为在一个日期上加上天数,然后得到加上天数之后的日期


代码如下:也算是复习和运用之前的各种知识

36.png

深入拷贝构造

从上篇博客中,我们知道了什么是拷贝构造,并且大致学会了如何使用拷贝构造,此时我们就再来看看拷贝构造中的一些细节方面。

如下代码:

37.png

如图所示,我们可以发现拷贝构造是会直接对内置类型进行初始化的,其实按照昨天的知识,我们知道拷贝构造其实就是一个构造函数,但是它初始化的数据是从别的对象中复制过来的而已。但是拷贝构造和构造函数两者又有一点的不同,因为构造函数只会对自定义类型进行初始化而不会对内置类型进行初始化,但我的拷贝构造函数却不仅会对内置类型进行初始化,也会对自定义类型进行初始化(代码还没有演示),所以拷贝构造和构造函数在某些方面还是有很大的不同的。并且由于我们的拷贝构造函数是会自动对内置类型进行初始化的,所以以后我们在写内置类型的时候,是不需要去自我实现拷贝构造函数的,编译器会自己去调用拷贝构造函数。当然前提是我拷贝的对象已经被构造函数初始化过,这样才可以实现自己不实现拷贝构造,而使我的创建对象被初始化。


接着上面的内容,我们来看一下拷贝构造是如何对自定义类型进行初始化的,如下代码我们会发现一个很重要的问题:


38.png


我们发现我们在进行拷贝构造的时候,确实是不需要自己去实现拷贝构造函数的,编译器会自己去调用编译器系统内部的拷贝构造函数,从而实现内容数据的拷贝,但我们此时还会发现,我们的自定义类型中,虽然内容被拷贝了,但此时int* _a指向了一块空间,编译器指向拷贝构造之后,导致st2对象中的int*_a此时也被初始化成指向了st1指向的那块空间(从图中的两个内存地址相同可以看出),所以现在就会有一个致命问题需要我们解决,就是深拷贝问题,并且可以发现,我们的编译器虽然会自动调用拷贝构造,但是该拷贝构造只是一个浅拷贝,编译器没有执行深拷贝的能力,所以当我们遇到自定义类型需要进行拷贝的时候,无论是浅拷贝还是深拷贝,此时编译器是不敢乱自动去调用系统内部的拷贝构造,编译器此时必须优先调用我们自己实现的拷贝构造函数,而不是自动调用编译器系统内部的拷贝构造函数。


从深浅拷贝问题深入拷贝构造

所以如果按照上述的代码,我们没有自己去实现拷贝构造函数,而是让编译器去自动调用系统内部的拷贝构造,那么此时程序最终会崩溃,因为当程序运行到最后是,会去调用我们的析构函数,让析构函数帮我们把内部的空间资源给清理掉,然而此时因为st1和st2都存储在main函数的栈帧上,那么此时main函数栈帧在销毁前,会对st1和st2进行清理,并且按照栈帧原则(先进后出),此时是先对st2这个后定义的对象清理,再对st1这个先定义的对象清理,但st1和st2对象中的int*_a指向了同一块空间,那么在清理是导致st2中int*_a先被清理(清理就是析构函数,释放内存,并置成空指针),重点:虽然st2在清理时,将int*_a指向的空间置成了空指针,但是该空间的变化是针对于st2这个对象空间,此时并不会影响到st1对象的空间,如下图所示,此时就让我们带着这个问题,真正的进入到内存之中去看待深浅拷贝问题,此时我们参考如下代码,从代码方面去看看深浅拷贝和内存之间的关系。通过深浅拷贝问题来解决拷贝构造的问题。


39.png

此时当我们程序要结束时(就是执行到了return),我们的编译器会自动调用我们的析构函数,来清理资源,此时我们发现,析构函数是清理我们的st2,并且可以发现,当析构函数执行完之后,我们的int*_a指针确实指向了空,但我们的st1中int*_a指针指向的却并不是空。所以通过这个图,我们确实是可以知道清理st2对象的空间是不会影响到st1的空间的,但是,我们发现我们的程序却依然还是崩溃了,如下图:


40.png这是什么原因呢?为什么我的程序还是会崩溃呢?原因就是:我的st2对象中的int*_a指针指向的空间被释放之后,导致st1中的int*_a指针指向的空间被释放,此时就导致st1中的int*_a指针指向了一块随机的内存空间(就是变成了野指针),所以此时程序就因为野指针问题,导致崩溃了,所以当两个不一样的指针,指向了同一块空间之后,此时是会造成很大的问题的,所以为了避免造成两个指针指向同一块空间的问题,我们的编译器是不允许直接调用系统编译器内部的拷贝构造函数,必须优先调用我们自己实现的拷贝构造函数(只有这样才可以区分深拷贝和浅拷贝问题)。


总:指向同一块空间的问题,插入和删除数据会互相影响,并且导致该空间会析构两次,程序崩溃。所以不允许编译器自动调用系统编译器内部的拷贝构造,而是优先调用自己实现的拷贝构造。

并且拷贝构造的典型使用场景有:


使用已存在对象创建新对象
函数参数类型为类类型对象
函数返回值类型为类类型对象


见见猪跑,看一下到底什么是深拷贝:如下图

41.png

这样我们就把深拷贝和拷贝构造直接的一些不间接关系给搞清楚了,但此时我们会有一个疑问,那就是我们什么时候需要自己写拷贝构造,什么时候不要写拷贝构造呢?通过上述知识可能有的同学就会想,只要当我们有指针指向另一块空间的时候就要写拷贝构造,没有的时候就不要,这个是有一定道理的,但是不是绝对的,所以我们一般在我们自己实现了析构函数的时候,我们就需要自己实现一个拷贝构造,这个是很有道理的。

总:当我们自己实现了析构函数的时候,我们就要自己再实现一个拷贝构造,不然就会出大问题。


什么是运算符重载

运算符重载:C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。函数名字为:关键字operator后面跟上需要重载的运算符符号。函数原型:返回值类型operator操作符(参数列表),例:bool operator==(const Date& d1, const Date& d2);

注意:


1684832141086.png

搞明白了上述的基础知识,此时我们就可以真正的进入到运算符重载的学习了,首先搞定的第一点,我们想到,C语言中都没有什么运算符重载的概念,为什么C++中却有这个运算符重载的概念呢?强调,我们一直秉承着C++就是为了弥补C语言的不足而开创的,所以自然而然运算符重载这个概念的提出就是为了可以补充一些C语言中的不足,所以接下来我们就介绍一下运算符重载的好处和无运算符重载的坏处,同时我们都是以日期类代码来实现的。


如下图:


42.png


了解完了我们为什么要引入运算符重载的这个概念,此时我们就深入其内部,深入学习运算符重载和其相关的一些知识,首先我们可以知道运算符重载函数不仅可以放在全局中,也可以放在我们的类中,并且将运算符重载函数,放到了类中之后,此时就如上述注意中的第3点所说,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏this。并且使用运算符重载,就是把运算符重载的函数理解成是一个正常的函数,只是名字比较特殊一点点而已。当我们定义运算符重载函数的时候,运算符的返回值是bool值(真返回1,假返回0),参数由运算符的操作数决定(该运算符有几个操作数就有几个参数),并且第一个参数就是左操作数,第二个参数就是右操作数。所以知道了这些对运算符重载函数的基本使用之后,我们就看一下运算符重载函数是如何在我们的类中使用的。


如图所示:


43.png

运算符函数的复用

搞定了运算符重载函数在类中的使用,我们现在来学一下运算符函数的复用是如何实现的,如下代码所示:

44.png



赋值重载函数

首先我们的赋值重载函数是一个默认成员函数,它的特性满足我们上述所学的有关默认成员函数的所有特性,这里就不多加赘述了,如图所示就是我们的复制运算符重载函数的实现:

45.png


重点:我们这边再来了解一下赋值重载和拷贝构造,Date d1 =d2; 和 d1 = d2;之间的区别,首先Date d1 =d2这个是拷贝构造,这个是d1 = d2;赋值重载,为什么呢?原因就是:赋值重载是对两个已经实例化完成了的对象,而我的拷贝构造则是用一个实例化好的对象对另一个为实例化的对象进行初始化。


总结:北京时间:2023/2/7/1:20,不困,但是我只知道我熬夜了,我又要长痘了,呜呜呜!上述我们把类和对象中的赋值重载和六大默认函数中的拷贝构造和赋值重载函数给重点学习了一下,发现,这些东西并不难学,只是细节不好处理而已,所以小命要紧,撤!

相关文章
|
8月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
4月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
95 0
|
4月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
173 0
|
6月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
186 12
|
7月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
132 16
|
7月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
7月前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
7月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
362 6
|
7月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
8月前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)