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,不困,但是我只知道我熬夜了,我又要长痘了,呜呜呜!上述我们把类和对象中的赋值重载和六大默认函数中的拷贝构造和赋值重载函数给重点学习了一下,发现,这些东西并不难学,只是细节不好处理而已,所以小命要紧,撤!

相关文章
|
3天前
|
设计模式 安全 编译器
【C++11】特殊类设计
【C++11】特殊类设计
22 10
|
8天前
|
C++
C++友元函数和友元类的使用
C++中的友元(friend)是一种机制,允许类或函数访问其他类的私有成员,以实现数据共享或特殊功能。友元分为两类:类友元和函数友元。类友元允许一个类访问另一个类的私有数据,而函数友元是非成员函数,可以直接访问类的私有成员。虽然提供了便利,但友元破坏了封装性,应谨慎使用。
39 9
|
4天前
|
存储 编译器 C语言
【C++基础 】类和对象(上)
【C++基础 】类和对象(上)
|
12天前
|
编译器 C++
【C++】string类的使用④(字符串操作String operations )
这篇博客探讨了C++ STL中`std::string`的几个关键操作,如`c_str()`和`data()`,它们分别返回指向字符串的const char*指针,前者保证以'\0'结尾,后者不保证。`get_allocator()`返回内存分配器,通常不直接使用。`copy()`函数用于将字符串部分复制到字符数组,不添加'\0'。`find()`和`rfind()`用于向前和向后搜索子串或字符。`npos`是string类中的一个常量,表示找不到匹配项时的返回值。博客通过实例展示了这些函数的用法。
|
12天前
|
存储 C++
【C++】string类的使用③(非成员函数重载Non-member function overloads)
这篇文章探讨了C++中`std::string`的`replace`和`swap`函数以及非成员函数重载。`replace`提供了多种方式替换字符串中的部分内容,包括使用字符串、子串、字符、字符数组和填充字符。`swap`函数用于交换两个`string`对象的内容,成员函数版本效率更高。非成员函数重载包括`operator+`实现字符串连接,关系运算符(如`==`, `<`等)用于比较字符串,以及`swap`非成员函数。此外,还介绍了`getline`函数,用于按指定分隔符从输入流中读取字符串。文章强调了非成员函数在特定情况下的作用,并给出了多个示例代码。
|
12天前
|
C++
【C++】string类的使用④(常量成员Member constants)
C++ `std::string` 的 `find_first_of`, `find_last_of`, `find_first_not_of`, `find_last_not_of` 函数分别用于从不同方向查找目标字符或子串。它们都返回匹配位置,未找到则返回 `npos`。`substr` 用于提取子字符串,`compare` 则提供更灵活的字符串比较。`npos` 是一个表示最大值的常量,用于标记未找到匹配的情况。示例代码展示了这些函数的实际应用,如替换元音、分割路径、查找非字母字符等。
|
12天前
|
C++
C++】string类的使用③(修改器Modifiers)
这篇博客探讨了C++ STL中`string`类的修改器和非成员函数重载。文章介绍了`operator+=`用于在字符串末尾追加内容,并展示了不同重载形式。`append`函数提供了更多追加选项,包括子串、字符数组、单个字符等。`push_back`和`pop_back`分别用于在末尾添加和移除一个字符。`assign`用于替换字符串内容,而`insert`允许在任意位置插入字符串或字符。最后,`erase`函数用于删除字符串中的部分内容。每个函数都配以代码示例和说明。
|
12天前
|
安全 编译器 C++
【C++】string类的使用②(元素获取Element access)
```markdown 探索C++ `string`方法:`clear()`保持容量不变使字符串变空;`empty()`检查长度是否为0;C++11的`shrink_to_fit()`尝试减少容量。`operator[]`和`at()`安全访问元素,越界时`at()`抛异常。`back()`和`front()`分别访问首尾元素。了解这些,轻松操作字符串!💡 ```
|
12天前
|
存储 编译器 Linux
【C++】string类的使用②(容量接口Capacity )
这篇博客探讨了C++ STL中string的容量接口和元素访问方法。`size()`和`length()`函数等价,返回字符串的长度;`capacity()`提供已分配的字节数,可能大于长度;`max_size()`给出理论最大长度;`reserve()`预分配空间,不改变内容;`resize()`改变字符串长度,可指定填充字符。这些接口用于优化内存管理和适应字符串操作需求。
|
17天前
|
C++
【C++】日期类Date(详解)②
- `-=`通过复用`+=`实现,`Date operator-(int day)`则通过创建副本并调用`-=`。 - 前置`++`和后置`++`同样使用重载,类似地,前置`--`和后置`--`也复用了`+=`和`-=1`。 - 比较运算符重载如`>`, `==`, `<`, `<=`, `!=`,通常只需实现两个,其他可通过复合逻辑得出。 - `Date`减`Date`返回天数,通过迭代较小日期直到与较大日期相等,记录步数和符号。 ``` 这是236个字符的摘要,符合240字符以内的要求,涵盖了日期类中运算符重载的主要实现。