1.运算符重载
1.1 运算符重载概念:对已有的运算符进行重新定义,赋予其另一种功能,以适应不同的数据类型。可以通过全局函数或者成员函数实现。
运算符重载的规则:
1> 不引入新的运算符
2> 运算符的操作数不可变
3> 运算符的优先级不可变
4> 运算符函数的参数中至少要有一个类类型的参数
使用类成员函数实现与使用友元实现一定会多一个参数,就是this指针
1.2加号运算符重载
#include <iostream>
#include <string>
using namespace std;
class A
{
public:
A operator+(const A & a); //成员函数实现操作符重载
A();
int m_A;
int m_B;
};
A::A()
{
m_A = 1;
m_B = 2;
}
A A::operator+(const A & a) //成员函数实现操作符重载
{
A a1;
a1.m_A = this->m_A + a.m_A;
a1.m_B = this->m_B + a.m_B;
return a1;
}
//
//A operator+(const A & a,const A & b) //全局函数实现操作符重载
//{
// A a1;
// a1.m_A = b.m_A + a.m_A;
// a1.m_B = b.m_B + a.m_B;
// return a1;
//}
A operator+(const A & a,int num) //操作符重载也可以发生函数重载,建议都用全局函数或者都用成员函数实现重载,这样看起来怪别扭的
{
A a1;
a1.m_A = num + a.m_A;
a1.m_B = num + a.m_B;
return a1;
}
void test()
{
A a1;
A a2;
//A a3;
A a3 = a1 + a2; //其实就是A a3 = a1.operator+(a2)
cout<<"a3.m_A:"<<a3.m_A<<"\t"<<"a3.m_B:"<<a3.m_B<<endl; //a3.m_A:2 a3.m_B:4
A a4 = a1 + 100;
cout<<"a4.m_A:"<<a4.m_A<<"\t"<<"a4.m_B:"<<a4.m_B<<endl; //a4.m_A:101 a4.m_B:102
}
int main()
{
test();
return 0;
}
注意:操作符重载也可以发生函数重载。并且对于内置的数据类型表达式的运算符是不允许发生操作符重载的,例如1+1就不能就不能重载使得1+1变成减操作。不要滥用操作符重载
1.3左移运算符重载
class A
{
public:
A();
//operator<< (); //左移运算符不能用成员函数实现,因为无论如何都会出现对象a.operator<<……也就是a<<……显然我们要<<a,
//而且也无法链式使用了,cout<<"a"<<"b"<<endl;这样就不存在了
//但有时我们需要返回私有成员,那么这就必须用到友元了。把全局函数变成类的友元。
int m_A;
int m_B;
};
A::A()
{
m_A = 10;
m_B = 20;
}
//基础版
//void operator<< (ostream & out, const A &a) //这里我们不知道该返回什么时可以不用返回,即使用void。接着上面的注释,使用全局函数就解决了对象在<<左边的问题,但我们把cout落下了,因为我们实现的是cout<<a;能够输出我们想要的样子。cout显然也称为参数之一,数据类型为ostream,而未声明为const是因为每个output操作都会改变ostream的内部状态
//{
// out << "a.m_A=" << a.m_A << "a.m_B" << a.m_B << endl;
//}
//void operator<< (const A &a, ostream & out) //参数顺序改变后就成了a1 << cout ;,所以是先cout 再对象
//{
// out << "a.m_A=" << a.m_A << "a.m_B" << a.m_B << endl;
//}
//改进版,使用ostream类型返回,实现链式编程。而程序运行中只能有一个cout对象所以返回引用
ostream & operator<< (ostream & out, const A &a)
{
out << "a.m_A=" << a.m_A << "a.m_B" << a.m_B << endl;
return out;
}
void test()
{
A a1;
cout<< a1 ;
cout<<"adsf"<<a1; //成功
//cout<<a1<<"asdf"<<endl; //基础版失败,改进版成功
}
int main()
{
test();
return 0;
}
注意事项:
1>不能使用类的成员函数去实现左移运算符重载,理由是变成了对象<<……,这样不仅不能链式使用,而且与我们预期不符,显得很乱。
2>如果类中私有成员变量要输出那就使用友元,让全局函数变成类友元。
3>关于返回值,如果为void则无法链式使用。因为我们可以把输出流想成一节有长度的水管,使用void其实在cout<<a中就是调用一个函数,那个函数中的输出内容已经固定,所以我们可以当做这个水管长度已经确定并且由于返回void,所以后面也不再接水管。如果我们返回的是ostream对象,那就相当于可以在后面接水管可以继续输出。可见返回void时只能出现cout<<a;以及cout<<“前面可以加想输出的内容part1”<<a;因为part1部分相当于没调用重载函数并且有返回ostream对象。
1.4递增运算符重载
class MyInteger
{
friend ostream& operator <<(ostream & out, const MyInteger & myinteger);
public:
MyInteger();
MyInteger& operator ++();
MyInteger operator ++(int);
private:
int m_Num;
};
MyInteger::MyInteger()
{
m_Num = 0;
}
//重载左移运算符
ostream& operator <<(ostream & out, const MyInteger & myinteger)
{
out << myinteger.m_Num;
return out;
}
/*重载前置++
既然要输出对象那肯定要返回对象,如果为空那岂不没有输出
返回引用是为了当出现++(++m1)时也能得到正确结果,如果没有引用显然m1只自增一次,后面一次被临时对象自增
*/
MyInteger& MyInteger::operator ++()
{
++m_Num;
return *this;
}
void test01()
{
MyInteger m1;
cout << m1 << endl; //m1是自己写的整型类对象,想直接输出显然要先重载<<
cout << ++m1 << endl;
}
/*重载后置++
既然要输出对象那肯定要返回对象,如果为空那岂不没有输出
其中参数使用占位参数并且用int,目的是发生函数重载,让编译器知道是后置++
前置++当出现连续自增时,要先自增再使用自身值,所以返回引用。但后置是先使用值在自增,并且实现过程中是返回临时对象,所以不必用引用
*/
MyInteger MyInteger::operator ++(int)
{
//目的:先返回,再自增。实现:先创建临时对象接收,再自增,最后返回临时变量
MyInteger temp = *this;
this->m_Num++;
return temp;
}
void test02()
{
MyInteger m2;
cout << m2++ << endl;
cout << m2 << endl;
}
int main()
{
test01();
cout << "---------------" << endl;
test02();
return 0;
}
注意:前置与后置实现过程中返回值与参数列表的区别
1.5赋值运算符重载
C++编译器至少给类创建四个函数:
其一:默认构造函数(参数列表为空;函数体为空)
其二:析构函数(参数列表为空;函数体为空)
其三:拷贝构造函数(对属性值进行拷贝)
其四:赋值操作符重载(对属性值进行拷贝)
注意:如果类中有属性指向堆区,那么做赋值操作时也会出现深浅拷贝问题
class Person
{
public:
Person(int age);
~Person();
Person& operator=(Person &p);
int * m_Age;
};
Person::Person(int age)
{
m_Age = new int(age);
}
Person::~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
Person& Person::operator=(Person &p) //返回引用是为了可以链式使用
{
if (m_Age != NULL) //这里我们要的是p初始化该对象,所以该对象的m_Age先“清理”干净
{
delete m_Age;
m_Age = NULL;
}
m_Age = new int(*p.m_Age); //再重新在堆开辟空间
return *this;
}
void test()
{
Person p1(10);
Person p2(20);
Person p3(30);
p1 = p2 = p3;
cout << "p1=" << *p1.m_Age << " p2=" << *p2.m_Age << " p3=" << *p3.m_Age << endl;
//Person p4 = p1; //这里使用的是拷贝构造函数的隐式法,由于没写拷贝构造函数,显然会出现深浅拷贝问题
}
int main()
{
test();
return 0;
}
1.6关系运算符重载:可以让两个对象进行对比操作(==与!=)
class Person
{
public:
Person(string name, int age);
bool operator==(Person &p);
bool operator!=(Person &p);
string m_Name;
int m_Age;
};
Person::Person(string name, int age):m_Name(name),m_Age(age)
{
}
bool Person::operator==(Person &p)
{
if (m_Age == p.m_Age&&m_Name == p.m_Name)
return true;
else
return false;
}
bool Person::operator!=(Person &p)
{
if (m_Age == p.m_Age&&m_Name == p.m_Name)
return false;
else
return true;
}
void test()
{
Person p1("张三",19);
Person p2("李四",20);
Person p3(p1);
if (p1 == p2)
cout << "p1与p2相等!" << endl;
else
cout << "p1与p2不相等!" << endl;
if (p1 != p3)
cout << "p1与p3不相等!" << endl;
else
cout << "p1与p3相等!" << endl;
}
int main()
{
test();
return 0;
}
1.7函数调用运算符重载
函数调用运算符()也可以重载,由于重载后使用的方式很像函数的调用,因此称为仿函数。仿函数没有固定写法,非常灵活。
class Myprint
{
public:
void operator()(string text);
};
void Myprint::operator()(string text)
{
cout << text << endl;
}
class Myadd
{
public:
int operator()(int a, int b);
};
int Myadd::operator()(int a, int b)
{
return a + b;
}
void test01()
{
Myprint myprint;
myprint("hello");
Myprint()("hello"); //匿名函数对象,类名()就是调用匿名对象,当前行执行完就被释放
}
void test02()
{
Myadd myadd;
cout << myadd(100, 200) << endl;
cout << Myadd()(200, 300) << endl;//匿名函数对象
}
int main()
{
test01();
test02();
return 0;
}
注意:类名()为匿名对象,当前行执行完就被释放!见《C++笔记三》中第3.2节。