今天,继续和大家分享与类和对象相关的知识,本次的内容包含日期类的实现,const成员,static成员及友元函数等方面。
继上篇文章,我们说到了日期类前置++和后置++的实现。
现在,我们从前置--和后置--接着往下,完成我们日期类的实现。
日期类的实现
日期类前置--和后置--的实现
在实现前置--和后置--前,我们需要先去了解它们的功能。
通过程序运行的结果,我们可以了解到,前置--和后置--都会改变自身的值,它们不同在返回值,前置--返回改变后的数据,而后置--返回更改前的值。
了解完前置--和后置--的功能,我们来实现一下它们。
首先,我们根据运算符重载的格式,给出返回值,函数名,以及参数列表。前置--的返回值是修改后的,我们直接在原数据上修改,然后,进行返回。
接下来,我们把思路转换成代码:
后置--和前置--的思路类似,但后置--需要返回修改前的数据,我们在修改原数据前,需要拷贝一份,用于返回。我们在函数内定义的变量,出了函数后会被销毁,我们不能像前置--那样使用引用返回,而要使用传值返回。还有就是后置--和前置--的运算符相同,为了
能够正确形成重载,后置--的参数列表需要加上一个int类型。
理清了思路,我们来看看后置--实现的代码:
前置--和后置--的实现,我们都完成了,我们来简单的测试一下:
从结果看,我们的程序并没什么问题,可以实现前置--和后置--的功能。
除了日期加减天数有意义外,两个日期的相减的比较也是有意义的。
日期类的比较运算符的实现
由于两个日期的相减涉及到日期的比较,我们先把日期类的比较实现一下。
日期类的比较这块的思路很简单,就是年份小的小,年份相等,月份小的小,如果月份也相等,天数小的则小。
理清了思路,我们来看看代码的实现:
我们来简单的测试一下:
通过测试结果看,我们的程序符合比较的规则,能够得出正确的比较结果。
下面,我们来实现日期类的相减。
两个日期相减的实现方式有很多种,这里我们就选取一个比较简单便于理解的方式来实现。
两个日期类的相减问题,我们可以转换成小日期加上相差天数后得到大日期的问题。相差的天数,我们并不知道,这是要求的。我们只能一天一天的加,当两个日期相等,我们就可以求出相差的天数了。日期的相差天数解决了,可问又来了,我们怎么在小日期减大日期的情况下,返回负的天数呢?对于这个问题,我们可以加个符号位来表示是小日期减大日期还是大日期减小日期,从而确定返回天数该是正的还是负的。
理清了思路,我们来看看代码的实现:
我们来简单的做一下测试:
从两个结果看,我们的计算天数的程序,没什么问题,能够正确计算出相差天数。
关于日期类的运算符我们实现的差不多了。
大家有没有想过内置类型为什么可以自动识别类型输出,为什么我们自定义的类型却不行?
这是因为内置类型的输出运算符,在库里实现了。而它们能自动识别类型是应为它们重载了函数。自定类型,其实也可以自动识别输出,但我们需要自己实现。
从图中我们可以看到,输出流的返回值是ostream。我们试着来实现一下:
我们来测试一下:
从图中结果看,我们的程序是无法运行的,我们把前后位置调换一下试试:
通过运行结果看,位置调换后,程序便可以正常运行输出了。这是什么原因呢?这是因为成员函数,默认第一个传递的参数是this指针,不能是其他类型。这就导致了我们没法像内置类型那样进行输出。那我们该怎么办呢?既然类中规定了第一个传递的参数,那我们可不可在类外面,实现输出运算符的重载呢?我们来试试看:
从编译的结果看,编译器报错了。这是什么原因呢?这是因为类外部的函数无法访问类的私有成员。那我们有没有什么方法?让某些类外部的函数访问类的私有成员呢?方法是有的,使用友元函数关键字friend声明。对于友元,我们待会在讲,现在,我们先实现一个别的函数来打印日期。
我们来测试一下:
从运行结果看,程序并没有什么大问题。我们换个测试用例看看:
从图中可以看到,编译器已经报错了。这是什么原因呢?刚刚明明还能调用,可是到了对象d2这怎么就编译报错了呢?你还记得我们之前在引用时,讲的权限问题吗?权限在传递的时候,只能缩小,而不能放大。
这里就犯了这个权限错误,我们使用const修饰了对象d2,d2不可以被修改。编译器把对象d2的this指针传过去,而this指针并没有加const修饰,这就意味着,对象d2本来不能被修改,到了函数里面就可以修改,这显然是荒谬的。
const成员
那我们怎么去解决呢?由于this指针是隐匿传递,且不能显示传递。我们没法直接用const修饰this指针。C++思来想去,决定在函数后面加const,来表示const修饰this指针。
我们来验证一下,给我们之前的函数加上const修饰this指针:
通过运行结果看,加上const后程序,又能正常使用了。
像这样加上const修饰的函数,被称之为成员函数。对于函数内部不能修改成员变量的,我们都应该加上const修饰。
前面,我们已经学习了四个默认成员函数,不是有六个吗?剩下的两个是普通的对象和const对象的取地址符号的重载。
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
这两个默认函数一般我们不需要重写,编译器写的就已经够用了。
类中的const成员函数,我们了解完了。
接下来,我们来看看类的const成员变量:
我们先声明一个const成员变量_a,然后,依照往常的的方式进行初始化。可为什么编译器报错了。这是什么原因呢?这是因为const修饰的成员不可以修改。为次,我们有两种解决方案,第一种就是在声明的时候,给默认值。
第二种方法是利用初始化列表进行初始化。
再谈构造函数
构造函数的初始化列表
构造函数会先使用初始化列表对成员变量进行初始化,然后,再进行执行函数的实现部分。
构造函数的使用方法是在函数名后的第二行,以冒号开头,用逗号分隔开下一个成员变量,每个成员变量后跟着一个括号,里面放初始值或初始表达式。我们以日期类为例,使用以下初始化列表
我们在使用初始化列表时,需要几点要注意的:
1.成员变量只能在初始化列表出现一次,也就是成员变量只能在初始化列表初始化一次。
2.类中若包含以下成员变量,必须放在初始化列表进行初始化
(1)引用成员变量
(2)const成员变量
(3)自定义类型(该类没有显示定义构造函数)
以上这些成员变量,为什么必须在初始化列表进行
3.尽量是使用初始化列表进行初始化,因为不管是否使用初始化列表,对于自定义类型,一定会先使用初始化列表进行初始化。
4.初始化列表初始化成员变量的顺序,和成员变量声明的顺序相同,与初始化列表的初始化顺序无关
构造函数不仅可以构造和初始化对象,对于单参数和除了第一个参数无缺省值其余均有缺省值的构造函数,还具有类型转换的功能。我们来验证一下这个功能。
explicit关键字
要验证构造函数具有类型转换的功能,我们得先了解一个关键字explicit,它的功能是禁止单参构造函数类型转换的作用。
我们先定义一个单参数的构造函数
然后,我们定义一个日期类对象赋值1,运行输出结果。
除了月份和天数是随机数外,程序能够正常运行出结果。
我们给构造函数加上关键字explicit看看,结果又是怎么样的。
呃,编译器报错了,好像是因为int类型无法转换成Date类型。
这是什么原因呢?这是因为对于我们把整型1赋值给日期类,编译器会先用整型1构造一个日期类的临时对象,再把这个对象拷贝构造给日期类对象d1。而explict修饰后,禁止了构造函数构造这样的临时对象,这就导致了无法实现类型的转换。
对于下面这种多参数,只有第一个参数无缺省值的构造函数也是一样的。
当我们加上explicit关键字修饰这个构造函数后,编译器就会报错,无法完成类型的转换。
而当我们删除explicit关键字后,代码就可以编译运行了。
我们把这种类型的转换,称之为隐式类型转换。如果你不想允许隐式类型的转换,可以使用explicit来修饰构造函数。
下面,我们来看看另外比较重要的两个关键字,static和friend。
static成员
static修饰的成员,也叫静态成员。静态成员包含了静态成员变量和静态成员函数。静态成员也是成员变量,也受public,private,protected权限限定符的限制。一般情况下,我们成员变量都设置为私有。我们可以在不定义对象的情况下,通过静态函数访问静态成员。
我们来看看,他们的定义。
从图中看,很明显编译器报错了,这里报错的原因是静态成员变量不能这样初始化,它需要在全局区域定义初始化,
我们现在将变量_a初始化成0,运行一下程序:
从运行结果看,变量_a成功被初始化成了0。
从上面我们能看到静态成员与普通成员的区别,那静态成员函数与成员函数又有什么区别呢?
从图中看,编译器报错了。这是什么原因呢?这是因为函数没有隐藏的this指针,不能访问任何非静态成员。
现在,我们大致了解了静态成员的使用规则了,我们来使用一下它们:
首先,我们先给我们的类补充一个构造函数,每次调用都会对静态成员变量加1。然后,我们定义两个对象,输出静态成员变量的值。
通过运行结果,我们发现静态成员自增了两次,这个说明了两个对象对同一个静态成员进行了后置++的操作,也就是说静态成员为同一个类的所有对象所共有。
下面,我们来总结一下static的特性:
1.静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
2. 静态成员变量必须在类外定义进行初始化,定义时不添加static关键字,类中只是声明
3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
对于static成员,我们就大致了解完了,接下来,我们来看看friend友元。
友元
友元,是一种封装方式。它的作用是让类外部的函数能够访问类的私有成员。
友元函数
前面,我们在实现流输出的时候,就遇到了不能访问私有成员的问题,我们现在使用friend来重新实现一下流输出:
我们先在类外面实现一个流输出,然后,在日期类声明这个函数是我们的朋友,可以访问我们的私有成员。
我们来测试一下:
从结果看,我们的流输出重载没什么问题,既可以实现日期类的输出,还可以完成连续输出。像这种用friend关键字在类中声明的函数,称之为友元函数。
friend这一块还有一些注意事项:
·友元函数可访问类的私有和保护成员,但不是类的成员函数
·友元函数不能用const修饰
·友元函数可以在类定义的任何地方声明,不受类访问限定符限制
·一个函数可以是多个类的友元函数
·友元函数的调用与普通函数的调用原理相同
友元类
友元类也是友元的一个重要的部分。友元类的所有成员函数都可以是另一个类的友元函数,也就是都可以访问另一个类的非公有成员。
接下来,我们来了解一下,什么是内部类。
内部类
如果一个类定义在一个类的内部,那么这个类就称之为内部类。
内部类是天生的友元类,可以直接访问外部类的成员变量。
内部类有几点特性:
1.内部类可以定义在外部类public,private和pretected的任意位置。
2.内部类可以直接访问外部类种的static静态成员(不需要类名或对象)。
3.sizeof(外部类)=外部类,不包括内部类的大小,也就是和外部类无关。
好了,到这里,我们本次的分享就到此结束了,不知道我有没有说明白,给予你一点点收获。如果你有所收获,别忘了给我点个赞,这是对我最好的回馈,当然你也可以在评论发表一下你的收获和心得,亦或者指出我的不足之处。如果喜欢我的分享,别忘了给我点关注噢。
标签:初始化,const,函数,对象,成员,C++,--,我们 From: https://blog.51cto.com/u_15933803/7528854