类的默认成员函数以及 *** 作符的重载

类的默认成员函数以及 *** 作符的重载,第1张

目录

写在前面的话

一,构造函数

1.1构造函数的由来

1.2构造函数的三种形式

1.3构造函数的特性

1.4推荐使用全缺省构造函数

二,析构函数

2.1析构函数的由来

2.2析构函数的实现

2.3析构函数的特性

三,拷贝构造函数

3.1拷贝构造基本使用

3.2拷贝构造两点说明

第一点 引用传参

第二点 const 修饰形参对象

3.3“深拷贝”和“浅拷贝”

四, *** 作符重载

4.1什么是 *** 作符重载

4.2一定不能忘记的事项 

4.3为什么运算符重载第一个函数是 this 

五, *** 作符重载实现

5.1赋值 *** 作符

5.2日期类部分 *** 作符重载实现

5.2.1 == *** 作符的重载

5.2.2  > *** 作符的重载

5.2.3 += *** 作符重载实现

六,总结


写在前面的话

小伙伴们大家好啊!这里是库森,今天为大家带来的是,C++中类的六大成员函数以及有关 *** 作符重载的问题,那么我们废话不多说,直接进入主题。


                                          

那么在C++中,如果是一个空类,那么在我们什么都没写的情况下,其实是有六个默认成员函数存在的,那么接下来我们一起看看吧!

一,构造函数 1.1构造函数的由来

什么是构造函数呢?

我们可以这样理解。


比如对于一个栈而言,如果我们想要去实现一些比如插入,删除之类的 *** 作,那么首先我们需要对栈进行初始化,那么每次我们都需要自己去实现其实是有点冗余而且没必要的,所以C++中就提出了一个构造函数去初始化。


这也就是构造函数的由来。


1.2构造函数的三种形式

那么接下来我们来看看对于构造函数具体的内容。


构造函数,首先就是一个特殊的成员函数,没有返回值,没有参数类型,函数名字是类名。


在对象创建时调用,且在对象的生命周期内只会调用一次。


如下图所示:

如上图所示,因为构造函数是特殊的成员函数,所以构造函数是有多种形式的。


1.首先第一种就是我们实现的无参的构造函数,那么只要定义了对象而且没有在对象定义的括号中传参,此时对象一旦定义了之后,就会调用无参的构造函数来初始化它。


2.然后就是第二种形式,传递参数的构造函数。


上面我们用全缺省传递了参数,当然我们也可以用半缺省来实现(如果对于缺省参数有疑惑,可以移步up主另一篇文章哦!C++基础入门一_菜还不承认的库森的博客-CSDN博客)。


当然我们最推荐的就是使用全缺省实现哦!

3.接下来第三种就是我们对于我们不写编译器默认生成的构造函数。


那么对于这种构造函数来说,对象在定义时只能有一个对象名。


1.3构造函数的特性

虽然构造函数是有三种形式的,但是对于构造函数是有要求的。


首先,如果我们写了,编译器就不会生成了。


同时,构造函数只能有一个,也就是说上面我们那种写法是不行的,所以一般情况下,我们推荐将构造函数显示的写出来并且使用全缺省。


除此之外,因为构造函数的目的就是在对象定义的同时,将其初始化,所以是自动调用的函数。


1.4推荐使用全缺省构造函数

不知道小伙伴么是否有这样一个疑惑,对于编译器默认生成的构造函数,因为它初始化的是不传参数的对象,所以如果我们使用这种形式的构造函数去初始化对象,那么对象参数的值依旧是随机值,那么还有什么必要去调用呢?

其实这里C++有一个比较不好的地方。


通过我们实验发现,对于内置类型比如int/double/char 类型的成员变量C++不做初始化处理。


而对于自定义类型的成员变量是会去调用默认构造函数去初始化。


所以总结来说,我们一般不会使用编译器默认生成的构造函数,因为它对于内置类型和自定义类型的区分做的并不好,所以一般情况下,都会使用全缺省去实现。


二,析构函数 2.1析构函数的由来

因为在每次使用栈的时候需要初始化对象,所以C++中引入了构造函数。


同样的,又因为每次在使用栈结束的时候,我们需要调用销毁函数去销毁比如动态内存开辟的空间,所以C++又提出了析构函数去解决这个问题。


那么我们理所当然可以想到,析构函数的基本内容应该和构造函数是差不多的,那么接下来我们一起看看吧。


2.2析构函数的实现

首先对于析构函数,没有返回值,没有参数,函数名为类名前面加上一个 ~ ,然后在函数内部,需要释放动态开辟的空间等。


那么如下图所示,对于内置类型的对象变量,是不需要调用析构函数的。


 其次,对于自定义类型变量,是需要调用它的析构函数去销毁的,如下图所示:

2.3析构函数的特性

那么当然了,对于析构函数,以及有如下特性:

1.我们不写,编译器会自动生成默认的析构函数。


2.一个类仍旧只有一个析构函数。


3.在对象声明周期结束时,自动调用。


4.对于内置内型成员变量不做处理,对于自定义类型成员变量调用它的析构函数。


三,拷贝构造函数 3.1拷贝构造基本使用

那么上面我们看了有关构造函数的实现,那么如果我们想用一个对象去初始化另一个对象,此时我们就需要使用拷贝构造函数。


那么对于拷贝构造函数而言,首先只有一个参数,那就是对于类类型对象的引用,跟构造函数差不多的用法,在使用已经初始化的对象去初始化另一个对象时,编译器会自动调用该函数。


如下图所示:

                                

我们可以看到,当使用拷贝构造函数时,只有一个对象形参,而且这个对象形参必须有两个条件,第一个必须是用 const 修饰的,第二个必须是引用传参。


那么为什么一定有这两个规定呢?

3.2拷贝构造两点说明 第一点 引用传参

首先,第一个一定需要使用引用传参,如果不使用引用,直接使用传值,那么对于当前对象的拷贝,需要先调用这个对象的拷贝构造,才能将这个对象的值传递给形参。


如下图所示:

也就是说,如果调用拷贝构造,就会出现传值传参,如果传值传参了,就需要调用拷贝构造去进行传值传参,那么也就会进入无限的递归调用,最后导致栈溢出(这里不是死循环,因为这里是一直进行函数调用,一直进行压栈 *** 作,最后会导致栈溢出)。


那么为了避免这种情况的发生,我们就直接使用引用传参。


第二点 const 修饰形参对象

那么既然用了引用传参,我们就必须要注意的是,有可能误 *** 作将原对象改了,我们的本意只是将该对对象的值拷贝过来,但是不注意将原对象修改了,这就得不尝失了,所以一般情况下,我们都会使用 const 修饰该对象,保证我们一定不能修改形参的值。


3.3“深拷贝”和“浅拷贝”

那么上面我们看到了,对于拷贝构造来说,其实只是完成简单的值拷贝。


我们只是将 d1 对象的值拷贝给 d2 对象,那么当然,因为对于刚刚这个拷贝构造来说,确实只需要进行简单的值拷贝,因为没有更深层次的要求,也就是仅仅只是完成了“浅拷贝”。


其实当我们不主动实现时,编译器会自动调用默认的拷贝构造完成这里的“浅拷贝”。


那么小伙伴们有没有想过这样一个问题,如果我们要拷贝的对象是在堆上开辟的一个空间存放的内容,此时如果我们再去使用浅拷贝,会有问题出现吗?

答案是是的,一定会有问题,但是问题不是拷贝不成功,而是当对象生命周期结束时,调用析构函数去释放堆上开辟的空间时,一定会出现问题的,那么你知道问题在哪里吗?(这个问题如果不明白,可以留言评论哦!也可以蹲后续,在真正进行深拷贝的地方我们会讲解的哦!)

四, *** 作符重载 4.1什么是 *** 作符重载

重载在之前的文章中,我们是有说明的,也就是C++为了方便一些函数的使用,将一些比较相似的函数实现了重载。


那么这里对于运算符重载来说也是一样的,同样都是重载。


那么首先,运算符重载的是具有特殊函数名的函数,其基本结构如下所示:

返回值类型  operator *** 作符(参数列表)

4.2一定不能忘记的事项 

那么对于运算符的重载,我们有以下说明:

1.内置类型 *** 作符比如 +,不能改变它的含义(比如将其重载为减法);同时,不能通过连接多个 *** 作符来改变形成新的 *** 作符。


2.规定有五个 *** 作符不能实现 *** 作符重载: .*   sizeof   .    ::   ?: 

3. 如果在类中实现,重载函数的第一个参数和类的普通函数都一样,都是 this ,只不过也是隐式的 this 。


4.3为什么运算符重载第一个函数是 this 

那么对于第三点,我们需要做以下说明,首先,因为在C++中,很多函数都是在类中实现的,所以重载函数的第一个参数都是 this ;同时我们也有这样一个思考,那么对于C++中的变量而言,一般都是封装在类中的,如果在类外访问类的成员变量,就有问题了。


 

那么对于这个问题,我们有两个解决问题的方式。


第一个就是将成员变量的作用域修改为 public 公开的,那么这样类的封装性就没法保证了,所以是不可取的。


其次,就是使用右元函数(这个我们后面的文章会有实现哦!)。


简单来说,右元函数就是将一个全局函数设置为这个类的 “ 朋友 ”,然后这个函数就可以访问该类的成员变量了。


那么其实我们可以想的到,其实右元函数也是破坏了类的封装性。


所以结论就是:不管我们使用哪种方式去实现全局的 *** 作符重载函数,都是有问题的,所以我们一般不会去使用全局函数,所以理所当然 *** 作符重载函数的第一个参数都是this。


五, *** 作符重载实现

上面我们了解了有关 *** 作符重载内容,接下来我们通过一些 *** 作符重载的实现来体会一下 *** 作符重载函数和普通类函数有什么不同的地方。


5.1赋值 *** 作符

那么首先我们来了解一下有关赋值 *** 作符的用法,如下图所示:

那么我们看到,首先需要有关键字 operator ,然后后面跟着赋值运算符,在参数列表中,同样的我们选择 const 修饰的 引用对象。


当然一定不能忘记的是返回值的类型首先一定是类类型,其次因为我们是赋值 *** 作,为了避免再次进行一次拷贝传值,我们直接使用引用传参将 this 对象返回。


那么至于为什么使用引用,相信小伙伴们一定也知道了,是的,为了避免多次的拷贝。


5.2日期类部分 *** 作符重载实现 5.2.1 == *** 作符的重载

那么首先,我们看到如下所示,同样的,返回值,operator关键字, *** 作符,参数列表。


其次就是函数体,因为是判断是否相等,所以我们需要将每个参数都判断一次,如果全部相等,我们才能返回 true,否则则会返回 false。


那么这里需要另外注意的一个点就是,参数列表后面的 const 。


那么我们解释一下:

因为对于赋值语句来说,我们的 this 对象是不需要发生变化的,所以我们需要将其设置为 const 属性,以免误 *** 作。


但是因为 this 对象是隐藏的,不能显示的写出来,所以我们将 const 放在了最后面。


注:当然其他一些重载函数也是一样的,this 对象是不期望被改变的,所以一般都会加上 const 来修饰。


5.2.2  > *** 作符的重载

上面我们看到了有关等于判断的 *** 作。


那么其实对于判断大于的 *** 作符也是一样的。


具体的实现逻辑也就是我们需要挨个判断,依次是年/月/日。


同样的,这里的 this对象也是不变的,所以需要用const 修饰。


注:

那么通过 == 和  > *** 作符的复用,我们可以很快捷的实现很多类似的 *** 作符,比如 >=,!= 等,这里我们就不一一实现了。


 

5.2.3 += *** 作符重载实现

好的,那么对于+= *** 作符来说,是相对比较复杂的,因为我们需要 this 对象进行改变。


因为这里我们是以日期类作为例子的,所以首先我们想到的是加的是天数的情况下,如果天数满31或者30天的话,那么有可能会增加月份,或者如果年份增加了,那么我们同时就需要增加年数。


如上图所示,那么首先,我们需要将我们将需要加的天数加到当前this 对象的月份的天数上面去。


接下来我们通过函数GetMonthDay获取到当前对象的当月的天数,然后依次将每个月的天数减去,每减一次,月份就需要加1,而同时如果月份超过12的话,年份就应该加1 ,当然不能忘记的是年份加 1 的时候,月份是重新开始的,所以月份需要重置为 1。


 

当然,最后一定不能忘记将 this 对象返回。


六,总结

上面我们提到了四个类的默认成员函数,那么最后我们看啦,其实构造函数和析构是差不多的。


而拷贝构造和赋值重载也是类似的,这两个函数在使用的时候,都需要注意使用引用传参,以及注意对象是否需要改变。


那么对于类的另外两个成员员函数而言,用的不多,所以我们不做过多介绍。


分别是取地址和const 取地址,如果大家有兴趣,可以自行学习哦!

好的,那么对于这一部分类的成员函数以及 *** 作符的重载部分到这里就结束啦!如果觉得不错,up主跪求三连鸭!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/568633.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-09
下一篇 2022-04-09

发表评论

登录后才能评论

评论列表(0条)

保存