C++类和对象(中)六个默认成员函数

🌈类的六个默认成员函数

任何一个类,不管是否为空,都会在生成的时候默认调用六个成员函数,这些成员函数可以自动生成,也可以由程序员写出。这六个默认成员函数分别是:
在这里插入图片描述
最主要的是前四个:
初始化——构造函数
清理内存——析构函数
用对象创造对象——拷贝构造函数
把一对象赋值给另一对象——赋值重载函数

🌈函数一:构造函数

☀️一、功能:

给一个类中的成员变量赋上初始值。

☀️二、特性:

🎈1.函数名和类名相同,无返回值

🎈2.可重载

例如下面的Data类中,构造函数Data有两个重载
在这里插入图片描述

🎈3.实例化类的对象时,由编译器自动调用,但可能需要传值。

🌟(1)当构造函数不需要传参时,创建完对象就完事了,例如:
Date d1; // 调用无参构造函数

注意不能在对象后面加括号,这样编译器会以为你在声明函数:

//warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)      
Date d3();
🌟(2)当构造函数需要参数时,创建对象的同时给后面带个括号,按格式把参数写进去:
Date d2(2015, 1, 1); // 调用带参的构造函数

🎈4.可显式也可隐式

显示式定义构造函数(即自己写出来这个函数)的话,编译器就不自动生成这个函数了;只有在没有显式定义时编译器才会自动生成

☀️三、分类

根据显式调用或隐式调用,以及参数的不同,对构造函数进行以下分类。
所有例子都基于类Data,private部分有年月日三个变量:

class Date
 {
  public:
	  ......(各种函数)
  private:
      int _year;
      int _month;
      int _day;
 };

🎈1.显式调用

🌟(1)无参

例如如下的函数定义和调用:
函数定义:

 Date()
     {}

调用方式:
注意不能加括号。

 Date d1; // 调用无参构造函数
🌟(2)有参

例如如下的函数定义和调用:
函数定义:

 Date(int year, int month, int day)
     {
          _year = year;
          _month = month;
          _day = day;
     }

调用方式:

  Date d2(2015, 1, 1); 
🌟(3)缺省(半缺省同理)

如果创建对象的时候给值了,就用给出的值;如果没给,就用缺省值。
例如如下的函数定义和调用:
函数定义:

 Date(int year=2024, int month=1, int day=23)
     {
          _year = year;
          _month = month;
          _day = day;
     }

调用方式:
注意全缺省的调用也不能加括号。

  Date d2(2015, 1, 1); 
  //或者Date d2;等等

🎈2.隐式调用

当public部分肉眼找不到一个与类名相同的构造函数时,即没有显式调用时,编译器会在内部自动生成一个与类名相同的构造函数,但是会根据类的成员变量的数据类型进行有差别赋值:
对内置类型的变量不进行初始化,即这些变量存的是无意义的随机值;只有对自定义类型才初始化,初始化为该自定义类型成员的默认成员函数。

🌟(补充)数据类型:

①内置类型:比如int、char、指针类型等等。
②自定义类型:比如struct、class、union等等。

既然编译器自动生成的构造函数对内置类型变量不做处理,那还要这个函数干嘛,于是为了发挥这个函数的价值,可以打个补丁:

🌟打补丁

打补丁就是在给类的内置类型的成员变量声明时,就设置好默认值,当不得不隐式调用构造函数时,这些内置类型的成员变量就会被赋值为默认值。例如:

class Date
 {
  public:
	  ......(各种函数)
  private:
      int _year=1;
      int _month=1;
      int _day=1;
 };

注意:默认值不会影响显示调用的结果。

👻其中的三个特殊函数(默认构造函数)

①无参构造函数(显式调用)
②全缺省构造函数(显式调用)
③编译器自动生成的构造函数(隐式调用)

特点:三者只能出现其一。因为,当隐式出现时说明没有显式,即有③就无①②;同理,有①②无③;当调用时啥参数也不传,可能是调用无参函数的情况,也可能是调用缺省函数,因此①②同时存在时会产生歧义,即①②不可同时存在。综上三个函数只能出现一个。

🌈函数二:析构函数

☀️一、功能:

与构造函数功能相反,对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。并不是这个函数本身能清理资源,而是这个函数的“在对象销毁时自动调用”这个特性使得该函数的内部被填写上了相应的清理语句,从而使得该函数被用来清理内存。

☀️二、特性:

🎈1.析构函数名是在类名前加上字符 ~,无参数无返回值类型。

🎈2.一个类只能有一个析构函数,也说明析构函数不能重载。

🎈3.类对象生命周期结束时,C++编译系统系统自动调用析构函数。

不同类对象有不同生命周期:

🌟(1)局部对象:对象所在函数结束时调用。

局部对象的声明:类名 变量名(构造函数参数列表)
例如,有一个类Base,创建该类类型的局部变量,则析构函数是在main函数结束时调用:

int main{
Base base(a,b,...;
}
🌟(2)临时对象:该语句结束时调用。

临时对象的声明:类名 (构造函数参数列表)
例如,有一个类Base,创建该类类型的临时变量,则析构函数是在这句语句执行完后调用:

int main{
Base(a,b,...);
}
🌟(3)指针对象:delete时调用。

指针对象的声明:类名 ∗ * 变量名 = new 变量名(构造函数参数列表)
例如,有一个类Base,创建该类类型的指针变量,则析构函数是在delete时执行(如果忘记写delete语句,则不会执行析构函数,会造成内存泄露):

int main{
Base* base = new Base;
delete base;
}

🎈4.若未显式定义,系统会自动生成默认的析构函数。

🎈5.内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;自定义类型的对象在销毁时要调用该对象所处类的析构函数。

结合特性4和特性5,分析下面这个例子看一看编译器内部帮我们处理了哪些工作:

class Time
{
public:
 ~Time()
 {
 cout << "~Time()" << endl;
 }
private:
 int _hour;
 int _minute;
 int _second;
};
class Date
{
private:
 // 基本类型(内置类型)
 int _year = 1970;
 int _month = 1;
 int _day = 1;
 // 自定义类型
 Time _t;
};
int main()
{
 Date d;
 return 0;
}

运行结果:
在这里插入图片描述
分析:
main方法中创建了Date对象d,而d中包含4个成员变量;
在4个成员变量中,_year、_month、 _day三个是内置型变量,_t是自定义类型变量;
对于内置型变量,销毁时不需要资源清理,最后系统直接将其内存回收即可;
对于一个自定义型变量_t,需要调用该变量所处的类即Time中的析构函数~Time;
肉眼找不到一个名为~Date的函数,说明编译器在内部生成了这个函数;
实际上,隐式生成的~~Date函数,调用了~Time函数。

🎈5.大部分时候可以不写析构函数,用系统默认生成的,只有动态分配情况下必须显式写。

默认生成的构造函数不会释放动态开辟的空间,因此当类中的成员变量涉及到动态分配,必须显式定义析构函数,否则内存泄露。以下面这个例子看一看动态分配时该怎样显式写析构函数。

typedef int DataType;
class Stack
{
public:
 Stack(size_t capacity = 3)
 {
 _array = (DataType*)malloc(sizeof(DataType) * capacity);
 if (NULL == _array)
 {
 perror("malloc申请空间失败!!!");
 return;
 }
 _capacity = capacity;
 _size = 0;
 }
 void Push(DataType data)
 {
 // CheckCapacity();
 _array[_size] = data;
 _size++;
 }
 // 其他方法...
 ~Stack()
 {
 if (_array)
 {
 free(_array);
 _array = NULL;
 _capacity = 0;
 _size = 0;
 }
 }
private:
 DataType* _array;
 int _capacity;
 int _size;
};

🌈函数三:拷贝构造函数

☀️一、功能:

已经有一个类对象,当我想要创建一个新对象且该对象的值和已经存在的那个对象的值相同时,用拷贝构造函数。
如果在程序中我们想要复制一个整型变量,则直接再创建一个变量并对其赋值即可;然而当我们想要复制类比如类类型的变量时,并不像整型变量先创建再复制那么简单,因此需要拷贝构造函数来实现这一功能。
内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

☀️二、特性:

🎈1.拷贝构造函数是构造函数的一个重载形式,即函数名和类名相同。

🎈2.函数参数形式唯一,即类类型对象的引用。

eg:
如果不是传引用,而是使用传值方式,则编译器直接报错,因为会引发无穷递归调用。
在这里插入图片描述

🎈3.拷贝方式分为浅拷贝(值拷贝)和深拷贝。

浅拷贝(值拷贝)是将对象按字节序完成拷贝,用于拷贝非动态开辟内存中的内容;深拷贝本质上是memcpy,先开同样大小的空间,再把值复制过去,用于拷贝动态开辟内存中的内容。

🎈4.调用方式分为隐式和显式。

隐式就是不自己写这个名字与类名相同、参数为类对象的引用的函数,编译器会默认生成,但只要显式地写了,不管写了啥写的对不对,编译器就不生成了,撒手不管了。

🎈5.不同调用方式和拷贝方式间的关系:

隐式调用的拷贝函数都是浅拷贝;显式调用既可以实现浅拷贝也可以实现深拷贝,但前提是要写对。

🎈6.现实中,只有涉及动态分配的问题,才显式写拷贝构造函数来实现深拷贝,其他情况统统不写等着用编译器生成的。

原因:
🌟(1)只要是浅拷贝,编译器都能出色的完成,没必要显式地写,这样还反而增加风险。分析下面的程序:
class Time
{
public:
 Time()
 {
 _hour = 1;
 _minute = 1;
 _second = 1;
 }
 Time(const Time& t)
 {
  _hour = t._hour;
 _minute = t._minute;
 _second = t._second;
 cout << "Time::Time(const Time&)" << endl;
 }
private:
 int _hour;
 int _minute;
 int _second;
};
class Date
{
private:
 // 基本类型(内置类型)
 int _year = 1970;
 int _month = 1;
 int _day = 1;
 // 自定义类型
 Time _t;
};
int main()
{
 Date d1;
 Date d2(d1);
 return 0;
}

分析:
①类Data中的成员变量有四个,其中三个是内置类型,另外一个是自定义Time类型;
②而Time这个类中也全是内置类型的成员变量,因此没有涉及动态分配内存;
③此时拷贝构造函数最好别写,因为在不涉及动态分配内存的时候,编译器可以出色的完成所有成员变量的拷贝工作,即内部生成一个拷贝构造函数,完成对Data类中的三个内置型成员变量的复制,再调用Time类的拷贝构造函数,完成Time类中的三个内置型成员变量的复制;而如果显式地写了,则需要程序员十分仔细地考虑到每一个成员变量甚至是嵌套的成员变量。本来不管的话啥事没有,管了还贼容易出事。

注:Time中的拷贝构造函数显式写出是为了增加最后的打印语句,从而证明Data默认生成的拷贝构造函数调用了Time的拷贝构造函数。
在这里插入图片描述

🌟(2)如果对涉及动态分配的类进行浅拷贝,则最终会出现两个指针指向同一块空间的状况,对象销毁时会对这同一个对象调用两次析构函数,多次释放同一块内存会导致程序崩溃。
typedef int DataType;
class Stack
{
public:
 Stack(size_t capacity = 10)
 {
 _array = (DataType*)malloc(capacity * sizeof(DataType));
 if (nullptr == _array)
 {
 perror("malloc申请空间失败");
 return;
 }
 _size = 0;
 _capacity = capacity;
 }
 void Push(const DataType& data)
 {
 // CheckCapacity();
 _array[_size] = data;
 _size++;
 }
 ~Stack()
 {
 if (_array)
 {
 free(_array);
 _array = nullptr;
 _capacity = 0;
 _size = 0;
 }
 }
private:
 DataType *_array;
 size_t _size;
 size_t _capacity;
};
int main()
{
 Stack s1;
 s1.Push(1);
 s1.Push(2);
 s1.Push(3);
 s1.Push(4);
 Stack s2(s1);
 return 0;
}

运行结果:程序崩溃
在这里插入图片描述
错误分析:
在这里插入图片描述
没有显示地写拷贝构造函数,于是编译器隐式调用,用浅拷贝的逻辑,先开辟一份相同大小的空间,再将原来的值原封不动地拷贝到新空间中。然而拷贝时编译器会把动态内存的指针也复制成一样的,导致两个指针指向同一块空间。在两个类对象的声明周期要结束时,又都会调用析构函数,于是同一块空间被释放了两次,对同一块空间的多次释放会导致程序崩溃。

🌟(3)既然默认生成的函数只能进行浅拷贝,但是此时我的类中有动态开辟空间的操作,那么我只能显式地写出深拷贝了。

基于上面的Stack类,显式地写出拷贝构造函数(同时加上一句打印Stack函数的语句验证是否调用了显式的拷贝构造函数):

Stack(Stack& copy) {
		cout << "Stack(Stack& copy)" << endl;
		_array = (DataType*)malloc(sizeof(DataType) * _capacity);
		if (!_array) {
			perror("malloc fail");
			exit(-1);
		}
		memcpy(_array, copy._array, sizeof(DataType) * _capacity);
		_size = copy._size;
		_capacity = copy._capacity;
	}

运行结果:
在这里插入图片描述

🎈7.有三种情况,肯定会去调用拷贝构造函数,进行拷贝复制动作:

🌟(1)定义对象(两种方式)

①类类型 新的类对象名(被复制的类对象名);
eg:Date d2(d1);
②类类型 新的类对象名 = 被复制的类对象名;
eg:Date d2= d1;

🌟(2)动态创建对象

eg:Date d1;
Date* p = new Date(a);

🌟(3)函数的传值调用和传值返回

eg1:void Test1(Date date);
eg2:Date Test2(int year,int month,int day);

如果参数是类、结构体并且内部有动态申请空间,不要用传值传参或传值返回,非常麻烦且耗费空间。这种情况用传引用会很香。

🎈8.拷贝构造函数传参时,最好前面加上const

当拷贝的顺序写反时,编译没有问题,但最终两个值都会是随机值,因为等号右边this->_year中的随机值覆盖掉了等号左边dd._year中的值:
在这里插入图片描述
在这里插入图片描述
规避掉这个问题,只用在引用前加上const,表示对象dd中的值不可以被改变,此时要是写反了导致dd中的值被改变,就能直接看到报错,从而很容易改对:
在这里插入图片描述
改成下面对的样子:
在这里插入图片描述

🌈函数四:赋值运算符重载函数

☀️赋值运算函数,如何最好地命名?

当有多个函数,分别对变量进行加、减、乘、除、等于、不等于、大于、小于等运算或判断时,应该如何给函数起名从而使程序员直接就知道这个函数是干什么的?肯定不能Compare1、Compare2…这样命名,以英文或中文拼音命名可能别的国家的人看不懂。
👻引入赋值运算符重载,使得命名问题被完美解决。

☀️赋值运算符重载功能与概念:

🎈1.功能:

内置类型对象可以直接用运算符,自定义类型不可以。赋值运算符重载用于对自定义类型变量进行运算。
内置类型:
在这里插入图片描述
自定义类型:比如Date日期类对象
在这里插入图片描述

🎈2.概念:

运算符重载是具有特殊函数名的函数;
函数名:关键字operator后面接需要重载的运算符符号;
具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似;
函数原型:返回值类型 operator操作符(参数列表)。函数重载是意义是允许同名函数存在。

🎈3.种类

内置类型变量间可以有多重关系或运算,那自定义类型也可以通过函数重载的方式拥有。

🌟1.赋值

operator=

🌟1.判断关系

operator==、operator!=(判断相等关系)
operator>、operator<=、operator>=、operator<(判断大小关系)

🌟2.计算结果

operator+、operator+=
operator-、operator-=
operator*、operator%
operator++、++operator
operator–、–operator

☀️拷贝构造函数&operator=:

operator=是用来将一个对象赋值给另一个对象的,和拷贝构造函数功能很像,那二者一样吗?

🎈1.区别:

在这里插入图片描述
用一个已存在的对象构造另一个对象叫做拷贝构造函数;两个存在的对象,将一个对象赋值给另一个对象,这叫赋值运算符重载(中的=赋值运算)。

🎈2.相同点:

在这里插入图片描述
和拷贝构造函数的显式调用(生成)和隐式调用(生成)、浅拷贝(值拷贝)和深拷贝逻辑一样。

用户没有显式实现时,编译器会生成一个默认的operator=函数,即隐式生成,以值的方式逐字节拷贝。对内置类型成员变量是直接赋值的,自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。但隐式生成的无法对涉及到动态内存分配的类进行赋值。

例如:日期类Date、队列类Queue可以用隐式生成的赋值运算符重载,但栈类Stack必须自己实现赋值运算符重载。

☀️注意事项:

🎈1.不能通过连接其他符号来创建新的操作符,比如operator@。

🎈2. “.* 、:: 、sizeof 、?:、 . ”这5个运算符不能重载。

🎈3.需要重载哪些运算符取决于这种运算对于当下场景有没有意义。

在这里插入图片描述
对于日期类型而言,这些运算中,只有日期减日期是有意义的,没必要重载乘除。

🎈4.赋值运算符只能重载成类的成员函数,不能重载成全局函数。

如果要重载成全局函数的话,首先为了在类外部能访问到成员变量,需要将成员变量改为公有的;同时没有了this指针,就要把所有参数显式写出来,例如operator=函数:
在这里插入图片描述

但又会出现一个问题,赋值运算符如果不在类内部显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。

因此上面的程序最终编译失败,不可以在类外部实现运算符重载:
在这里插入图片描述

🎈5.函数形参默认第一个是this指针,且该参数隐藏,只写另一个参数,导致其形参看起来比操作数数目少1。

例如:执行 x == y;语句。
错误写法:
默认第一个是this,this隐藏,然后再加上两个显式写出来的,一共就3个参数了,对于赋值运算而言,3个参数明显过多,因此错误显式函数的参数太多:
在这里插入图片描述
正确写法:
在这里插入图片描述
符号(==)左边的默认是函数第一个参数,右边的默认是第二个参数。
隐藏的this指向等号左边的对象x;显式写出来的y接收等号右边的对象y
_year实际上是this->_year;
_month实际上是this->_month;
_day实际上是this->_day;

🎈6.赋值重载函数有返回值的前提下,类对象是可以连续赋值的

当operator=函数无返回值时,不可以连续赋值:
在这里插入图片描述
在这里插入图片描述
此时借用整型变量连续赋值的思想,即设置返回值:
在这里插入图片描述
对于连等式右边的“j=10”的运算其实是有返回值的,返回值是10,从而i也能被赋值为10。
同理,给operator=函数也设置类指针(Date*)类型的返回值,返回this指针,即返回d1的数据,则可以实现类对象的连续赋值:
在这里插入图片描述
在这里插入图片描述

☀️完整实现一个日期类

链接:http://t.csdnimg.cn/1Xa62

🌈补充:流操作符重载函数(非默认成员函数)

对内置类型数据的打印和提取,一般的语句是:
在这里插入图片描述
而cout、cin和<<、>>符号具体是什么,具体是如何完成流插入和留提取操作的?

☀️深入了解cout和cin

  1. <<是流插入操作符,将内容输出到控制台上面;>>是留提取操作符,将控制台上的内容读取到变量中;
  2. cout是ostream类型的对象,用于流插入(内置类型数据);cin是istream类型的对象,用于流提取(内置类型数据);
    在这里插入图片描述
  3. 流操作符和流操作对象结合:“cout << 2”是将2这个内置类型数据插入到cout中,从而完成流插入,即数据的打印;“2 << cin”是将2内置类型数据提取到cin中,从而完成流提取,即数据的存储。
  4. 为何对于任意的内置类型数据都可以完成流插入和提取?因为内部的operator<<(或operator>>)函数已将所有的内置类型都进行了重载,从而可以随意匹配。
    在这里插入图片描述

☀️插入和提取自定义类型——流操作符函数重载

上述的插入和提取操作只能用于内置类型数据,如果想用该符号插入或提取自定义类型,则需要重载这两个运算符,即使用operator<<函数和operator>>函数。

🎈1.流插入操作符重载:

🌟(1)第一种重载函数写法:写到类内部

将流操作符重载函数写在类的内部,充当成员函数:
函数实现:
在这里插入图片描述
成员函数默认第一个参数是this,且该参数隐藏。
函数调用:
❌错误方式:cout << d;
如果调用方式为cout << d;的话,则第一个参数指针this指向<<操作符左边的cout,第二个参数out则接收对象d的值。最终函数内部进行的操作是将cout中的数据放到d中,显然顺序反了,无法进行流插入(打印)。
✅正确方式:
在这里插入图片描述

第一个参数指针this指向<<操作符左边的d,第二个参数out接收对象cout的值。最终函数内部进行的操作是将d中的数据放到cout中,从而完成流插入(打印)。

但是此种调用方法不符合可读性,因此介绍第二种函数写法。

🌟(2)(推荐)第二种重载函数写法:写到类外部
①前提:能访问到类内成员变量:

在类外部不可以访问类的私有变量,此时有两种方式:
1.将成员变量改成public
2.保持成员变量私有,用函数(例如GetYear等)得到变量的值
3.用友元函数:
在这里插入图片描述
意思是,我(函数)是你(类)的朋友,你的私有成员变量我可以直接拿来用。

②函数实现和函数调用

流操作符重载函数作为全局函数时,不会默认第一个参数this,因此可以随意控制参数顺序,需要传入两个参数。
函数实现:
在这里插入图片描述
函数调用:
在这里插入图片描述

cout被函数的第一个参数out接收,d被函数的第二个参数d接收,最终函数内部进行的操作是将d中的数据放到cout中,从而在保证可读性的情况下也完成了流插入(打印)。

③自定义类型的连续打印

在operator<<函数重载函数的返回值为空的情况下,无法连续打印:
在这里插入图片描述
想要连续打印,必须设置返回值。而连续打印的顺序是从左往右(这一点和连等相反),即先运行cout << d1,因此只有函数重载的返回值是cout的引用,才能完成cout << d2的操作,从而实现连续打印。cout是ostream类型的,返回值类型为ostream&:
在这里插入图片描述

🎈2.流提取操作符重载

函数名为operator>>。把cout改成cin,把<<改成>>,把ostream改成istream,注意,流提取符号两边的类都发生了变化,因此两个参数都不能加const。
比如“cin>>d”这个语句,符号>>左边是cin,右边是d,表示将cin的内容提取到d对象中。
如果想实现连续提取,operator>>就必须返回cin的引用,返回值类型为istream&。

🎈完整函数

Date.h中有Date类的声明和两个全局函数的声明,其中Date类中还包含了两个友元函数的声明;Date.cpp中是所有函数的定义。

🌟1.Date.h中类的声明:
Date{
public:
	......
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
private:
	int _year;
	int _month;
	int _day;
}
ostream& operator<<(ostream& out,const Date& d);
istream& operator>>(istream& in, Date& d);
🌟2.Date.cpp中函数重载的实现
#include "Date.h"
using namespace std;

ostream& operator<<(ostream& out, const Date& d1)
{
	out << d1._year << d1._month <<  d1._day << endl;
	return out;
}
istream& operator>>(istream& in, const Date& d1)
{
	in >> d1._year >> d1._month >>  d1._day >> endl;
	return in;
}

🌈函数五:const成员函数

☀️什么是const修饰的成员函数?

  1. 将const修饰的“成员函数”称之为const成员函数,即函数声明和定义的后面加上const。
    (注意:const在函数声明和定义时都要写)
    例如Print函数(声明和定义):
void Print() const;

在这里插入图片描述
2. const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
在这里插入图片描述

☀️用const修饰成员函数的意义

🎈1.一个被const修饰的对象,只能调用被const修饰的函数

🌟例1:

日期类对象d1被const修饰,然而成员函数Print没有被const修饰,因此无法用d1调用Print函数。

原因分析:Print函数没有被const修饰->this指针没有被const修饰->将有const修饰的对象d1传递给无const修饰的this属于权限放大->无法调用
在这里插入图片描述
只有给Print函数加上了const,才能让const修饰this指针,此时再将d1传递给this就属于权限平移了,可以实现调用:
在这里插入图片描述

🌟例2:

对象d1被const修饰,对象d2未被const修饰,需要比较两个变量的大小关系,但是d1<d2这句话报错了:
在这里插入图片描述
编译器会调用operator<函数,该函数未被const修饰:
在这里插入图片描述
错误分析:d1<d2这句话在运行时,编译器会用this指针接收d1变量,即用非const变量接收const变量,属于权限放大。
在这里插入图片描述

🎈2.而任何对象都可以调用有const修饰的函数。

在这里插入图片描述

在这里插入图片描述
Print函数被const修饰,this指针因此被const修饰,没有被const修饰的对象d2也可以被this接收,属于权限的缩小,是允许的。

☀️最好给每个用于比较的函数后面都加上const

这样可以使每个参数都被const修饰,因此所有参数都即可以接收有const对象(权限平移)也可以接受无const对象(权限缩小)(比较不会改变成员)
在这里插入图片描述

☀️需要对参数进行改变的话不能加const

比如operator+=函数,需要对传进来的那个类进行值的变动,加了const后就变不了了。
在这里插入图片描述
在这里插入图片描述

🌈函数六:取地址及const取地址操作符重载函数

(设计这两个函数主要是为了凑个逻辑闭环,实际上不用太了解,一般用不到)

☀️1.功能:返回对象的地址

☀️2.区别:

🎈(1)取地址操作符重载:对象无const修饰时调用,即Date* operator&( )

🎈(2)const取地址操作符重载:对象有const修饰时调用,即const Date* operator&( ) const

🎈(3)函数调用匹配

Date ∗ * 和const Date ∗ * 被编译器识别为两个类型

🌟①对象无const修饰时,调用上面的取地址重载函数,对象被const修饰时,调用下面的取地址重载函数。

在这里插入图片描述

🌟②当把无const取地址重载函数屏蔽掉之后,const对象和非const对象都只能调const取地址重载函数了,并且不会产生权限错误

在这里插入图片描述

🌟③当两个取地址重载函数都不写时,这两个函数也可以跑,因为取地址重载函数也是默认成员函数,不显式写的时候编译器会自动生成

☀️3.应用场景

因此大部分情况下不用考虑显式写这个函数,只有极端场景下,可以显式调用从而对返回值进行更改,如:

🎈(1)不想返回对象的地址,要返回空地址

在这里插入图片描述
在这里插入图片描述

🎈(2)想要返回一个假地址

在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/361470.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DDD学习使用

简介 DDD(Domain-Driven Design)&#xff1a;领域驱动设计。 Eric Evans “领域驱动设计之父” DDD不是架构&#xff0c;而是一种方法论&#xff08;Methodology&#xff09;微服务架构从一出来就没有很好的理论支撑如何合理的划分服务边界&#xff0c;人们常常为服务要划分多…

6.3 内存池模式

Bruce Powel Douglass大师介绍-CSDN博客https://blog.csdn.net/ChatCoding/article/details/134665868嵌入式软件开发从小工到专家-CSDN博客https://blog.csdn.net/ChatCoding/article/details/135297955C嵌入式编程设计模式源码-CSDN博客https://blog.csdn.net/ChatCoding/art…

Android 基础技术——Handler

笔者希望做一个系列&#xff0c;整理 Android 基础技术&#xff0c;本章是关于 Handler 为什么一个线程对应一个Looper&#xff1f; 核心&#xff1a;通过ThreadLocal保证 Looper.prepare的时候&#xff0c;ThreadLocal.get如果不空报异常&#xff1b;否则调用ThreadLocal.set,…

2、趋势Trend (copy)

利用移动平均数和时间虚拟模型对长期变化进行建模。 文章目录 1、什么是趋势?2、移动平均图3、工程趋势4、示例 - 隧道交通1、什么是趋势? 时间序列的趋势组成部分代表了序列均值的持久、长期变化。趋势是序列中变化最慢的部分,代表了最重要的大时间尺度。在产品销售的时间…

Unity中使用Ultraleap的Slider组件

Unity中使用Ultraleap的Slider组件&#xff0c;实现物体在指定范围内滑动&#xff1a; 本节在上一节基础上进行&#xff0c;上一小结参考如下&#xff1a; Unity中使用Ultraleap的InteractionButton组件 本节工程文件如下&#xff1a; Unity中使用Ultraleap的Slider组件 1、在…

【Algorithms 4】算法(第4版)学习笔记 02 - 1.4 算法分析

文章目录 前言参考目录学习笔记1&#xff1a;科学方法2&#xff1a;观察举例&#xff1a;三数之和3&#xff1a;近似4&#xff1a;增长数量级4.1&#xff1a;二分查找 demo4.2&#xff1a;二分查找代码实现4.3&#xff1a;二分查找比较次数的证明&#xff08;比较次数最多为lgN…

MYSQL的配置和安装

下载安装 打开官网 MYSQL官网 点击DOWNLOADS 滑到最低下点击&#xff1a;MYSQL Community(GPL) Downlads 点击Download Archives 点击MySQL Community Server进入网站 选择相应版本下载&#xff0c;这里选择的是5.7.24版本,x86 64位【按需选择】 下载解压 配置文件…

H5022B降压恒流芯片 内置MOS PWM调光 高性价比 支持48V 60V 80V 100V

内置MOSFET的100V降压恒流芯片是一种能够将高输入电压降低到稳定的输出电流的降压稳流器。以下是其基本工作原理&#xff1a; 输入电压检测&#xff1a;芯片首先检测输入电压&#xff0c;即来自电源的100V。这涉及使用电压检测电路&#xff0c;以确保输入电压在可接受范围内。…

springboot 怎么设置局域网访问

如何配置Spring Boot应用以实现局域网访问 在开发一个Spring Boot应用时&#xff0c;我们通常会通过localhost来访问和测试我们的应用。但是&#xff0c;当我们想要在局域网中分享我们的应用&#xff0c;供其他设备访问时&#xff0c;仅仅使用localhost是不够的。本文将引导你…

PyNest 一个可以搭建微服务的 Python 包

PyNest 在构建 Python API 和微服务方面崭露头角&#xff0c;解决了 FastAPI 中存在的关键问题&#xff0c;因此成为卓越的框架。凭借其模块化的架构和先进的特性&#xff0c;PyNest 在 2024 年及以后有望成为 Python 开发者的首选选择。 随着 Python 生态系统的不断成熟&…

关于信号处理中的测量精度与频谱细化问题及其仿真实践

说明 频谱细化问题其实很早之前就想研究并整理一下了&#xff0c;车载雷达中我们似乎对这个话题并不太涉及(最多只是在测角时用补0 FFT的方法)&#xff0c;想要了解这个话题的源头是很早之前的一次面试时面试官问我&#xff1a;有哪些提高测量精度的方法&#xff1f;并进而引申…

Linux 文件IO

目录 linux下的文件分类&#xff1a; 文件描述符原理&#xff1a;&#xff08;底层原理&#xff0c;可跳过&#xff09; 虚拟文件系统&#xff1a; 内存中的inode与磁盘中的inode open函数 函数原型&#xff1a; 形参列表&#xff1a; 代码&#xff1a; close函数 er…

eNSP学习——华为交换机STP配置和选路规则

目录 原理概述 实验内容 实验目的 实验步骤 实验拓扑 实验步骤 基本配置 配置网络中的根交换机 理解根端口的选举 理解指定端口的选举&#xff08;首先比较根路径开销&#xff09; 原理概述 生成树协议&#xff08;英语&#xff1a;Spanning Tree Protocol&#…

excel 选中指定区域

问题 excel 选中指定区域 详细问题 笔者有一个excel数据集&#xff0c;数据量较大&#xff0c;如何快速选中指定区域 解决方案 步骤1、 点击起始单元格 确定单元格坐标&#xff08;建议直接CtrlC复制至剪贴板&#xff09; 具体操作入下图所示 步骤2、 点击结束单元格 …

微信小程序|推箱子小游戏

推箱子游戏是一种经典的益智游戏,通过移动箱子将其推到指定位置,完成关卡的过程。随着小程序的发展,越来越多的人开始在手机上玩推箱子游戏。本文将介绍如何利用小程序实现推箱子游戏,并分享一些技术实现的方法。 目录 引言游戏背景介绍游戏规则及挑战技术实现步骤创建游戏…

Leetcode—1570. 两个稀疏向量的点积【中等】Plus

2024每日刷题&#xff08;一零四&#xff09; Leetcode—1570. 两个稀疏向量的点积 实现代码 class SparseVector { public:SparseVector(vector<int> &nums) {for(int i 0; i < nums.size(); i) {if(nums[i]) {indexNum[i] nums[i];}}}// Return the dotProd…

3 款最好的电脑硬盘数据迁移软件

您将从本页了解 3 款最好的 SSD硬盘数据迁移软件&#xff0c;磁盘供应商提供的软件和可靠的第三方软件。仔细阅读本文并做出您的选择。 什么是数据迁移&#xff1f; 数据迁移是将数据移动到其他计算机或存储设备的过程。在日常工作活动中&#xff0c;常见的数据迁移有三种&…

类Markdown实时绘图编辑器mermaid-live-editor

什么是 Mermaid &#xff1f; Mermaid 是一个基于文本的图表描述语言&#xff0c;它允许你使用简洁的语法来描述各种不同类型的图表和图示&#xff0c;例如流程图、时序图、甘特图等。 什么是 mermaid-live-editor &#xff1f; mermaid-live-editor 是一个基于 Javascript 的在…

springboot3-web开发

跟着尚硅谷学springboot3 0.配置application语法 表示复杂对象person Component ConfigurationProperties(prefix "person") public class Person {private String name;private Integer age;private Date birthday;private Child chlid;private List<Dog>…

实战Vue.js与MySQL:爱心商城项目开发指南

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…