C++语言学习(八)——操作符重载
一、操作符重载基础
1、操作符重载的语法
通过operator关键字可以定义特殊的函数,operator本质是通过函数重载操作符。
Type operator operatorname(const Type p1, const Type p2)
{
Type ret;
return ret;
}
2、友元函数重载操作符
可以将操作符重载函数声明为友元函数。
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(float x=0, float y=0)
{
this->x = x;
this->y = y;
}
void print()
{
cout<<"("<<x<<","<<y<<")"<<endl;
}
friend const Complex operator+(const Complex &c1,const Complex &c2);
private:
float x;
float y;
};
const Complex operator+(const Complex &c1,const Complex &c2)
{
return Complex(c1.x + c2.x,c1.y + c2.y);
}
int main(int argc, char *argv[])
{
Complex c1(2,3);
Complex c2(3,4);
c1.print();
c2.print();
Complex c3 = c1 + c2;
c3.print();
Complex c4 = operator+(c1,c2);
c4.print();
return 0;
}
上述代码中,编译器会检查是否有可用的操作符重载函数,因此Complex c3 = c1 + c2;代码也是合法的。
3、成员函数重载操作符
将操作符重载函数定义为类成员函数。
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(float x=0, float y=0)
{
this->x = x;
this->y = y;
}
void print()
{
cout<<"("<<x<<","<<y<<")"<<endl;
}
const Complex operator+(const Complex &another)
{
cout << "member function." << endl;
return Complex(this->x + another.x, this->y + another.y);
}
friend const Complex operator+(const Complex &c1,const Complex &c2);
private:
float x;
float y;
};
const Complex operator+(const Complex &c1,const Complex &c2)
{
cout << "friend global function." << endl;
return Complex(c1.x + c2.x,c1.y + c2.y);
}
int main(int argc, char *argv[])
{
Complex c1(2,3);
Complex c2(3,4);
c1.print();
c2.print();
//成员函数
Complex c3 = c1 + c2;
c3.print();
//成员函数
Complex c4 = c1.operator +(c2);
c4.print();
//全局函数
Complex c5 = operator+(c1,c2);
c4.print();
return 0;
}
操作符重载函数作为类的成员函数时,比全局操作符重载函数少一个参数,不需要依赖友元就可以完成操作符重载,编译器会优先在类的成员函数中查找操作符重载函数。因此Complex c3 = c1 + c2;代码会优先调用类的操作符重载成员函数。
4、操作符重载的规则
操作符重载的规则:
A、C++不允许用户自己定义新的运算符,只能对已有的 C++运算符进行重载。
B、C++语言中大部分运算符都可以重载,成员选择符(.)、成员对象选择符(.*)、域解析操作符(::)、条件操作符(?:)、sizeof不可以重载。除了赋值操作符(=)外,基类中重载的操作符都将被派生类继承。
C、重载不能改变运算符运算对象(即操作数)的个数。
D、重载不能改变运算符的优先级别。
E、重载不能改变运算符的结合性。
F、重载运算符的函数不能有默认的参数
G、重载的运算符必须和用户定义的自定义类型的对象一起使用,其参数至少应有一个是类对象(或类对象的引用)。参数不能全部是 C++的标准类型,以防止用户修改用于标准类型数据成员的运算符的性质。
H、用于类对象的运算符一般必须重载,但有两个例外,运算符”=“和运算符”&“不必用户重载。
I、应当使重载运算符的功能类似于该运算符作用于标准类型数据时候时所实现的功能。
J、运算符重载函数可以是类的成员函数,也可以是类的友元函数,还可以是既非类的成员函数也不是友元函数的普通函数。
K、赋值操作符只能重载为成员函数
二、操作符重载实例
1、双目运算符重载
形式:L#R
全局函数:operator#(L,R);
成员函数:L.operator#(R)
operator+=实例:
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(double x = 0, double y = 0)
{
this->x = x;
this->y = y;
}
void print()
{
cout<<"("<<x<<","<<y<<")"<<endl;
}
Complex& operator +=(const Complex &c)
{
this->x += c.x;
this->y += c.y;
return * this;
}
private:
double x;
double y;
};
2、单目运算符重载
形式:#M 或 M#
全局函数:operator#(M)
成员函数:M.operator#()
operator-实例:
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(double x = 0, double y = 0)
{
this->x = x;
this->y = y;
}
void print()
{
cout<<"("<<x<<","<<y<<")"<<endl;
}
const Complex operator-(void) const
{
return Complex(-x,-y);
}
private:
double x;
double y;
};
3、流输入输出运算符重载
函数形式
istream & operator>>(istream &,自定义类&);
ostream & operator<<(ostream &,自定义类&);
流输入输出运算符重载通过友元来实现,避免修改C++的标准库。
operator<< 和operator>>实例:
class Complex
{
public:
Complex(double x = 0, double y = 0)
{
this->x = x;
this->y = y;
}
void print()
{
cout<<"("<<x<<","<<y<<")"<<endl;
}
friend ostream & operator<<(ostream &os, const Complex & c);
friend istream & operator>>(istream &is, Complex &c);
private:
double x;
double y;
};
ostream & operator<<(ostream &os, const Complex & c)
{
os<<"("<<c.x<<","<<c.y<<")";
return os;
}
istream & operator>>(istream &is, Complex &c)
{
is>>c.x>>c.y;
return is;
}
三、操作符重载总结
1、不可以重载的操作符
. (成员访问运算符)
.* (成员指针访问运算符)
:: (域运算符)
sizeof (长度运算符)
?: (条件运算符)
2、只能重载为成员函数的运算符
= 赋值运算符
[] 下标运算符
() 函数运算符
-> 间接成员访问
编译器默认重载了赋值运算符,但编译器默认重载的赋值操作符仅完成浅拷贝,需要深拷贝操作必须自定义重载赋值操作符。
3、运算符重载与友元
A、一个操作符的左右操作数不一定是相同类型的对象,这就涉及到将该操作符函数定义为谁的友元,谁的成员问题。
B、一个操作符函数,被声明为哪个类的成员,取决于该函数的调用对象(通常是左操作数)。
C、一个操作符函数,被声明为哪个类的友员,取决于该函数的参数对象(通常是右操作数)。
#include <iostream>
using namespace std;
class Mail;
class Sender
{
public:
Sender(string s):_addr(s){}
Sender& operator<<(const Mail & mail); //成员
private:
string _addr;
};
class Mail
{
public:
Mail(string _t,string _c ):_title(_t),_content(_c){}
friend Sender& Sender::operator<<(const Mail & mail);
//友元
private:
string _title;
string _content;
};
Sender& Sender::operator<<(const Mail & mail)
{
cout<<"Address:"<<mail._addr<<endl;
cout<<"Title :"<<mail._title<<endl;
cout<<"Content:"<<mail._content<<endl;
return *this;
}
int main()
{
Sender sender("[email protected]");
Mail mail("note","meeting at 3:00 pm");
Mail mail2("tour","One night in beijing");
sender<<mail<<mail2;
return 0;
}
5、操作符重载的陷阱
A、逻辑运算操作符:
运算符重载本质是函数重载,C++对逻辑操作符重载时将逻辑操作符定义为函数,但是由于函数参数的计算次序是不确定的,导致逻辑操作符原生的短路法则将失效,因此不推荐对逻辑操作符进行重载。工程实践中需要使用重载比较操作符等方法避免重载逻辑操作符的陷阱,直接使用成员函数代替逻辑操作符重载,使用全局函数对逻辑操作符进行重载。
#include <iostream>
using namespace std;
class Test
{
public:
Test(int ok = true)
{
this->ok = ok;
}
int value()const
{
cout << "call value(),ok = " << ok << endl;
return ok;
}
Test operator +(const Test& another)
{
this->ok += another.ok;
return *this;
}
private:
int ok;
};
//&&操作符重载函数
bool operator &&(const Test& left, const Test& right)
{
return left.value() && right.value();
}
//||操作符重载函数
bool operator ||(const Test& left, const Test& right)
{
return left.value() || right.value();
}
Test func(Test test)
{
cout << "Test func(Test test): i = "<< test.value() <<endl;
return test;
}
int main(int argc, char *argv[])
{
Test test0(0);
Test test1(1);
Test test2(2);
Test test3(3);
if(test0 && test1)
{
cout << "result is true" << endl;
}
else
{
cout << "result is false" << endl;
}
/*****************************
*call value(),ok = 0
*result is false
* 上述测试代码:短路法则正常
* **************************/
cout << endl;
if(operator &&(func(test0), func(test1)))
{
cout << "result is true" << endl;
}
else
{
cout << "result is false" << endl;
}
cout << endl;
/*****************************
*call value(),ok = 1
*Test func(Test test): i = 1
*call value(),ok = 0
*Test func(Test test): i = 0
*call value(),ok = 0
*result is false
*上述测试代码:短路法则失效
* **************************/
if((test2 + test3) && test0)
{
cout << "result is true" << endl;
}
else
{
cout << "result is false" << endl;
}
cout << endl;
/*****************************
*call value(),ok = 5
*call value(),ok = 0
*result is false
* **************************/
if(test0 || test1)
{
cout << "result is true" << endl;
}
else
{
cout << "result is false" << endl;
}
cout << endl;
/*****************************
*call value(),ok = 0
*call value(),ok = 1
*result is true
* **************************/
if(operator &&(test0, test1))
{
cout << "result is true" << endl;
}
else
{
cout << "result is false" << endl;
}
cout << endl;
/*****************************
*call value(),ok = 0
*result is false
*上述测试代码:短路法则正常
* **************************/
if(operator ||(test0 + test1, test3))
{
cout << "result is true" << endl;
}
else
{
cout << "result is false" << endl;
}
/*****************************
*call value(),ok = 1
*result is true
*上述测试代码:短路法则正常
* **************************/
return 0;
}
上述测试代码中,如果逻辑操作符的操作数中是需要计算的函数调用,短路法则可能会失效。
B、逗号操作符:
可以使用全局函数对逗号操作符进行重载,重载函数的参数必须有一个是类类型,返回值类型必须是引用。
class& operator,(const class& a, const class& b)
{
return const_cast<class&>(b);
}
重载逗号操作符后,逗号表达式无法严格从左向右计算表达式,不能重载逗号操作符,重载后的逗号操作符没有了原生的语义。
原因:操作符的重载本质是函数调用,函数调用在进入函数体前需要完成所有参数的计算,参数的计算次序是不确定的,因此重载逗号操作符后无法保证逗号操作符的原生语义。
逗号操作符不需要重载,重载的逗号操作符无法严格从左向右计算逗号表达式,失去了原生逗号操作符的语义。
C、前置操作符与后置操作符
前置操作符和后置操作符支持全局函数、类成员函数重载。
前置的++、--运算操作符可以重载,不需要额外的参数。
后置的++、--运算操作符可以重载,需要一个int类型的占位参数。
#include <iostream>
using namespace std;
class Test
{
public:
Test(int i = 0)
{
this->i = i;
}
//前置操作符++
Test& operator ++()
{
++i;
return *this;
}
//前置操作符--
Test& operator --()
{
--i;
return *this;
}
//后置操作符--
Test operator ++(int)
{
Test ret(i);
i++;
return ret;
}
//后置操作符--
Test operator --(int)
{
Test ret(i);
i--;
return ret;
}
int value()const
{
return i;
}
private:
int i;
};
int main(int argc, char *argv[])
{
Test test1(1);
cout << (++test1).value() << endl;
Test test2(1);
cout << (--test2).value() << endl;
Test test3(1);
cout << (test3++).value() << endl;
Test test4(1);
cout << (test4--).value() << endl;
return 0;
}
由于类的前置操作符重载函数内部没有额外的临时对象开销,类的前置操作符重载函数效率比后置操作符高。
int i = 0;
i++;
++i;
对于C++基础类型,前置操作符和后置操作符效率基本相同。
现代C++编译器会对编译代码进行优化,使得编译后的二进制代码更加高效,优化后的二进制代码可能失去C/C++代码的原生语义。
三、类型转换
1、类型转换简介
C++语言中,标准类型之间的转换一般有隐式和显示转换,用户自定义类型间的转换则需要自定义专门的转换函数。
C语言中,基本类型间会进行隐式的类型安全转换,转换规则如下:
int a = -2000;
unsigned int b = 1000;
cout << a + b << endl;//4294966296
上述代码中,int与unsigned int运算时int会被转换为unsigned int,此时a会被转换为unsigned int,是一个非常大的数。
short s = 12;
char c = ‘1‘;
cout << sizeof(s + c) << endl;//4
上述代码中,C++编译器会进行优化,编译器遇到short与char进行运算时会将short和char都转换为int,便于高效计算。
C++语言兼容了C语言的隐式类型安全转换。
2、标准类型间转换
基本类型间的转换如下:
A、隐式转换5.0/8
B、显示转换(float)5/8
3、C++类类型的转换
C++的类类型之间的转换的规则如下:
A、转换函数定义在源对象类(待转换对象中)中,是转换源的成员函数。
B、一旦为转换源类型提供了到目标类型的转化操作符函数,就可以将源类型对象以隐式转化的方式得的目标类型的对象。
C、应用于构造及初始化,赋值,传参,返回等等场合。
#include <iostream>
using namespace std;
class Test
{
public:
Test(int i = 0)
{
this->i = i;
cout << "Test(int i = 0) i = " << i << endl;
}
private:
int i;
};
int main(int argc, char *argv[])
{
Test test;//Test(int i = 0) i = 0
test = 10;//Test(int i = 0) i = 10
return 0;
}
上述代码中,编译器会将10使用构造函数Test(int i = 0)隐式转换为Test对象。
实际工程中类的隐式类型转换是不安全的,编译器会尽力去查找构造函数转化不同的类型,如果没有匹配的构造函数才会报错,因此不可以使用隐式类型转换,需要使用explicit关键字声明编译器不能隐式类型转换,而需要显示的声明类型转换。
显示声明类对象类型转换的使用方式:
static_cast<classname>(value);
classname(value);
(calssname)value;//不推荐
#include <iostream>
using namespace std;
class Test
{
public:
explicit Test(int i = 0)
{
this->i = i;
cout << "Test(int i = 0) i = " << i << endl;
}
private:
int i;
};
int main(int argc, char *argv[])
{
Test test;//Test(int i = 0) i = 0
test = static_cast<Test>(10);//Test(int i = 0) i = 10
test = Test(100);//Test(int i = 0) i = 100
return 0;
}
上述代码中,构造函数使用explicit关键字进行声明,编译器不能再进行隐式的类型转换,只能使用显示的类型转换。
4、用类型转换构造函数进行类型转换
类型转换构造函数声明如下:classname(const anotherclass & another);
#include <iostream>
using namespace std;
class Point3D;
class Point2D
{
public:
Point2D(int x = 0,int y = 0)
{
this->x = x;
this->y = y;
}
void print()
{
cout<<"("<<x<<","<<y<<")"<<endl;
}
friend class Point3D;
private:
int x;
int y;
};
class Point3D
{
public:
Point3D(int x = 0, int y = 0, int z = 0)
{
this->x = x;
this->y = y;
this->z = z;
}
//类型转换构造函数
Point3D(const Point2D &p)
{
this->x = p.x;
this->y = p.y;
this->z = 0;
cout << "Point3D(const Point2D &p)" <<endl;
}
void print()
{
cout<<"("<<x<<","<<y<<","<<z<<")"<<endl;
}
private:
int x;
int y;
int z;
};
int main(int argc, char *argv[])
{
Point2D p2(1,2);
p2.print();
Point3D p3(3,4,5);
p3.print();
Point3D p3a = p2;//Point3D(const Point2D &p)
p3a.print();
return 0;
}
上述代码中,Point3D类提供了一个类型转换构造函数,用于将Point2D类对象转换为Point3D类型。
5、用类型转换操作符函数进行转换
类型转换函数可以将类对象转换为其它类型。
类型转换函数声明的语法如下:operator Type(void);
#include <iostream>
using namespace std;
class Point3D;
class Point2D
{
public:
explicit Point2D(int x = 0,int y = 0)
{
this->x = x;
this->y = y;
}
void print()
{
cout<<"("<<x<<","<<y<<")"<<endl;
}
private:
int x;
int y;
friend class Point3D;
};
class Point3D
{
public:
explicit Point3D(int x = 0, int y = 0, int z = 0)
{
this->x = x;
this->y = y;
this->z = z;
}
//类型转换构造函数
Point3D(const Point2D &p)
{
this->x = p.x;
this->y = p.y;
this->z = 0;
cout << "Point3D(const Point2D &p)" <<endl;
}
operator Point2D()
{
cout << "operator Point2D()" << endl;
Point2D p2;
p2.x = x;
p2.y = y;
return p2;
}
void print()
{
cout<<"("<<x<<","<<y<<","<<z<<")"<<endl;
}
private:
int x;
int y;
int z;
};
int main(int argc, char *argv[])
{
Point3D p3(3,4,5);
p3.print();
Point2D p2(1,2);
p2.print();
Point2D p2a = p3;//operator Point2D()
p2a.print();
return 0;
}
上述代码中,使用Point3D对象对Point2D对象进行初始化时会调用operator Point2D()类型转换操作符函数。
类型转换操作符函数可能和类型转换构造函数冲突,可以使用explicit对类型转换构造函数声明避免冲突。工程中实际使用普通的classname toClassName()公有成员函数进行类型转换。
#include <iostream>
using namespace std;
class Point3D;
class Point2D
{
public:
explicit Point2D(int x = 0,int y = 0)
{
this->x = x;
this->y = y;
}
void print()
{
cout<<"("<<x<<","<<y<<")"<<endl;
}
private:
int x;
int y;
friend class Point3D;
};
class Point3D
{
public:
explicit Point3D(int x = 0, int y = 0, int z = 0)
{
this->x = x;
this->y = y;
this->z = z;
}
//类型转换构造函数
Point3D(const Point2D &p)
{
this->x = p.x;
this->y = p.y;
this->z = 0;
cout << "Point3D(const Point2D &p)" <<endl;
}
operator Point2D()
{
cout << "operator Point2D()" << endl;
Point2D p2;
p2.x = x;
p2.y = y;
return p2;
}
Point2D toPoint2D()
{
Point2D p2;
p2.x = x;
p2.y = y;
return p2;
}
void print()
{
cout<<"("<<x<<","<<y<<","<<z<<")"<<endl;
}
private:
int x;
int y;
int z;
};
int main(int argc, char *argv[])
{
Point3D p3(3,4,5);
p3.print();
Point2D p2(1,2);
p2.print();
Point2D p2a = p3;//operator Point2D()
p2a.print();
Point2D p2b = p3.toPoint2D();
p2b.print();
return 0;
}
五、操作符重载应用
1、函数操作符
函数对象也成仿函数(functor),可以使用具体的类对象取代类成员函数。函数对象通过重载函数操作符实现,函数操作符只能重载为类的成员函数,可以定义不同参数的多个重载函数。
函数操作符重载函数声明如下:Type operator()()
函数操作符主要应用于STL和模板。函数操作符只能通过类成员函数重载,函数对象用于在工程中取代函数指针。
#include <iostream>
using namespace std;
class Fib
{
private:
int a0;
int a1;
public:
Fib()
{
a0 = 0;
a1 = 1;
}
Fib(int n)
{
a0 = 0;
a1 = 1;
for(int i = 0; i < n; i++)
{
int t = a1;
a1 = a0 + a1;
a0 = t;
}
}
int operator()()
{
int ret = a1;
a1 = a0 + a1;
a0 = ret;
return ret;
}
};
int main()
{
Fib fib;
for(int i = 0; i < 10; i++)
{
cout << fib() << endl;
}
cout << endl;
Fib fib2(10);//从第10项开始
for(int i = 0; i < 5; i++)
{
cout << fib2() << endl;
}
return 0;
}
2、堆内存操作符
定义:
operator new
operator delete
operator new[]
operator delete[]
A、全局函数重载
#include <iostream>
#include <stdlib.h>
using namespace std;
class A
{
public:
A()
{
cout<<"A constructor"<<endl;
}
~A()
{
cout<<"A destructor"<<endl;
}
private:
int a;
};
void * operator new (size_t size)
{
cout<<"new "<<size<<endl;
return malloc(size);
}
void operator delete(void *p)
{
cout<<"delete"<<endl;
free(p);
}
void * operator new[] (size_t size)
{
cout<<"new[] "<<size<<endl;
return malloc(size);
}
void operator delete[](void *p)
{
cout<<"delete[] "<<endl;
free(p);
}
int main()
{
int *p = new int;
delete p;
int *pa = new int[20];
delete []pa;
A * cp = new A;
delete cp;
A * cpa = new A[20];
delete []cpa;
return 0;
}
B、类成员函数重载
#include <iostream>
#include <stdlib.h>
using namespace std;
class A
{
public:
A()
{
cout<<"A constructor"<<endl;
}
~A()
{
cout<<"A destructor"<<endl;
}
void * operator new (size_t size)
{
cout<<"new "<<size<<endl;
return malloc(size);
}
void operator delete(void *p)
{
cout<<"delete"<<endl;
free(p);
}
void * operator new[] (size_t size)
{
cout<<"new[] "<<size<<endl;
return malloc(size);
}
void operator delete[](void *p)
{
cout<<"delete[] "<<endl;
free(p);
}
private:
int a;
};
int main()
{
// int *p = new int;
// delete p;
// int *pa = new int[20];
// delete []pa;
A * cp = new A;
delete cp;
A * cpa = new A[20];
delete []cpa;
return 0;
}
3、赋值操作符重载
编译器默认为每个类重载了赋值操作符,但默认的赋值操作符只完成浅拷贝。与拷贝构造函数一样,当需要深拷贝时需要显示重载赋值操作符。
#include <iostream>
using namespace std;
class Test
{
private:
int* pointer;
public:
Test()
{
pointer = NULL;
}
Test(int n)
{
pointer = new int(n);
}
Test(const Test& another)
{
pointer = new int(*another.pointer);
}
Test& operator=(const Test& another)
{
if(this != &another)
{
delete pointer;
pointer = new int(*another.pointer);
}
return *this;
}
void print()
{
cout << this << endl;
}
};
int main()
{
Test t1 = 1;
Test t2 = t1;
Test t3;
t3 = t2;
t1.print();
t2.print();
t3.print();
return 0;
}
4、下标访问操作符重载``
下标访问操作符([])只能通过类的成员函数重载,并且重载函数只能使用一个参数。
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
class Test
{
int m_array[5];
public:
//使用位置索引作为下标访问
int& operator [](int index)
{
return m_array[index];
}
//使用字符串作为下标访问
int& operator [](const char* index)
{
if(index == "1st")
{
return m_array[0];
}
if(index == "2nd")
{
return m_array[1];
}
if(index == "3rd")
{
return m_array[2];
}
if(index == "4th")
{
return m_array[3];
}
if(index == "5th")
{
return m_array[4];
}
return m_array[0];
}
int length()const
{
return sizeof(m_array)/sizeof(int);
}
};
int main(int argc, char *argv[])
{
Test test;
for(int i = 0; i < test.length(); i++)
{
test[i] = i;
}
for(int i = 0; i < test.length(); i++)
{
cout << test[i] << endl;
}
cout << test["1st"] << endl;
cout << test["2nd"] << endl;
cout << test["3rd"] << endl;
cout << test["4th"] << endl;
cout << test["5th"] << endl;
return 0;
}
原文地址:http://blog.51cto.com/9291927/2143237