关于C++成员函数和运算符的重载

来源:互联网 发布:下载中文ae软件 编辑:程序博客网 时间:2024/05/18 09:00

1.为啥要重载操作符:

通过重载操作符,程序员可以针对“类”类型的操作数定义不同的操作符版本。良好的操作符定义可以使class类型的使用想内置类型一样直观简洁,使用重定义的操作符而不是命名函数使得程序可以用表达式代替函数调用,使程序编写和阅读更容易~

2.哪些不能重载

::     .*      .     ?:    这些不能重载

3.需要注意的地方:

重载必须有一个class类型的操作数,短路求值失去,

重载操作符和内置操作符结合型相同,优先级操作数个数均相同

不要重载一些含有内置定义的操作符 & , && || 这些

·赋值(=)下标(【】)调用(())和成员访问操作符必须定义为成员

·对称的操作符一般定义为普通非成员函数

++ -- 一般设置为成员函数,因为可能改变对象状态

4.定义输入输出操作符

io操作符只能重载为非成员函数,否则做操作符只能是对象成员 用法变成了 object<<cin  不符合我们的习惯,经常把他们设置成为友元,因为可能触及私有变量。

输入必须加入文件结束和输入错误的错误处理

istream&
operator>>(istream& in, Sales_item& s)
{
     double price;
     in >> s.isbn >> s.units_sold >> price;
     // check that the inputs succeeded
     if (in)
        s.revenue = s.units_sold * price;
     else
        s = Sales_item(); // input failed: reset object to default state
     return in;
}

如果输入失败,调用默认构造函数恢复对象状态。

5.算术运算符和关系运算符

Sales_item& operator -=(const Sales_item& item){
    units_sold-=item.units_sold;
    revenue-=item.revenue;
    return *this;
}
Sales_item operator - (const Sales_item& lhs,const Sales_item& rhs){
    Sales_item res(lhs);
    res+=rhs;
    return res;
}

 

一般算术运算符设置为非成员函数,与内置运算符对应,选择返回value 而不是引用。赋值运算符重载为成员函数,并用它来实现算术运算符,这样算术运算符不用friend

相等运算符和不等运算符一般成对出现,且用一个实现另一个

关系运算符 == != > < 一般重载成非成员函数

6.赋值操作符

必须为成员函数(=号)

=和+=  -= 一般都需要返回左操作数的引用

Sales_item& operator = (string is){
    isbn = is;
    return *this;
}

6.下标操作符

必须为类成员函数   返回引用使其可以在赋值操作的任意一边

一般定义一种const 返回常量引用一种not const 引用

class Foo { public: int &operator[] (const size_t); const int &operator[] (const size_t) const; // other interface members private: vector<int> data; // other member data and private utility functions };

int& Foo::operator[] (const size_t index)
     {
         return data[index];  // no range checking on index
     }
     const int& Foo::operator[] (const size_t index) const
     {
         return data[index];  // no range checking on index
     }

pari<string,string>& operator[] (const  vector< pair<string,string>* > ::size_type index){
    return *wait_list.at(index);//使用at判断是否越界
}

6.成员访问操作符

-> 一般要求重载为成员运算符,*没有要求 ,但成员比较常见~~~

例子:auto-ptr~~~~

ScreenPtr 的用户将会传递一个指针,该指针指向动态分配的ScreenScreenPtr 类将拥有该指针,并安排在指向基础对象的最后一个ScreenPtr 消失时删除基础对象。另外,不用为 ScreenPtr 类定义默认构造函数。因此,我们知道一个 ScreenPtr 对象将总是指向一个 Screen 对象,不会有未绑定的 ScreenPtr,这一点与内置指针不同。应用程序可以使用ScreenPtr 对象而无须首先测试它是否指向一个 Screen 对象。

     // private class for use by ScreenPtr only 私有类,
     class ScrPtr {
         friend class ScreenPtr;
         Screen *sp;
         size_t use;
         ScrPtr(Screen *p): sp(p), use(1) { }
         ~ScrPtr() { delete sp; }
     };
       
     class ScreenPtr {
     public:
         //  no default constructor: ScreenPtrs must be bound to an object
         ScreenPtr(Screen *p): ptr(new ScrPtr(p)) { }
         //  copy members and increment the use count
         ScreenPtr(const ScreenPtr &orig):
            ptr(orig.ptr) { ++ptr->use; }
         ScreenPtr& operator=(const ScreenPtr&);
         //  if use count goes to zero, delete the ScrPtr object
         ~ScreenPtr() { if (--ptr->use == 0) delete ptr; }
     private:
         ScrPtr *ptr;    // points to use-counted ScrPtr class
     };

指针支持的基本操作有解引用操作和箭头操作。我们的类可以这样定义这些操作:

class ScreenPtr { public: // constructor and copy control members as before Screen &operator*() { return *ptr->sp; } Screen *operator->() { return ptr->sp; } const Screen &operator*() const { return *ptr->sp; } const Screen *operator->() const { return ptr->sp; } private: ScrPtr *ptr; // points to use-counted ScrPtr class };

解引用操作符是个一元操作符。在这个类中,解引用操作符定义为成员,因此没有显式形参,该操作符返回对 ScreenPtr 所指向的 Screen 的引用。

箭头操作符不接受显式形参。point->action();   等价于  (point->action)();

可以这样使用 ScreenPtr 对象访问 Screen 对象的成员:

ScreenPtr p(&myScreen); // copies the underlying Screen p->display(cout);

因为 p 是一个 ScreenPtr 对象,p->display 的含义与对 (p.operator->())->display 求值相同。对 p.operator->() 求值将调用 ScreenPtr 类的 operator->,它返回指向 Screen 对象的指针,该指针用于获取并运行 ScreenPtr 所指对象的 display 成员。

重载箭头操作符必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象。

6.自增自减操作符

一般重载为成员函数,为了与内置类型一致,前置操作符返回运算结果引用,后置操作符返回运算前的值,value not ref ,为了区分,后置操作符提供了一个实参0;

// prefix: return reference to incremented/decremented object
    CheckedPtr& CheckedPtr::operator++()
    {
        if (curr == end)
            throw out_of_range
                  ("increment past the end of CheckedPtr");
        ++curr;                // advance current state
        return *this;
    }

CheckedPtr CheckedPtr::operator++(int)
     {

         // no check needed here, the call to prefix increment will do the check
         CheckedPtr ret(*this);        // save current value
         ++*this;                     // advance one element, checking the increment 用前置实现它,不用判断出界了
         return ret;                   // return saved state
     }

显式调用:

CheckedPtr parr(ia, ia + size);        // iapoints to an array of ints
parr.operator++(0);                    // call postfix operator++
parr.operator++();                     // call prefix operator++

7 调用操作符和函数对象

struct absInt {
       int operator() (int val) {
           return val < 0 ? -val : val;
       }
   };

 

通过为类类型的对象提供一个实参表而使用调用操作符,所用的方式看起来像一个函数调用:

     int i = -42;
     absInt absObj;  // object that defines function call operator
     unsigned int ui = absObj(i);     // calls absInt::operator(int)

函数调用操作符必须声明为成员函数。一个类可以定义函数调用操作符的多个版本,由形参的数目或类型加以区别。

定义了调用操作符的类,其对象常称为函数对象,即它们是行为类似函数的对象。

函数:

// determine whether a length of a given word is 6 or more bool GT6(const string &s) { return s.size() >= 6; }

函数对象:

// determine whether a length of a given word is longer than a stored bound
     class GT_cls {
     public:
         GT_cls(size_t val = 0): bound(val) { }
         bool operator()(const string &s)
                            { return s.size() >= bound; }
     private:
         std::string::size_type bound;
     };

for (size_t i = 0; i != 11; ++i)
     cout << count_if(words.begin(), words.end(), GT(i))
          << " words " << i
          << " characters or longer" << endl;

函数对象的便捷性】

         plus<int> intAdd;         // function object that can add two int values
     negate<int> intNegate;   //  function object that can negate an int value
     // uses intAdd::operator(int, int) to add 10 and 20
     int sum = intAdd(10, 20);          // sum = 30
     // uses intNegate::operator(int) to generate -10 as second parameter
     // to intAdd::operator(int, int)
     sum = intAdd(10, intNegate(10));    // sum = 0

函数适配器:

banding器,它通过将一个操作数绑定到给定值而将二元函数对象转换为一元函数对象

求反器是一种函数适配器,它将谓词函数对象的真值求反。标准库定义了两个求反器:not1 和 not2 分别求反一元二元对象

8。实参匹配和转换(俺来看重载操作符的原因啊,,,)

转换操作符是一种特殊的类成员函数。它定义将类类型值转变为其他类型值的转换。转换操作符在类定义体内声明,在保留字 operator 之后跟着转换的目标类型:

转换函数采用如下通用形式:

operator type();

转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空。

虽然转换函数不能指定返回类型,但是每个转换函数必须显式返回一个指定类型的值。例如,operator int 返回一个 int 值;如果定义 operator Sales_item,它将返回一个 Sales_item 对象,诸如此类。

转换函数一般不应该改变被转换的对象。因此,转换操作符通常应定义为const 成员。

只要存在转换,编译器将在可以使用内置转换的地方自动调用它

  • In expressions:

    在表达式中:

    SmallInt si; double dval; si >= dval // si converted to int and then convert to double
  • In conditions:

    在条件中:

    if (si) // si converted to int and then convert to bool
  • When passing arguments to or returning values from a function:

    将实参传给函数或从函数返回值:

    int calc(int); SmallInt si; int i = calc(si); // convert si to int and call calc
  • As operands to overloaded operators:

    作为重载操作符的操作数:

    // convert si to int then call opeator<< on the int value cout << si << endl;
  • In an explicit cast:

    在显式类型转换中:

    int ival; SmallInt si = 3.541; // instruct compiler to cast si to int ival = static_cast<int>(si) + 3;

    类类型转换之后不能再跟另一个类类型转换。如果需要多个类类型转换,则代码将出错。(指的是不能连续两个自定义的类型转换,但是内置类型转换可以的)

 

 

  • 一、构造函数的重载

    构造函数可以重载,使得生成实例时非常方便。构造函数一般要对成员变量赋初值,有2种写法:

    关于C++成员函数和运算符的重载#include <iostream> #include <string> using namespace std; class stuff { string name; intage; public: stuff() { //这是写法一 cout << name << "---" << age << endl; name = "空"; age = 0;cout << name << "---" << age << endl; } stuff(string n, int a):name(n),age(a) //这是写法二 { cout << name << "---" << age << endl; } string getName() { return name; } intgetAge() { return age; } }; int main ( ) { stuff st2; stuff st1("小雅", 27);return 0; }

    写法一是在构造函数体中赋值,赋值前成员变量已经有了地址空间,尚未有值。写法二是一种特殊方法,是在成员变量分配空间的同时将参数的值赋给成员变量。虽然写法二用的人少,但明显优于写法一。

    事实上,如果将成员变量的定义改为常量,“const string name;”和“const int age;”,写法一将出错,而写法二仍然正确。

    二、运算符重载

    运算符重载对于普通函数和成员函数来说,格式稍有不同。

    //单目运算符 成员函数: 返回值类型 operator 运算符 () ; 普通函数: 返回值类型operator 运算符 (对象的类型) ; //双目运算符 成员函数: 返回值类型 operator 运算符(对象的类型) ; 普通函数: 返回值类型 operator 运算符 (对象的类型1, 对象的类型2) ;//函数调用 成员函数: 返回值类型 operator (任意的参数列) ; //数组元素 成员函数:返回值类型 operator[] (参数类型) ; //增1/减1运算符 成员函数: 返回值类型 operator运算符 (int) ; 普通函数: 返回值类型 operator 运算符 (对象的类型, int) ;
    关于C++成员函数和运算符的重载#include <iostream> #include <string> using namespace std; class stuff { string name; intage; public: stuff(string n, inta):name(n),age(a) { cout << name << "---" << age << endl; } string getName() { return name; } intgetAge() { return age; } void operator +(int x); //运算符重载的定义 void operator +(string s); //运算符重载的定义 }; void stuff::operator +(int x) //运算符重载的实装 { age = age + x; } void stuff::operator +(string s) //运算符重载的实装 { name = name + s; } int main ( ) { stuff st2("小雅", 27); st2 + 3; //+运算 st2 + ".诗经";//+运算 cout << st2.getName() << st2.getAge() << endl; return 0; }


     

    三、拷贝构造函数和赋值运算符

    本节内容较深,初学者请跳过。“拷贝构造函数”和“赋值运算符”都是将对象的值复制一份然后传给另一对象。这二个功能也是类本身就具有的,但有很多场合原封不动地复制给另外一个对象时反而会出错,例如在成员函数中有动态分配内存,或者参数指针指向外部某一地址时,就有可能出错。

    要避免这些错误,我们可以重载“=”运算符以及拷贝构造函数,将出错的因素排除。下例中为了演示,故意将赋值运算符重载函数中不复制“姓名”,而拷贝构造函数中固定“年龄”。

    关于C++成员函数和运算符的重载#include <iostream> #include <string> using namespace std; class stuff { string name; int age;public: stuff(string n, int a):name(n),age(a) { cout << "构造函数 " << name << age << endl; } string getName() { return name; } int getAge() {return age; } stuff& operator =(stuff& x); //赋值运算符重载 stuff(stuff& x):name(x.name),age(20) //拷贝构造函数重载 { cout << "拷贝构造函数 " << name << age << endl; } }; stuff& stuff::operator =(stuff& x) { age = x.age; cout << "赋值运算符 " << name << age << endl; return*this; } int main ( ) { stuff st("小雅", 25); //调用通常的构造函数 stuff st1("劝学网", 2); //调用通常的构造函数 st1 = st; //因为不产生新的实例,所以调用的是赋值运算符 stuff st2 = st; //因为产生新的实例,所以调用的是拷贝构造函数 cout << st.getName() << st.getAge() << endl; cout << st1.getName() << st1.getAge() << endl; cout << st2.getName() << st2.getAge() << endl; return 0; }


     

    四、类型转换

    当需要将当前类的实例直接赋值给其它类型的变量时自动转换类型,这其实还是运算符重载。当需要其它类型直接赋值给当前类的实例时,只要增加构造函数就行。

    关于C++成员函数和运算符的重载#include <iostream> #include <string> using namespace std; class stuff { string name; int age;public: stuff(string n, int a):name(n),age(a) { } string getName() { return name; } int getAge() {return age; } operator int() { //stuff → intreturn age; } operator string() { //stuff → string return name; } }; int main ( ) { stuff st("小雅", 25); string m_name = st; //stuff → string int m_age = st;//stuff → int cout << m_name << endl; cout << m_age << endl; return 0; }


    嘻嘻, 四处找来的

    C++中预定义的运算符的操作对象只能是基本数据类型。但实际上,对于许多用户自定义类型(例如类),也需要类似的运算操作。这时就必须在C++中重新定义这些运算符,赋予已有运算符新的功能,使它能够用于特定类型执行特定的操作。运算符重载的实质是函数重载,它提供了C++的可扩展性,也是C++最吸引人的特性之一。 运算符重载是通过创建运算符函数实现的,运算符函数定义了重载的运算符将要进行的操作。运算符函数的定义与其他函数的定义类似,惟一的区别是运算符函数的函数名是由关键字operator和其后要重载的运算符符号构成的。运算符函数定义的一般格式如下: <返回类型说明符> operator <运算符符号>(<参数表>) { <函数体> } 运算符重载时要遵循以下规则: (1) 除了类属关系运算符"."、成员指针运算符".*"、作用域运算符"::"、sizeof运算符和三目运算符"?:"以外,C++中的所有运算符都可以重载。 (2) 重载运算符限制在C++语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符。 (3) 运算符重载实质上是函数重载,因此编译程序对运算符重载的选择,遵循函数重载的选择原则。 (4) 重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符操作数的个数及语法结构。 (5) 运算符重载不能改变该运算符用于内部类型对象的含义。它只能和用户自定义类型的对象一起使用,或者用于用户自定义类型的对象和内部类型的对象混合使用时。 (6) 运算符重载是针对新类型数据的实际需要对原有运算符进行的适当的改造,重载的功能应当与原有功能相类似,避免没有目的地使用重载运算符。 运算符函数重载一般有两种形式:重载为类的成员函数和重载为类的非成员函数。非成员函数通常是友元。(可以把一个运算符作为一个非成员、非友元函数重载。但是,这样的运算符函数访问类的私有和保护成员时,必须使用类的公有接口中提供的设置数据和读取数据的函数,调用这些函数时会降低性能。可以内联这些函数以提高性能。) 成员函数运算符 运算符重载为类的成员函数的一般格式为: <函数类型> operator <运算符>(<参数表>) { <函数体> } 当运算符重载为类的成员函数时,函数的参数个数比原来的操作数要少一个(后置单目运算符除外),这是因为成员函数用this指针隐式地访问了类的一个对象,它充当了运算符函数最左边的操作数。因此: (1) 双目运算符重载为类的成员函数时,函数只显式说明一个参数,该形参是运算符的右操作数。 (2) 前置单目运算符重载为类的成员函数时,不需要显式说明参数,即函数没有形参。 (3) 后置单目运算符重载为类的成员函数时,函数要带有一个整型形参。 调用成员函数运算符的格式如下: <对象名>.operator <运算符>(<参数>) 它等价于 <对象名><运算符><参数> 例如:a+b等价于a.operator +(b)。一般情况下,我们采用运算符的习惯表达方式。 友元函数运算符 运算符重载为类的友元函数的一般格式为: friend <函数类型> operator <运算符>(<参数表>) { <函数体> } 当运算符重载为类的友元函数时,由于没有隐含的this指针,因此操作数的个数没有变化,所有的操作数都必须通过函数的形参进行传递,函数的参数与操作数自左至右一一对应。 调用友元函数运算符的格式如下: operator <运算符>(<参数1>,<参数2>) 它等价于 <参数1><运算符><参数2> 例如:a+b等价于operator +(a,b)。 两种重载形式的比较 在多数情况下,将运算符重载为类的成员函数和类的友元函数都是可以的。但成员函数运算符与友元函数运算符也具有各自的一些特点: (1) 一般情况下,单目运算符最好重载为类的成员函数;双目运算符则最好重载为类的友元函数。 (2) 以下一些双目运算符不能重载为类的友元函数:=、()、[]、->。 (3) 类型转换函数只能定义为一个类的成员函数而不能定义为类的友元函数。 (4) 若一个运算符的操作需要修改对象的状态,选择重载为成员函数较好。 (5) 若运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,则只能选用友元函数。 (6) 当运算符函数是一个成员函数时,最左边的操作数(或者只有最左边的操作数)必须是运算符类的一个类对象(或者是对该类对象的引用)。如果左边的操作数必须是一个不同类的对象,或者是一个内部类型的对象,该运算符函数必须作为一个友元函数来实现。 (7) 当需要重载运算符具有可交换性时,选择重载为友元函数。一个简单的运算符重载的类 #include <iostream>using namespace std;class CInt {public:CInt(int i) : m_i(i) {};virtual ~CInt(){};void operator<<(ostream& os, const CInt &i){os << '[' << i.m_i << ']';}public:CInt &operator++(){++(this->m_i);return *this;}const CInt operator++(int){CInt temp = *this;++(*this);return temp;}CInt &operator--(){--(this->m_i);return *this;}const CInt operator--(int){CInt temp = *this;--(*this);return temp;}int &operator*() const{return (int &)m_i;}private:int m_i;};//测试函数int main(int argc, char* argv[]){CInt I(5);cout << I++;cout << ++I;cout << I--;cout << --I;cout << *I;return 0;}
0 0