C++类和对象:运行符重载、取地址运算符重载、const 修饰的类如何作为参数
引言
介绍:C++类和对象:运行符重载、取地址运算符重载、const 修饰的类如何作为参数
_涂色_-博主主页
C++基础专栏
一、运算符重载
• 当运算符被用于类类型的对象时,C++语言允许我们通过运算符重载的形式指定新的含义。C++规定 类 类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错。
• 运算符重载是具有特殊名字的函数,他的名字是由operator和后面要定义的运算符共同构成。和其他函数⼀样,它也具有其返回类型和参数列表以及函数体。
• 重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数,而二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。
• 如果一个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少⼀个。
• 运算符重载以后,其优先级和结合性与对应的内置类型运算符保持⼀致。
#include<iostream>
using namespace std;class Date
{
public:Date(int year = 1, int month = 1, int day = 1){cout << "Date(int year = 1, int month = 1, int day = 1)";_year = year;_month = month;_day = day;}1、提供Getxxx函数2、友元3、直接放到类里面
//这里先使用第3点,前两点后面会讲bool operator==(const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}
private:int _year;int _month;int _day;
};int main()
{Date d3(2025, 1, 1);Date d4(2025, 2, 2);cout << (d3 == d4) << endl;cout << d3.operator==(d4) << endl;return 0;
}
• 不能通过连接语法中没有的符号来创建新的操作符:比如operator@。
• .* :: sizeof ?: . 注意以上5个运算符不能重载。(选择题里面常考)
对 .* 操作符的理解
C++中规定:取类里面函数的地址的时候,要在函数名前加上&符号。函数指针需要声明类域
#include<iostream>
using namespace std;void Fun()
{cout << "Fun()" << endl;
}
class A
{
public:void func(){cout << "A::func()" << endl;}
};void f()
{cout << "f()" << endl;
}
int main()
{//正常函数指针void(*func1)() = Fun;func1();//类里面的函数指针:void(A:: * func2)() = &A::func;A aa;(aa.*func2)(); //这里会使用 .* 符号。
}
• 重载操作符至少有⼀个类类型参数,不能通过运算符重载改变内置类型对象的含义,如: operator+(int x, int y)
• 一个类需要重载哪些运算符,是看哪些运算符重载后有意义,比如Date类重载operator-就有意 义,但是重载operator+就没有意义。
• 重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,无法很好的区分。 C++规定,后置++重载时,增加一个int形参,跟前置++构成函数重载,方便区分。
• 重载 << 和 >> 时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第一个形参位置,第一个形参位置是左侧运算对象,调用时就变成了对象 <<cout,不符合使用习惯和可读性。重载为全局函数把ostream/istream放到第一个形参位置就可以了,第二个形参位置当前类类型对象。
二、赋值运算符重载
赋值运算符重载是一个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,这里要注意跟拷贝构造区分,拷贝构造用于一个对象拷贝初始化给另一个要创建的对象。
赋值运算符重载的特点:
1. 赋值运算符重载是一个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成 const 当前类类型引用,否则会传值传参会有拷贝。
2. 有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续赋值场景。
3. 没有显式实现时,编译器会自动生成一个默认赋值运算符重载,默认赋值运算符重载行为跟默认拷贝构造函数类似,对内置类型成员变量会完成值拷贝 / 浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用他的赋值重载函数。
4.像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的赋值运算符重载就可以完成需要的拷贝,所以不需要我们显示实现赋值运算符重载。像Stack这样的类,虽然也都是 内置类型,但是_a指向了资源,编译器自动生成的赋值运算符重载完成的值拷贝 / 浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝 (对指向的资源也进行拷贝)。像MyQueue这样的类型内部主要是自定义类型Stack成员,编译器自动生成的赋值运算符重载会调用Stack的赋值运算符重载, 也不需要我们显示实现MyQueue的赋值运算符重载。
这里还有一个小技巧,如果一个类显示实现了析构并释放资源,那么他就需要显示写赋值运算符重载,否则就不需要。
#include<iostream>
using namespace std;class Date
{
public:Date(int year = 1, int month = 1, int day = 1){cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;_year = year;_month = month;_day = day;}bool operator==(const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}Date& operator=(const Date& d){//不需要自己给自己赋值if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2025, 1, 1);Date d2(2025, 2, 2);d2 = d1;d1.print();d2.print();
}
三、const 修饰的类如何作为参数
const成员函数
• 将 const 修饰的成员函数称之为 const 成员函数,const 修饰成员函数放到成员函数参数列表的后面。
• const 实际修饰该成员函数隐含的 this 指针,表明在该成员函数中不能对类的任何成员进行修改。 如:const 修饰 Date类的Print成员函数,Print隐含的this指针由 Date* const this 变为:const Date* const this
不想对类里面的内容改变,就在函数后面加上const修饰。
#include<iostream>
using namespace std;class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// void Print(const Date* const this) constvoid Print() const{cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
const
int main()
{// 这⾥⾮const对象也可以调⽤const成员函数是⼀种权限的缩⼩Date d1(2024, 7, 5);d1.Print();const Date d2(2024, 8, 5);d2.Print();return 0;
}
四、取地址运算符重载
取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,⼀般这两个函数编译器自动生成的就可以够我们用了,不需要去显示实现。除非一些很特殊的场景,比如我们不想让别人取到当前类对象的地址,就可以自己实现一份,胡乱返回⼀个地址。
class Date
{
public:Date* operator&(){//return this;return nullptr;}const Date* operator&()const{//return this;return nullptr;}
private:int _year; // 年int _month; // ⽉int _day; // ⽇
};