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

简介: 北京时间:2023/2/5/16:38,睡了一个午觉睡到刚刚,我发现我真的能睡,也许是因为今天是下雨天的原因吧!不过下雨天确实好睡觉,也可能是因为今天起太早了,或者说昨天睡太迟了,好像只睡了7个小时。因为昨天晚上我玩了一个游戏,叫鹅鸭杀,这个游戏不怎么适合我,但是确实很有意思,游戏机制没什么意思,有意思的是跟自己的小伙伴玩,非常有意思。特别是高中同学或者初中同学,这个游戏就像是活过来了一样,变得非常搞笑。今天晚上如果该博客不能产出,原因就是有人叫我玩鹅鸭杀去了,哈哈哈!上篇博客我们把类和对象的表层给学习了一下,今天该博客,我们就延这上篇博客的内容再逐渐深入学习一下类和对象,进入类的内部。

引言:

北京时间:2023/2/5/16:38,睡了一个午觉睡到刚刚,我发现我真的能睡,也许是因为今天是下雨天的原因吧!不过下雨天确实好睡觉,也可能是因为今天起太早了,或者说昨天睡太迟了,好像只睡了7个小时。因为昨天晚上我玩了一个游戏,叫鹅鸭杀,这个游戏不怎么适合我,但是确实很有意思,游戏机制没什么意思,有意思的是跟自己的小伙伴玩,非常有意思。特别是高中同学或者初中同学,这个游戏就像是活过来了一样,变得非常搞笑。今天晚上如果该博客不能产出,原因就是有人叫我玩鹅鸭杀去了,哈哈哈!上篇博客我们把类和对象的表层给学习了一下,今天该博客,我们就延这上篇博客的内容再逐渐深入学习一下类和对象,进入类的内部。


深入C++类的内部

当我们学习了之前的有关类的基本知识之后,此时我们明白了什么类,什么是对象,所以我们现在完全是有能力自己使用C++中的类的形式,自己实现出一个封装了的栈或者队列出来的,因为类的使用上是并没有什么门槛的,不过就是把我们的函数也给写进到类中,难只是难在一些细节方面的处理上,很容易出现问题而已,并且因为我们使用类后把我们的数据结构给封装在了类中,这样我们在使用该数据结构的时候就会变得更加的安全和方便,虽然底层的实现原理都是一样的,但是我的C++类的概念就是弥补C语言中太过自由的缺陷。并且在C语言中,我们实现了数据结构类型之后,在调用过程中,是很容易把该数据结构的初始化和资源销毁给忘记了的,所以我们的C++为了解决该问题,就提出了一个默认成员函数的概念,就是你可以不需要去调用初始化和资源销毁函数,编译器会自动调用默认成员函数实现。所以接下来我们就来学习一下,C++中的默认成员函数吧!


如图我们先看一下C语言中没有默认成员函数和C++中有默认成员函数的代码区别:


27.png


类中的默认成员函数

28.png

它们的特性:如下表

构造函数主要完成初始化工作
析构函数主要完成清理工作
拷贝构造主要是使用同类对象初始化创建对象
赋值重载主要是把一个对象赋值给另一个对象
取地址重载主要是普通对象和const对象取地址


总:这6个默认成员函数,我们如果不写,编译器也会自动生成一个,但是如果我们写了,编译器就不会生成,而是调用我们自己实现的,注:取地址重载函数我们一般不会自己实现。

1.构造函数

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是对对象进行初始化。并且此时我们要明白一个概念,无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。


总:只要是不需要传参就可以调用的构造函数,就是默认构造函数。建议每一个类我们都自己都写一个默认构造函数。

构造函数特性:


函数名与类名相同(类名就是class后的)
无返回值
对象实例化时编译器自动调用对应的构造函数
构造函数可以重载,一个类可以有多个构造函数

如下图在一个类中将构造函数的特征显示的淋漓尽致:

29.png


2.析构函数

通过前面构造函数的学习,我们知道一个对象是怎么来的,那我们构建出来的对象又是怎么没的呢?此时面对这个问题,最好的解释就是用我的析构函数来解决,析构函数和构造函数的功能相反,析构函数不是完成对对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。所以总的来说就是我的局部对象在被编译器销毁之前,它会自动的调用我的析构函数来对对象中的资源(堆上开辟的空间)进行清理,只有当对象中的资源被清理完成之后,我的局部对象才可以被编译器销毁,否则就要去调用析构函数。


析构函数特性:


析构函数名是在类名前加上一个~(表示取反的意思)
无参数无返回值类型
一个类只能有一个析构函数,若未显示定义,系统会自动生成默认的析构函数。注意:析构函数不允许重载
对象生命周期结束时(栈帧被销毁时),C++编译系统会自动调用析构函数

如下图在一个类中将析构函数的特征显示的淋漓尽致:


30.png


此时我们的打印框中打印了我们需要的信息,所以证明在对象被销毁(栈帧被销毁之前),我们的编译器确实是有自动去调用我们的析构函数,此时我在堆上开辟的空间等不是栈上的资源就都可以被析构函数给清理了。


拷贝构造函数

当我们了解完什么是构造函数和什么是析构函数之后,我们来聊一聊6个默认成员函数中的拷贝构造函数。但在学习拷贝构造之前,我们通过上述的知识,我们了解到了,默认生成函数在我们没有自我实现的时候,编译器会自动生成,那么此时我们会发现一个问题,如下图代码中所示:

31.png


我们可以发现在我们没有自己实现一个构造函数的时候,我们的对象并没有因为编译器会自动调用初始化函数而被初始化,这是什么原因呢?原因此时涉及一个C++中的小缺陷,C++中把类型分为了内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int、char、double等!自定义类型就是我们使用class、struct、union等自己定义出来的类型,所以因为C++中的小缺陷,此时编译器在调用默认成员函数的时候,只会对自定义类型调用,而我们的内置类型编译器是不会去自动调用默认成员函数的。所以上图由于int _year、int _month、int _day都是属于我们的内置类型,所以我们如果没有自己实现一个初始化构造函数,此时编译器并不会对我们的对象就行初始化,所以出现的就是随机值。


通过下图,我们进一步感受一下,编译器自动调用构造函数:


32.png


此时如上图所示,我们发现当我们的对象类型不是内置类型,是自定义类型的时候,我们的编译器是会自动调用我们的默认构造函数去对其进行初始化的,然后也发现,当对Stack进行初始化时,我们的Stack是一个内置类型的类对象,所以此时由于我们自己实现了初始化函数,所以我们的Stack也被初始化了。这样就很成功的实现了自定义类型和内置类型的同时初始化,也很好的证明了上述的知识。


总:在C++中我们的默认成员函数对内置类型不作处理,对自定义类型才会处理。


正式学习拷贝构造

我们在搞明白了上述的这个问题之后,我们就可以正式的来学习我们的拷贝构造了,首先我们明白一个点,就是,构造函数本质只是用来初始化的而已,而我的拷贝构造其实在本质上也只是用来初始化的而已,只是此时拷贝构造初始化的内容是从别的已经被初始化的对象中复制到该对象的数据。所以构造函数是我们自己给数据,或者编译器给数据,而拷贝构造是通过复制已经初始化过的对象中的数据。 明白了这个点之后,我们来正式了解一下拷贝构造,拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类型对象创建新对象时由编译器自动调用。

拷贝构造特征:


1.拷贝构造函数是构造函数的一个重载形式

2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会发生无穷递归(这个无穷递归是针对于编译器内部进行的,因为在编译器的内部可以理解成有无数个拷贝构造函数,所以会无穷递归)


代码如图所示:初步了解拷贝构造


33.png


深入理解拷贝构造

此时当发现不好理解拷贝构造的时候,此时我们先换一个角度,我们先从深浅拷贝的理解来理解拷贝构造,本来按照我们的理解内置类型编译器是可以直接通过赋值的形式进行浅拷贝,自定义类型编译器才通过调用拷贝构造完成拷贝。但是由于涉及到深浅拷贝问题,如下图:


34.png

通过图示,我们可以发现,此时如果是一个自定义类型,并且这个自定义类型此时指向了另一块空间,那么此时就不可以进行浅拷贝,因为如果进行浅拷贝的话,就会导致编译器按照我们的字节大小进行拷贝,那么此时就会导致两个指针,指向了同一块空间,这样就会出问题,导致该空间在最后调用析构函数释放资源的时候,该空间被释放了两次,此时程序就会崩溃。所以当我们遇到自定义类型的时候,我们不可以盲目的去调用拷贝构造,一定要注意是否涉及深拷贝问题,如果有深拷贝的话,我们要自己进行拷贝构造函数的实现。所以归在一起,不管是内置类型还是自定义类型,编译器都是优先去调用拷贝构造,从而防止深浅拷贝问题。


总:不管是自定义类型还是内置类型,编译器都是默认直接去调用拷贝构造。

重点,当我们明白了这个道理之后,我们就可以发现,只要我们进行传值传参,编译器就会去直接调用拷贝构造,根本就没有赋值拷贝这一步骤,但是当我们进行传引用传参时,编译器不会直接去调用拷贝构造,而是进行赋值拷贝,如下图:


35.png


通过仔细的观察上图,我们发现传值会调用拷贝构造,传引用不会调用拷贝构造,所以我们在上述过程中的代码中就不可以使用Date(Date d)传值传参,因为如果使用传值传参就会导致无穷调用拷贝构造,所以我们在进行拷贝的时候,就要使用传引用传参Date(const Date& d),这样就不会去无穷调用拷贝构造,而是老老实实的进行赋值拷贝。


以下内容由于现在是 1:07,避免熬夜,我们明天再写了,See you.


赋值运算符重载

const成员函数

取地址及const取地址操作符重载

总结:什么东西都是一回生二回熟,所以如果我们没听懂,只要再听一遍、两遍,就没有搞不定的问题。

相关文章
|
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++篇)