第四周 运算符的重载

运算符重载
运算符重载,就是对已有的运算符(C++中预定义的运算符)赋予多
重的含义,使同一运算符作用于不同类型的数据时导致不同类型的
行为。
运算符重载的目的是:扩展C++中提供的运算符的适用范围,使之
能作用于对象。
同一个运算符,对不同类型的操作数,所发生的行为不同。
 complex_a + complex_b 生成新的复数对象
5 + 4 = 9

运算符重载的形式
运算符重载的实质是函数重载
 可以重载为普通函数,也可以重载为成员函数
把含运算符的表达式转换成对运算符函数的调用。 把运算符的操作数转换成运算符函数的参数。
 运算符被多次重载时,根据实参的类型决定调用哪个运算符函数。
返回值类型 operator  运算符(形参表)
{
……
}

重载为成员函数时 , 参数个数为运算符目数减一 。(减一是有个隐藏的this参数)
重载为普通函数时 , 参数个数为运算符目数 。

例子

#include<iostream>
using namespace std;

class Complex
{
public:
    double real,imag;
    Complex( double r = 0.0, double i= 0.0 ):real(r),imag(i) { }
    Complex operator-(const Complex & c);
};
Complex operator+( const Complex & a, const Complex & b)
{
    return Complex( a.real+b.real,a.imag+b.imag); // 返回一个临时对象
}
Complex Complex::operator-(const Complex & c)
{
    return Complex(real - c.real, imag - c.imag); // 返回一个临时对象
}
int main()
{
    Complex a(4,4),b(1,1),c;
    c = a + b; // 等价于c=operator+(a,b);
    cout << c.real << "," << c.imag << endl;
    cout << (a-b).real << "," << (a-b).imag << endl;//a-b 等价于a.operator-(b)
    return 0;
}
/*
输出:
5,5
3,3
*/

赋值运算符 ‘=’重载
有时候希望赋值运算符两边的类型可以不匹配,
比如,把一个int类型变量赋值给一个Complex对象,
或把一个 char * 类型的字符串赋值给一个字符串对
象,此时就需要重载赋值运算符“=”。
赋值运算符“=”只能重载为成员函数

#include<iostream>
#include<cstring>
using namespace std;

class String
{
private:
    char * str;
public:
    String ():str(new char[1])
    {
        str[0] = 0;
    }
    const char * c_str()
    {
        return str;
    };
    String & operator = (const char * s);
    ~String()
    {
        delete [] str;
    }
};
String & String::operator = (const char * s)
{
    // 重载“=”得 以使得 obj = “hello” 能够成立
    delete [] str;
    str = new char[strlen(s)+1];
    strcpy( str, s);
    return * this;
}
int main()
{
    String s;
    s = "Good Luck," ; //等价于 s.operator=("Good Luck,");
    cout << s.c_str() << endl;
// String s2 = "hello!"; // 这条语句要是不注释掉就会出错
    s = "Shenzhou 8!"; //等价于 s.operator=("Shenzhou 8!");
    cout << s.c_str() << endl;
    return 0;
}
/*
输出:
Good Luck,
Shenzhou 8!
*/

浅拷贝和深拷贝

#include<iostream>
#include<cstring>
using namespace std;

class String
{
private:
    char * str;
public:
    String ():str(new char[1])
    {
        str[0] = 0;
    }
    const char * c_str()
    {
        return str;
    };
    String & operator = (const char * s)
    {
        delete [] str;
        str = new char[strlen(s)+1];
        strcpy( str, s);
        return * this;
    };
    ~String( )
    {
        delete [] str;
    }
};
int main()
{
    String S1, S2;
    S1 = "this";
    S2 = "that";
    S1 = S2;
    //cout<<S1<<" "<<S2<<endl;这句将会报错
}


如不定义自己的赋值运算符,那么S1=S2实际上导致 S1.str和 S2.str
指向同一地方。
如果S1对象消亡,析构函数将释放 S1.str指向的空间,则S2消亡时还
要释放一次,不妥。
另外,如果执行 S1 = "other";会导致S2.str指向的地方被dele

 因此要在 class String里添加成员函数:
String & operator = (const String & s) {
delete [] str;
str = new char[strlen( s.str)+1];
strcpy( str,s.str);
return * this;
}

上述例子的第2个问题

考虑下面语句:
String s;
s = "Hello";
s = s;//把自己的空间delete了
是否会有问题?
解决办法:
String & operator = (const String & s){
if( this == & s)
return * this;
delete [] str;
str = new char[strlen(s.str)+1];
strcpy( str,s.str);
return * this;
}

对 operator = 返回值类型的讨论
void 好不好?
String 好不好?
为什么是 String &
对运算符进行重载的时候,好的风格是应该尽量保留运算符原本的特性
考虑: a = b = c;
和 (a=b)=c; //会修改a的值
分别等价于:
a.operator=(b.operator=(c));
(a.operator=(b)).operator=(c);

所以用String & 才能做左值

上述例子的第3个问题
String s1 = "aa";
String s2 = s1;
这会导致s1和s2指向同一个地方

如果不写赋值构造函数,那调用默认的为 String类编写复制构造函数的时候,
会面临和 = 同样的问题,用同样的方法处理。

String( String & s)
{
str = new char[strlen(s.str)+1];
strcpy(str,s.str);
}

运算符重载为友元函数
一般情况下,将运算符重载为类的成员函数,是较好的选择。
但有时,重载为成员函数不能满足使用要求,重载为普通函数,又不能访问类的私有
成员,所以需要将运算符重载为友元。

#include<iostream>
#include<cstring>
using namespace std;
class Complex
{
    double real,imag;
public:
    Complex( double r, double i):real(r),imag(i) { };
    Complex operator+( double r );
};
Complex Complex::operator+( double r )
{
    //能解释 c+5
    return Complex(real + r,imag);
}
int main()
{
    Complex c(0,0);
    c = c + 5; // 有定义,相当于 c = c.operator +(5);
    c = 5 + c; //编译出错 编译出错
}

修改为友元后

#include<iostream>
#include<cstring>
using namespace std;
class Complex
{
    double real,imag;
public:
    Complex( double r, double i):real(r),imag(i) { };
    Complex operator+( double r );
    friend Complex operator + (double r,const Complex & c);
};
Complex Complex::operator+( double r )
{
    //释 能解释 c+5
    return Complex(real + r,imag);
}
// 所以,为了使得上述的表达式能成立,需要将 + 重载为普通函数。
//但是普通函数又不能访问私有成员,所以,需要将运算符 + 重载为友元。
Complex operator+ (double r,const Complex & c)
{
    //释 能解释 5+c
    return Complex( c.real + r, c.imag);
}
int main()
{
    Complex c(0,0);
    c = c + 5; // 有定义,相当于 c = c.operator +(5);
    c = 5 + c; //编译出错 编译出错
}

运算符重载实例:可变长整型数组
 

#include<iostream>
#include<cstring>

using namespace std;

class CArray
{
    int size; //数组元素的个数
    int *ptr; //指向动态分配的数组
public:
    CArray(int s = 0); //s代表数组元素的个数
    CArray(CArray & a);
    ~CArray();
    void push_back(int v); //用于在数组尾部添加一个元素v
    CArray & operator=( const CArray & a);
//用于数组对象间的赋值
    int length()
    {
        return size;    //返回数组元素个数
    }
    int &operator[](int i) //返回值为 int 不行!不支持 a[i] = 4
    {
        //用以支持根据下标访问数组元素,
// 如n = a[i] 和a[i] = 4; 这样的语句
        return ptr[i];
    }
};

CArray::CArray(int s):size(s)//构造函数
{
    //cout<<"in CArray(int s) "<<s<<endl;
    if( s == 0)
        ptr = NULL;
    else
        ptr = new int[s];
}
CArray::CArray(CArray & a)
{
    if( !a.ptr)
    {
        ptr = NULL;
        size = 0;
        return;
    }
    ptr = new int[a.size];
    memcpy( ptr, a.ptr, sizeof(int ) * a.size);
    size = a.size;
}
CArray::~CArray()
{
    if( ptr)
        delete [] ptr;
}
CArray & CArray::operator=( const CArray & a)
{
    // 赋值号的作用是使“=” 左边对象里存放的数组,大小和内容都和右边的对象一样
    if( ptr == a.ptr) // 防止a=a 这样的赋值导致出错
        return * this;
    if( a.ptr == NULL)   // 如果a 里面的数组是空的
    {
        if( ptr )
            delete [] ptr;
        ptr = NULL;
        size = 0;
        return * this;
    }
    if( size < a.size)    // 如果原有空间够大,就不用分配新的空间
    {
        if(ptr)
            delete [] ptr;
        ptr = new int[a.size];
    }
    memcpy( ptr,a.ptr,sizeof(int)*a.size);
    size = a.size;
    return * this;
} // CArray & CArray::operator=( const CArray & a)
void CArray::push_back(int v)
{
    // 在数组尾部添加一个元素
    if( ptr)
    {
        int * tmpPtr = new int[size+1]; // 重新分配空间
        memcpy(tmpPtr,ptr,sizeof(int)*size); // 拷贝原数组内容
        delete [] ptr;
        ptr = tmpPtr;
    }
    else // 数组本来是空的
        ptr = new int[1];
    ptr[size++] = v; // 加入新的数组元素
}
int main()   //要编写可变长整型数组类,使之能如下使用:
{
    CArray a; //开始里的数组是空的
    //cout<<"hh"<<endl;
    for( int i = 0; i < 5; ++i)
        a.push_back(i);//要用动态分配的内存来存放数组元素,需要一个指针成员变量
    CArray a2,a3;
    a2 = a;//要重载“=”
    for( int i = 0; i < a.length(); ++i )
        cout << a2[i] << " " ;//要重载“[ ]”
    a2 = a3; //a2是空的
    for( int i = 0; i < a2.length(); ++i ) //a2.length()返回0
        cout << a2[i] << " ";
    cout << endl;
    a[3] = 100;
    CArray a4(a);//要自己写复制构造函数
    for( int i = 0; i < a4.length(); ++i )
        cout << a4[i] << " ";
    return 0;
}



/*
程序输出结果是:
0 1 2 3 4
0 1 2 100 4
*/

流插入运算符
和流提取运算符的重载
 cout 是在 iostream 中定义的,ostream 类的对象。
“<<” 能用在cout 上是因为,在iostream里对 “<<” 进行了重载。
考虑,怎么重载才能使得cout << 5; 和 cout << this 都能成立?

cout << 5 ; 即 cout.operator<<(5);
cout << “this”; 即 cout.operator<<(this);
流插入运算符的重载

ostream & ostream::operator<<(int n)
{
    …… // 输出n 的代码
    return * this;
}
ostream & ostream::operator<<(const char * s )
{
    …… // 输出s 的代码
    return * this;
}
//等价cout.operator<<(5).operator<<(this);
//要做左值,才能满足上式

• 假定下面程序输出为 5hello, 该补写些什么

class CStudent
{
public:
    int nAge;
};
int main()
{
    CStudent s ;
    s.nAge = 5;
    cout << s <<"hello";
    return 0;
}
//解决:
ostream & operator<<( ostream & o,const CStudent & s)
{
    o << s.nAge ;
    return o;
}

例题
假定c是Complex复数类的对象,现在希望写“cout << c;”,就能以“a+bi”的形式输出c的值,写“cin>>c;”,就能从键盘接受“a+bi”形式的输入,并且使得c.real = a,c.imag = b。

#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;
class Complex
{
    double real,imag;
public:
    Complex( double r=0, double i=0):real(r),imag(i) { };
    friend ostream & operator<<( ostream & os,
                                 const Complex & c);
    friend istream & operator>>( istream & is,Complex & c);
};
ostream & operator<<( ostream & os,const Complex & c)
{
    os << c.real << "+" << c.imag << "i"; // 以"a+bi" 的形式输出
    return os;
}
istream & operator>>( istream & is,Complex & c)
{
    string s;
    is >> s; // 将"a+bi" 作为字符串读入, “a+bi”  中间不能有空格
    int pos = s.find("+",0);
    string sTmp = s.substr(0,pos); // 分离出代表实部的字符串
    c.real = atof(sTmp.c_str()); //atof 库函数能将const char* 指针指向的内容转换成 float
    sTmp = s.substr(pos+1, s.length()-pos-2); // 分离出代表虚部的字符串
    c.imag = atof(sTmp.c_str());
    return is;
}
int main()
{
    Complex c;
    int n;
    cin >> c >> n;
    cout << c << "," << n;
    return 0;
}
/*
运行结果可以如下:

input:  13.2+133i 87
output: 13.2+133i, 87
*/

类型转换运算符和自增、自减运算符的重载

//重载类型转换运算符
#include <iostream>
using namespace std;
class Complex
{
    double real,imag;
public:
    Complex(double r=0,double i=0):real(r),imag(i) { };
    operator double ()
    {
        return real;
    }
//符 重载强制类型转换运算符 double
};
int main()
{
    Complex c(1.2,3.4);
    cout << (double)c << endl; //出 输出 1.2
    double n = 2 + c; //于 等价于 double n=2+c.operator double()
    cout << n; //出 输出 3.2
}

自增运算符++、自减运算符--有前置/后置之分,
为了区分所重载的是前置运算符还是后置运算符,C++规定:
前置运算符作为一元运算符重载重载为成员函数:
T & operator++();
T & operator--();
2.重载为全局函数:
T1 & operator++(T2);
T1 & operator—(T2);
自增,自减运算符的重载(P221)
后置运算符作为二元运算符重载,多写一个没用的参数:
重载为成员函数:
T operator++(int);
T operator--(int);
重载为全局函数:
T1 operator++(T2,int );
T1 operator—( T2,int);

#include<iostream>
using namespace std;

class CDemo
{
private :
    int n;
public:
    CDemo(int i=0):n(i) { }
    CDemo & operator++(); // 用于前置形式
    CDemo operator++( int ); // 用于后置形式
    operator int ( )
    {
        return n;
    }
    friend CDemo & operator--(CDemo & );
    friend CDemo operator--(CDemo &,int);
};
CDemo & CDemo::operator++()
{
    //前置 ++
    n ++;
    return * this;
} // ++s即为: s.operator++();
CDemo CDemo::operator++( int k )
{
    //后置 ++
    CDemo tmp(*this); // 记录修改前的对象
    n ++;
    return tmp; // 返回修改前的对象
} // s++即为: s.operator++(0);
CDemo & operator--(CDemo & d)
{
    // 前置--
    d.n--;
    return d;
} //--s即为: operator--(s);
CDemo operator--(CDemo & d,int)
{
    // 后置--
    CDemo tmp(d);
    d.n --;
    return tmp;
} //s--即为: operator--(s, 0)

int main()
{
    CDemo d(5);
    cout << (d++ ) << ","; //等价于 d.operator++(0);
    cout << d << ",";
    cout << (++d) << ","; //等价于 d.operator++();
    cout << d << endl;
    cout << (d-- ) << ","; //等价于 operator--(d,0);
    cout << d << ",";
    cout << (--d) << ","; //等价于 operator--(d);
    cout << d << endl;
    return 0;
}
/*
输出结果:
5,6,7,7
7,6,5,5
*/

operator int ( ) { return n; }

这里,int 作为一个类型强制转换运算符被重载, 此后
Demo s;
(int) s ; //于 等效于 s.int();
类型强制转换运算符被重载时不能写返回值类型,实际上其返回值类型就
是该类型强制转换运算符代表的类型

运算符重载的注意事项
1. C++不允许定义新的运算符 ;
2. 重载后运算符的含义应该符合日常习惯;
 complex_a + complex_b
 word_a > word_b
 date_b = date_a + n
3. 运算符重载不改变运算符的优先级;
4. 以下运算符不能被重载:“.”、“.*”、“::”、“?:”、sizeof;
5. 重载运算符()、[]、->或者赋值运算符=时,运算符重载函数必须声明为
类的成员函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值