Learning records for C + + beginners 14

Keywords: C C++

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:

  1. Exact match
  2. Constant version
  3. Type promotion
  4. Arithmetic conversion or pointer conversion     14.51 arithmetic conversion used
  5. 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

Posted by eric_e on Fri, 03 Sep 2021 13:25:17 -0700