c++运算符重载

1.运算符重载定义:

C++中预定义的运算符的操作对象只能是基本数据类型。但实际上,对于许多用户自定义类型(例如类),也需要类似的运算操作。这时就必须在C++中重新定义这些运算符,赋予已有运算符新的功能,使它能够用于特定类型执行特定的操作。运算符重载的实质是函数重载,它提供了C++的可扩展性,也是C++最吸引人的特性之一。

运算符重载是通过创建运算符函数实现的,运算符函数定义了重载的运算符将要进行的操作。运算符函数的定义与其他函数的定义类似,惟一的区别是运算符函数的函数名是由关键字operator和其后要重载的运算符符号构成的。运算符函数定义的一般格式如下:

  <返回类型说明符> operator <运算符符号>(<参数表>)

{

     <函数体>

}

2.运算符重载时要遵循以下规则:

(1) 除了类属关系运算符"."、成员指针运算符".*"、作用域运算符"::"、sizeof运算符和三目运算符"?:"以外,C++中的所有运算符都可以重载。

(2) 重载运算符限制在C++语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符。

(3) 运算符重载实质上是函数重载,因此编译程序对运算符重载的选择,遵循函数重载的选择原则。

(4) 重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符操作数的个数及语法结构。

(5) 运算符重载不能改变该运算符用于内部类型对象的含义。它只能和用户自定义类型的对象一起使用,或者用于用户自定义类型的对象和内部类型的对象混合使用时。

(6) 运算符重载是针对新类型数据的实际需要对原有运算符进行的适当的改造,重载的功能应当与原有功能相类似,避免没有目的地使用重载运算符。

(7)重载运算符的函数不能有默认的参数,否则就改变了运算符的参数个数,与前面第3点相矛盾了;

(8)重载的运算符只能是用户自定义类型,否则就不是重载而是改变了现有的C++标准数据类型的运算符的规则了,会引会天下大乱的;

(9)用户自定义类的运算符一般都必须重载后方可使用,但两个例外,运算符=&不必用户重载;

(10)运算符重载可以通过成员函数的形式,也可是通过友元函数,非成员非友元的普通函数。

 

3.运算符重载的形式:

运算符函数重载一般有两种形式:重载为类的成员函数和重载为类的非成员函数。非成员函数通常是友元。(可以把一个运算符作为一个非成员、非友元函数重载。但是,这样的运算符函数访问类的私有和保护成员时,必须使用类的公有接口中提供的设置数据和读取数据的函数,调用这些函数时会降低性能。可以内联这些函数以提高性能。)   

1) 成员函数运算符

 运算符重载为类的成员函数的一般格式为:

    <函数类型> operator <运算符>(<参数表>)

    {

     <函数体>

    }

 当运算符重载为类的成员函数时,函数的参数个数比原来的操作数要少一个(后置单目运算符除外),这是因为成员函数用this指针隐式地访问了类的一个对象,它充当了运算符函数最左边的操作数。因此:

(1) 双目运算符重载为类的成员函数时,函数只显式说明一个参数,该形参是运算符的右操作数。

(2) 前置单目运算符重载为类的成员函数时,不需要显式说明参数,即函数没有形参。

(3) 后置单目运算符重载为类的成员函数时,函数要带有一个整型形参。

    调用成员函数运算符的格式如下:

    <对象名>.operator <运算符>(<参数>)

    它等价于

    <对象名><运算符><参数>

 例如a+b等价于a.operator +(b)。一般情况下,我们采用运算符的习惯表达方式。

2) 友元函数运算符

 运算符重载为类的友元函数的一般格式为:

    friend <函数类型> operator <运算符>(<参数表>)

   {

     <函数体>

    }

当运算符重载为类的友元函数时,由于没有隐含的this指针,因此操作数的个数没有变化,所有的操作数都必须通过函数的形参进行传递,函数的参数与操作数自左至右一一对应。

 调用友元函数运算符的格式如下:

    operator <运算符>(<参数1>,<参数2>)

    它等价于

    <参数1><运算符><参数2>

    例如a+b等价于operator +(a,b)

4.两种重载形式的比较

在多数情况下,将运算符重载为类的成员函数和类的友元函数都是可以的。但成员函数运算符与友元函数运算符也具有各自的一些特点:

(1) 一般情况下,单目运算符最好重载为类的成员函数;双目运算符则最好重载为类的友元函数。

(2) 以下一些双目运算符不能重载为类的友元函数:=、()、[]、->。

(3) 类型转换函数只能定义为一个类的成员函数而不能定义为类的友元函数。

(4) 若一个运算符的操作需要修改对象的状态,选择重载为成员函数较好。

(5) 若运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,则只能选用友元函数。

(6) 当运算符函数是一个成员函数时,最左边的操作数(或者只有最左边的操作数)必须是运算符类的一个类对象(或者是对该类对象的引用)。如果左边的操作数必须是一个不同类的对象,或者是一个内部类型的对象,该运算符函数必须作为一个友元函数来实现。

(7) 当需要重载运算符具有可交换性时,选择重载为友元函数。


5学习过程中的代码实现,以及出现的bug分析:

cpp1:

#include<iostream>

using namespace std;
class Cadd{
public :
    int value;
    Cadd(){//定义构造函数,为成员变量赋初值
        value = 0;
    }
    Cadd(int value){//重载构造函数
        this->value = value;
    }
//    Cadd operator+(/*Cadd a,*/int b)
    /*该程序的运行结果与上例相同。前面已讲过,
    对又目运算符,重载为成员函数时,仅一个参数,另一个被隐含;重载为友元函数时,有两个参数,没有隐含参数。*/
//    {
//        Cadd sum;
//        sum.value = this->value + b;
//        return sum;
//    }
};

Cadd operator+(Cadd a,int b)
    /*该程序的运行结果与上例相同。前面已讲过,
    对又目运算符,重载为成员函数时,仅一个参数,另一个被隐含;重载为友元函数时,有两个参数,没有隐含参数。*/
    //一个程序中运算符只能被重载一次,否会就会报错,出现二义性
{
    Cadd sum;
    sum.value = a.value + b;
    return sum;
}
int main()
{
    Cadd value(5),value2,value3;
    
//    value2=value.operator+(8);
    // error C2593: 'operator +' is ambiguous
    value3=value+8;
    cout<<"the sum(value2) is: "<<value2.value<<endl;
    cout<<"the sum(value3) is: "<<value3.value<<endl;
    return 0;
}

cpp2:

/*
    运算符的重载有两种形式:一种是重载为类的成员函数。一种是重载为类的友元函数。
    将运算符重载为他将要操作的的类的成员函数时,称为成员运算符函数
    
*/
//下面是双目运算符重载为成员函数
/*
    双目运算符重载为成员函数时,左操作数是访问该重载运算符的对象本身的数据,由this指针指出,右操作数通过成员运算符函数的参数指出,所以
    此时成员运算符函数只能有一个参数
    显式调用:对象名.operator运算符符号(参数);例如:aa.operator+(bb)
    隐式调用:对象名 重载运算符号 对象名;例如aa+bb
*/
#include<iostream>

using namespace std;
class point
{
private:
    int x,y;
public :
    point(int xx=0,int yy=0)
    {    
        x=xx;
        y=yy;
    }
    int getx()
    {
        return x;
    }
    int gety()
    {
        return y;
    }
    point operator+(point p)
    {
        point temp;
        temp.x=x+p.x;
        temp.y=y+p.y;
        return temp;
    }
};
//注意:在c++中类的的结束花括号后要有分号
int main()
{
    point ob1(1,2),ob2(3,4),ob3,ob4;
    ob3=ob1+ob2;//显式调用
    ob4=ob1.operator +(ob2);//隐式调用
    cout<<"ob3.x= "<<ob3.getx()<<"   ob3.y= "<<ob3.gety()<<endl;
    cout<<"ob4.x= "<<ob4.getx()<<"   ob4.y= "<<ob4.gety()<<endl;
    return 0;
}
/*
output:
    ob3.x= 4   ob3.y= 6
    ob4.x= 4   ob4.y= 6
    Press any key to continue
*/


cpp3:

/*
    单目运算符在重载为成员函数时,操作数是访问该重载运算符的对象本身,
    所以重载运算符在定义为成员运算符函数时没有参数
    显示调用:对象名.operator运算符号();如:aa.operator++()
    隐式调用:重载的运算符号 如:++aa;
*/

#include <iostream>

using namespace std;
class point
{
private:
    int x,y;
public :
    point(int xx=0,int yy=0)
    {    
        x=xx;
        y=yy;
    }
    int getx()
    {
        return x;
    }
    int gety()
    {
        return y;
    }
    point operator ++()
    {
        ++x;
        ++y;
        return *this;//因为单目运算符的操作数是对象本身,所以利用this指针返回对象本身
    }
};
int main()
{
    point ob(3,4);
    cout<<"ob.x= "<<ob.getx()<<"   ob.y= "<<ob.gety()<<endl;
    ++ob;//隐式调用
    cout<<"ob.x= "<<ob.getx()<<"   ob.y= "<<ob.gety()<<endl;
    ob.operator ++();//显式调用
    cout<<"ob.x= "<<ob.getx()<<"   ob.y= "<<ob.gety()<<endl;
    return 0;
}
/*
oouput:
    ob.x= 3   ob.y= 4
    ob.x= 4   ob.y= 5
    ob.x= 5   ob.y= 6
    Press any key to continue
*/

cpp3:

/*
    双目运算符重载为友元函数时,由于没有this指针,所以两个操作数必须通过函数参数进行传递,调用方式
    分为显示调用和隐式调用
    显示调用:operator 运算符号(参数1,参数2);
    隐式调用:对象名 重载的运算符 对象名

*/

#include"iostream.h"

//using namespace std;
class point
{
private:
    int x,y;
public :
    point(int xx=0,int yy=0)
    {    
        x=xx;
        y=yy;
    }
    int getx()
    {
        return x;
    }
    int gety()
    {
        return y;
    }
    friend point operator+(point p,point q);//在类中声明友元函数
    /*
    fatal error C1001: INTERNAL COMPILER ERROR
    在window98下使用vc6.0时,如果预编译头文件(stdafx.h)中包含了模板类的头文件,比如atl的头文件时,编译器会报错:
    fatal error C1001: INTERNAL COMPILER ERROR
    (compiler file 'msc1.cpp', line 1786)

    造成这种问题的原因是编译器分配的内存超过了限制。
    解决方法:
    1.把#include <iostream>改为#include <iostream.h>,再把using namespace std;这句删掉
    2.出现这种错误时,重新编译一下应该可以成功,如果还是不成功的话,可以试试重新启动开发环境,必要是重启系统。
    */
};

point operator+(point p,point q)//由于友元函数必须在类外进行定义,将重载的友元函数定义类外,
                                //由于友元函数没有this指针,所以必须利用两个参数进行重载
{
    point temp;
    temp.x=q.x+p.x;
    temp.y=q.y+p.y;
    return temp;
}
int main()
{
    point ob1(1,2),ob2(3,4),ob3,ob4;
    ob3=ob1+ob2;//显式调用
    ob4=operator+(ob1,ob2);//隐式调用
    cout<<"ob3.x= "<<ob3.getx()<<"   ob3.y= "<<ob3.gety()<<endl;
    cout<<"ob4.x= "<<ob4.getx()<<"   ob4.y= "<<ob4.gety()<<endl;
    return 0;
}

cpp4:

/*
    "++"和"--"的重载
    
      如果部分前置和后置,则使用operator++()或者是operator--()即可,否则要使用operator++()或者operator--()
      来重载前置运算符,使用operator++(int)或者operator--(int)来重载后置运算符,调用时,参数int被传递给值0。
      为什么要带参数?
            为了区分前置++与后置++的区别,需要在参数后增加一个"int"以示区分。含有"int"的重载方式为后置++,否则为前置++。
      前置--与后置--类似用法。前面说过,成员函数与友元函数的重载如果同时存在时,会先调用成员函数的重载,但是在++或--时,
      成员函数与友元函数的重载是不能同时存在的。
*/
#include<iostream>


using namespace std;
class point
{
private :
    int x,y;
public :
    point(int x=0,int y=0)
    {
        this->x=x;
        this->y=y;
    }
    int getx()
    {
        return x;
    }
    int gety()
    {
        return y;
    }
    point operator++()
    {
        ++y;
        ++x;
        return *this;
    }
    point operator--(int)
    {
        y--;
        x--;
        return *this;
    }
};
int main()
{
    point ob(3,4);
    cout<<"ob.x= "<<ob.getx()<<"   ob.y= "<<ob.gety()<<endl;
    ++ob;
    cout<<"ob.x= "<<ob.getx()<<"   ob.y= "<<ob.gety()<<endl;
    ob.operator --(0);
    cout<<"ob.x= "<<ob.getx()<<"   ob.y= "<<ob.gety()<<endl;
    return 0;
}
cpp5:

/*
    事实上,对于任何一个类,如果用户没有自定义的赋值运算符函数,系统会自动的为其生成一个默认的赋值运算符函数
    ,已完成数据成员之间的复制。例如:
    X & X::operator=(const X &source)
    {
    //类对象成员之间的赋值语句
    }
    一旦类x的两个对象ob1和ob2已经创建,就可用ob1=ob2进行赋值。通常情况下,默认的赋值运算符函数就可以完成赋值任务,但在某些
    特殊情况下,例如,类中有一种指针类的形式,如果使用默认的赋值运算符函数就会产生指针悬挂的错误。此时,就必须显示的定义一个复制运算符
    重载函数,是参与复制的两个对象有各自的存储空间,来解决这个问题。
*/


#include<iostream>

using namespace std;
class internet
{
public:
    char *name;
    char *url;

public:
    internet(char *name,char *url)
    {
        internet::name=new char(sizeof(name)+1);
        this->url=new char(sizeof(url)+1);
        if(name)
        {
            strcpy(internet::name,name);
        }
        if(url)
        {
            strcpy(internet::url,url);
        }
    }
    internet(internet &temp)
    {
        internet::name=new char (strlen(temp.name)+1);
        internet::url=new char (strlen(temp.url)+1);
        if(name)
        {
            strcpy(internet::name,temp.name);
        }
        if(url)
        {
            strcpy(internet::url,url);
        }
    }
    ~internet()
    {
        delete []name;
        delete []url;
    }
    internet &operator=(internet &temp)
    {
        delete []this->name;
        delete []this->url;
        this->name = new char(strlen(temp.name)+1);
        this->url = new char (strlen(temp.url)+1);
        if(this->name)
        {
            strcpy(this->name,temp.name);
        }
        if(this->url)
        {
            strcpy(this->url,temp.url);
        }
        return *this;
    }
};

int main()
{
    internet a("education","www.edu.cn");
    internet b=a;
    cout<<b.name<<endl<<b.url<<endl;
    internet c("Tsinghua","www.tsinghua.edu.cn");
    b=c;
    cout<<b.name<<endl<<b.url<<endl;
    return 0;
}

cpp6:

/*
    一般来说下标运算符的定义形式如下:
    T:是定义下标运算符的类,其可以不必是常量
    T2:表示下标,其可以是任意类型,如整形、字符型或者个类
    T3:是数组运算的结果,其也可以是任意类型,但是为了能对数组赋值,一般姜齐声明为引用形式

*/
#include<iostream>
#include <stdlib.h>

using namespace std;
class ainteger
{
    int *a;
    int sz;

public :
    ainteger(int size)
    {
        sz=size;
        a=new int[size];
    }
    int &operator[](int i)
    {
        if(i<0||i>=sz)
        {
            cout<<"error"<<endl;
            exit(1);
        }
        return a[i];
    }
    ~ainteger()
    {
        delete []a;
    }
};
int main()
{
    ainteger a(5);
    a[3]=0;
    cout<<"a[3]=  "<<a[3]<<endl;
    a.operator [](3)=0;
    cout<<"a.operator[](3)= "<<a[3]<<endl;
    cout<<"a[6]= ";
    a.operator [](6)=6;
    return 0;
}

cpp7:

/*
    类类型是指某个对象的数据类型为类,而不是标准的数据类型。在c++中标准的数据类型和类类型之间的转换有三种方式:
    1)通过构造函数转换,通过构造函数将标准数据类型向类类型转换,但是不能将类类型转换为标准类型。
    2)通过类类型转换函数,要将构造函数转换为标准数据类型时,需要采用显式类型转换机制,定义类型转换函数。
    3)通过运算符重载实现类型转换。


    其中,通过类类型转换函数转换需要定义一个类的类型转换函数,一般来说,c++中定义一个类的类型转换函数形式为:
    <类名>::operator type()
    {
    return type 类型的数据  //返回type类型对象
    }


    其中,type为要转换的目标类型,通常是标准类型,但是,使用类类型转换函数进行类型转换时,要注意:
    1)此函数的功能是将类的对象转换为类型为type的数据,他既没有参数,也没有返回类型,但在函数中必须返回
    具有type类型的一个数据。
    2)类类型转换函数只能定义为类的成员函数。
    3)一个类可以定义多个类类型转换函数,编译器会根据操作数的类型自动选择一个合适的类型转换函数与之匹配,
    但在可能出现二义性时,应显示的使用类类型转换函数进行转换
*/

#include<iostream>

using namespace std;
class Test{
public :
    int a;
public:
    Test(int a =0)
    {
        cout<<this<<": 输入构造函数!"<<a<<endl;
        Test::a=a;
    }
    Test(Test &temp)//定义拷贝构造函数
    {
        cout<<"载入拷贝构造函数!"<<endl;
        Test::a = temp.a;
    }
    ~Test()
    {
        cout<<this<<": "<<"载入析构函数!"<<this->a<<endl;
        cin.get();
    }
    operator int()//转换运算符
    {
        cout<<this<<": "<<"载入转换运算符函数!"<<this->a<<endl;
        return Test::a;
    }
};

int main()
{
    Test b(99);
    cout<<"b的内存地址 "<<&b<<endl;
    cout<<(int)b<<endl;//强转
    return 0;
}

以上内容部分是教材知识,部分是个人理解,愿听斧正~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值