美文网首页
运算符重载

运算符重载

作者: 漫游之光 | 来源:发表于2019-04-29 15:58 被阅读0次

运算符重载允许把标准运算符(如+、—、*、/、<、>等)应用于自定义数据类型的对象。直观自然,可以提高程序的可读性,体现了C++的可扩充性。运算符重载仅仅只是语法上的方便,它是另一种函数调用的方式。运算符重载,本质上是函数重载,所以不要滥用重载、因为它只是语法上的方便,所以只有在涉及的代码更容易写、尤其是更易读时才有必要重载。

运算符重载有两种方式,一种是重载为成员函数,函数原型如下。

返回值类型 operator 运算符(参数表);

还可以重载为友元函数的方式,函数原型如下。

friend 返回值类型 operator 运算符(参数表);

可重载的运算符有一元运算符和二元运算符,一元运算符有一个参数,二元运算符有两个参数,对应于函数调用的参数。重载为成员函数时,this指针默认为第一个参数,而重载为友元时需要显示表示所有参数。请看下面这个例子。

#include <iostream>
using namespace std;

class Integer{
    friend ostream& operator<<(ostream& out, const Integer& i);
public:
    Integer(int val):val_(val){}
    Integer& operator++(){
        val_++;
        return *this;
    }
    Integer operator++(int i){
        Integer temp(val_);
        val_++;
        return temp;
    }
    operator int(){
        return val_;
    }
private:
    int val_;
};

ostream& operator<<(ostream& out, const Integer& i){
    out<<i.val_;
    return out;
}

int main(int argc, char const *argv[])
{
    Integer i(2);
    cout<<i++<<endl; /* cout<<i.operator++()<<endl; */
    cout<<++i<<endl; /* cout<<i.operator++(0)<<endl; */
    int a = 1 + i;
    cout<<a<<endl;
    system("pause");
    return 0;
}

这个例子想说明的是,第一,可以手动调用重载的运算符;第二,因为++,--运算符分为前置和后置,所以在重载运算符的时候,后置的++,--需要增加一个int参数,这个参数没什么用,只是为了区分是前置还是后置。并且,可以看出,由于后置的++,--为了满足语义需要创建一个临时对象,所以用前置++,--的效率要高一点。

此外,运算符重载有以下的注意点。

  • 运算符重载不允许发明新的运算符。
  • 不能改变运算符操作对象的个数。
  • 运算符被重载后,其优先级和结合性不会改变。
  • 以下运算符不能重载:::(作用域解析运算符),?:( 条件运算符),.(直接成员访问运算符),.*(类成员指针引用的运算符 ),sizeof(sizeof运算符 )。
  • 一般情况下,单目运算符最好重载为类的成员函数;双目运算符则最好重载为类的友元函数。
  • 以下一些双目运算符不能重载为类的友元函数:=、()、[]、->。
  • 类型转换运算符只能以成员函数方式重载,没有参数,不能指定返回类型,函数原型如下。
operator 类型名();
  • 流运算符只能以友元的方式重载。

运算符重载例子,String类的简单实现

String类需要重载的运算符为,=运算符,[]运算符重载,+运算符重载,+=运算符重载,<<运算符重载, > >运算符重载。下面是具体的代码。

#include <iostream>
#include <exception>
#include <string.h>
using namespace std;

class String{
    friend ostream& operator<<(ostream& os, const String& s);
    friend istream& operator>>(istream& is, const String& s);
    friend String operator+(const String& s1, const String& s2);
public:
    String(const char *str = ""){
        str_ = new char[strlen(str) + 1];
        strcpy(str_,str);
    }
    ~String(){
        delete[] str_;
    }

    char *str_;

    String(const String& other){
        delete[] str_;
        str_ = new char[strlen(other.str_)+1];
        strcpy(str_,other.str_);
    }

    String& operator=(const String& other){
        if(this != &other){
            delete[] str_;
            str_ = new char[strlen(other.str_) + 1];
            strcpy(str_,other.str_);
        }
        return *this;
    }

    String& operator=(const char *s){
        delete[] str_;
        str_ = new char[strlen(s) + 1];
        strcpy(str_,s);
        return *this;
    }

    String& operator+=(const String& other){
        char *str = new char[strlen(str_) + strlen(other.str_) + 1];
        strcpy(str,str_);
        strcat(str,other.str_);
        delete[] str_;
        str_ = str;
        return *this;
    }

    const char& operator[](int index) const{
        if(index >= strlen(str_)){
            throw out_of_range("index is out of range");
        }
        return str_[index];
    }

    char& operator[](int index){
        const char& c = (static_cast<const String&>(*this))[index];
        return const_cast<char &>(c);
    }

    
};

ostream& operator<<(ostream& os, const String& s){
    os<<s.str_;
    return os;
}

istream& operator>>(istream& is, String& s){
    char temp[1024];
    is>>temp;
    s = temp;
    return is;
}

String operator+(const String& s1, const String& s2){
    char * str = new char[strlen(s1.str_) + strlen(s2.str_) + 1];
    strcpy(str,s1.str_);
    strcat(str,s2.str_);
    return String(str);
}

int main(int argc, char const *argv[])
{
    String s1,s2;
    cin>>s1>>s2;
    s1 += s2;
    for(int i=0;i<strlen(s1.str_);i++){
        cout<<s1[i];
    }
    cout<<endl;
    String s3 = s1 + "!!!";
    cout<<s3;
    system("pause");
    return 0;
}

重载->运算符

这个运算符的重载有点奇怪,先看看下面的代码。

#include <iostream>
using namespace std;

class DB{
public:
    DB(){cout<<"DB()"<<endl;}
    ~DB(){cout<<"~DB()"<<endl;}
    void open(){cout<<"open()"<<endl;}
    void close(){cout<<"close()"<<endl;}
    void query(){cout<<"query()"<<endl;}
};

class DBHelper{
public:
    DBHelper(){
        db_ = new DB();
    }
    ~DBHelper(){
        delete db_;
    }
    DB* operator->(){
        return db_;
    }
private:
    DB* db_;
};

int main(int argc, char const *argv[])
{
    
    DBHelper db;
    db->open();
    db->close();
    return 0;
}

虽然->运算符返回了一个DB指针,但是它的后面可以直接跟上一个函数调用,这让我感觉有点奇怪。

重载operator new、operator delete

先看下面一段代码。

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

int main(int argc, char const *argv[])
{
    char buf[1024];
    string *str = new(buf) string("hello");
    cout<<&buf<<" "<<str<<endl;
    str->~basic_string();
    return 0;
}

一个new操作符执行的操作是,operator new + 调用构造函数。operator new的主要作用是分配内存,这个操作符是可以重载的,并且有几种形式。上面的这种用法是placement new,不分配内存,而是指定一个内存。从上面的代码可以看出,在这种情况下,需要手动调用析构函数,不然构造函数中分配的内存无法释放。operator new和operator delete可以重载为局部的,也可以重载为全局的。我们可以通过重载一个全局的operator new和delete来实现一个简单的内存追踪器。内存跟踪器的大体思路是,我们重载全局的operator new和delete,自己实现内存的分配,然后使用一个全局的map,每次有内存分配,就添加一条记录;每次有释放内存,就去掉对应的记录。这个有点像模板设计模式,通过自定义一些功能来实现一些额外的功能。下面是具体的代码。

#include <map>
#include <iostream>

class Tracer{
    friend class Lock;
public:
    bool ready_;
    Tracer():lockCount_(0),ready_(true){}
    ~Tracer(){
        ready_ = false;
        dump();
    }
    void add(void *p,const char *file, unsigned int line){
        if(lockCount_ > 0){
            return;
        }
        Tracer::Lock lock(*this);
        entryMap_.insert(std::make_pair(p,Entry(file,line)));
    }

    void remove(void *p){
        if(lockCount_ > 0){
            return;
        }
        Tracer::Lock lock(*this);
        std::map<void*,Entry> :: const_iterator it = entryMap_.find(p);
        if(it != entryMap_.end()){
            entryMap_.erase(it);
        }
        
    }

    void dump(){
        if(entryMap_.size() > 0){
            std::cout << "-----------------------Memory leak(s)-----------------------" << std::endl;
            std::map<void*,Entry>::const_iterator it;
            for(it = entryMap_.begin();it != entryMap_.end(); ++it){
                std::cout << "0x" << std::hex << it->first << ":"
                    << it->second.file_ << "  line: " << std::dec << it->second.line_ << std::endl;
            }
            std::cout<<std::endl;
        }
    }
private:
    class  Entry{
    public:
        const char *file_;
        unsigned int line_;
        Entry(const char *file = NULL, unsigned int line = 0):file_(file),line_(line){}
    };

    class Lock{
    private:
        Tracer& tracer_;
    public:
        Lock(Tracer& tracer):tracer_(tracer){
            tracer_.lock();
        }
        ~Lock(){
            tracer_.unlock();
        }
    };
    unsigned int lockCount_;
    std::map<void*,Entry> entryMap_;
    void lock(){
        lockCount_++;
    }
    void unlock(){
        lockCount_--;
    }
};

Tracer gTracer;

void* operator new(size_t size, const char * file, unsigned int line){
    if(size == 0){
        size = 1;
    }
    void *p = malloc(size);
    if(gTracer.ready_ )
        gTracer.add(p,file,line);
    return p;
}
void operator delete(void *p, const char*, unsigned int line){
    if(gTracer.ready_ )
        gTracer.remove(p);
    free(p);
}

void* operator new(size_t size){
    if(size == 0){
        size = 1;
    }
    void *p = malloc(size);
    if(gTracer.ready_ )
        gTracer.add(p,"?",0);
    return p;
}

void operator delete(void *p){
    if(gTracer.ready_ )
        gTracer.remove(p);
    free(p);
}

void* operator new[](size_t size){
    if(size == 0){
        size = 1;
    }
    void *p = malloc(size);
    if(gTracer.ready_ )
        gTracer.add(p,"?",0);
    return p;
}

void operator delete[](void *p){
    if(gTracer.ready_ )
        gTracer.remove(p);
    free(p);
}

void* operator new[](size_t size, const char * file, unsigned int line){
    if(size == 0){
        size = 1;
    }
    void *p = malloc(size);
    if(gTracer.ready_ )
        gTracer.add(p,file,line);
    return p;
}

void operator delete[](void *p, const char * file, unsigned int line){
    if(gTracer.ready_ )
        gTracer.remove(p);
    free(p);
}

#define new new(__FILE__,__LINE__)

在构造函数中调用构造函数

class A{
public:
    A(int val):val_(val){
        cout<<val<<endl;
    }
    A(){
        new(this) A(10); //如果使用A(10)只会在栈上创建一个临时对象
    }
    int val_;
};

相关文章

  • 运算符重载及其他约定

    7.1 重载算术运算符 重载二元算术运算符 重载复合赋值运算符 重载一元运算符 重载比较运算符 等号运算符:‘eq...

  • C++ 部分运算符重载

    可重载的运算符 不可重载的运算符和符号 重载运算符为类的成员函数 重载运算符为友元函数 重载赋值运算符 重载流插入...

  • 2019-07-11 运算符的重载

    运算符的重载 重载运算符 (“::”,“.*”,“.”,“?:”)不能被重载。 重载运算符时,不能改变其本质,如不...

  • 运算符重载

    一.+号运算符重载 二.<<号运算符重载 三.指针运算符重载(智能指针)

  • C++运算符重载-下篇 (Boolan)

    C++运算符重载-下篇 (Boolan) 本章内容:1. 运算符重载的概述2. 重载算术运算符3. 重载按位运算符...

  • C++运算符重载-上篇 (Boolan)

    C++运算符重载-上篇 (Boolan) 本章内容:1. 运算符重载的概述2. 重载算术运算符3. 重载按位运算符...

  • C++重载

    重载 C++语言规定: 重载的运算符要保持原运算符的意义。只能对已有的运算符重载,不能增加新的运算符。重载的运算符...

  • C++运算符重载详解

    运算符重载规则 1.被重载的运算符必须是已经存在的C++运算符,不能重载自己创建的运算符; 2.运算符被重载之后,...

  • 第十一章 使用类

    运算符重载 运算符重载是一种形式的C++多态。运算符重载将重载的概念扩展到运算符上,允许赋予C++运算符多种含义。...

  • 4.0 C++远征:重载运算符

    重载运算符 [TOC] 四、重载运算符 ​ 概念 : 给原有运算符赋予新功能。 ​ 本质 : 函数重载。 ...

网友评论

      本文标题:运算符重载

      本文链接:https://www.haomeiwen.com/subject/bxnpwqtx.html