C++:渴望力量吗,少年?
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
(1)函数名字为:关键字operator后面接需要重载的运算符符号。
(2)函数原型:返回值类型 operator操作符 (参数列表)
但是要注意的是:
(3)不能通过连接其他符号来创建新的操作符:比如operator@
(4)重载操作符必须有一个类类型参数
(5)作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this指针
(6).* :: sizeof ? : . 注意以上5个运算符不能重载。
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
//d1 < d2 或者 d1.operator<(d2)
bool operator<(const Date& d)//列表隐含了第一个参数this,在这里d1就是this,d2就是d
{ //可以把这个重载定义在类中,这样可以解决private成员无法在类外使用的问题
if (_year < d._year)
{
return true;
}
else if (_year == d._year && _month < d._month)
{
return true;
}
else if (_year == d._year && _month == d._month && _day < d._day)
{
return true;
}
else
{
return false;
}
}
private:
// 内置类型
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 7, 21);
Date d2(2022, 8, 21);
cout << (d1 < d2) << endl;//这两条语句等价
cout << (d1.operator<(d2)) << endl;
return 0;
}
(1)赋值运算符重载格式
a. 参数类型:const T&,传递引用可以提高传参效率
b. 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
c. 检测是否自己给自己赋值
d. 返回*this :要符合连续赋值的含义
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
// d1 < d2
// d1.operator<(d2)
bool operator<(const Date& d)
{
if (_year < d._year)
{
return true;
}
else if (_year == d._year && _month < d._month)
{
return true;
}
else if (_year == d._year && _month == d._month && _day < d._day)
{
return true;
}
else
{
return false;
}
}
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
// d1 <= d2:this就是d1,d就是d2
bool operator<=(const Date& d)
{
return *this < d || *this == d;
}
bool operator>(const Date& d)
{
return !(*this <= d);
}
bool operator>=(const Date& d)
{
return !(*this < d);
}
bool operator!=(const Date& d)
{
return !(*this == d);
}
int GetMonthDay(int year, int month)
{
int monthArray[13] = { 0, 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 monthArray[month];
}
Date& operator+=(int day)//修改了原来的日期
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
// 月进位
_day -= GetMonthDay(_year, _month);
++_month;
// 月满了
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;//注意this只是一个指针,需要解引用
}
Date operator+(int day)//没有修改原来的日期
{//函数名不可以加 & ,因为函数结束tmp就销毁了
Date tmp(*this);//调用拷贝构造函数,复制出一份新的日期
tmp += day;//复用上面的代码
return tmp;
}
private:
// 内置类型
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 7, 21);
Date d2(2022, 8, 21);
cout << (d1 < d2) << endl;
// cout << (d1.operator<(d2)) << endl;
cout << (d1 == d2) << endl;
//Date ret = d1 += 50;
//ret.Print();
//d1.Print();
Date ret = d1 + 50;
ret.Print();
d1.Print();
return 0;
}
(2)赋值运算符只能重载成类的成员函数,不能重载成全局函数。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
int _year;
int _month;
int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{
if (&left != &right)
{
left._year = right._year; left._month = right._month; left._day = right._day;
}
return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
(3)用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
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;
}
// 后置++:
// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1
// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
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;
}
将 const修饰的“成员函数” 称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
注意:const修饰成员函数时要放在函数的末尾(也就是必须以上图左边的代码形式书写,右边是表示实际等价于左边的代码),另外内部不涉及修改成员的都是只读函数,这样的函数可以加const,这样是有利于避免权限上冲突的问题的。
这两个默认成员函数一般不用重新定义,编译器默认会生成。
// 不想被取到有效地址的时候才会考虑重载取地址运算符 &
Date* operator&()
{
// return (Date*)0x01202;
return nullptr;//不想返回自己的地址,返回了空指针
}
const Date* operator&() const
{
return this;//返回自己的地址,其实也不用写,默认生成的就是这样的
}
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
class Date
{
public:
/*Date()//无参构造函数
{
cout << "Date()" << endl;
_year = 1;
_month = 1;
_day = 1;
}*/
//Date(int year, int month, int day)//有参构造函数,和上面的函数重载
//{
// _year = year;
// _month = month;
// _day = day;
//}
Date(int year = 1, int month = 1, int day = 1)//采用全缺省,相当于上面的两个函数合并
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
//cout << this << endl;
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year; // 声明
int _month;
int _day;
};
class Stack
{
public:
/*Stack()
{
a = nullptr;
top = capacity = 0;
}*/
Stack(size_t n = 4)
{
if (n == 0)
{
a = nullptr;
top = capacity = 0;
}
else
{
a = (int*)malloc(sizeof(int) * n);
if(a == nullptr)
{
perror("realloc fail");
exit(-1);//以异常的形式结束程序,剩下的程序不会继续执行
}
top = 0;
capacity = n;
}
}
// 成员函数
//void Init()
//{
// a = nullptr;
// top = capacity = 0;
//}
void Push(int x)
{
if (top == capacity)
{
size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
if (tmp == nullptr)
{
perror("realloc fail");
exit(-1);
}
if (tmp == a) //根据首地址验证是原地扩容还是异地扩容
{
cout << capacity << "原地扩容" << endl;
}
else //异地扩容比较麻烦,需要找到一块新的空间,然后拷贝数据,最后释放旧的空间
{
cout << capacity << "异地扩容" << endl;
}
a = tmp;
capacity = newcapacity;
}
a[top++] = x;
}
int Top()
{
return a[top - 1];
}
void Pop()
{
assert(top > 0);
--top;
}
void Destroy()
{
free(a);
a = nullptr;
top = capacity = 0;
}
bool Empty()
{
return top == 0;
}
private:
// 成员变量
int* a;
int top;
int capacity;
};
int main()
{
Stack st1;
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Push(4);
while (!st1.Empty())
{
cout << st1.Top() << " ";
st1.Pop();
}
cout << endl;
st1.Destroy();
Stack st2(1000);//利用缺省参数提前开好了足够大的空间,避免了多次异地扩容,比下面的语句效率更高
//Stack st2;
for (size_t i = 0; i < 1000; i++)
{
st2.Push(i);
}
while (!st2.Empty())
{
cout << st2.Top() << " ";
st2.Pop();
}
cout << endl;
st2.Destroy();
// 调用无参构造函数不能像下面这么写,不需要加括号,因为声明函数就是Date Func()的形式
/*Date d1();
d1.Print();*/
Date d1;//自动调用无参构造函数或者上面全缺省的函数
d1.Print();
Date d2(2023, 7, 20);
d2.Print();
Date d3(2023);
d3.Print();
Date d4(2023, 7);
d4.Print();
return 0;
}
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。所以就需要我们学习下一篇文章提到的初始化列表。