操作符重载

1.c++中运算符可以被重载
在c++中,几乎所有的运算符都可以被重载。


2.不可以被重载的运算符
有6个运算符是不能被重载的,它们分别是。
::
? :
.
.*
sizeof
#
##


3. 重载的格式
返回值 operator被重载的符号(参数)
{
    重载的内容;
}   

(1)如果重载是字母字符,例如new delete,那么operator与字幕字符之间至少留
    一个空格,其它的可以留也可以不留。
(2)运算符被从重载后,原有的功能依然存在。
(3)运算符重载不是发明新的运算符,也不能修改运算符的优先级和操作数的个数。


4. 重载举例
(1)对<进行重载举例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>

using namespace std;

class Student
{    
        string name;
        int age;

public:
        Student(const string name=" ", int age=0):name(name), age(age) {}
        bool operator<(const Student &stu) {   
                return (this->age < stu.get_age());                 
        }   
        int get_age() const { return age; }
};          

int main(void)
{
        Student stu1("zhangsan", 24);
        Student stu2("wangwu", 20);

        if(stu1 < stu2) {
                cout << "zhangsan' age < wangwu'age" << endl;
        } else {
                cout << "zhangsan' age > wangwu'age" << endl;
        }

        return 0;
}

运行结果:
zhangsan' age > wangwu'age  

例子分析:
在本例中,对<进行了重载,所谓重载就是写一个重载函数,对符号的含义进行重新定义
,比如这个例子中按照年龄进行比较对象的大小,如果没有对<进行重载的话,由于<
默认的含义是只能对基本类型的数据进行比较,那么比较的代码就应该这样写,

        if(stu1.get_age() < stu2.get_age()) {
                cout << "zhangsan' age < wangwu'age" << endl;
        } else {
                cout << "zhangsan' age > wangwu'age" << endl;
        }

显然可以很明显的看出这样写很繁琐,如果对象的成员要是在复杂点的话,这样写很不简洁,
如果要是能够直接写成stu1 < stu2 不就很直观吗,但问题是<只能对基本类型的数据
进行比较,不能直接对对象进行比较,因此我们就需要重载<符号,当我们按照stu1<stu2
进行比较时,它们能够自动的是使用年龄进行比较。

当<被重载后,执行stu1<stu2时,其实际的含义就是调用stu1的<重载函数,并将stu2
作为参数传递过去,也就是说stu1<stu2该成下面的调用形式完全是可以的。
    stu1.operator<(stu2);


需要注意一点的是,由于重载函数中的参数是const的,当该参数调用get_age()函数
时,get_age()函数必须是const的。


(2)重载时需要注意的一些问题     

(1)为了提高传参的效率,尽量传递引用或者指针
(2)使用引用或者指针时,如果不用修改内容的话,尽量使用const修饰
(3)尽量将运算符的重载写成内联函数形式,因为内联函数的效率会搞很多,所以很多时候
    都是将重载函数直接写在类里面,编译器会自动对齐进行甄别,需不需要编译成为内
    联函数。
(4)除非该类是某个友元类的私有类,可以将其设置为private,否者一般情况下,我们都将
    该类的重载函数需要声明为public。

(3)例子2
接着上面的例子,定义一个学生对象数组,找出年龄最大的学生。类的定义保持不变。
主函数内容改为如下内容。

int main(void)
{
        Student *stu[10];

        for(int i=0; i<sizeof(stu)/sizeof(Student *); i++) {
                char I = i+'0';
                stu[i] = new Student(name+I, i+20);
        }   

        int max = 0;
        for(int i=0; i<sizeof(stu)/sizeof(Student *); i++) {
                if(*stu[max] < *stu[i]) {
                        max = i;
                }        
        }

        cout << stu[max]->get_name() << "  ";
        cout << stu[max]->get_age() << endl;

        return 0;
}



        5. 全局重载函数
    前面的例子中,<的重载函数是Student的成员函数,但是实际上我们完全可以将重载
    函数定义为全局重载函数,只是相对于成员函数来说,
    (1)对于二元操作符来说,全局的重载函数需要传递两个参数
    (2)如果希望编译为内联函数,必须显式的声明为inline,否者编译器只会将其编
        译为普通函数。从前面知道,成员函数是不需要显式写出的,当然显示写出
        也不会错。

    如果将前面的<的重载函数重载为全局函数的格式如下,

        bool operator<(const Student &stu1, const Student &stu2) {
                return (stu1.get_age() < stu2.get_age());   
        }   

    调用的时候,stu1<stu2与operator<(*stu[max], *stu[i])是完全等价的。

    那么什么时候会用到全局的符号重载呢?这个在后面将会讲到。
    6.重载运算符全部功能
    (1)什么是运算符的全功能
    以上面重载<运算符为例来说,其完整的功能应该有如下:
    (1)可以对基本类型数值进行比较:12.5 < 32,这个是<原本就有的功能,无需重载
    (2)左操作数和右操作数都是对象:stu1 < stu2,这个需要重载是实现
    (3)左操作数为对象,右操作数为数值:stu < 34.0,这个需要重载是实现
    (4)左操作数为数值,右操作数为对象:43 < stu,这个也需要重载实现

(2)重载时选择成员函数实现呢?还是选择外部函数实现呢?
对于左操作数为对象时,选择成员函数或者外函数实现都可以,之所以可以选择成员函
数,是因为左操作数为对象时,可以直接调用成员函数。
以stu < 34.0为例来说,等价于stu.operator<(34.0);

反过来,如果左操作数是数值右操作数是对象时,必须使用外部函数重载,
43 < stu,如果将其实现为成员函数重载时,就等价于
43.operator<(stu),这样的写法完全错误的。


总结起来就是,如果左操作数是对象,重载函数可以是成员函数,也可以是外部函数,
但是如果左操作数是数值,如果需要重载的话,必须使用外部函数进行重载。


(3)<全功重载例子
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>

using namespace std;

class Student
{
        string name;
        int age;

public:
        Student(const string name=" ", int age=0):name(name), age(age) {  }
        int get_age() const { return age; }

        bool operator<(const Student &stu) {   
                return (this->age < stu.get_age());     
        }   

        bool operator<(double age) {   
                return (this->age < age);     
        }   

        string get_name() const { return name; }
};      

bool operator<(double age, const Student &stu) {
        return (age < stu.get_age());
}

int main(void)
{
        Student stu1("aaa", 20);
        Student stu2("bbb", 15);

        if(stu1 < stu2) {
                printf("stu1 < stu2\n");
        } else {
                printf("stu1 > stu2\n");
        }

        if(stu1 < 34) {
                printf("stu1 < 34\n");
        } else {
                printf("stu1 > 34\n");
        }

        if(25 < stu2) {
                printf("25 < stu2\n");
        } else {
                printf("25 > stu2\n");
        }

        return 0;
}

例子分析:

在本例子中实现了<号的全部比较功能。


7. 重载小总结
(1)二元操作数的重载
(1)如果左操作数是对象,使用成员函数或者全局函数进行重载都可以
    (1)如果成员函数是对二元操作符进行的重载,只需要一个传参,这个传参
        就是二元操作符的右操作数

(2)如果左操作数不是对象而需要被重载的话,只能使用全局函数重载
    (1)成员函数对二元操作符进行的重载时,必须传递两个参数,即运算符的左
        操作数和右操作数。

(3)二元操作数的重载函数的返回值
    (1)如果是关系运算或者逻辑运算的话,返回值为bool型
    (2)如果是算数运算,比如*和+时候,返回值视具体情况而定       

(2)一元操作符的重载
(1)一元操作符只有一个操作数
(2)如果操作的数值是基本类型没有必要重载,因为一元操作符本来就支持
    基本类型数据的操作。
(3)如果操作数是对象时,使用成员函数和全局函数重载都可以。
    (1)使用成员函数重载时,除了++和--外,都不需要传递参数
    (2)如果是全局函数重载时,将一元操作符的操作数传递过去
    (3)返回值的类型视情况而定


8. 赋值运算符=的重载
说到赋值运算符,实际上除了=之外,还有+=/*=/-=等等,这些都可以被重载,这里
就以=为例讲解赋值运算符在重载的过程中遇到的一些问题。

=实际上有两个方面的功能,一个是初始化,一个是赋值,比如:
    Student stu1;
    Student stu2 = stu1;//等价于Student stu2(stu1);


这就是初始化。

    Student stu1;
    Student stu2;
    stu2 = stu1;
这就是赋值。

这里讲的对=的重载,指的是对=的赋值功能的重载。


(1)默认=重载函数
如果我们自己不给类重载一个赋值运算符的话,编译器会自动的提供一个默认的
operator=()。

编译器提供的默认的=重载运算符对于一些普通的对象赋值来说是没有问题的,
比如还是以Student类为例。

例子:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <iostream>
    #include <string>
    #include <cassert>
    #include <errno.h>

    using namespace std;

    class Student
    {    
    public:
            string name;
            int age;

            Student(const string name=" ", int age=0):name(name), age(age) {}
    };          

    int main(void)
    {
            Student stu1("aaa", 20);
            Student stu2;

            stu2 = stu1;


            cout << "stu1的信息"<<stu1.name <<" "<<stu1.age<<endl;
            cout << "stu1的信息"<<stu2.name <<" "<<stu2.age<<endl;

        cout<<"\n修改了stu2的信息后"<<endl;
            stu2.name="www";
            stu2.age = 25;
            cout << "stu1的信息"<<stu1.name <<" "<<stu1.age<<endl;
            cout << "stu1的信息"<<stu2.name <<" "<<stu2.age<<endl;

            return 0;
    }

    运行结果:
        stu1的信息aaa 20
        stu1的信息aaa 20

        修改了stu2的信息后
        stu1的信息aaa 20
        stu1的信息www 25


    例子分析:
    例子中,将stu1的内容通过=赋值给stu2,在没有修改stu2之前,stu1和stu2
    的内容是一致的,当stu2的内容修改后,它们的信息不同了,说明stu2空间
    与stu1是完全分离的,说明这个默认的=的重载函数起作用了。

    如果将这个默认的=的重载函数显式的写出的话,应该是这样的。

            Student &operator=(const Student &stu) {
                    this->name = stu.name;
                    this->age = stu.age;
                    return *this;
            }   
    当然打印的结果是一致的。

(2)编译器提供的默认的=重载函数存在的问题
(1)=重载函数存在的问题
    实际上默认的=重载函数是存在很大问题的,那就是如果类中包含动态分配
    内存的成员的话,对于这样的成员=复制的只是指向动态内存的地址,这会
    导致两个对象的动态内存成员空间是共享的,这其实就是浅复制和深复制的
    问题。

(2)副本构造函数与=重载函数区别
    上面提到=重载函数的问题时,估计都会联想到副本构造函数,因为副本构造
    函数在面对动态分配内存的成员时,会出现深拷贝和浅拷贝的问题,这一点
    和=重载函数的浅复制和深赋值的问题的根本原理的是一样的。

    但是副本构造函数与=重载函数总是有区别的吧,那么它们的区别是什么。
    如果我们不弄清它们的区别,我们非常容易将他们进行混淆。

    (1)副本构造函数会被调用的情况
        (1)使用一个对象直接去初始化另一个对象时,例如:
            Student stu1("aaa", 20);

            Student stu2(stu1);
            或者
            Student stu2 = stu1;
            以上两种情况是等价的。

        (2)传参时,间接实现一个对象直接去初始化另一个对象时
            void show(const Student stu) {
                ......  
            }

            Student stu("aaa", 20);
            show(stu)

            注意:如果函数直接传递的是引用的话,是不会掉用副本
            构造函数的,当然=重载函数更不会调用了。


    (2)=重载函数被调用的情况
        Student stu1("aaa", 20);
            Student stu2;

            stu2 = stu1;    

        因为stu2 = stu1;执行的是赋值操作,因此会调用=重载函数。
(2)例子   
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <iostream>
    #include <string>
    #include <cassert>
    #include <errno.h>

    using namespace std;
    class Student
    {
    public:
            string name;
            int age;

            Student(const string name=" ", int age=0):name(name), age(age) {}

            Student &operator=(const Student &stu) {
                    printf("=重载函数\n");
                    this->name = stu.name;
                    this->age = stu.age;
                    return *this;
            }
            Student(Student &stu) {
                    printf("副本构造函数\n");
                    this->name = stu.name;
                    this->age = stu.age;
            }
    };
    void show(const Student stu) {

    }

    int main(void)
    {
            Student stu1("aaa", 20);
            Student stu2=stu1;//调用副本构造函数

            cout<<endl;
            show(stu2);//调用副本构造函数   

            cout<<endl;
            stu2 = stu1;//调用=重载函数

            return 0;
    }



(3)一种特殊情举例
    任然上面的例子为例,但是将=重载函数的参数从引用改为对象值传递:
        Student &operator=(const Student stu) {
        内容不变
        }

    main函数的内容改为:            
    int main(void)
    {
            Student stu1("aaa", 20);
            Student stu2;
            stu2 = stu1;

            return 0;
    }

    运行结果:
        副本构造函数
        =重载函数


    例子分析:
    这个例子的打印结果很是让人迷惑,因为main函数的如下语句,
            Student stu1("aaa", 20);
            Student stu2;
            stu2 = stu1;
    似乎很明显,只是进行了赋值操作,但是为什么即调用了副本构造函数,也调用
    了=重载函数呢?

    其实原因处在=重载函数的形参写法上,
        Student &operator=(const Student stu) {
            ......
        }
    注意,形参写的不是引用,所以当使用=进行复制操作而调用=重载函数时,首先
    会调用副本构造函数使用实参初始化形参stu,因此这里即调用了副本构造函数
    也调用了=的重载函数,这样的结果一点也不奇怪。


(3)显式的写=重载函数,实现深复制
前面说过,如果类中包含动态分配的成员时,默认的=重载函数已经不够用了,这个时候
我们需要自己重载=,还是以Student类为例,给它定义一个需要动态分配的Birthday成员。

例子:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <iostream>
    #include <string>
    #include <cassert>
    #include <errno.h>

    using namespace std;

    class Birthday
    {
    public:
            string year;
            string month;
            string day;
            Birthday(const string year="0", const string month="0", const string day="0")
                    :year(year), month(month), day(day) { } 
    };

    class Student
    {
    public:
            string name;
            Birthday *brhtday;

            Student(const string name=" ", const string year=" ", const string month=" ", const string day=" ") 
                    :name(name), brhtday(new Birthday(year, month, day)) {}
    };

    int main(void)
    {       
            Student stu1("zhangsan", "2003", "12", "1");
            Student stu2;

            stu2 = stu1;

            cout<<"stu1的信息:"<<stu1.name<<" "<<stu1.brhtday->year<<" "<<
                    stu1.brhtday->month<<" "<<stu1.brhtday->day<<" "<<endl;
            cout<<"stu2的信息:"<<stu2.name<<" "<<stu2.brhtday->year<<" "
                    <<stu2.brhtday->month<<" "<<stu2.brhtday->day<<" "<<endl;

            cout<<"\n修改stu2的生日信息后"<<endl;
            stu1.brhtday->year="2009";
            stu1.brhtday->month="9";
            stu1.brhtday->day="2";
            cout<<"stu1的信息:"<<stu1.name<<" "<<stu1.brhtday->year<<" "<<
                    stu1.brhtday->month<<" "<<stu1.brhtday->day<<" "<<endl;
            cout<<"stu2的信息:"<<stu2.name<<" "<<stu2.brhtday->year<<" "
                    <<stu2.brhtday->month<<" "<<stu2.brhtday->day<<" "<<endl;

            return 0;
    }


运行结果:
    stu1的信息:zhangsan 2003 12 1 
    stu2的信息:zhangsan 2003 12 1 

    修改stu2的生日信息后
    stu1的信息:zhangsan 2009 9 2 
    stu2的信息:zhangsan 2009 9 2 

例子分析:
从打印的结果可以看出,stu2的生日修改后,stu1的生日也修改了,原因是它们各自
的brthday成员都指向了同一个动态分配空间,因为=进行复制时,只复制了地址,也
就是说只进行了浅复制。

如果要改进这个问题,我们只需要添加自定义=的重载函数,其实现如下:

        Student &operator=(const Student &stu) {
                if(this == &stu) return *this;

                this->name = stu.name;
                this->brhtday = new Birthday();
                this->brhtday->year = stu.brhtday->year;
                this->brhtday->month = stu.brhtday->month;
                this->brhtday->day = stu.brhtday->day;

                return *this;
        }  

重新运行的结果:
    =重载函数
    stu1的信息:zhangsan 2003 12 1 
    stu2的信息:        

    修改stu2的生日信息后
    stu1的信息:zhangsan 2009 9 2 
    stu2的信息:   

分析:
显然,这个结果已经表明,自定义的=重载函数已经实现了深复制。

之所以能够实现深拷贝的原因是因为,在=重载函数中,对this指向的对象中的brthday
成员开辟了新的空间,然后再将被赋值对象的birthday指向空间的内容复制过来,如此
便实现了深拷贝。


(4)自定义=重载函数两个疑惑
(1)疑惑1:
    为什么要在=重载函数中加如下这句话。
        if(this == &stu) return *this;

    这句话主要是为防止如下这样的赋值语句,
        stu1 = stu1;
    这条赋值语句实际上没什么实际意义,但是它的确是能够通过编译,如果出现
    这种情况时,显然只需要直接return *this;返回即可。


(2)疑惑2:
    为什么返回*this。

    很多时候会出现连续赋值的情况,比如:
    stu3=stu2=stu1; 

    将这话改为它的等价形式:
    stu3.operator=(stu2.operator=(stu1));

    显然从上面连续赋值中看出,operator=()的返回值在这种情况下会成为其它
    operator=()的参数,而operator=()参数要求是一个引用,因此需要返回*this。
9. 算数运算符重载
(1)+重载的例子

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>

using namespace std;
class Box
{
public:
        int length;
        int witdh;
        int height;

        Box(int length=1, int witdh=1, int height=1): length(length), witdh(witdh), height(height) { }

        Box& operator=(const Box &box) {
                if(this == &box) return *this;
                this->length = box.length;
                this->witdh = box.witdh;
                this->height = box.height;
                return *this;
        }   

        Box operator+(const Box &box) const {
                Box tmpbox((this->length+box.length), (this->witdh+box.witdh), (this->height+box.height));
                return tmpbox;   
        }   
        /*这么写是错误的
        Box & operator+(const Box &box) const {
                Box tmpbox((this->length+box.length), (this->witdh+box.witdh), (this->height+box.height));
                return tmpbox;   
        } */  
};

void show(const Box &box) {
        cout<<"length:"<<box.length<<"  witdh:"<<box.witdh<<"  height:"<<box.height<<endl;
        cout<<"体积="<<box.length*box.witdh*box.height<<endl;
}

int main(void)
{
        Box box1 = Box(12, 33, 45);
        Box box2 = Box(43, 23, 14);

        Box box3 = box1+box2;

        show(box3);

        return 0;
}

运行结果:
length:55  witdh:56  height:59
体积=181720

例子分析:
例子中,定义了两个盒子box1和box2,然后将两个盒子对象相加,因此需要重载+。

(2)+号重载需要注意的地方
上面重载+的例子中,可以发现,+的重载需要注意如下问题。
(1)为了提高效率,最好传递引用,并指定为const(因为右操作数并不会被修改)
(2)由于+的左操作数也不会被修改,因此我们将operator+()函数也定义为const
(3)对于其返回值为返回动态空间或者返回引用的方式,都不是很可取。
    返回动态分配空间的地址,不利于管理内存。而box因为是自动局部对象
    又不能返回其引用,如果将+重载函数中的box定义为staitc的话,是可以返回
    其引用的。(能不能返回局部的变量,完全看变量生存期,只要分析这个就可以。)
(4)因为+运算符返回值涉及赋值操作,准确的说是初始化,用一个对象初始化另一个独象,所以
   返回函数内的临时对象调用的是复制构造函数。并不是等号复制运算。


(3)+=的重载
如果重载了+=,并可以使用+=快速重载+。+=比较特殊,它含有算数运算符的功能,同时
又含有赋值运算符的功能,这一点需要注意,正是由于它含有赋值运算符的功能,因此
重载函数需要像重载"="时一样返回*this。

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <iostream>
    #include <string>
    #include <cassert>
    #include <errno.h>

    using namespace std;
    class Box
    {
    public:
            int length;
            int witdh;
            int height;

            Box(int length=1, int witdh=1, int height=1): length(length), witdh(witdh), height(height) { }

            Box &operator+=(const Box &box) {
                    this->length += box.length;
                    this->witdh += box.length;
                    this->height += box.height;
                    return *this;
            }   

            Box operator+(const Box &box) {
                    return Box(*this)+=box;
            }               
    };

    void show(const Box &box) {
            cout<<"length:"<<box.length<<"  witdh:"<<box.witdh<<"  height:"<<box.height<<endl;
            cout<<"体积="<<box.length*box.witdh*box.height<<endl;
    }

    int main(void)
    {
            Box box1 = Box(12, 33, 45);
            Box box2 = Box(43, 23, 14);
            Box box3(box2);

            Box box4=box1+=box2+=box3;

            Box box5 = box4+box1;

            show(box5);

            return 0;
    }

    例子分析:
    本例子中重载了+=,然后借助于+=的重载函数,快速实现了+的重载。

    对于main函数中Box box4=box1+=box2+=box3;语句需要注意,第一个=不能
    改为+=,因为第一个是初始化而不是赋值,换句话说Box box4=box1+=box2+=box3;
    等价于如下形式:
    Box box4(box1+=box2+=box3);
    box4还没有初始化不能参加+=运算。

(4)-/-=,/=等算数符号的重载
这些符号的重载与+/+=完全类似,请自己完成。

10. 下标运算符的重载

(1)[]与左值和右值

上一章链表的例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>
using namespace std;
class Studata
{
public:
        int num;
        string name;

        Studata(int num=0, const string name=" "):num(num), name(name) {}
        int get_num() const{ return num; }
        string get_name() const{ return name; }
        void set_num(int num) { this->num = num; }
        void set_name(string name) { this->name = name; }
};

class Stunode
{
        Stunode *prev;
        Stunode *next;
        Studata *studata;

public:
        Stunode(int num=0, const string name=" ", Stunode *prev=NULL, \
                Stunode *next=NULL):studata(new Studata(num, name)) {}
        Stunode(const Studata &studata) {
                this->studata = new Studata();
                this->studata->set_num(studata.get_num());
                this->studata->set_name(studata.get_name());
                prev = next = NULL;
        }
        ~Stunode() {
                delete studata;
        }

        /*获取器*/
        Stunode *get_prev() const {return prev;}
        Stunode *get_next() const {return next;}
        Studata *get_studata() const {return studata;}
        /*设置器*/
        void set_prev(Stunode *stunode) {this->prev = stunode;}
        void set_next(Stunode *stunode) {this->next = stunode;}
        void set_studata(Studata *studata) {this->studata = studata;}
};

class Dlist
{
        Stunode *hp;
        Stunode *current;
        Stunode *tmp;

public:
        Dlist() {
                hp = current = new Stunode();//空头节点 
                hp->set_prev(hp);
                hp->set_prev(hp);
                tmp = NULL;
        }

                void err_fun(string filename, int line, string funname, int err_no) {
                cerr<<filename<<" "<<line<<" "<<funname<<" fail"<<":"<<strerror(err_no)<<endl;
                exit(-1);
        }

        void init_Dlist(string filename);
        void insert_tail();
        void insert_head();
        void display();
        Studata &operator[](int i);
};

Studata &Dlist::operator[](int index)
{
        if(index < 0) {
                cout<<"非法下标" << endl;
                return *(hp->get_studata());
        }

        int i=0;
        for(current=hp->get_next(); ; current=current->get_next(), i++) {
                if(current == hp) {
                        cout << "无此元素" << endl;
                        return *(hp->get_studata());
                }
                else if(i == index) return *(current->get_studata());
        }
}

void Dlist::display()
{
        for(current=hp->get_next(); hp!=current; current=current->get_next()) {
                cout<<current->get_studata()->get_num()<<"\t"<<current->get_studata()->get_name()<<endl;
        }
}

void Dlist::insert_tail()
{
        hp->get_prev()->set_next(tmp);
        tmp->set_prev(hp->get_prev());

        hp->set_prev(tmp);
        tmp->set_next(hp);
}

using namespace std;
int main(void)
{
        Studata data;
        Dlist dlist;

        dlist.init_Dlist("./stu.txt");

        data = dlist[2];
        cout << data.get_num()<<"\t"<<data.get_name() <<endl;

        Studata data1(12, "qqqq");
        dlist[1] = data1;
        data = dlist[10];
        cout << data.get_num()<<"\t"<<data.get_name() <<endl;

        return 0;
}


例子分析:
为了让[]既能够做左值又能够做右值,需要将[]重载函数的返回值改为引用。[]重载
函数具体实现如下:
    Studata &Dlist::operator[](int index)
    {
            if(index < 0) {
             cout<<"非法下标" << endl;
            return current->studatanul;
        }
            int i=0;
            for(current=hp->get_next(); ; current=current->get_next(), i++) {
                    if(current == hp) {
                            cout << "无此元素" << endl;
                            return current->studatanul;
                    }
                    else if(i == index) return *(current->get_studata());
            }
    }
该重载函数先是判断下标的合法性,然后再去搜索链表,找到下标指定的链表节点,
但是由于返回值是引用,引用的空间不能为空,但是当下标非法或者没有找到该下标
对应的链表节点时,这个时候是没有有效节点的数据空间的,所以这个时候就将头节点
无效数据空间返回。

由于返回值是引用,因此在主函数中,[]既能作为左值也能作为右值。


(2)const与[]重载函数
有些时候我们是不允许对Studata的对象做修改的,但是有的时候又允许做修改,那
什么时候会遇到这种情况呢?如果定义的是一个const的Dlist,那么就不允许[]重载
函数作为左值,而且从前面的学习我们知道,const的对象只能调用const的函数。

如果我们希望这两种情况都能存在,那怎么办呢?
我们可以重载一个const的版本的[]重载函数,还是以上例为例,const版本的[]重载
函数为:
    const Studata &Dlist::operator[](int index) const
    {   
            if(index < 0) cout<<"非法下标" << endl;

            int i=0;
            for(current=hp->get_next(); ; current=current->get_next(), i++) {
                    if(current == hp) {
                            cout << "无此元素" << endl;
                            return current->studatanul;
                    }
                    else if(i == index) return *(current->get_studata());
            }
    }

同时不要忘了在Dlist里面做如下声明,
    const Studata &operator[](int i) const;

我们看,这个const版本的[]重载函数,返回值是const的引用,因此不能作为左值,
同将函数也声明为了const。

当我们使用的是const的Dlist类对象时,会自动调用const版本的[]重载函数,如果
将其作为左值的话,将无法通过编译。

11. 重载递增++递减--
(1)++/--的两种情况
    ++和--运算符的重载需要分为两种,一种是前++/--,一种是后++/--,针对
    这两种不同的情况需要给出不同重载。

    (1)前++/--含义
        前++/--表示,先在给this对象做++/--运算,再将运算后的值返回,为了
        提高效率往往返回的是引用。

    (2)后++/--含义
        后++/--表示,先返回一个this对象的副本,然后在对this对象做运算,为了
        避免出现连续的+++++/----这种容易混淆的情况,我们要求都将++/--的重载
        函数返回值进行const限制。

(2)前++/--和后++/--的重载格式
    这里只给出++的范例,--与之同。
    (1)前++
        const Object &operator++() {
            ......
        }

    (2)后++
        const Object operator++(int) {
            ......
        }

        注意:这里的int仅仅是为了区分这两个重载函数,没有具体含义,c++
        对于后++重载的格式就是这么规定的,如果经int写为其它类型是无
        无法编译通过的。

(3)例子
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <iostream>
    #include <string>
    #include <cassert>
    #include <errno.h>

    using namespace std;
    class Box
    {
    public:
            int length;
            int witdh;
            int height;

            Box(int length=1, int witdh=1, int height=1): length(length), witdh(witdh), height(height) { }   

            const Box &operator++() {
                    ++this->length;
                    ++this->witdh;
                    ++this->height;
                    return *this;
            }   

            const Box operator++(int) {
                    Box box(*this);
                    this->length++;
                    this->witdh++;
                    this->height++;
                    return box;
            }
    };


    void show(const Box &box) {
            cout<<"length:"<<box.length<<"  witdh:"<<box.witdh<<"  height:"<<box.height<<endl;
            cout<<"体积="<<box.length*box.witdh*box.height<<endl;
    }


    int main(void)
    {
            Box box1 = Box(12, 33, 45);

            show(box1++);
            show(++box1);

            return 0;
    }

12. 重载类型转换
(1)类类型转换为其它类型
我们知道正常情况下要将类类型转换为另一种类型是不行的,比如下面的例子无法
通过编译,因为c++中不允许将类类型强制转换为其它类型。

但是我们可以通过重载类型转换来实现这样的要求,当然另一种类型可以是基本类
型,也可以是其它类类型。

例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>

using namespace std;
class Box 
{       
public:
        int length;
        int witdh;
        int height;

        Box(int length=1, int witdh=1, int height=1)
        : length(length), witdh(witdh), height(height) { }   
};   

int main(void)
{           
        Box box = Box(1, 1, 1); 

        int vol = box;
        //或者int vol = static_cast<int> (box);

        cout<<"体积:"<< vol << endl;

        return 0;
}

例子分析:
上面的例子是无法通过编译的,因为main函数中,试图将Box类型对象转换为int,
默认是不支持的。

(2)使用类型重载解决上例的问题
(1)类型重载的格式
    operator Type() {
        ......
    }
    格式中,没有表明返回值的类型,但是它默认的返回类型就是Type类型

(2)在例子中的Box类型中添加int型类型重载函数
    记得将函数放在public下面,具体函数如下:
        operator int() const {
            cout<< "类型重载函数" << endl;
            return (this->length*this->witdh*this->height);
        }
    然后编译代码运行即可:

    分析:
    当执行int vol = box;时, operator int()函数就会被调用,就会将
    计算得到的体积值返回给vol,当然
        int vol = box;
    也可以使用
        int vol = static_cast<int> (box);
    进行替换,这二者是等价的。
12. 智能指针(*与->重载)
智能指针并不是完全意义上的指针,它只是一个类对象,这个对象实现了对
指针的封装,用于向前和向后索引(比如索引链表)。

实现智能指针涉及到*和->的重载,迭代器就是智能指针,在实际开发中,我们
很少会去自己去写智能指针,因为对于提高开发效率和代码运行效率的帮助并不
是很大,为了使用效率,我们往往时直接使用c++提供的智能指针(迭代器)。

但是我们需要了解到底什么是智能指针。智能指针主要用于遍历。

例子:
stulist.cpp

#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <string>
#include <cassert>
#include <errno.h>

using namespace std;
class Studata
{
public:
        int num;
        string name;

        Studata(int num=0, const string name=" "):num(num), name(name) {}

        int get_num() const{ return num; }
        string get_name() const{ return name; }

        void set_num(int num) { this->num = num; }
        void set_name(string name) { this->name = name; }
};

class Stunode
{
        Studata *studata;
        Stunode *prev;
        Stunode *next;

public:
        Stunode(int num=0, const string name=" ", Stunode *prev=NULL, \
                Stunode *next=NULL):studata(new Studata(num, name)) {}
        Stunode(const Studata &studata) {
                this->studata = new Studata();
                this->studata->set_num(studata.get_num());
                this->studata->set_name(studata.get_name());
                prev = next = NULL;
        }
        ~Stunode() {
                delete studata;
        }
        /*获取器*/
        Stunode *get_prev() const {return prev;}
        Stunode *get_next() const {return next;}
        Studata *get_studata() const {return studata;}

        /*设置器*/
        void set_prev(Stunode *stunode) {this->prev = stunode;}
        void set_next(Stunode *stunode) {this->next = stunode;}
        void set_studata(Studata *studata) {this->studata = studata;}
};

class Dlist
{
        Stunode *hp;
        Stunode *current;
        Stunode *tmp;
public:
        Dlist() {
                hp = current = new Stunode();//空头节点 
                bzero(hp, sizeof(Stunode));
                hp->set_prev(hp);
                hp->set_prev(hp);
                tmp = NULL;
        }

        void err_fun(string filename, int line, string funname, int err_no) {
                cerr<<filename<<" "<<line<<" "<<funname<<" fail"<<":"<<strerror(err_no)<<endl;
                exit(-1);
        }

        Stunode *getfirsNode() {
                if(hp->get_next() == hp) return NULL;
                else return hp->get_next();
        }

        void init_Dlist(string filename);
        void insert_tail();
};

void Dlist::insert_tail()
{
        hp->get_prev()->set_next(tmp);
        tmp->set_prev(hp->get_prev());

        hp->set_prev(tmp);
        tmp->set_next(hp);
}
void Dlist::init_Dlist(string filename)
{
        FILE *fp = NULL;
        fp = fopen(filename.c_str(), "r");
        if(NULL == fp) err_fun(__FILE__, __LINE__, "fopen", errno);

        while(1)
        {
                int num;
                char name[40];
                fscanf(fp, "%d %s\n", &num, name);
                //printf("%d %s\n", num, name);
                tmp = new Stunode(num, name);
                insert_tail();
                if(1 == feof(fp)) break;
        }
}

/* 定义一个智能指针(迭代器) */
class Dlistiterator {
public:
        Dlistiterator(Dlist &dlist) {
                pStunode = dlist.getfirsNode();
        }
        Studata &operator*() {
                return *(pStunode->get_studata());
        }
        Studata *operator->() {
                return pStunode->get_studata();
        }
        Studata *operator++() {
                pStunode = pStunode->get_next();
                return pStunode->get_studata();
        }
        Studata *operator++(int) {
                Studata *tmpnode = pStunode->get_studata();
                pStunode = pStunode->get_next();
                return tmpnode;
        }
        operator bool () {
                return(pStunode->get_studata()!=0);
        }

private:
        Stunode *pStunode;
};

int main(void)
{
        Studata data;
        Dlist dlist;

        dlist.init_Dlist("./stu.txt");

        class Dlistiterator dliter = Dlistiterator(dlist);

        while(dliter) {
                cout << dliter->get_name() << "  "<<dliter->get_num() << endl;
                dliter++;
        }

        return 0;
}

stu.txt
1       aaa
4       fff
3       sss
5       www
2       vvv



13. 重载new和delete
没有必要,但是我们要知道这样是可以重载的,当你看到有地方重载它们时,不应该   
感到惊讶。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值