[C++ Primer] 14.1-14.8 Overloaded operations and type conversion

14.1 Basic concepts

There is basically nothing to talk about in this section. Pay attention to whether the overloaded operator is chosen as a member or non-member function.

  • Assignment (=), subscript ([]), call (( )), and member access arrow (->) operator must be a member.

  • Compound assignment operators are usually members, but not required.

  • Operators that change the state of an object or operators that are closely related to a given type, such as increment, decrement, dereference, should usually be members.

  • Operators withsymmetrymayconvert the operands at either end, such asarithmetic, equality, relationaloperators, etc., should be non-members.

When we define an operator as a member function, its left-hand operand must be an object of the class to which the operator belongs.

14.2 Input and output operators

Overloaded input and output operatorsmust be non-member functions.

Because we usually call the output operator in the form of cout << item. If it is implemented with a member function, it means that the overloaded output operator should be cout which belongs to the class < A member of code>ostream is obviously unreasonable. We cannot modify ostream in the standard library.

If it must be implemented as a member function, it can only be a member of item. At this time, the calling form is item << cout, which is easy to cause confusion in reference.

It is usually necessary to read and write private data members of the class, which are generally declared as friends.

14.2.1 Overloaded output operators

Usually, the first parameter of an output operator is a reference to a non-constostream object. Non-const: writing to a stream changes its state; reference: ostream objects cannot be copied. The second formal parameter is usually a constant reference. Consistent with other output operators, operator<< returns its ostream formal parameters.

ostream & amp;operator(ostream & amp;os, const Sales_data & amp;item) // The ostream object cannot be copied and can only return a reference.
{<!-- --> // Returning a reference also facilitates continuous output?
    os << item.isbn() << " " << item.units_sold << " "
        << item.revenue << " " << item.avg_price();
    return os;
}
14.2.2 Overloading input operators

Unlike the overloaded output operator, the second parameter of the input operator is usually a non-const reference because the data is to be read into this object.

istream & amp;operator(istream & amp;is, Sales_data & amp;item)
{<!-- -->
    ... // read data
    if(is) // Check whether the input is successful
        item.revenue = item.units_sold * price;
    else
        item = Sales_data(); // Input failed: object is assigned default constructed state
    return is;
}

14.3 Arithmetic and relational operators

Typically, arithmetic and relational operators are defined as non-member functions to allow conversion of the left-hand or right-hand operand. Generally, there is no need to change the state of the object, and the formal parameters are all constant references. Arithmetic operators usually evaluate two operands and return the result in a temporary object.

If an arithmetic operator is defined, a corresponding compound assignment operator is generally defined. The most efficient way at this time is to define arithmetic operators using compound assignment.

Sales_data operator + (const Sales_data & amp;lhs, const Sales_data & amp;rhs)
{<!-- -->
    Sales_data sum = lhs;
    lhs + = rhs;
    return sum;
}
14.3.1 Equality operator

As long as one of the equality operator and the inequality operator is actually defined, the other one can be called directly.

bool operator==(const Sales_data & amp;lhs, const Sales_data & amp;rhs)
{<!-- -->
    return lhs.isbn() == rhs.isbn() & amp; & amp;
           lhs.units_sold == rhs.units_sold & amp; & amp;
           lhs.revenue == rhs.revenue;
}

bool operator!=operator(const Sales_data & amp;lhs, const Sales_data & amp;rhs)
{<!-- -->
return !(rhs == lhs);
}
14.3.2 Relational operators

There's nothing to say, more or less than that.

14.4 Assignment Operator

The assignment operator must be defined as a member function of the class. This is usually recommended for compound assignment operators, both of which must return a reference to the left operand.

14.5 Subscript operator

Must be a member function. The subscript operator usually defines two versions, one that returns a normal reference, and one that operates on a constant object and returns a constant reference.

14.6 Increment and decrement operators

It is usually recommended to define it as a member function of the class.

Prefix

The prepended operator should return a reference to the incremented or decremented object.

Posted

The postfix operator should return the original value of the object, returning a value rather than a reference. (Cannot return a reference to a local object)

Adding int to the parameter can be used to distinguish postfix and prefix.

14.7 Member access operators

It doesn’t feel important, I’ll look back and record it later.

14.8 Function call operator

The function call operator must be a member function. If a class defines a call operator, an object of that class is called a function object.

Function objects can contain data members that are used to customize the operations in the call operator.
Function objects are often used as actual parameters of generic algorithms.

class PrintString
{<!-- -->
public:
    PrintString(ostream & amp;o = cout, char c = ''): os(o), sep(c) {<!-- --> }
    void operator()(const string & amp;s) const {<!-- --> os << s << sep; }
private:
    ostream &os;
    char sep;
}

for_each(vec.begin(), vec.end(), PrintString(cerr, '\\
'));
14.8.1 lambda is a function object

In for_each(vec.begin(), vec.end(), PrintString(cerr, '\
'))
, the compiler translates the lambda expression into anunknown Unnamed object of a named classContains an overloaded function call operator in the class generated by the lambda expression.

By default a lambda cannot change the variables it captures. Therefore, by default, the function call operator in the class generated by lambda is a **const member function**. If the lambda is declared mutable, the call operator is not const.

Class representing lambda and corresponding capture behavior
  • When a lambda expression captures a variable by reference, the compiler can use the variable directly without copying it to the class generated by the lambda and storing it as a data member.

  • Variables captured by value will be copied to lambda. Corresponding data members must be established in the class generated by lambda and initialized through the constructor.

14.8.2 Function objects defined by the standard library

Use standard library function objects in algorithms:

sort(vec.begin(), vec.end(), less<int>());

Sort vec in ascending order. The third actual parameter is an unnamed object of type less. When sort When comparing elements, the given less function object will be called.

14.8.3 Callable objects and functions

Callable Objects (Callable Objects) in C++ include: functions, function pointers, lambda expressions, function objects, bind-generated objects, and function-generated objects.

Like other objects, callable objects also have their own types. For example, lambda has its own unique class type. The types of functions and function pointers are determined by the return value and parameter types, and so on. However, callable objects of different types may share the same calling form, which specifies the return type and parameter type.

Standard library function type: function is a template. When creating a specific function type, you need to give the calling form of the object.

function<int (int, int)> f1 = add;
function<int (int, int)> f2 = divide(); // Object of function object class
function<int (int, int)> f3 = [](int i, int j) {<!-- --> return i * j; }; // lambda expression

Overloaded functions and functions: You cannot directly store the name of an overloaded function into an object of function type.