0
点赞
收藏
分享

微信扫一扫

C++-类和对象(5)

今天,继续和大家分享与类和对象相关的知识,本次的内容包含日期类的实现,const成员,static成员及友元函数等方面。

继上篇文章,我们说到了日期类前置++和后置++的实现。

现在,我们从前置--和后置--接着往下,完成我们日期类的实现。

日期类的实现

日期类前置--和后置--的实现

在实现前置--和后置--前,我们需要先去了解它们的功能。

C++-类和对象(5)_静态成员

通过程序运行的结果,我们可以了解到,前置--和后置--都会改变自身的值,它们不同在返回值,前置--返回改变后的数据,而后置--返回更改前的值。

了解完前置--和后置--的功能,我们来实现一下它们。

首先,我们根据运算符重载的格式,给出返回值,函数名,以及参数列表。前置--的返回值是修改后的,我们直接在原数据上修改,然后,进行返回。

接下来,我们把思路转换成代码:

后置--和前置--的思路类似,但后置--需要返回修改前的数据,我们在修改原数据前,需要拷贝一份,用于返回。我们在函数内定义的变量,出了函数后会被销毁,我们不能像前置--那样使用引用返回,而要使用传值返回。还有就是后置--和前置--的运算符相同,为了

C++-类和对象(5)_成员变量_02

能够正确形成重载,后置--的参数列表需要加上一个int类型。

理清了思路,我们来看看后置--实现的代码:

C++-类和对象(5)_日期类_03

前置--和后置--的实现,我们都完成了,我们来简单的测试一下:

C++-类和对象(5)_日期类_04

从结果看,我们的程序并没什么问题,可以实现前置--和后置--的功能。

除了日期加减天数有意义外,两个日期的相减的比较也是有意义的。

日期类的比较运算符的实现

由于两个日期的相减涉及到日期的比较,我们先把日期类的比较实现一下。

日期类的比较这块的思路很简单,就是年份小的小,年份相等,月份小的小,如果月份也相等,天数小的则小。

理清了思路,我们来看看代码的实现:

C++-类和对象(5)_日期类_05

我们来简单的测试一下:

C++-类和对象(5)_成员变量_06

通过测试结果看,我们的程序符合比较的规则,能够得出正确的比较结果。

下面,我们来实现日期类的相减。

两个日期相减的实现方式有很多种,这里我们就选取一个比较简单便于理解的方式来实现。

两个日期类的相减问题,我们可以转换成小日期加上相差天数后得到大日期的问题。相差的天数,我们并不知道,这是要求的。我们只能一天一天的加,当两个日期相等,我们就可以求出相差的天数了。日期的相差天数解决了,可问又来了,我们怎么在小日期减大日期的情况下,返回负的天数呢?对于这个问题,我们可以加个符号位来表示是小日期减大日期还是大日期减小日期,从而确定返回天数该是正的还是负的。

理清了思路,我们来看看代码的实现:

C++-类和对象(5)_日期类_07

我们来简单的做一下测试:

C++-类和对象(5)_静态成员_08

从两个结果看,我们的计算天数的程序,没什么问题,能够正确计算出相差天数。

关于日期类的运算符我们实现的差不多了。

大家有没有想过内置类型为什么可以自动识别类型输出,为什么我们自定义的类型却不行?

C++-类和对象(5)_成员变量_09

这是因为内置类型的输出运算符,在库里实现了。而它们能自动识别类型是应为它们重载了函数。自定类型,其实也可以自动识别输出,但我们需要自己实现。

从图中我们可以看到,输出流的返回值是ostream。我们试着来实现一下:

C++-类和对象(5)_日期类_10

我们来测试一下:

C++-类和对象(5)_日期类_11

从图中结果看,我们的程序是无法运行的,我们把前后位置调换一下试试:

C++-类和对象(5)_静态成员_12

通过运行结果看,位置调换后,程序便可以正常运行输出了。这是什么原因呢?这是因为成员函数,默认第一个传递的参数是this指针,不能是其他类型。这就导致了我们没法像内置类型那样进行输出。那我们该怎么办呢?既然类中规定了第一个传递的参数,那我们可不可在类外面,实现输出运算符的重载呢?我们来试试看:

C++-类和对象(5)_日期类_13

从编译的结果看,编译器报错了。这是什么原因呢?这是因为类外部的函数无法访问类的私有成员。那我们有没有什么方法?让某些类外部的函数访问类的私有成员呢?方法是有的,使用友元函数关键字friend声明。对于友元,我们待会在讲,现在,我们先实现一个别的函数来打印日期。

C++-类和对象(5)_成员变量_14

我们来测试一下:

C++-类和对象(5)_静态成员_15

从运行结果看,程序并没有什么大问题。我们换个测试用例看看:

C++-类和对象(5)_成员变量_16

从图中可以看到,编译器已经报错了。这是什么原因呢?刚刚明明还能调用,可是到了对象d2这怎么就编译报错了呢?你还记得我们之前在引用时,讲的权限问题吗?权限在传递的时候,只能缩小,而不能放大。

这里就犯了这个权限错误,我们使用const修饰了对象d2,d2不可以被修改。编译器把对象d2的this指针传过去,而this指针并没有加const修饰,这就意味着,对象d2本来不能被修改,到了函数里面就可以修改,这显然是荒谬的。

const成员

那我们怎么去解决呢?由于this指针是隐匿传递,且不能显示传递。我们没法直接用const修饰this指针。C++思来想去,决定在函数后面加const,来表示const修饰this指针。

C++-类和对象(5)_日期类_17

我们来验证一下,给我们之前的函数加上const修饰this指针:

C++-类和对象(5)_成员变量_18

C++-类和对象(5)_成员变量_19

通过运行结果看,加上const后程序,又能正常使用了。

像这样加上const修饰的函数,被称之为成员函数。对于函数内部不能修改成员变量的,我们都应该加上const修饰。

C++-类和对象(5)_静态成员_20

前面,我们已经学习了四个默认成员函数,不是有六个吗?剩下的两个是普通的对象和const对象的取地址符号的重载。

class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};

这两个默认函数一般我们不需要重写,编译器写的就已经够用了。

类中的const成员函数,我们了解完了。

接下来,我们来看看类的const成员变量:

C++-类和对象(5)_日期类_21

我们先声明一个const成员变量_a,然后,依照往常的的方式进行初始化。可为什么编译器报错了。这是什么原因呢?这是因为const修饰的成员不可以修改。为次,我们有两种解决方案,第一种就是在声明的时候,给默认值。

C++-类和对象(5)_静态成员_22

第二种方法是利用初始化列表进行初始化。

再谈构造函数

构造函数的初始化列表

构造函数会先使用初始化列表对成员变量进行初始化,然后,再进行执行函数的实现部分。

构造函数的使用方法是在函数名后的第二行,以冒号开头,用逗号分隔开下一个成员变量,每个成员变量后跟着一个括号,里面放初始值或初始表达式。我们以日期类为例,使用以下初始化列表

C++-类和对象(5)_成员变量_23

我们在使用初始化列表时,需要几点要注意的:

1.成员变量只能在初始化列表出现一次,也就是成员变量只能在初始化列表初始化一次。

2.类中若包含以下成员变量,必须放在初始化列表进行初始化

(1)引用成员变量

(2)const成员变量

(3)自定义类型(该类没有显示定义构造函数)

以上这些成员变量,为什么必须在初始化列表进行

3.尽量是使用初始化列表进行初始化,因为不管是否使用初始化列表,对于自定义类型,一定会先使用初始化列表进行初始化。

4.初始化列表初始化成员变量的顺序,和成员变量声明的顺序相同,与初始化列表的初始化顺序无关

构造函数不仅可以构造和初始化对象,对于单参数和除了第一个参数无缺省值其余均有缺省值的构造函数,还具有类型转换的功能。我们来验证一下这个功能。

explicit关键字

要验证构造函数具有类型转换的功能,我们得先了解一个关键字explicit,它的功能是禁止单参构造函数类型转换的作用。

我们先定义一个单参数的构造函数

C++-类和对象(5)_静态成员_24

然后,我们定义一个日期类对象赋值1,运行输出结果。

C++-类和对象(5)_日期类_25

除了月份和天数是随机数外,程序能够正常运行出结果。

我们给构造函数加上关键字explicit看看,结果又是怎么样的。

C++-类和对象(5)_成员变量_26

C++-类和对象(5)_成员变量_27

呃,编译器报错了,好像是因为int类型无法转换成Date类型。

这是什么原因呢?这是因为对于我们把整型1赋值给日期类,编译器会先用整型1构造一个日期类的临时对象,再把这个对象拷贝构造给日期类对象d1。而explict修饰后,禁止了构造函数构造这样的临时对象,这就导致了无法实现类型的转换。

对于下面这种多参数,只有第一个参数无缺省值的构造函数也是一样的。

C++-类和对象(5)_成员变量_28

当我们加上explicit关键字修饰这个构造函数后,编译器就会报错,无法完成类型的转换。

C++-类和对象(5)_日期类_29

C++-类和对象(5)_成员变量_30

而当我们删除explicit关键字后,代码就可以编译运行了。

C++-类和对象(5)_成员变量_31

我们把这种类型的转换,称之为隐式类型转换。如果你不想允许隐式类型的转换,可以使用explicit来修饰构造函数。

下面,我们来看看另外比较重要的两个关键字,static和friend。

static成员

static修饰的成员,也叫静态成员。静态成员包含了静态成员变量和静态成员函数。静态成员也是成员变量,也受public,private,protected权限限定符的限制。一般情况下,我们成员变量都设置为私有。我们可以在不定义对象的情况下,通过静态函数访问静态成员。

我们来看看,他们的定义。

C++-类和对象(5)_成员变量_32

从图中看,很明显编译器报错了,这里报错的原因是静态成员变量不能这样初始化,它需要在全局区域定义初始化,

C++-类和对象(5)_日期类_33

我们现在将变量_a初始化成0,运行一下程序:

C++-类和对象(5)_静态成员_34

从运行结果看,变量_a成功被初始化成了0。

从上面我们能看到静态成员与普通成员的区别,那静态成员函数与成员函数又有什么区别呢?

C++-类和对象(5)_静态成员_35

从图中看,编译器报错了。这是什么原因呢?这是因为函数没有隐藏的this指针,不能访问任何非静态成员。

现在,我们大致了解了静态成员的使用规则了,我们来使用一下它们:

C++-类和对象(5)_静态成员_36

C++-类和对象(5)_日期类_37

首先,我们先给我们的类补充一个构造函数,每次调用都会对静态成员变量加1。然后,我们定义两个对象,输出静态成员变量的值。

通过运行结果,我们发现静态成员自增了两次,这个说明了两个对象对同一个静态成员进行了后置++的操作,也就是说静态成员为同一个类的所有对象所共有。

下面,我们来总结一下static的特性:

1.静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区

2. 静态成员变量必须在类外定义进行初始化,定义时不添加static关键字,类中只是声明

3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问

4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员

5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制

对于static成员,我们就大致了解完了,接下来,我们来看看friend友元。

友元

友元,是一种封装方式。它的作用是让类外部的函数能够访问类的私有成员。

友元函数

前面,我们在实现流输出的时候,就遇到了不能访问私有成员的问题,我们现在使用friend来重新实现一下流输出:

C++-类和对象(5)_日期类_38

C++-类和对象(5)_日期类_39

我们先在类外面实现一个流输出,然后,在日期类声明这个函数是我们的朋友,可以访问我们的私有成员。

我们来测试一下:

C++-类和对象(5)_静态成员_40

从结果看,我们的流输出重载没什么问题,既可以实现日期类的输出,还可以完成连续输出。像这种用friend关键字在类中声明的函数,称之为友元函数。

friend这一块还有一些注意事项:

·友元函数可访问类的私有和保护成员,但不是类的成员函数

·友元函数不能用const修饰

·友元函数可以在类定义的任何地方声明,不受类访问限定符限制

·一个函数可以是多个类的友元函数

·友元函数的调用与普通函数的调用原理相同

友元类

友元类也是友元的一个重要的部分。友元类的所有成员函数都可以是另一个类的友元函数,也就是都可以访问另一个类的非公有成员。

接下来,我们来了解一下,什么是内部类。

内部类

如果一个类定义在一个类的内部,那么这个类就称之为内部类。

内部类是天生的友元类,可以直接访问外部类的成员变量。

内部类有几点特性:

1.内部类可以定义在外部类public,private和pretected的任意位置。

2.内部类可以直接访问外部类种的static静态成员(不需要类名或对象)。

3.sizeof(外部类)=外部类,不包括内部类的大小,也就是和外部类无关。

好了,到这里,我们本次的分享就到此结束了,不知道我有没有说明白,给予你一点点收获。如果你有所收获,别忘了给我点个赞,这是对我最好的回馈,当然你也可以在评论发表一下你的收获和心得,亦或者指出我的不足之处。如果喜欢我的分享,别忘了给我点关注噢。

举报

相关推荐

0 条评论