这里谈谈c++里面两个特别的语法糖 —-运算符重载和有元。为什么说是特别?
- 难以理解,特别装逼,完全可用其他方式替代,比如运算符重载就是一个函数,完全可用函数替代,而且重载的运算符函数根本无法重名字理解函数的意思….
- 很多语言都没有,比如java 不会有这么复杂的事情,感觉像块鸡肋
如果不是考试,我永远不会学这种东西。
目录
运算符重载
首先你要有一个观念就是运算符也是一个函数,也是有参数的,所以他也能重载,比如 <
二元运算符 。我们举个第4节Date类的例子我们可以,我们重载运算符来比较 两个Date的大小
class Date{
....
bool operator < (const Date & o)const{
if(this->yy == o.yy){
if(this->mm == o.mm)return this-> dd < o.dd;
else return this-> mm < o.mm;
}else return this-> yy < o.yy;
}
...
};
int main(){
Date d1(2018,6,5);
Date d2(2019 ,6,5);
cout << (d1 < d2) << "\n";
return 0;
}
你应该能从上面的 重载比较运算符中总结出一个特殊的声明模式
<返回值类型> operator <重载运算符号> (函数参数){
....
}
可能你会问 <
运算符是二元运算符为什么只有一个参数?
别忘了在类中的成员函数默认会有一个参数就是该类自己,即成员函数总是应该与类相关的,所以这里我特别用了一个 this
指针,this
指针是指向对象自己的指针,每个类中都有,只能在类中使用,默认情况下访问类中的成员时可以不加 this
当然重载在外边就需要用两个参数了
bool operator < (const Date & a,const Date & o){
if(a.yy == o.yy){
if(a.mm == o.mm)return a.dd < o.dd;
else return a.mm < o.mm;
}else return a.yy < o.yy;
}
你编译一下,你会发现还是错误的,编译器告诉你 yy是私有的… 解决这个问题需要有元,我们在后面讨论
当然你可能会问为什么要把 <
的返回值设置为 bool ,确实这不是必须的,但是作为运算符他应该有约定俗成的模式(pattern), 让你代码的使用者看到名字就知道他是什么意思。所以你只需要看看某些特别的运算符重载就行了
这里提供一个运算符重载的链接你自己去看,我这里只简要的讲几个特别的运算符的重载。
重载 []
重载 Vec 类的 []
使他能像数组一样访问。 结合上一章的Vec类
class Vec{
...
int& operator[](int idx){
return ele[idx];// 注意没有check idx 的合法性
}
};
int main(){
Vec v1;
v1.push_back(1);
v1.push_back(3);
v1.push_back(2);
std::cout << "v1 now is " << '\n';
for(int i=0 ; i< v1.size() ; ++ i)
std::cout << v1[i] << ' ';
std::cout << '\n';
v1[1] = 999;
std::cout << "v1 now is update" << '\n';
for(int i=0 ; i< v1.size() ; ++ i)
std::cout << v1[i] << ' ';
std::cout << '\n';
return 0;
}
你需要注意这个引用作为返回值的妙用 是这个函数可以作为左边的值进行赋值,并且不改变语义,(作用的位置一样), 如果你不清楚,建议你去掉引用试试看看编译器会说什么
重载 << 流输入输出符号
再讲这个之前我们有必要先讲一讲有元….
友元函数
所谓友元 C++之父说的很好,
一个常规的成员函数声明描述了三件在逻辑上互不相同的事情:
1. 该函数能访问类中的私有部分
2. 该函数位于类的作用域之中
3. 该函数必须由一个对象去激活(注: 有一个默认参数this)
而 static 函数仅仅干了前两件事情,而有元函数只干了前面一件事情
也就是说友元函数只是 获得类私有变量的访问权限,而与该类无关。我们先用友元函数解决第一个问题
class Date{
...
friend bool operator < (const Date &a, const Date & b);// 声明
};
// 定义
bool operator < (const Date & a,const Date & o){
if(a.yy == o.yy){
if(a.mm == o.mm)return a.dd < o.dd;
else return a.mm < o.mm;
}else return a.yy < o.yy;
}
同样总结他的语法,在函数前面加个 friend
关键字就行了
有3点需要注意 :
如果过某个友元函数与多个类相关,那么每个相关的类中都需要声明友元函数,e.g 重载一个矩阵Matrix与Vec相乘的函数
operator *
,class Matrix{ friend Vec operator*(const Matrix& m, const Vec& v); }; class Vec{ friend Vec operator*(const Matrix& m, const Vec& v); }; //定义 Vec operator*(const Matrix& m, const Vec& v){ ... }
如果有类中的某个函数需要访问另外一个类中的私有变量可以将该函数设置为有元,例如 有一个类
List_iter
中的函数 next 需要访问List
中的私有成员class List_iter{ .... int * next(){ ... }; }; class List{ friend int* List_iter::next(); //... }
如果某个类的所有成员函数都需要访问另外一个类的私有成员,可以声明将该类声明为有元变量(java 中用内部类解决了)
例如有一个类List_iter
需要访问List
的私有成员变量class List_iter{ .... int * next(){ ... }; }; class List{ friend class List_iter;// 有元类,注意这不是List的成员 //... }
如前所述这些破坏了类设计的封装原则,私有变量就是不能给外部访问的,要访问应该通过 public 函数才是,所以这个设计有点 鸡肋(笔者无知 ……)
接下来我们解决最后一个问题,重载 << 运算符,你可以将他当做一个模版,因为像我说的,重载运算符的语义的 约定俗成的
重载 流输入输出符号 << and >>
我们重载日期类 Date 的输出输入运算符
#include <iostream>
using namespace std;
class Date{
....
friend ostream& operator<<(ostream& out,const Date& d){
out <<d.yy <<"-"<<d.mm<<"-"<<d.dd<<"\n";
return out;
}
friend istream& operator>>(istream &in, Date& d){
in>>d.yy>>d.mm>>d.dd;
return in;
}
};
int main(){
Date d;
cin>>d;
cout <<" print Date\n" <<d;
return 0;
}
运行结果
这里你就把参数作为模版(约定俗成的形式来记忆就行了),简要说一下, ostream 表示输出流(out-stream),`cout
就是一种输出流,而且需要注意:
- 返回值都是引用,这个第三节应对高校C++考试(三) : 指针和引用讲过,
- 输入和输出参数一个有const,一个没有,想想为什么??
好了最后讲一下模版也就是,派生和继承就不讲了(PS: 因为hwd说不考 :) ).