类和对象(2)(重点)

 个人主页:Jason_from_China-CSDN博客

所属栏目:C++系统性学习_Jason_from_China的博客-CSDN博客

所属栏目:C++知识点的补充_Jason_from_China的博客-CSDN博客

类的默认成员函数

概念概述

  • 默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。一个类,我们不写的情况下编译器会默认生成以下 6 个默认成员函数,需要注意的是这 6 个中最重要的是前 4 个,最后两个取地址重载不重要,我们稍微了解一下即可。其次就是 C++11 以后还会增加两个默认成员函数,移动构造和移动赋值,这个我们后面再讲解。默认成员函数很重要,也比较复杂,我们要从两个方面去学习。

包括

构造函数

概念概述

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象 (我们常使用的局部对象是栈帧创建时,空间就开好了),而是对象实例化时初始化对象。构造函数的本质是要替代我们以前 Stack 和 Date 类中写的 Init 函数的功能,构造函数自动调用的特点就完美的替代的了 Init。

构造函数的特点:

  1. 函数名与类名相同。
  2. 无返回值。(返回值啥都不需要给,也不需要写 void,不要纠结,C++ 规定如此)
  3. 对象实例化时系统会自动调用对应的构造函数。
  4. 构造函数可以重载(因为需要不同的数据,不同的初始化,会使开发更加简单)。
  5. 如果类中没有显式定义构造函数,则 C++ 编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
  6. 无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数有且只有一个存在,不能同时存在。无参构造函数和全缺省构造函数虽然构成函数重载,但是调用时会存在歧义。要注意很多同学会认为默认构造函数是编译器默认生成那个叫默认构造,实际上无参构造函数、全缺省构造函数也是默认构造,总结一下就是不传实参就可以调用的构造就叫默认构造。
  7. 我们不写,编译器默认生成的构造,对内置类型成员变量的初始化没有要求,也就是说是是否初始化是不确定的,看编译器。对于自定义类型成员变量,要求调用这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要用初始化列表才能解决,初始化列表,我们下个章节再细细讲解。
  8. 简单的说构造函数就是用来初始化的函数,而且默认构造函数会自动调用

注意:

C++ 把类型分成内置类型 (基本类型) 和自定义类型。内置类型就是语言提供的原生数据类型,如:int/char/double/ 指针等,自定义类型就是我们使用 class/struct 等关键字自己定义的类型。

构造函数的实现

不带参数构造函数

//.h///默认构造函数的使用
class MyClass
{
public://默认构造函数MyClass()//类名相同,没有参数,叫做无参数构造函数{_year = 1999;_month = 12;_day = 1;cout << "默认构造函数的自动调用:" ;cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;
};//.cpp
int main()
{MyClass d1;return 0;
}

此时我们发现这里在初始化的时候自动调用无参数构造函数(也就是默认构造函数),当然这里初始化成功

带参数构造函数

//.h
class MyClass1
{
public:MyClass1(int year, int month, int day){_year = year;_month = month;_day = day;cout << "带参数构造函数的调用:";cout << _year << "/" << _month << "/" << _day << endl << endl;}private:int _year;int _month;int _day;
};//.cpp
int main()
{//不带参数构造函数MyClass d1;//带参数构造函数MyClass1 d2(2024, 9, 14);//带参构造函数这里是需要传递一个参数return 0;
}

半缺省构造函数

//.h
//半缺省构造函数(从右往左进行赋值)
class MyClass2
{
public:MyClass2(int year, int month, int day = 28){_year = year;_month = month;_day = day;cout << "半缺省构造函数(从右往左进行赋值):";cout << _year << "/" << _month << "/" << _day << endl << endl;}private://私有变量,只能类里面的函数进行访问int _year;int _month;int _day;
};//.cpp
int main()
{//不带参数构造函数MyClass d1;//带参数构造函数MyClass1 d2(2024, 9, 14);//半缺省构造函数(从右往左进行赋值),赋值的时候,从左往右哦,就是为了和构造函数的缺省参数进行分开MyClass2 d3(2000, 2);return 0;
}

半缺省构造参数(从右往左进行赋值),赋值的时候,从左往右哦,就是为了和构造函数的缺省参数进行分开

全缺省构造函数

//全缺省构造函数(从右往左进行赋值)
class MyClass2
{
public:MyClass2(int year, int month, int day = 28){_year = year;_month = month;_day = day;cout << "半缺省构造参数(从右往左进行赋值):";cout << _year << "/" << _month << "/" << _day << endl << endl;}private://私有变量,只能类里面的函数进行访问int _year;int _month;int _day;
};int main()
{//不带参数构造函数MyClass d1;//带参数构造函数MyClass1 d2(2024, 9, 14);//半缺省构造函数(从右往左进行赋值)MyClass2 d3(2000, 2);//全缺省构造函数MyClass3 d4;return 0;
}

默认构造函数

无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数有且只有一个存在,不能同时存在。无参构造函数和全缺省构造函数虽然构成函数重载,但是调用时会存在歧义。要注意很多同学会认为默认构造函数是编译器默认生成那个叫默认构造,实际上无参构造函数、全缺省构造函数也是默认构造,总结一下就是不传实参就可以调用的构造就叫默认构造。

总结:

简单的说就是,在创建对象初始化的时候,会自动调用的函数,带参数构造函数不是默认构造函数,是需要调用的时候传参的。默认构造函数在调用创建对象的时候是不需要传参的。上面我们的代码很清晰了。

构造函数注意事项

对于自定义类型来说,构造函数往往是需要自己实现的。因为自定义类型的初始化可能较为复杂,需要根据特定的逻辑来进行成员变量的初始化等操作。例如,一个包含多个成员变量且有特定初始化需求的自定义类,通常需要定义构造函数来确保对象在创建时被正确初始化。

 

对于内置类型来说是不需要的。内置类型如 int、char、double 等,在创建对象时如果不进行显式初始化,它们会被自动赋予默认值(对于数值类型通常为 0,对于指针类型通常为 nullptr 等)。当然,如果需要特定的初始值,也可以在定义变量时进行显式初始化,但一般情况下不需要专门为内置类型定义构造函数。

析构函数(~)

概念概述

析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁,比如局部对象是存在栈帧的,函数结束栈帧销毁,他就释放了,不需要我们管,C++规定对象在销毁时会自动调用析构函数,完成对象中资源的清理释放工作。析构函数的功能类比我们之前 Stack 实现的 Destroy 功能,而像 Date 没有 Destroy,其实就是没有资源需要释放,所以严格说 Date 是不需要析构函数的。

析构函数的特点:

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值。(这里跟构造类似,也不需要加 void)
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
  4. 对象生命周期结束时,系统会自动调用析构函数。
  5. 跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理,自定义类型成员会调用他的析构函数。
  6. 还需要注意的是我们显示写析构函数,对于自定义类型成员也会调用他的析构,也就是说自定义类型成员无论什么情况都会自动调用析构函数。
  7. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,如 Date;如果默认生成的析构就可以用,也就不需要显式写析构,如 MyQueue;但是有资源申请时,一定要自己写析构,否则会造成资源泄漏,如 Stack。
  8. 一个局部域的多个对象,C++规定后定义的先析构。
  9. 简单的说,析构函数就是函数的销毁,是编译器会自动调用的销毁

什么时候需要析构函数

当有资源申请的时候,就需要进行析构函数,析构函数是和构造函数一样的,内置类型不需要析构,自定义类型需要进行析构,也需要自己写析构函数,但是调用的时候是会自己调用的。

像如下图:此时开辟了空间,产生了资源的使用,所以就需要析构函数,如果在main函数调用结束的时候没有进行销毁资源,就会导致资源的泄露,这是很麻烦的。所以此时我们需要进行析构函数。

什么时候不需要析构函数 

1,没有资源申请的时候

2,内置类型不做处理

3,两个栈实现队列的时候,内置类型不做处理,自定义类型会调用栈的析构(可以在队列的析构里面写一行,cout

但是这里有一个特殊情况,就是两个栈实现队列下面有其他资源

这里的指针需要析构,因为指向一个空间,需要把这个资源释放了

析构函数的使用

书写,其实就是C语言的函数的销毁

调用,main函数在结束的时候,会自动调用析构函数,因为这里有资源的申请

多个对象的时候,析构顺序

当有资源需要进行析构的时候,你没有写内容,但是定义了析构函数,也是会调用的,只是没有析构成功(析构函数的自动调用:当一个对象的生命周期结束时,如果该对象的类定义了析构函数,那么析构函数会被自动调用。这是由C++运行时环境保证的。如果没有定义析构函数,那么不会有任何特别的函数被调用来处理对象的销毁。)

规定来说哦,后定义的先析构

所以这里是st2先析构,st1后析构

析构函数调用的时候-注意事项

  1. 规定来说哦,后定义的先析构
  2. 类的析构函数调用一般是按照调用的相反顺序进行调用,但是需要注意static对象的存在,因为static改变了对象的生存作用域,需要等待程序结束的时候才会释放对象
  3. 全局变量先于局部对象进行构造。

    (在 C++ 中,在程序启动时,全局变量的构造在进入main函数之前完成

    也就是说,先构造全局变量,然后程序的执行流程进入main函数,再构造main函数中的局部变量等。)

  4. 局部对象按照出现的顺序进行构造,无论是否为static。(简单的说,也就是在main,调用Func函数,Func函数会按照后定义的先析构顺序进行析构)
  5. 题型精讲:

    全局变量优先局部进行构造,所以往往最后进行析构。(C)
    局部变量先构造的后析构。(A B)
    被静态修饰的变量因为static改变了对象的生存作用域,需要等待程序结束的时候才会释放对象(D)

拷贝构造函数

概念概述

如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是一个特殊的构造函数。

同时C++规定了,这里是规定,传值传参必须调用拷贝构造

拷贝构造函数的特点

  1. 拷贝构造函数是构造函数的一个重载。
  2. 拷贝构造函数的第一个参数必须是类类型对象的引用,使用传值方式编译器直接报错,因为语法逻辑上会引发无穷递归调用。拷贝构造函数也可以多个参数,但是第一个参数必须是类类型对象的引用,后面的参数必须有缺省值。
  3. C++ 规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。
  4. 若未显式定义拷贝构造,编译器会生成自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成值拷贝 / 浅拷贝 (一个字节一个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造。
  5. 像 Date 这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的拷贝构造就可以完成需要的拷贝,所以不需要我们显式实现拷贝构造。像 Stack 这样的类,虽然也都是内置类型,但是 _a 指向了资源,编译器自动生成的拷贝构造完成的值拷贝 / 浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝 (对指向的资源也进行拷贝)。像 MyQueue 这样的类型内部主要是自定义类型 Stack 成员,编译器自动生成的拷贝构造会调用 Stack 的拷贝构造,也不需要我们显式实现 MyQueue 的拷贝构造。这里还有一个小技巧,如果一个类显示实现了析构并释放资源,那么他就需要显示写拷贝构造,否则就不需要。
  6. 传值返回会产生一个临时对象调用拷贝构造,传值引用返回,返回的是返回对象的别名 (引用),没有产生拷贝。但是如果返回对象是一个当前函数局部域的局部对象,函数结束就销毁了,那么使用引用返回是有问题的,这时的引用相当于一个野引用,类似一个野指针一样。传引用返回可以减少拷贝,但是一定要确保返回对象,在当前函数结束后还在,才能用引用返回

拷贝函数的使用

拷贝构造的实现其实是很简单,这里先举出实例讲解,之后会进行解释,主要就是传参的时候,如果传递的是传值传参,那么需要用引用进行接收,不然会导致死循环从而导致崩溃

下面的的代码里面首先我们实现声明和实现的分离

//.h
//拷贝构造函数的实现
class Date
{
public://构造函数的实现Date();//拷贝构造的实现Date(Date& d);//打印函数的实现void print();
private:int _year;int _month;int _day;
};//构造函数的实现
Date::Date()
{_year = 2000;_month = 2;_day = 28;cout << "构造函数的实现";cout << _year << "/" << _month << "/" << _day << endl << endl;
}//拷贝构造的实现
Date::Date(Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}//打印函数的实现
void Date::print()
{cout << _year << "/" << _month << "/" << _day << endl << endl;
}//.cpp
int main()
{//构造函数的实现Date d1;//拷贝构造的实现:1Date d2(d1);d2.print();//拷贝构造的实现:2Date d3 = d2;d3.print();return 0;
}

运行代码,可以清楚的看见,两个拷贝构造都实现成功,这两种形式,看看你喜欢哪一种拷贝构造实现形式

拷贝构造的过程

//.h//拷贝构造函数的实现
class Date
{
public://构造函数的实现Date();//拷贝构造的实现Date(Date& d);//打印函数的实现void print();
private:int _year;int _month;int _day;
};//构造函数的实现
Date::Date()
{_year = 2000;_month = 2;_day = 28;cout << "构造函数的实现";cout << _year << "/" << _month << "/" << _day << endl << endl;
}//拷贝构造的实现
Date::Date(Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}//打印函数的实现
void Date::print()
{cout << _year << "/" << _month << "/" << _day << endl << endl;
}//.cpp
void Func(Date& d)
{cout << "拷贝构造函数的调用" << endl;d.print();
}
int main()
{//构造函数的实现Date d1;//拷贝构造的实现:1Date d2(d1);d2.print();//拷贝构造的实现:2Date d3 = d2;d3.print();//传值传参的调用Func(d3);return 0;
}

这里我们观察拷贝构造是如何完成的

第一步,类类型的,传值传参

第二步,返回函数

全过程图解

在这个例子中,void Func(Date& d)中的&可以写也可以不写,但加上引用可以避免不必要的拷贝构造函数调用,提高效率。

如果写成void Func(Date d),在调用Func函数时,会调用拷贝构造函数将实参对象拷贝一份传递给函数参数d,可能会有一定的性能开销,尤其是当Date类比较复杂时。

如果写成void Func(Date& d),则是通过引用传递参数,不会调用拷贝构造函数,只是传递了对象的引用,效率更高。综上所述,从性能角度考虑,建议加上引用符号&。

所以可以进行与优化,我们函数依旧采取引用接收,减少拷贝

拷贝构造函数的原理解释

首先我们需要明确一点就是,C++在传值传参的时候会调用拷贝构造

什么是传值传参,顾名思义肯定是传递数值。

如果我们传值传参,但是接收的话不采取引用接收,就会导致一直情况

拷贝构造和指针

class MyClass
{
public:MyClass();MyClass(MyClass* M);//打印函数的实现void print(){cout << _year << "/" << _month << "/" << _day << endl << endl;}private:int _year;int _month;int _day;
};
//构造函数
MyClass::MyClass()
{_year = 1999;_month = 2;_day = 28;cout << "MyClass构造函数的实现";cout << _year << "/" << _month << "/" << _day << endl << endl;
}
//拷贝构造,和指针的联合实现
MyClass::MyClass(MyClass* M)
{_year = M->_year;_month = M->_month;_day = M->_day;
}int main()
{//构造函数的实现Date d1;//拷贝构造的实现:1Date d2(d1);d2.print();//拷贝构造的实现:2Date d3 = d2;d3.print();//传值传参的调用Func(d3);//拷贝构造和指针的联合实现MyClass M1;MyClass M2 = M1;M2.print();return 0;
}

拷贝构造函数需要加上conts和不加const的区别

这里是有一点难度的

接下来 我们给出一个代码,但是这个代码是一个典型的错误代码:此时我们发现是报错的

这里解决报错的原因需要在拷贝构造函数上面加上const,因为这里存在权限放大的行为,之前我们就说过权限可以缩小,但是不能放大。

这里就产生了权限放大的问题,所以我们需要解决权限放大的问题

接下来我们上正确的代码

class MyClass
{
public:MyClass(MyClass* M);MyClass(int year, int month, int day);MyClass(const MyClass& M);//打印函数的实现void print(){cout << _year << "/" << _month << "/" << _day << endl << endl;}private:int _year;int _month;int _day;
};
//构造函数
MyClass::MyClass(int year = 1999, int month = 2, int day = 28)
{_year = year;_month = month;_day = day;cout << "MyClass构造函数的实现";cout << _year << "/" << _month << "/" << _day << endl << endl;
}
//拷贝构造,和指针的联合实现,这里就会导致这里只是一个普通的构造函数
//MyClass::MyClass(MyClass* M)
//{
//	_year = M->_year;
//	_month = M->_month;
//	_day = M->_day;
//}
//拷贝构造的实现
MyClass::MyClass(const MyClass& M)
{_year = M._year;_month = M._month;_day = M._day;
}//拷贝构造函数的实现
class Date
{
public://构造函数的实现Date();//拷贝构造的实现Date(const Date& d);//打印函数的实现void print();
private:int _year;int _month;int _day;
};//构造函数的实现
Date::Date()
{_year = 2000;_month = 2;_day = 28;cout << "构造函数的实现";cout << _year << "/" << _month << "/" << _day << endl << endl;
}//拷贝构造的实现
//Date::Date(Date* const this, Date& d)
Date::Date(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}//打印函数的实现
void Date::print()
{cout << _year << "/" << _month << "/" << _day << endl << endl;
}void Func(Date& d)
{cout << "拷贝构造函数的调用" << endl;d.print();
}Date F()
{Date d1;return d1;
}MyClass f()
{MyClass M1(12, 12, 12);return M1;
}
int main()
{//构造函数的实现Date d1;//拷贝构造的实现:1Date d2(d1);d2.print();//拷贝构造的实现:2Date d3 = d2;d3.print();//传值传参的调用Func(d3);拷贝构造和指针的联合实现MyClass M1(2000, 1, 1);//初始化2000MyClass M2;//默认初始化 1999MyClass M3 = M1;//M2.print();M3.print();//const在拷贝构造函数里面的使用cout << "拷贝构造const的实现"<<endl;MyClass M4 = f();Date d4 = F();return 0;
}

运算符重载

概念概述

  • 当运算符被用于类类型的对象时,C++ 语言允许我们通过运算符重载的形式指定新的含义。C++ 规定类类型对象使用运算符时,必须转换成调⽤对应运算符重载,若没有对应的运算符重载,则会编译报错。
  • 运算符重载是具有特殊名字的函数,他的名字是由 operator 和后面要定义的运算符共同构成。和其他函数一样,它也具有其返回类型和参数列表以及函数体。
  • 重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。
  • 如果一个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的 this 指针,因此运算符重载作为成员函数时,参数比运算对象少一个。
  • 运算符重载以后,其优先级和结合性与对应的内置类型运算符保持一致。
  • 不能通过连接语法中没有的符号来创建新的操作符:比如 operator@。
  • .*   ::   sizeof   ?:   . 注意以上 5 个运算符不能重载。(选择题里面常考,大家要记一下)
  • 重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义,如:int operator+(int x, int y)。
  • 一个类需要重载哪些运算符,是看哪些运算符重载后有意义,比如 Date 类重载 operator - 就有意义,但是重载 operator * 就没有意义(因为:日期之间的乘法运算在现实生活中没有一个直观且被广泛接受的含义。不像减法运算可以表示两个日期之间的时间间隔,乘法运算很难找到一个符合日期概念的自然解释)。
  • 重载 ++ 运算符时,有前置 ++ 和后置 ++,运算符重载函数名都是 operator++,无法很好的区分。C++ 规定,后置 ++ 重载时,增加一个 int 形参,跟前置 ++ 构成函数重载,方便区分。
  • 重载 <<(流插入) 和 >>(流提取) 时,需要重载为全局函数,因为重载为成员函数,this 指针默认抢占了第一个形参位置,第一个形参位置是左侧运算对象,调用时就变成了对象 <<cout,不符合使用习惯和可读性。重载为全局函数把 ostream/istream 放到第一个形参位置就可以了,第二个形参位置当类类型对象。

一元二元三元运算符:

一、一元运算符

 
  1. 算术运算符:
    • 正号(+):例如+a,一般对数值类型起作用,通常意义不大,因为数值默认就是正的。
    • 负号(-):如-b,用于取负值。
  2. 逻辑运算符:
    • 逻辑非(!):例如!c,对布尔值进行取反操作,如果操作数为 true,则结果为 false;如果操作数为 false,则结果为 true
  3. 自增自减运算符:
    • 前置自增(++a):先将变量的值加 1,然后再使用变量的值。
    • 后置自增(a++):先使用变量的值,然后再将变量的值加 1。
    • 前置自减(--a):先将变量的值减 1,然后再使用变量的值。
    • 后置自减(a--):先使用变量的值,然后再将变量的值减 1。
  4. 地址运算符:
    • 取地址符(&):例如&d,用于获取变量的内存地址。
  5. 间接寻址运算符:
    • 解引用符(*):如果有一个指针变量 p*p 用于访问指针所指向的对象。
 

二、二元运算符

 
  1. 算术运算符:
    • 加法(+):如 a + b,用于数值相加或字符串连接(对于 C++ 中的 std::string 对象)。
    • 减法(-):如 a - b,用于数值相减。
    • 乘法(*):如 a * b,用于数值相乘。
    • 除法(/):如 a / b,用于数值相除。
    • 取余(%):如 a % b,用于求整数除法的余数。
  2. 位运算符:
    • 按位与(&):如 a & b,对两个操作数的每一位进行与操作。
    • 按位或(|):如 a | b,对两个操作数的每一位进行或操作。
    • 按位异或(^):如 a ^ b,对两个操作数的每一位进行异或操作。
    • 左移(<<):如 a << b,将 a 的二进制表示向左移动 b 位。
    • 右移(>>):如 a >> b,将 a 的二进制表示向右移动 b 位。
  3. 关系运算符:
    • 等于(==):如 a == b,判断两个操作数是否相等。
    • 不等于(!=):如 a!= b,判断两个操作数是否不相等。
    • 大于(>):如 a > b,判断 a 是否大于 b
    • 小于(<):如 a < b,判断 a 是否小于 b
    • 大于等于(>=):如 a >= b,判断 a 是否大于等于 b
    • 小于等于(<=):如 a <= b,判断 a 是否小于等于 b
  4. 逻辑运算符:
    • 逻辑与(&&):如 a && b,当且仅当两个操作数都为 true 时,结果为 true
    • 逻辑或(||):如 a || b,当至少一个操作数为 true 时,结果为 true
  5. 赋值运算符:
    • 简单赋值(=):如 a = b,将 b 的值赋给 a
    • 复合赋值(如 +=、-=、*=、/=、%=、<<=、>>=、&=、|=、^=):例如 a += b 相当于 a = a + b
 

三、三元运算符(条件运算符)

 

条件运算符(?:),例如 a? b : c,如果 a 为 true,则结果为 b;如果 a 为 false,则结果为 c

内置类型和自定义类型

内置类型(简单类型):

一般情况下系统对于内置类型,直接就有对应的指令,会自动识别进行比较,核心就是比较,这里就是i和j直接进行比较 存到ret里面返回

自定义类型(按照你自己的比较方式,按照自己的需求,进行比较):

1,自定义类型不能直接转换成指令,就不能直接进行比较。尤其是像日期类里面的,年 月 日的比较,更是无法直接进行比较,你是需要返回更大的日期,还是更小的,你是需要加减,多少进位一次,这些都不是编译器决定的,是右你自己决定的。编译器不能直接进行识别。

2,内置类型比较简单,可以直接转换识别,但是自定义类型就不是

3,为了可以自己对自定义类型进行比较,语法结构就是:operator ➕运算符

自定义类型运算符重载的使用

原因:

日期类的比较日期,这里会存在一个问题,就是类外面无法访问私有变量,所以这里我们有三种解决办法

  • get函数
  • 重载为类的成员函数
  • 友元函数

这里我们以重载为成员函数为例子进行举例,至于经常用到的友元函数,类的第三章我们会讲到:

这里还有一个关键点,我们直接上代码(这里发现报错)

  

原因:可以放到类里面,重载为成员函数,但是直接拷贝放到类里面,会导致报错,因为隐藏了this指针,隐藏的this指针会指向第一个参数

  

解决:

但是此时调用,从全局调用,变成了成员函数的调用,调用方式发生了改变

此时d1传给了this,d2传给了d

试验一下 发现没有问题

//.h文件
//运算符重载,比较大小的实现,不在类里面实现
class Date
{
public:Date(int year = 1000, int month = 1, int day = 1);//声明给,定义不给Date(Date& d);void print();bool operator<(const Date& d);//比较日期大小(重载为成员函数)
private:int _year;int _month;int _day;
};//.cpp实现文件
#include"类和对象的通篇实现.h"
//构造函数的实现(全缺省构造函数的实现)
Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;
}
//拷贝构造的实现
Date::Date(Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}
//打印函数的实现
void Date::print()
{cout << _year << "/" << _month << "/" << _day << endl;
}//日期类的比较日期的大小,this-> < d.year 此时返回
bool Date::operator<(const Date& d)
{//比较年if (this->_year < d._year){return true;}else{if (this->_year == d._year && this->_month < d._month)//比较月{return true;}else if (this->_year == d._year && this->_month < d._month && this->_day < d._day)//比较日{return true;}}return false;
}//.cpp测试文件
//运算符重载,比较大小
int main()
{//构造和拷贝构造函数的使用Date d1(1000, 2, 1);d1.print();Date d2 = d1;d2.print();//比较年月日Date d3(999, 1, 1);bool ret1 = d1.operator<(d3);cout << ret1 << endl;Date d4(1100, 1, 1);bool ret2 = d1.operator<(d4);cout << ret2 << endl;return 0;
}

重载成员函数至少有一个类类型的形参

前置++和后置++的区分

在C++里面,d++和++d,在汇编层,其实都是一样的,都是,operater++,所以是无法是分辨的

所以我们需要在书写的时候做出分辨

这里重载的函数,函数名是一样的

前置++,和后置++,运算符的复用

前置++,直接进行复用

后置++

1,保存之前的数值(拷贝下来)

2,然后进行++

3,最后返回++之前的数值

总结

注意,

在 C++ 中,后置 ++ 运算符重载函数的参数必须是 int 类型,这是 C++ 语言的规定,不能使用 double 或其他类型。

这样规定的原因主要是为了保持语言的一致性和可识别性。编译器通过参数为 int 来区分前置 ++ 和后置 ++ 的重载版本。如果允许使用其他类型,会使编译器难以确定到底是哪种 ++ 操作,增加了语言的复杂性和不确定性。

传递 int 类型参数通常只是作为一个占位符,实际在函数实现中一般不会用到这个参数的值,它的存在仅仅是为了满足语法要求以区分前置和后置自增操作。所以一般情况下,都是默认传递 int 类型参数,而不是 0 或者 1 这样的特定值,并且也不能传递 double 等其他类型。

代码的实现:

这里声明一下,上面图片的讲解的代码,是实现+=的,所以可以直接使用this+=1,这里并没有实现+=的函数重载,所以这里我们就会跑到对象里面实现day的+1

//.h文件
//运算符重载,比较大小的实现,不在类里面实现
class Date
{
public:Date(int year = 1000, int month = 1, int day = 1);//声明给,定义不给Date(Date& d);void print();bool operator<(const Date& d);Date& operator++();Date& operator++(int);//比较日期大小(重载为成员函数)
private:int _year;int _month;int _day;
};//.cpp实现文件
#include"类和对象的通篇实现.h"
//构造函数的实现(全缺省构造函数的实现)
Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;
}
//拷贝构造的实现
Date::Date(Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}
//打印函数的实现
void Date::print()
{cout << _year << "/" << _month << "/" << _day << endl;
}//日期类的比较日期的大小,this-> < d.year 此时返回
bool Date::operator<(const Date& d)
{//比较年if (this->_year < d._year){return true;}else{if (this->_year == d._year && this->_month < d._month)//比较月{return true;}else if (this->_year == d._year && this->_month < d._month && this->_day < d._day)//比较日{return true;}}return false;
}//前置++
Date& Date::operator++()
{this->_day += 1;return *this;
}
//后置++
Date& Date::operator++(int)
{Date tmp = *this;this->_day += 1;return tmp;
}//.cpp测试文件
//运算符重载,比较大小
int main()
{//构造和拷贝构造函数的使用Date d1(1000, 2, 1);d1.print();Date d2 = d1;d2.print();//比较年月日Date d3(999, 1, 1);bool ret1 = d1.operator<(d3);cout << ret1 << endl;Date d4(1100, 1, 1);bool ret2 = d1.operator<(d4);cout << ret2 << endl;//前置++和后置++cout << "前置++和后置++" << endl;d4.print();//1100 ,1,1d4.operator++();d4.print();Date ret = d4.operator++(1);ret.print();d4.print();return 0;
}

赋值运算符重载

概念概述

赋值运算符重载的特点:

  1. 成员函数:赋值运算符重载必须定义为类的成员函数。
  2. 参数:建议将参数声明为const类型的类引用,以避免不必要的拷贝。
  3. 返回值:应有返回值,且建议为当前类类型的引用,这样可以支持连续赋值操作,并提高效率。

编译器自动生成的赋值运算符:

  • 如果没有显式实现赋值运算符重载,编译器会提供一个默认实现。
  • 默认赋值运算符对内置类型成员变量执行值拷贝或浅拷贝。
  • 对自定义类型成员变量,会调用其赋值运算符重载函数。

特定情况下的赋值运算符重载:

  • 对于像Date这样只有内置类型成员的类,编译器自动生成的赋值运算符通常足够使用。
  • 对于像Stack这样包含指向资源的成员的类,需要自定义赋值运算符以实现深拷贝。
  • 对于像MyQueue这样包含自定义类型成员的类,如果这些成员的赋值运算符已经正确实现,通常不需要为MyQueue显式实现赋值运算符重载。

额外技巧:

  • 如果一个类显式实现了析构函数并释放资源,通常也需要显式实现赋值运算符重载,以确保资源被正确管理。

赋值运算符重载的使用以及注意事项

1,有返回值,建议写成const类类型的引用。因为C++规定类类型的传值传参,会调用拷贝构造,传递引用会减少拷贝(提高效率)

2,连续赋值的返回值

d3=d1;

返回值是d3,所以我们写代码的时候,要注意返回值,很可能是this

第一次赋值

第二次赋值

3,当返回的节点是d2,如何实现返回d2(用this,this本身指向的就是返回值)(这里注意:有返回值就支持连续赋值,但是返回的时候,需要注意)

注意:这里返回是可以用引用返回的,因为赋值运算符重载是两个已有的对象。出去作用域是依旧存在的(这里代码是传值返回,会增加拷贝)

4,这里还有一个问题,就是d1可以复制給給d1,所以为了防止自己給自己赋值 ,会进行判断

代码实现

//.h文件
//运算符重载,比较大小的实现,不在类里面实现
class Date
{
public:Date(int year = 1000, int month = 1, int day = 1);//声明给,定义不给Date(Date& d);void print();bool operator<(const Date& d);//比较日期大小(重载为成员函数)Date& operator++();Date& operator++(int);//赋值运算符重载(这里是可以加上const的,因为这里改变的是this不是d)Date& operator=(const Date& d);private:int _year;int _month;int _day;
};//.cpp实现文件
#include"类和对象的通篇实现.h"
//构造函数的实现(全缺省构造函数的实现)
Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;
}
//拷贝构造的实现
Date::Date(Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}
//打印函数的实现
void Date::print()
{cout << _year << "/" << _month << "/" << _day << endl;
}//日期类的比较日期的大小,this-> < d.year 此时返回
bool Date::operator<(const Date& d)
{//比较年if (this->_year < d._year){return true;}else{if (this->_year == d._year && this->_month < d._month)//比较月{return true;}else if (this->_year == d._year && this->_month < d._month && this->_day < d._day)//比较日{return true;}}return false;
}//前置++
Date& Date::operator++()
{this->_day += 1;return *this;
}
//后置++
Date& Date::operator++(int)
{Date tmp = *this;this->_day += 1;return tmp;
}//赋值运算符重载(这里是可以加上const的,因为这里改变的是this不是d)
Date& Date::operator=(const Date& d)
{//地址不一样才进行赋值if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;
}//.cpp测试文件
//运算符重载,比较大小
int main()
{//构造和拷贝构造函数的使用Date d1(1000, 2, 1);d1.print();Date d2 = d1;d2.print();//比较年月日Date d3(999, 1, 1);bool ret1 = d1.operator<(d3);cout << ret1 << endl;Date d4(1100, 1, 1);bool ret2 = d1.operator<(d4);cout << ret2 << endl;//前置++和后置++cout << "前置++和后置++" << endl;d4.print();//1100 ,1,1d4.operator++();d4.print();Date ret = d4.operator++(1);ret.print();d4.print();//赋值运算符重载 cout << "赋值运算符重载" << endl;Date d5(1, 1, 1);d5.print();d5 = d4;//这样写也是对的//d5.operator=(d4);//这样写也是对的d5.print();return 0;
}

赋值运算符重载什么时候需要自己实现

注意:默认的赋值运算符重载也会完成浅拷贝(有资源,就需要自己完成赋值运算符重载)

1,内置类型不指向什么资源,不需要自己实现,编译器自动生成的就可以实现

2,栈和深拷贝,都需要自己写,和拷贝构造非常相似

3,如果写了析构函数,那么赋值运算符重载就需要自己写

几个函数之间进行对比

1、构造一般都需要自己写,自己传参定义初始化

2、析构,构造时有资源申请(如malloc或者fopen)等,就需要显示写析构函数

3、拷贝构造和赋值重载,显示写了析构,内部管理资源,就需要显示实现深拷贝

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

项目扩展一:信道池的实现

项目扩展一&#xff1a;信道池的实现 一、为何要设计信道池1.引入信道的好处2.为何要设计信道池 二、信道池的设计1.服务器需要设计信道池吗&#xff1f;2.设计&#xff1a;动态变化的信道池1.为什么&#xff1f;2.怎么办&#xff1f;1.动态扩容和缩容2.LRU风格的信道置换3.小总…

0基础学习HTML(十三)布局

HTML 布局 网页布局对改善网站的外观非常重要。 请慎重设计您的网页布局。 如何使用 <table> 元素添加布局。 网站布局 大多数网站会把内容安排到多个列中&#xff08;就像杂志或报纸那样&#xff09;。 大多数网站可以使用 <div> 或者 <table> 元素来创建…

软件测试分类篇(下)

目录 一、按照测试阶段分类 1. 单元测试 2. 集成测试 3. 系统测试 3.1 冒烟测试 3.2 回归测试 4. 验收测试 二、按照是否手工测试分类 1. 手工测试 2. 自动化测试 3. 手工测试和自动化测试的优缺点 三、按照实施组织分类 1. α测试(Alpha Testing) 2. β测试(Beta…

【LTW】Domain General Face Forgery Detection by Learning to Weight

文章目录 Domain General Face Forgery Detection by Learning to Weightkey points方法LTW元分割策略学习过程损失函数实验评价结果消融实验总结Domain General Face Forgery Detection by Learning to Weight 会议:AAAI-21 作者: code: https://github.com/skJack/LTW 上…

理解JVM中的死锁:原因及解决方案

死锁是并发应用程序中的常见问题。在此类应用程序中&#xff0c;我们使用锁定机制来确保线程安全。此外&#xff0c;我们使用线程池和信号量来管理资源消耗。然而&#xff0c;在某些情况下&#xff0c;这些技术可能会导致死锁。 在本文中&#xff0c;我们将探讨死锁、死锁出现…

旋转机械故障诊断 震动故障分析与诊断

旋转机械故障诊断 机理资料整理 电气故障&#xff0c;机械故障(不平衡&#xff0c;不对中&#xff0c;松动&#xff0c;轴承&#xff0c;共振&#xff0c;流体振动&#xff0c;皮带松动)&#xff0c;低速与高速机器故障诊断等 旋转机械故障诊断&#xff1a;机理资料整理 目录…

河钢数字PMO牛红卫受邀为第四届中国项目经理大会演讲嘉宾

全国项目经理专业人士年度盛会 河钢数字技术股份有限公司项目管理部PMO牛红卫受邀为PMO评论主办的全国项目经理专业人士年度盛会——2024第四届中国项目经理大会演讲嘉宾&#xff0c;演讲议题为“从技术到领导力——项目经理成长进阶之道”。大会将于10月26-27日在北京举办&…

数据结构——串的模式匹配算法(BF算法和KMP算法)

算法目的&#xff1a; 确定主串中所含子串&#xff08;模式串&#xff09;第一次出现的位置&#xff08;定位&#xff09; 算法应用&#xff1a; 搜索引擎、拼写检查、语言翻译、数据压缩 算法种类&#xff1a; BF算法&#xff08;Brute-Force&#xff0c;又称古典的…

NASA:ATLAS/ICESat-2 L3 B每周网格大气数据集V005

目录 简介 摘要 代码 引用 网址推荐 0代码在线构建地图应用 机器学习 ATLAS/ICESat-2 L3B Weekly Gridded Atmosphere V005 简介 该产品报告每周全球云覆盖率、海洋上总列光学深度、极地云覆盖率、风吹雪频率、视表面反照率以及地面探测频率。 参数&#xff1a;云光学…

Java 每日一刊(第15期):内部类

文章目录 前言内部类成员内部类&#xff08;Member Inner Class&#xff09;静态内部类&#xff08;Static Nested Class&#xff09;局部内部类&#xff08;Local Inner Class&#xff09;匿名内部类&#xff08;Anonymous Inner Class&#xff09; 内部类的详细对比内部类字节…

新增用户 开发

原型分析 接口设计 数据库设计 代码开发 controller /*** 新增员工** param employeeDTO* return*/ApiOperation("新增员工")PostMappingpublic Result<String> save(RequestBody EmployeeDTO employeeDTO) {log.info("新增员工&#xff1a;{}", emp…

C++离线查询

前言 C算法与数据结构 打开打包代码的方法兼述单元测试 概念及原理 离线算法( offline algorithms)&#xff0c;离线计算就是在计算开始前已知所有输入数据&#xff0c;输入数据不会产生变化&#xff0c;且在解决一个问题后就要立即得出结果的前提下进行的计算。 通俗的说&a…

智能优化算法-遗传算法(GA)(附源码)

目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1.内容介绍 遗传算法 (Genetic Algorithm, GA) 是一种基于自然选择和遗传学原理的元启发式优化算法&#xff0c;它模仿了生物进化过程中的选择、交叉和变异操作来搜索最优解。 GA的工作机制主要包括&#xff1a; 选择&am…

73 矩阵置零

解题思路&#xff1a; \qquad 原地算法&#xff0c;指除原有输入资料所占空间外&#xff0c;使用额外空间尽可能少(常数空间)的算法。本题容易想到的一种解法是&#xff0c;对于m x n的矩阵&#xff0c;一次遍历把含有0元素的行号、列号记录下来&#xff0c;然后再一次遍历把对…

中序遍历二叉树全过程图解

文章目录 中序遍历图解总结拓展&#xff1a;回归与回溯 中序遍历图解 首先看下中序遍历的代码&#xff0c;其接受一个根结点root作为参数&#xff0c;判断根节点是否为nil&#xff0c;不为nil则先递归遍历左子树。 func traversal(root *TreeNode,res *[]int) {if root nil …

阿⾥编码规范⾥⾯Manager分层介绍-专⽤名词和POJO实体类约定

开发⼈员&#xff1a;张三、李四、王五 ⼀定要避免单点故障 ⼀个微服务起码两个⼈熟悉&#xff1a;⼀个是主程⼀个是技术leader 推荐是团队⾥⾯两个开发⼈员 N⽅库说明 ⼀⽅库: 本⼯程内部⼦项⽬模块依赖的库(jar 包)⼆⽅库: 公司内部发布到中央仓库&#xff0c;可供公司…

计算机毕业设计推荐-基于python的白酒销售数据可视化分析

精彩专栏推荐订阅&#xff1a;在下方主页&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f496;&#x1f525;作者主页&#xff1a;计算机毕设木哥&#x1f525; &#x1f496; 文章目录 一、白酒销售数据…

记一次Meilisearch轻量级搜索引擎使用

以前使用的是mysql的全文索引、最开始还行。后续觉得就不好用了&#xff0c;但是服务器资源有限&#xff0c;没法上ES&#xff0c;只好找一个轻量级的搜索引擎、找了半天&#xff0c;决定使用这一个&#xff0c;目前效果还不错的。 参考网址 官网&#xff1a;https://www.meil…

基于单片机的智能小车的开发与设计

摘要&#xff1a;本文论述了基于 STC89C52 单片机的智能小车的开发与设计过程。该设计采用单片机、电机驱动及光电循迹等技术&#xff0c;保证小车在无人管理状态下&#xff0c;能按照预先设定的线路实现自动循迹功能。在电路结构设计中力求方便&#xff0c;可操作&#xff0c;…

麦克斯韦方程组

目录 1. 高斯定律&#xff08;电场部分&#xff09; 2. 高斯定律&#xff08;磁场部分&#xff09; 3. 法拉第电磁感应定律 4. 安培定律&#xff08;带有位移电流项&#xff09; 5.麦克斯韦方程组的物理意义 麦克斯韦方程组为我们提供了一个完整的电磁场理论框架&#xff…