运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
运算符重载(operator overloading)只是一种”语法上的方便”,也就是它只是另一种函数调用的方式。
在c++中,可以定义一个处理类的新运算符。这种定义很像一个普通的函数定义,只是函数的名字由关键字operator及其紧跟的运算符组成。差别仅此而已。它像任何其他函数一样也是一个函数,当编译器遇到适当的模式时,就会调用这个函数。
注意:
有些人很容易滥用运算符重载。它确实是一个有趣的工具。但是应该注意,它仅仅是一种语法上的方便而已,是另外一种函数调用的方式。从这个角度来看,只有在能使涉及类的代码更易写,尤其是更易读时(请记住,读代码的机会比我们写代码多多了)才有理由重载运算符。如果不是这样,就改用其他更易用,更易读的方式。
对于运算符重载,另外一个常见的反应是恐慌:突然之间,C运算符的含义变得不同寻常了,一切都变了,所有C代码的功能都要改变!并非如此,对于内置的数据类型的表示总的所有运算符是不可能改变的。
友元函数是一个全局函数,和我们上例写的全局函数类似,只是友元函数可以访问某个类私有数据。
#include
using namespace std;
class Person
{
friend ostream& operator<<(ostream& cout, Person& person);
public:
Person(int id,int age)
{
this->id = id;
this->age = age;
}
private:
int id;
int age;
};
ostream& operator<<(ostream& os, Person& person)
{
os << "ID:" << person.id << " Age:" << person.age;
return os;
}
int main()
{
Person p(1001, 30);
cout << p << endl;
system("pause");
return 0;
}
几乎C++中所有的运算符都可以重载,但运算符重载的使用时相当受限制的。特别是不能使用C++中当前没有意义的运算符(例如用**求幂)不能改变运算符优先级,不能改变运算符的参数个数。这样的限制有意义,否则,所有这些行为产生的运算符只会混淆而不是澄清寓语意。
重载的++和–运算符有点让人不知所措,因为我们总是希望能根据它们出现在所作用对象的前面还是后面来调用不同的函数。解决办法很简单,例如当编译器看到++a(前置++),它就调用operator++(a),当编译器看到a++(后置++),它就会去调用operator++(a,int).
#include
using namespace std;
class Complex
{
friend ostream& operator<<(ostream& os, Complex& complex)
{
os << "A:" << complex.a << " B:" << complex.b << endl;
return os;
}
public:
Complex()
{
a = 0;
b = 0;
}
//重载前置++
Complex& operator++()
{
a++;
b++;
return *this;
}
//重载后置++
Complex operator++(int)
{
Complex temp;
temp.a = this->a;
temp.b = this->b;
a++;
b++;
return temp;
}
private:
int a;
int b;
};
int main()
{
Complex c;
cout << ++c << endl;//前置++
//cout << c++ << endl;//后置++
system("pause");
return 0;
}
#include
using namespace std;
class Person
{
public:
Person(int param)
{
this->param = param;
}
void PrintPerson()
{
cout << "Param:" << param << endl;
}
private:
int param;
};
class SmartPointer
{
public:
SmartPointer(Person *p)
{
this->pPerson = p;
}
//重载指针的->,*操作符
Person* operator->()
{
return pPerson;
}
Person& operator*()
{
return *pPerson;
}
~SmartPointer()
{
if (pPerson != nullptr)
{
delete pPerson;
}
}
Person* pPerson;
};
int main()
{
SmartPointer s(new Person(100));
s->PrintPerson();
system("pause");
return 0;
}
赋值符常常初学者的混淆。这是毫无疑问的,因为’=’在编程中是最基本的运算符,可以进行赋值操作,也能引起拷贝构造函数的调用。
class Person{
friend ostream& operator<<(ostream& os,const Person& person){
os << "ID:" << person.mID << " Age:" << person.mAge << endl;
return os;
}
public:
Person(int id,int age){
this->mID = id;
this->mAge = age;
}
//重载赋值运算符
Person& operator=(const Person& person){
this->mID = person.mID;
this->mAge = person.mAge;
return *this;
}
private:
int mID;
int mAge;
};
//1. =号混淆的地方
void test01(){
Person person1(10, 20);
Person person2 = person1; //调用拷贝构造
//如果一个对象还没有被创建,则必须初始化,也就是调用构造函数
//上述例子由于person2还没有初始化,所以会调用构造函数
//由于person2是从已有的person1来创建的,所以只有一个选择
//就是调用拷贝构造函数
person2 = person1; //调用operator=函数
//由于person2已经创建,不需要再调用构造函数,这时候调用的是重载的赋值运算符
}
//2. 赋值重载案例
void test02(){
Person person1(20, 20);
Person person2(30, 30);
cout << "person1:" << person1;
cout << "person2:" << person2;
person2 = person1;
cout << "person2:" << person2;
}
//常见错误,当准备给两个相同对象赋值时,应该首先检查一下这个对象是否对自身赋值了
//对于本例来讲,无论如何执行这些赋值运算都是无害的,但如果对类的实现进行修改,那么将会出现差异;
//3. 类中指针
class Person2{
friend ostream& operator<<(ostream& os, const Person2& person){
os << "Name:" << person.pName << " ID:" << person.mID << " Age:" << person.mAge << endl;
return os;
}
public:
Person2(char* name,int id, int age){
this->pName = new char[strlen(name) + 1];
strcpy(this->pName, name);
this->mID = id;
this->mAge = age;
}
#if 1
//重载赋值运算符
Person2& operator=(const Person2& person){
//注意:由于当前对象已经创建完毕,那么就有可能pName指向堆内存
//这个时候如果直接赋值,会导致内存没有及时释放
if (this->pName != NULL){
delete[] this->pName;
}
this->pName = new char[strlen(person.pName) + 1];
strcpy(this->pName,person.pName);
this->mID = person.mID;
this->mAge = person.mAge;
return *this;
}
#endif
//析构函数
~Person2(){
if (this->pName != NULL){
delete[] this->pName;
}
}
private:
char* pName;
int mID;
int mAge;
};
void test03(){
Person2 person1("John",20, 20);
Person2 person2("Edward",30, 30);
cout << "person1:" << person1;
cout << "person2:" << person2;
person2 = person1;
cout << "person2:" << person2;
}
如果没有重载赋值运算符,编译器会自动创建默认的赋值运算符重载函数。行为类似默认拷贝构造,进行简单值拷贝。
class Complex
{
public:
Complex(char* name,int id,int age)
{
this->pName = new char[strlen(name) + 1];
strcpy(this->pName, name);
this->mID = id;
this->mAge = age;
}
//重载==号操作符
bool operator==(const Complex& complex)
{
if (strcmp(this->pName,complex.pName) == 0 &&
this->mID == complex.mID &&
this->mAge == complex.mAge)
{
return true;
}
return false;
}
//重载!=操作符
bool operator!=(const Complex& complex)
{
if (strcmp(this->pName, complex.pName) != 0 ||
this->mID != complex.mID ||
this->mAge != complex.mAge)
{
return true;
}
return false;
}
~Complex()
{
if (this->pName != NULL)
{
delete[] this->pName;
}
}
private:
char* pName;
int mID;
int mAge;
};
void test()
{
Complex complex1("aaa", 10, 20);
Complex complex2("bbb", 10, 20);
if (complex1 == complex2){ cout << "相等!" << endl; }
if (complex1 != complex2){ cout << "不相等!" << endl; }
}
class Complex
{
public:
int Add(int x,int y)
{
return x + y;
}
int operator()(int x,int y)
{
return x + y;
}
};
void test01()
{
Complex complex;
cout << complex.Add(10,20) << endl;
//对象当做函数来调用
cout << complex(10, 20) << endl;
}
不能重载operator&& 和 operator|| 的原因是,无法在这两种情况下实现内置操作符的完整语义。说得更具体一些,内置版本版本特殊之处在于:内置版本的&&和||首先计算左边的表达式,如果这完全能够决定结果,就无需计算右边的表达式了–而且能够保证不需要。我们都已经习惯这种方便的特性了。
我们说操作符重载其实是另一种形式的函数调用而已,对于函数调用总是在函数执行之前对所有参数进行求值。
class Complex
{
public:
Complex(int flag)
{
this->flag = flag;
}
Complex& operator+=(Complex& complex)
{
this->flag = this->flag + complex.flag;
return *this;
}
bool operator&&(Complex& complex)
{
return this->flag && complex.flag;
}
public:
int flag;
};
int main()
{
Complex complex1(0); //flag 0
Complex complex2(1); //flag 1
//原来情况,应该从左往右运算,左边为假,则退出运算,结果为假
//这边却是,先运算(complex1+complex2),导致,complex1的flag变为complex1+complex2的值, complex1.a = 1
// 1 && 1
//complex1.operator&&(complex1.operator+=(complex2))
if (complex1 && (complex1 += complex2))
{
cout << "真!" << endl;
}
else
{
cout << "假!" << endl;
}
return 0;
}
根据内置&&的执行顺序,我们发现这个案例中执行顺序并不是从左向右,而是先右猴左,这就是不满足我们习惯的特性了。由于complex1 += complex2先执行,导致complex1 本身发生了变化,初始值是0,现在经过+=运算变成1,1 && 1输出了真。