Chapter 14 overloaded operators and type conversion
14.1 basic concepts
A unary operator has one argument
Binary operator: the left operand is passed to the first parameter, and the right operand is the second parameter
Except overloaded function call operator(), it cannot contain default arguments
If the operator function is a member function, the first operand is implicitly the this pointer
When an operator acts on an operand of a built-in type, the meaning of the operator cannot be changed: that is, the + operator of type int cannot be redefined
You can only overload existing operators, not invent them
Operators that can be overloaded:
Operators that cannot be overloaded:
If the class contains arithmetic operators or bitwise operators, it is better to also provide the corresponding compound assignment operator, because the + = operator may be used
Judge whether to define an operator as a member / nonmember function:
- Assignment =, subscript [], call (), member access arrow - > Must be a member function
- Coincidence operators are generally members, but they are not required
- Operators that change the state of an object or are closely related to a given type, such as + +, -, dereference * is usually a member function
- Symmetric operators may convert the operation objects at either end, such as arithmetic, equality, relationship and bit operations. Usually, they are non member functions, that is, the member function can exchange the operation positions of the two at will. The first parameter of the member function is this by default
14.2 input and output operators
The output operator should print a newline character. It is usually only responsible for printing the content, not the output format
The output function must be a nonmember function
The input operator must handle possible input failures, and the output operator is not required
The input operator needs to handle input errors
14.3 arithmetic and relational operators
14.3.1 equality operator
If = =, you should also define! =, On the contrary, It's basically based on using like
14.3.2 relational operators
The definition of < > should meet the actual needs, and the actual situation of member variables in the class should be considered comprehensively
14.4 assignment operator
The assignment operator must be a member function and return an lvalue
Note the assignment similar to = {} in vector
StrVec &StrVec::operator=(initializer_list<string> il){ auto data = alloc_n_copy(il.begin(),il.end()); free(); elements = data.first; first_free = cap = data.second; return *this; }
14.5 subscript operator
The subscript operator must be a member function, and there are usually two versions of the return value: normal reference and constant reference
14.6 increment and decrement operators
As member function
Definition of pre version: Note the order of checking pointers in the increment and decrement definitions
StrBlobPtr& operator++(); StrBlobPtr& operator--(); StrBlobPtr& StrBlobPtr::operator++() { check(xxxx); // Check whether curr has pointed to the trailing position, otherwise an exception will be thrown ++curr; return *this; } StrBlobPtr& StrBlobPtr::operator--() { --curr; // If curr is 0, further decrement will result in an invalid subscript check(xxxx); return *this; }
Definition of the Post version. The Post version needs to provide a useless int parameter to distinguish the pre version. The return value is a value rather than a reference
// The formal parameter int provided here has no meaning. It is only used to distinguish the version. The function is a post version StrBlobPtr operator++(int); StrBlobPtr operator--(int); // The int here has no name, so it doesn't even need a name because it can't be used StrBlobPtr& StrBlobPtr::operator++(int) { StrBlobPtr ret=*this; ++*this; return ret; } StrBlobPtr& StrBlobPtr::operator--(int) { StrBlobPtr ret = *this; --*this; return ret; }
Difference between pre version and Post version
- Pre return reference, post return value
- The validity needs to be verified before and not after
- There is no parameter in the front, and there is a useless parameter in the rear to distinguish the front or rear
14.7 member access operator
Dereference and arrow functions are member functions
string& operator*() const; string* operator->() const;
The arrow function is equivalent to (* p).mem and p.operator() - > MEM;
An overloaded arrow operator must return a pointer to a class or an object of a class that has customized the arrow operator
14.8 function call operator
struct absInt { int operator()(int val) const { return val < 0 ? -val : val; } }; int i = -32; absInt absObj; int ui = absObj(i);
It feels like the object adds a default function
It must be a member function. You can define multiple functions according to the parameters
14.8.1 lambda is a function object
14.8.2 function objects defined in the standard library
Class method representations of some operators < functional >:
For example:
plus<int> intAdd; int sum = intAdd(10,20); // sum = 30
One use:
Sort uses < by default to compare and then sort, You can use greater < T > () to change the default sort comparison operator of sort to >, thus changing the sort method
14.8.3 callable objects and function s
Use map to store functions with the same call form. The so-called functions with the same call form, such as:
int add(int, int) int mod(int, int) int divide(int, int)
Wait, No matter how the function is executed internally, its common feature is that it has the same parameter type, number and return value type when defined. This kind of function can use the unified call method int x(int, int). These functions can be put into a map
map<string, int(*)<int,int>> binmap; binmap.insert({"+", add}); // Add is a pointer to the add function
But how can you put a lambda in it? Thus, function type appears, which can be understood as function repackaging function
function<int(int, int)> fAdd = add; function<int<int, int>> fDivide = divide(); function<int<int, int>> fLambda = [](int i, int j) {return i*j;};
At this point, change the definition of the map to
map<string, function<int(int, int)>> exeMaps; exeMaps.insert({"+", add}); exeMaps.insert({"*", fLambda}); // call exeMaps["+"](10, 10); exeMaps["*"](2, 33);
That is, any function with the same calling method, including lambda expression, can be added to the map. The convenience of this method can be fully demonstrated when calling
How do overloaded functions use this method? Same function name when overloaded
int add(int a, int b) {return a+b;} ClassA add(const ClassA&, const Class&); map<string, function<int<int, int)>> exeMaps; exeMaps.insert({"*", add}); //It is not possible to determine which one add is pointing to
You can use function pointers:
int (*fAdd)(int, int) = add; // Because the definition indicates the parameter type and return value of the function, // Thus, it can be determined that add refers to the first function exeMaps.insert({"+", fAdd});
You can also wrap a layer of lambda to determine which add:
exeMaps.insert({"+", [](int a, int b){return add(a, b);}});
14.9 overloaded type conversion and operator
14.9.1 type conversion operator
It is responsible for converting the value of one class type to other types
operator type() const;
- Must be a member function of a class
- The return type cannot be declared. It is unnecessary. The target class has specified the type of the return value
- The formal parameter list must be empty The argument cannot be passed because the type converter is implicitly executed
- Usually const
A small example:
class SamllInt { public: SamllInt(int i = 0) : val(i) { if (i < 0 || i> 255) throw std::out_of_range("Invalid value"); } operator int() const { return val; } // Here is the type conversion operator private: std::size_t val; }; // use SmallInt s =3; s+3; // Implicit conversion occurs here, samllint - > int, and int() is called
Implicit type conversion operators need to be cautious, otherwise the operation result will be far from the expected result
class SmallInt{ public: SmallInt(int i = 0) : val(i) { if (i < 0 || i> 255) throw std::out_of_range("Invalid value"); } explicit operator int() const { return val; } // Here is the type conversion operator private: std::size_t val; }; // call SmallInt s = 3; s+3; // An error of type mismatch is thrown, and int() is not implicitly called for type conversion static_cast<int>(s) + 3; // Show call int() to perform type conversion
Where an explicit call is automatically called: when used as a condition:
- Condition part of if while do
- In the for conditional expression
- Logical non operator!, Logical or operator 𞓜, logical and operator&&
- Conditional operator?:
The type conversion to bool is usually used in the condition part, so operator bool is generally defined as explicit
14.9.2 avoid ambiguous type conversion
Two situations may lead to ambiguous conversion:
- A's constructor can convert B to a, and B defines the conversion constructor to a
- Multiple conversion rules are defined, and the designed type itself can be linked by other type conversions
You need to be especially careful with arithmetic types. Try not to build two or more The source / destination is a conversion of arithmetic type
In addition to the explicit conversion to bool type, we should avoid defining type conversion functions and limit those non explicit constructors as much as possible
// 14.51
Conversion matching order:
- Exact match
- Constant version
- Type promotion
- Arithmetic conversion or pointer conversion 14.51 arithmetic conversion used
- Class type conversion
void calc(string a) { cout << "int a" << endl; }; void calc(LongDouble l) { cout << "LongDouble l" << endl; } int main(int argc, char** argv) { //14.51 double dval = 1.0; calc(dval); // Class type conversion will be used and the result will be LongDouble l } // If void Calc (int a) Calc (dval) / / the arithmetic type conversion result will be int a // If void Calc (double d) {cout < < double d "< < endl;} Results Jiang Wei double d
14.9.3 function matching and overloading operators
The backyard function set of operators in the expression should include both member and nonmember functions
class SmallInt { friend SmallInt operator+(const SmallInt&, const SmallInt&); public: SmallInt(int i = 0) : val(i) { if (i < 0 || i> 255) throw std::out_of_range("Invalid value"); } explicit operator int() const { return val; } // Here is the type conversion operator private: std::size_t val; }; int main(int argc, char** argv) { SmallInt s1, s2; SmallInt s3 = s1 + s2; // Here, you can call SmallInt's+ int a = s3 + 0; // This is ambiguous. You can convert 0 to SmallInt or S3 to int }
If a class not only defines the type conversion whose conversion target is arithmetic type, but also provides overloaded operators, it will encounter the ambiguity between overloaded operators and built-in operators