• 第14章 重载运算与类型转换


    • l  通常情况下,不应该重载逗号、取地址、逻辑或与运算符,这样可能会改变求值顺序、短路特性。对于逗号、取地址运算符,重载之后还会改变内置的含义。
    • l  运算符重载可以定义在类,是内成员函数,也可以是非成员函数。当做为成员函数定义时,this会默认成为第一个参数,绑定到左侧运算对象。
    • l  需要改变左侧运算对象状态、访问左侧运算对象内容,必须定义成成员函数。
    • l  具有对称性的运算、可能发生转换任意一端的运算,一般应该定义成非成员

    14.2输入输出运算符

    • l  输入输出运算符必须是非成员函数,这样第一个参数才会是运算符左侧的stream
    • l  输入输出运算符的第一个参数应该是stream的非const引用(写入或读取会改变stream状态),在函数最后返回(实现链式调用)。
    • l  通常需要读写类的private成员,所以一般声明为友元函数
    • l  输入运算符需要处理可能的错误,而输出运算符一般不需要
    class ClassName
    {
        friend ostream& operator<<(ostream&, ClassName&);
        friend istream& operator>>(istream&, ClassName&);
    private:
        string name;
    };
    ostream& operator<<(ostream& os, ClassName& obj)
    {
        os << obj.name;
        return os;
    }
    istream& operator>>(istream& is, ClassName& obj)
    {
        is >> obj.name;
        //处理输入错误
        //设置foilbit、eofbit、badbit
        return is;
    }

    14.3算术关系运算符

    • l  算术运算符一般不需要改变运算对象内容,并且要允许左侧运算对象进行类型转换,所以一般都是非成员函数,形参为const引用
    • l  算术运算一般会得到新值,新值是局部变量,所以返回其副本作为结果(不能返回引用)
    • l  一般来讲会定义对应的复合赋值运算,可以使用之进行算术运算(用其中一个对象构造副本,对副本使用复合赋值运算符,返回副本)
    • l  相等运算符和不等运算符应该成对出现,并且使用其中一个进行比较就可以了。而且,如果定义了相等/不等运算符,标准库容器和算法也可以使用了。
    • l  关系运算符定义了顺序关系,应该与关系容器中对关键字的要求一致(参见有序容器的要求:严格弱化的”<”操作)
    • l  如果有==运算符,则关系运算应该与之保持一致,特别是当对象!=时,必定有less than的一个。
    Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs)
    {
        Sales_data sum = lhs;  // copy data members from lhs into sum
        sum += rhs;  // add rhs into sum
        return sum;
    }
    bool operator==(cons tSales_data &lhs, const Sales_data &rhs)
    {
        return lhs.isbn() == rhs.isbn() &&
            lhs.units_sold == rhs.units_sold &&
            lhs.revenue == rhs.revenue;
    }
    bool operator!=(const Sales_data &lhs, const Sales_data &rhs)
    {
        return!(lhs == rhs);
    }

    14.4赋值运算符

    在拷贝控制中讲解了赋值运算符的拷贝赋值和移动赋值,还有一种是使用花括号列表进行赋值。

    vector<string> vec;
    vec = { "123" ,"456","789" };

    对于之前编写的动态内存管理类,我们可以添加这个赋值特性

    //列表赋值运算符
    StrVec &operator= (std::initializer_list<std::string> ls)
    {
        auto data = alloc_n_copy(ls.begin(), ls.end());
        free();
        elements = data.first;
        first_free = cap = data.second;
        return *this;
    }

    14.5下标运算符

    下标运算符通常定义两个版本,一个是const版本,另一个是nonconst版本。

    下标运算符必须是成员函数。

    //下标运算符
    std::string&operator[](std::size_t n)
    {
        return elements[n];
    }
    const std::string& operator[](std::size_t n) const
    {
        return elements[n];
    }

    14.6自增自减运算符

    There are both prefix and postfix versions. These operators usually should be defined as members.

    1. 对于迭代器自增自减的时候,需要检查边界。
    2. 前置返回值为引用,后置返回值为值。
    3. 后置多了一个int参数,但是没有使用,在执行的时候使用了前置的函数。
    class StrBlobPtr {
    public:
        // increment and decrement
        StrBlobPtr&operator++();  // prefix operators
        StrBlobPtr&operator--();
    
        StrBlobPtr operator++(int);  // postfix operators
        StrBlobPtr operator--(int);
        // other members as before
    };
    // prefix:return a reference to the incremented/decremented object
    StrBlobPtr& StrBlobPtr::operator++()
    {
        // if curr already points past the end of the container, can't increment it
        check(curr, "increment past end of StrBlobPtr");
        ++curr;  // advance the current state
        return*this;
    }
    StrBlobPtr& StrBlobPtr::operator--()
    {
        // if curr is zero, decrementing it will yield an invalid subscript
        --curr;  // move the current state back one element
        check(-1, "decrement past begin of StrBlobPtr");
        return*this;
    }
    // postfix: increment/decrementthe object but return the unchanged value
    StrBlobPtr StrBlobPtr::operator++(int)
    {
        // no check needed here; the call to prefix increment will do the check
        StrBlobPtrret = *this;  // save the current value
        ++*this;  // advance one element; prefix ++ checks the increment
        returnret;  // return the saved state
    }
    StrBlobPtr StrBlobPtr::operator--(int)
    {
        // no check needed here; the call to prefix decrement will do the check
        StrBlobPtrret = *this;  // save the current value
        --*this;    // movebackward one element; prefix -- checks the
        decrement
            returnret;  // return the saved state
    }

    14.7成员访问运算符

    迭代器类以及智能指针类中,拥有成员访问运算符,包括解引用运算符、箭头运算符。

    对于内置的指针类型,访问其成员我们使用(*ptr).mem,使用ptr->men与之等价,“ptr->”实际上就成为了“(*ptr).”。

    对于定义了->运算符的对象,我们使用*和->都将使用自定义的版本。

    1. operator*可以当做一个一般的函数进行调用。
    2. operator->在函数调用完成后有附加动作。如果他返回的结果是一个内置指针,则在函数调用完成之后,对返回的内置指针执行->操作;如果返回的结果本身含有重载的operator->,则继续执行返回对象的operator->调用过程。(所以,其返回值必须是一个内置指针或者重载了operator->操作的对象
    class Ptr
    {
        std::string& operator*() const
        {
            auto p = check(curr, "dereference past end");
            return(*p)[curr];  // (*p) is the vector to which this object points
        }
        std::string*operator->() const
        {// delegate the real work to the dereference operator
            return&this->operator*();
        }
    };

    14.8函数调用运算符

    Objects of classes that define the call operator are referred to as function objects. Such objects “act like functions” because we can call them.

    class absInt
    {
    public:
        int operator()(int integer)
        {
            return integer > 0 ? integer : -integer;
        }
    };
    int main()
    {
        absInt funObj;
        int b = funObj(-155);
    }

    14.8.1函数调用与lambda表达式

    lambda表达式实际上就是一个函数对象,捕获列表中的数据将会成为对象的成员。

    14.8.2标准库的函数对象

    Arithmetic算术

    plus<Type>

    minus<Type>

    multiplies<Type>

    divides<Type>

    modulus<Type>

    negate<Type>

    Relational关系

    equal_to<Type>

    note_equal_to<Type>

    greater<Type>

    greater_equal<Type>

    less<Type>

    less_equal<Type>

    Logical逻辑

    logical_amd<Type>

    logical_or<Type>

    logical_not<Type>

    默认情况下sort使用<进行排序,为降序。可以如下使用升序。

    #include<functional>
    sort(svec.begin(), svec.end(), greater<string>());

     

    14.8.3可调用对象与function

    调用形式(call signature):int(int,int)

    不同的类型,具有相同的调用形式

    //普通函数
    int add(int i, int j) { return i + j; }
    //lambda,其产生一个未命名的函数对象类
    auto add = [](int i, int j) { return i + j; };
    //函数对象类
    class add
    {
        int operator()(int i, int j) { return i + j; }
    };

     

    这种调用形式,可以用一种模板类型代替

    #include<functional>
    function<int(int, int)> fun;

     

    function不能识别重载函数

    //普通函数
    int add(int i, int j) { return i + j; }
    double add(int i, double j) { return i + j; }
    //这句话会出错
    function<int(int, int)> fun = add;

     

    函数指针可以识别重载

    //函数指针可以识别重载
    int(*f)(int, int) = add;
    function<int(int, int)> fun = f;

     

    14.9重载、类型转换与运算符

    转换构造函数(将其他类型转换成自己)

    构造函数只接受一个实参,实际上定义了此类型的隐式转换机制。当然,可以使用explicit声明阻止这个转换。

    隐式类型转换运算符(将自己转换成其他类型)

    operator type() const;

    1. 转换成可以作为函数返回值的类型(除了void、当然不包括数组、函数类型)
    2. 没有显式返回值(通常返回值是类型转换运算符自己),没有形参
    3. 一般不应该改变待转换对象的内容,所以定义成const
    class SmallInt
    {
    public:
        operator int()const { return val; }
    private:
        std::size_t val;
    };

     

    显式类型转换运算符(将自己转换成其他类型)

    explicit operator type() const;

    如果表达式用在条件语句,则会隐式执行

    1. if、else if、while、do、for的条件部分
    2. 逻辑!、||、&&的运算对象
    3. ?:的条件表达式
    class SmallInt
    {
    public:
        explicit operator int()const { return val; }
    private:
        std::size_t val;
    } 
    int main()
    {
        SmallInt si;
        int a = static_cast<int>(si);
    }

     

    转换为bool

    while(std::cin>>value)

    cin的>>运算符返回cin本身,因为在条件语句,隐式转换成bool类型。

    非条件语句中,如cin<<2,因为cin没有定义<<、如果cin可以隐式转换成bool,则可以将<<用作左移符号。但是非条件语句,不能隐式转换,所以这句话是错误的。

    14.9.2避免二义性的类型转换

    1. A有转换构造函数,将B转换成A。同时,B定义了转换运算符,可以将B转换成A类型。则在用到B转换为A的语句时,不能确定适用的函数。
    2. 所有算术类型的转换级别都是相同的,若A由转换构造函数,将int/double转换为A,则给定long long类型转换成A就会错误(long类型可能直接使用int转换,因为某些编译器中long和int是同一类型,如visual C++);同理,转换运算符也会有这个问题。
    3. 对于2,可以只定义一个算术类型的转换,如果使用时需要转换成其他算术类型,编译器会自动使用内置的转换。

    The easiest rule of all: With the exception of an explicitconversion to bool, avoid defining conversion functions and limit nonexplicit constructors to those that are “obviously right.”

    1. 若一组重载的函数接收A、B类型的参数,A、B类型都有接收int类型的转换构造函数,则int类型做参数调用这个函数的时候,会出现二义性。(这通常意味着A、B类设计存在不足)
    2. 如果重载函数接收C类型参数,C类型有接收double类型的转换构造函数,则仍会出现二义性,因为抵用额外标准类型转换后再调用自定义转换级别是相同的。

    14.9.3函数匹配与重载运算符

    当我们使用内置类型与类类型进行运算的时候,若有运算符重载和类定义的类型转换,则可能会发生二义性。

    A定义了参数为int的转换构造函数和转换为int的类型转换函数,并且有重载的运算符+,则A的对象a进行运算a+10就会产生二义性,无法确定是对a转换成int进行的内置加法,还是10转换成A的重载加法。

  • 相关阅读:
    编程模式
    第六章类(十九)readonly
    Javascript----实现鼠标背景效果(同时不影响其操作)
    Javascript----input事件实现动态监听textarea内容变化
    javascript----mouseover和mouseenter的区别
    Javascript----实现火箭按钮网页置顶
    Javascript----scroll事件进度条监听
    Javascript----生成省-市下拉表单
    Javascript----增删改查
    javascript-----轮播图插件
  • 原文地址:https://www.cnblogs.com/qiusuo/p/5106024.html
Copyright © 2020-2023  润新知