1.运算符重载
在C++中,运算符重载是一种强大的特性,它允许我们为已有的运算符赋予新的意义,以便它们能够应用于自定义类型上。
这一特性极大地增强了C++的表达能力,使得自定义类型的使用更加直观和自然。例如,如果我们定义了一个复数类,那么通过重载+运算符,我们就可以直接对两个复数对象进行加法运算,而无需编写复杂的函数调用来实现相同的功能。
然而,运算符重载并非没有限制。我们需要遵守C++语言对运算符重载的规则,以避免潜在的问题和混淆。
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐 藏的this
- .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出 现。
- 重载后的运算符不能改变其原有的优先级和结合性。
- 某些运算符(如=、()、[]、->)只能通过成员函数进行重载,因为这些运算符的左侧操作数必须是类的对象。
- 运算符可以重载为成员函数或友元函数。成员函数重载时,左侧操作数隐式地为*this;而友元函数重载则更加灵活,可以访问类的私有和保护成员。
1. 运算符重载的基本概念
定义:
他的定义其实和我们的函数很相似。
函数名字为:关键字operator后面接需要重载的运算符符号。
返回值 +operator+运算符(传的参数)。
例子:
// 全局的operator==
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//private:
int _year;
int _month;
int _day;
};
// 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
// 这里其实可以用友元解决,或者干脆重载成成员函数。
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
void Test ()
{
Date d1(2018, 9, 26);
Date d2(2018, 9, 27);
cout<<(d1 == d2)<<endl;
}
2. 运算符重载的实现
成员函数重载:
当运算符的左侧操作数是类的对象时,我们通常将该运算符重载为成员函数。例如,重载+运算符以支持两个复数对象的加法运算:
class Complex {
public:
double real, imag;
// 构造函数、其他成员函数...
// 重载+运算符为成员函数
Complex operator+(const Complex& rhs) const {
return Complex(real + rhs.real, imag + rhs.imag);
}
};
当我们的将假发重载时,我们在进行两个复数进行相加,此时就会自动调用这个函数。
如:Complex s2,s1; Complex s3=s2+s1;此时s3就是s1和s2这两个复数的和。
如果我们不对加法进行重载的话,我们就不能对自定义类型进行相加。
友元函数重载:
当运算符需要访问类的私有或保护成员,或者当运算符的左侧操作数不是类的对象时(如输出运算符<<),我们通常将该运算符重载为友元函数。
例如,重载<<运算符以支持复数对象的输出:
class Complex {
friend std::ostream& operator<<(std::ostream& os, const Complex& c);
public:
double real, imag;
// 构造函数、其他成员函数...
};
std::ostream& operator<<(std::ostream& os, const Complex& c) {
os << c.real << "+" << c.imag << "i";
return os;
}
3. 常见的运算符重载示例
在C++中,我们可以重载几乎所有的算术运算符、关系运算符、赋值运算符等。以下是一些常见的运算符重载示例:
- 算术运算符:+、-、*、/等,用于自定义类型的算术运算。
- 关系运算符:==、!=、<、>、<=、>=等,用于自定义类型的比较操作。
- 赋值运算符:=,用于自定义类型的赋值操作。注意,当自定义类型包含动态分配的内存时,需要深拷贝以避免悬挂指针等问题。
- 自增自减运算符:++、--,用于自定义类型的自增和自减操作。
- 下标运算符:[],用于自定义类型的数组或类似数组的操作。
- 流插入和提取运算符:<<、>>,用于自定义类型的输入输出操作。
- 函数调用运算符:(),允许自定义类型的对象像函数一样被调用。
- 成员访问运算符:->,通常与智能指针或类似智能指针的类一起使用,用于访问指针所指向对象的成员。
4. 运算符重载的最佳实践
保持运算符的原有语义:在重载运算符时,应尽可能保持其原有的语义和用法,避免引起混淆。
适度使用:虽然运算符重载非常强大,但过度使用可能会使代码难以阅读和理解。因此,在使用时应权衡利弊,适度使用。
考虑运算符的对称性:对于某些运算符(如+、*等),其操作应该是对称的,即a op b和b op a应该等价。在重载这些运算符时,应注意保持这种对称性。
运算符重载函数应简短高效:运算符重载函数通常会被频繁调用,因此应尽可能简短和高效,以减少不必要的开销。
前置++和后置++重载
对于我们的前置++和后置++重载函数怎么区别也是一个问题。
前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器
自动传递。
我们前置++和后置++的区别:
后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
一份,然后给this+1 而temp是临时对象,因此只能以值的方式返回,不能返回引用
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 前置++:返回+1之后的结果
// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
Date& operator++()
{
_day += 1;
return *this;
}
Date operator++(int)
{
Date temp(*this);
_day += 1;
return temp;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
Date d1(2022, 1, 13);
d = d1++; // d: 2022,1,13 d1:2022,1,14
d = ++d1; // d: 2022,1,15 d1:2022,1,15
return 0;
}
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;
s2 = s1;
return 0;
}
注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必
须要实现。
为什么栈浅拷贝栈不行。
我们可以实现日期类来帮助我们熟悉我们的运算符重载。
日期类实现的代码:
我们的.h文件
#include<iostream>
using namespace std;
#include<assert.h>
class Date
{
// Ԫ
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
public:
bool CheckDate() const;
Date(int year = 1900, int month = 1, int day = 1);
void Print() const;
// Ĭinline
int GetMonthDay(int year, int month) const
{
assert(month > 0 && month < 13);
static int monthDayArray[13] = { -1, 31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31 };
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
{
return 29;
}
return monthDayArray[month];
}
bool operator<(const Date& d) const;
bool operator<=(const Date& d) const;
bool operator>(const Date& d) const;
bool operator>=(const Date& d) const;
bool operator==(const Date& d) const;
bool operator!=(const Date& d) const;
Date operator+(int day) const;
Date& operator+=(int day);
Date operator-(int day) const;
Date& operator-=(int day);
// d1++;
// d1.operator++(0);
Date operator++(int);
// ++d1;
// d1.operator++();
Date& operator++();
// d1--;
// d1.operator--(0);
Date operator--(int);
// --d1;
// d1.operator--();
Date& operator--();
// d1 - d2
int operator-(const Date& d) const;
//void operator<<(ostream& out);
Date* operator&()
{
//return this;
//return nullptr;
return (Date*)0x2673FF40;
}
const Date* operator&() const
{
//return this;
//return nullptr;
return (Date*)0x2673FE30;
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);
.cpp文件
#include"Date.h"
bool Date::CheckDate() const
{
if (_month < 1 || _month > 12
|| _day < 1 || _day > GetMonthDay(_year, _month))
{
return false;
}
else
{
return true;
}
}
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
if (!CheckDate())
{
cout << "非法日期:";
Print();
}
}
// void Date::Print(const Date* const this)
void Date::Print() const
{
//++_year;
cout << _year << "/" << _month << "/" << _day << endl;
}
// d1 < d2
bool Date::operator<(const Date& d) const
{
if (_year < d._year)
{
return true;
}
else if (_year == d._year)
{
if (_month < d._month)
{
return true;
}
else if (_month == d._month)
{
return _day < d._day;
}
}
return false;
}
// d1 <= d2
bool Date::operator<=(const Date& d) const
{
return *this < d || *this == d;
}
bool Date::operator>(const Date& d) const
{
return !(*this <= d);
}
bool Date::operator>=(const Date& d) const
{
return !(*this < d);
}
bool Date::operator==(const Date& d) const
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool Date::operator!=(const Date& d) const
{
return !(*this == d);
}
// d1 += 100
Date& Date::operator+=(int day)
{
if (day < 0)
{
return *this -= (-day);
}
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
// d1 + 100
Date Date::operator+(int day) const
{
Date tmp = *this;
tmp += day;
return tmp;
}
Date Date::operator-(int day) const
{
Date tmp = *this;
tmp -= day;
return tmp;
}
// d1 -= 100
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += (-day);
}
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
_month = 12;
--_year;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
// d1++;
// d1.operator++(0);
Date Date::operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
// ++d1;
// d1.operator++();
Date& Date::operator++()
{
*this += 1;
return *this;
}
// d1 - d2
int Date::operator-(const Date& d) const
{
int flag = 1;
Date max = *this;
Date min = d;
if (*this < d)
{
max = d;
min = *this;
flag = -1;
}
int n = 0;
while (min != max)
{
++min;
++n;
}
return n * flag;
}
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
// 11:45
istream& operator>>(istream& in, Date& d)
{
while (1)
{
cout << "请依次输入年月日:>";
in >> d._year >> d._month >> d._day;
if (!d.CheckDate())
{
cout << "输入日期非法:";
d.Print();
cout << "请重新输入!!!" << endl;
}
else
{
break;
}
}
return in;
}
3. const成员函数
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数 隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
形如: 成员函数+const
例子:
class Date{
public;
void Display()
{
cout<<" "<<_year<<" "<<_month<<" "<<_day<<endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
相关问题:
在C++中,关于const对象、非const对象、const成员函数和非const成员函数之间的调用关系,有一些明确的规则:
const对象能调用非const成员函数吗?
不可以。因为非const成员函数可以修改对象的状态(即成员变量的值),而const对象在定义时就被承诺不会修改其任何成员变量。因此,从逻辑上讲,const对象不应该能够调用可能会修改其状态的非const成员函数。
非const对象能调用const成员函数吗?
可以。const成员函数保证不会修改对象的任何成员变量,因此非const对象可以安全地调用它们。非const对象没有关于其状态不被修改的承诺,因此它可以调用任何成员函数,包括const成员函数。
const成员函数内可以调用非const成员函数吗?
不可以(直接地)。由于const成员函数承诺不会修改对象的状态,因此它不能直接调用可能修改对象状态的非const成员函数。如果确实需要在const成员函数内部执行某些需要修改对象状态的操作,通常的做法是将这些操作封装在另一个函数中,并通过某种机制(如使用指向非const对象的指针或引用)来绕过const限制,但这通常不是推荐的做法,因为它违背了const成员函数的设计初衷。
非const成员函数内可以调用其他const成员函数吗?
可以。非const成员函数可以调用const成员函数,因为const成员函数不会修改对象的状态,这符合非const成员函数可能进行的任何操作。
所以,const成员函数和非const成员函数之间的调用关系主要基于它们对对象状态修改的承诺。const成员函数保证不会修改对象的状态,因此它们可以安全地被任何类型的对象(包括const对象)调用,但不能直接调用可能修改对象状态的非const成员函数。反之,非const成员函数可以调用const成员函数,因为它们没有关于不修改对象状态的限制。
4. 取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
关于我们的&操作符的重载,是一个很有意思的整蛊你朋友的函数,你可以重载我们的&操作符,当我们的人使用我们的&操作符就是不会成功的使用默认的,而会使用我们自己重载的,这个时候你就可以让取不到我们的地址,我们就而已给他们一个另外的错误的地址。
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!