首页 > 编程语言 >C++-类和对象(5)

C++-类和对象(5)

时间:2023-09-19 20:31:37浏览次数:35  
标签:初始化 const 函数 对象 成员 C++ -- 我们

今天,继续和大家分享与类和对象相关的知识,本次的内容包含日期类的实现,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(外部类)=外部类,不包括内部类的大小,也就是和外部类无关。

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

标签:初始化,const,函数,对象,成员,C++,--,我们
From: https://blog.51cto.com/u_15933803/7528854

相关文章

  • 开发微信支付C/C++代码
     抄自:  https://zhuanlan.zhihu.com/p/606909332?utm_id=0 在QtC++中实现微信收费使用功能需要借助微信开放平台提供的API接口,具体步骤如下:注册微信开放平台账号,并创建应用。在创建应用时,需要选择相应的应用类型(如网页应用、移动应用等)和使用场景(如公众号、小程序等),并......
  • visual studio 配置 c++ 引入三方库 注意事项:
    1、首先需要注意配置的选项要和运行的选项一致(如果要运行debug下32位的程序就需要配置Configuration选择Debug,platform选32) 2、这个是根据需要生成lib、dll、还是windows程序来选择:  VC++目录默认不要修改: 这里添加引入#include所需要的头文件 这个地方配置链......
  • 关联式数据结构_红黑树剖析 #C++
    红黑树的性质和定义红黑树的性质红黑树是一种平衡搜索二叉树。红黑树的每个节点存储了一个标记颜色的变量(红色或黑色),通过对任意一条从根到叶子结点的路径中节点着色方式的限制,使树的最长路径不超过最短路径的两倍,因而红黑树处于一种近似平衡的状态。与AVL树相比,红黑的平衡条件更......
  • 常量池中的字符串仅是符号,第一次使用时才变为对象(加入到运行时常量池),可以避免重复
    常量池中的字符串仅是符号,第一次使用时才变为对象(加入到运行时常量池),可以避免重复创建字符串对象 intern()JDK1.8:将这个字符串对象尝试放入串池,如果StringPool中:存在一个字符串和该字符串值相等,就会返回StringPool中字符串的引用(需要变量接收)不存在,会把对象的......
  • 线程劫持-进程注入C++示例和检测思考
    线程劫持:运行方法C:\Users\l00379637\source\repos\thread_hijack\x64\Release\thread_hijack.exe18132C:\Users\l00379637\source\repos\injected_dll\x64\Release\injected_dll.dllProcessID:18132Injected!劫持效果: 劫持代码如下:#include<iostream......
  • 面向对象编程特征?
    面向对象编程(Object-OrientedProgramming,OOP)的特征包括以下几个方面:封装(Encapsulation):封装是将对象的状态(属性)和行为(方法)捆绑在一起,并对外部隐藏对象的内部细节。通过访问修饰符(如public、private、protected等)来限制对对象属性的直接访问,以确保数据的安全性和一致性。封......
  • C++序列式容器
    需要注意的是,序列容器只是一类容器的统称,并不指具体的某个容器,序列容器大致包含以下几类容器:array<T,N>(数组容器):表示可以存储 N个T类型的元素,是 C++ 本身提供的一种容器。此类容器一旦建立,其长度就是固定不变的,这意味着不能增加或删除元素,只能改变某个元素的值;vector<T>......
  • C++匿名对象生存期
    classSome{intn;public:Some(ints){n=s;}~Some(){cout<<"destroy\n";}intret(){returnn;}};intmain(intargc,char*argv[]){cout<<Some(111).ret()<<"\n";cout<<"wait......
  • 个人项目——C++实现论文查重(简易版)
    本次项目GitHub地址:https://github.com/Focuspresent/Paper_Review这个作业属于哪个课程https://edu.cnblogs.com/campus/jmu/ComputerScience21这个作业的要求https://edu.cnblogs.com/campus/jmu/ComputerScience21/homework/13034这个作业的目标完成一次编程练......
  • C++系列十:日常学习-Lambda表达式
    目录前言必备理论知识:例子:前言有C#经验,使用起来,驾轻就熟。就是语法糖。但是也要熟悉用法,才好众享丝滑。内容参考:Chatjpt、文心一言必备理论知识:捕获列表:[]:默认不捕获任何变量;[=]:默认以值捕获所有变量;内部有一个相应的副本[&]:默认以引用捕获所有变量;[x]:仅以值捕获x,其它......