对类的对象进行操作,可以通过成员函数,或者定义一个友元函数(如果需要访问类的私有成员,类需要指定其为友元函数)。
如果要用C++的操作符,对类的对象进行操作,就需要针对相应的类,通过操作符重载机制来给出这些操作符的定义
不能重载的操作符: 成员选择符.
间接成员选择符.*
域解析符::
条件操作符?:
数据占内存大小sizeof
双目操作符重载:
假设把operator +作为Complex的成员函数来定义:
成员函数中已经有了隐藏的参数this,因此作为成员函数重载双目操作符时只需要给出一个参数作为第二个操作数
使用方法为a+b或a.operator+(b)
class Complex{ double real , imag; public: Complex operator + (const Complex& x) const{ Complex temp; temp.real = real + x.real; temp.imag = imag + x.imag; return temp; } }
作为全局函数来定义,此时需要两个参数,且至少有一个为类、结构、枚举或它们的引用类型
使用方法为a+b或operator+(a , b)
class Complex{ double real , imag; friend Complex operator + (const Complex& c1 , const Complex& c2); } Complex operator + (const Complex& c1 , const Complex& c2) const{ Complex temp; temp.real = c1.eal + c2.real; temp.imag = c1.imag + c2.imag; return temp; }
cout是一个ostream类的对象,能够识别C++中的所有基本类型。因为对每种基本类型,ostrearn类声明中都包含了相应的重载的operator<<()定义。
通过友元函数,可以这样重载运算符:
ostream& operator << (ostream& os , const Time& t){ os << t.hours << “hours” << t.minutes << “minutes”; return os; //函数的返回值就是传递给它的对象 }
这样可以使用下面的语句:
cout << trip;
此时cou<<trip将被转换为调用operator<<(cout, trip),而该调用返回ostream对象。
cout<<x<<y其实相当于(cout<<x)<<y。因为<<运算符要求左边是一个ostream对象,所以(cout<<x)表达式也是一个 ostream 类型的对象。
如果使用一个Time成员函数来重载<<,Time对象将是第一个操作数,就必须这样使用<<:
trip << cout;
operator<<()函数访问了Time对象的各个成员,但从始至终都将ostream对象作为一个整体使用。因为operator<<()直接访问Time对象的私有成员,所以它必须是Time 类的友元。但由于它并不直接访问ostream对象的私有成员,所以并不一定必须是ostream类的友元。
单目操作符重载:
假设把operator-()作为Complex的成员函数来定义:
成员函数中已经有了隐藏的参数this,因此作为成员函数重载单目操作符时不需要再给出参数
使用方法为a.operator()-或-a
class Complex{ double real , imag; public: Complex operator - () const{ //对复数类重载了取负数操作 Complex temp; temp.real = -real; temp.imag = -imag; return temp; } } Complex a(1,2) , b; b = -a;
作为全局函数来定义,此时需要两个参数,且必须为为类、结构、枚举或它们的引用类型:
使用方法为operator -(a)或-a
区分前置和后置的++和--的重载:另外定义带有一个int型参数的操作符的重载函数给后置用法使用
class Counter{ int value; public: Counter(){value = 0;} Counter& operator ++(){ value++; return *this; } Counter& operator ++(int){ Counter temp = *this; ++(*this); return temp; } } Counter a , b ,v; b = ++a; c = a++;
特殊操作符重载:
(1)赋值操作符=重载
C++的编译器其实为每个类都定义了隐式的赋值操作符重载函数
A& A::operator=(const A& a);
它逐个成员进行赋值操作:对普通成员直接赋值,对成员对象则调用该成员对象的赋值操作符重载函数进行赋值。
如果某个成员是指针,隐式赋值操作会产生隐式拷贝构造函数类似的问题,需要自己定义赋值操作符重载函数。
一般需要自定义拷贝构造函数的类都需要自定义赋值操作符:A b=a会调用拷贝构造函数,b=a会调用赋值操作符。
而且开头一般要加上
if(&a==this) return *this;
防止自我拷贝
(2)数组元素访问操作符[]重载
运行时系统不对数组元素下标越界检查。最好把数组表示的数据封装在一个类里,在重载的数组元素访问操作符函数中进行下标的有效性检查
class String{ char *p; public: char& operator [] (int i){ if(i>=strlen(p) || i<0) { cerr<<“下标越界错误 ”; exit(-1); } return p[i]; } } String s(“abcd”); for(int i=0; i<s.length(); i++) if(s[i] >= ‘a’ && s[i] <= ‘z’) s[i] = s[i]-'a' + ‘A’;
(3)动态存储分配操作符new和去配操作符delete重载
new:为动态对象分配空间、调用对象类构造函数
delete:调用对象的析构函数、释放对象的空间
new和delete只能作为类的静态成员来重载(static说明可以不写),因为它们属于类而非对象
class A{ int x , y; public: void *operator new(size_t size){ //重载函数根据形参size获得所需内存的大小,为对象分配空间 void *p = malloc(size); memset(p , 0 ,size); return p; } } A *p = new A; //系统自动计算对象所需内存大小,作为实参调用new的重载函数
函数operator new的参数size类型为size_t(unsigned int),返回参数必须为void*
有其它参数时的重载方法:
#include <cstring> class A{ public: A(int i){……} void *operator new(size_t size , void *p){return p;} char buf[sizeof(A)]; A *p = new(buf) A(0); }
buf是传给new操作符重载函数的第二个参数,0是传给类A构造函数的参数
创建的动态对象,内存空间不是在程序的堆区分配的,而是由创建者提供的,不能用delete操作。只能直接p->~A()调用析构函数实现。
重载new之后必须也相应的重载delete
void operator delete(void *p , size_t size);
第一个参数p为对象的内存地址空间,类型为void*,第二个参数如果有则必须是size_t类型,返回值必须是void
(4)函数调用操作符()重载
C++把函数调用也看作操作符,并且可以针对某个类来重载。重载后相应类的对象可以当作函数使用。
class A{ int value; public: A(){value = 0;} A(int i){value = i;} int operator()(int x){return x+value;} } A a(3); count<<a(10)<<endl;
a(10)会调用a.operator()(10)
(5)类成员访问操作符->重载
class A{ int x ,y; public: void f(); void g(); }
如果需要统计对A类对象a的访问次数,可以为A增加计数器count,A所有允许访问的外部成员函数count++。但A中有允许外界访问的数据成员,则对数据成员的访问无法统计
可以重新定义一个类B,它有一个指向A类对象的数据成员,在类B中重载操作符->,使类B在形式上可以当作一种指针类型来用。
第一个操作数为一个指向类/结构的指针,第二个操作数为第一个操作数指向的类/结构成员
class B{ A *p_a; int count; public: B(A *p){P_a=p; count=0;} A *operator ->(){count++; return p_a;} int num_of_a_access()const{return count;} } A a; B b(&a); //b相当于智能指针,指向a b->f(); //等价于b.operator->()->g() void func(B &p){ p->f(); //p是个智能指针对象 } func(b); b.number_of_a_access();
(6)类型转换操作符重载
C++提供了定义类型转换的机制,实现一个类与其它类型之间的转换
带一个参数的构造函数,可以用作从一个基本数据类型或其它类到一个类的转换。
class Complex{ double real , imag; public: Complex(){real=0; imag=0;} Complex(double r){ //在复数类中增加参数类型为double的构造函数,可以把double型数据转换成complex类的对象 real=r; imag=0 } Complex(double r , double i){ real = r; imag = i; } friend Complex operator +(const Complex& x , const Complex& y); } Complex operator +(const Complex& x , const Complex& y){ Complex temp; temp.real = x.real + y.real; temp.imag = x.imag + y.imag; return temp; } Complex c1(1,2) , c2 , c3; c2 = c1 + 1.7; //1.7隐式转换成复数对象Complex(1.7) c3 = 2.5 +c2; //2.5隐式转换成复数对象Complex(2.5)
自定义类型转换:
class A{ int x , y; public: operator int(){return x+y;} } A a; int i = 1; int z = i + a; //会调用类型转换操作符int的重载函数把对象a隐式转换为int性数据
但是,同时有这两种重载时会出现问题
class A{ int x, y; public: A(){x=0; y=0;} A(int i){x=i; y=0;} operator int(){return x+y;} friend A operator +(const A &a1 , const A &a2); } friend A operator +(const A &a1 , const A &a2){ A temp; temp.x = a1.x + a2.x; temp.y = a1.y + a2.y; return temp; } A a; int i ,z;
只能用显式类型转换来解决:z = (int)a + i或z = a + (A)i;
也可以给A类的构造函数A(int i)加上修饰符explicit,禁止把它当作隐式类型转换符,此时a+i就只能把a转换为int。