C + + learning notes

catalogue

I What is operator overloading

II How operators are overloaded

1. Friend overload

2. Class member function overloading

III Special operator overloading

1. Stream operator

2. Self increasing and self decreasing operator overloading and text overloading

(1) Self increasing and self decreasing

(2) Text overload

(3) Implicit conversion of class

IV Operator overload case

1. Handwriting dynamic array

2. Pack it

3. Other heavy loads

I What is operator overloading

Operator overloading is to give operators the function of operating user-defined type data. It is essentially a function call.

Composition of operator overloaded function:

Function return value: determined by the value completed after operation. Function name: operator plus the operator you want to overload. Parameters: determined by the operands of the operator and the overloading method.

II How operators are overloaded

1. Friend overload

The number of function parameters is the operand of the operator. Let's take the implementation of "+" as an example:

class Complex
{
public:
	Complex(int a,int b):a(a),b(b){}
	void print()
	{
		cout << a << " " << b << endl;
	}
	friend Complex operator+(Complex one, Complex two)
	{
		return Complex(one.a + two.a, one.b + two.b);
	}
protected:
	int a;
	int b;
};
int main()
{
	Complex a(1, 1);
	Complex b(2, 0);
	Complex c = a + b;
	c.print();
}

Note that "c = a + b" we see is an implicit call of operator overloading, and the explicit call is as follows:

Complex c = operator+(a, b);

In fact, it is no different from ordinary functions.

2. Class member function overloading

The operator overloading is realized by the way of class member function, and the number of parameters is equal to the operator operand - 1. Because the class member function is essentially something in the class and needs to be called with an object, there is naturally one less parameter. Let's take the implementation of ">" as an example:

bool operator>(Complex object)
{
	if (this->a > object.a) return true;
	else if (this->a == object.a && this->b > object.b) return true;
	else return false;
}

In this way, you can compare objects.

III Special operator overloading

1. Stream operator

cin type: object of istream class.

cout type: object of ostream class.

Stream operators: < <, > >.

friend void operator>>(istream& in, A& a)
{
	in >> a.name >> a.age;
}
friend void operator<<(ostream& out, A& a)
{
	out << a.name << '\t' << a.age << endl;
}

According to the above description, we can implement the input and output of class A defined by us, but this implementation is limited. Because the returned type is void, we can't do continuous input and output. How can we optimize this?

class A
{
public:
	A(string name = "", int age = 10) :name(name), age(age) {}

	friend istream& operator>>(istream& in, A& a)
	{
		in >> a.name >> a.age;
		cout << endl;
		return in;
	}
	friend ostream& operator<<(ostream& out, A& a)
	{
		out << a.name << '\t' << a.age << endl;
		return out;
	}
protected:
	string name;
	int age;
};
int main()
{
	A a("Blue ", 18);
	A b("Little green", 19);
	A c;
	A d;
	cin >> c >> d;
	cout << a << b << c << d;
}

As shown in the figure, when we modify the return value to the reference types of istream and ostream, we can realize continuous input and output.

2. Self increasing and self decreasing operator overloading and text overloading

(1) Self increasing and self decreasing

The overload of self increasing and self decreasing operator needs to solve the problem of pre and post, and a useless parameter needs to be added to indicate that the current operator is a post operation.

A operator++(int)
{
	return A(name, age++);
}	
A operator++()
{
	return A(name, ++age);
}

Let's write some test cases:

class A
{
public:
	A(string name = "", int age = 10) :name(name), age(age) {}
	friend ostream& operator<<(ostream& out, A& a)
	{
		out << a.name << '\t' << a.age << endl;
		return out;
	}
	A operator++(int)
	{
		return A(name, age++);
	}
	A operator++()
	{
		return A(name, ++age);
	}
protected:
	string name;
	int age;
};
int main()
{
	A a("Blue ", 18);
	A object = a++;
	cout << object << a;
	object = ++a;
	cout << object << a;
}

It can be seen that we have realized the self increment and self subtraction of user-defined types.

(2) Text overload

Fixed return value type:

unsigned long long

Fixed function name:

operator""

Fixed parameter type:

unsigned long long

Generally, text overloading can be used when multiple unit conversions are required. An example of conversion time is given below:

unsigned long long operator"" _h(unsigned long long num)
{
	return num * 60 * 60;
}
unsigned long long operator"" _min(unsigned long long num)
{
	return num * 60;
}
unsigned long long operator"" _s(unsigned long long num)
{
	return num;
}
int main()
{
	int second = 1_h + 10_min + 20_s;
	cout << second << "s";
}

(3) Implicit conversion of class

When defining a new object, there is no way to assign it directly to a basic data type -- > 1 Overloaded operator 2 Implicit conversion

class A
{
public:
	A(string name ,int age):name(name),age(age){}
	//Implicit conversion of objects of class  
	operator int()       
	{
		return age;       
	}
protected:
	string name;
	int age;
};
 
int main() 
{
 
	A a("a", 18);
	int age= a;    
	cout << age<< endl;
	return 0;
}

IV Operator overload case

1. Handwriting dynamic array

Step 1: design basic elements

class vector
{
public:
protected:
	int size;      //Number of current elements
	int* base;     //storage space 
	int capacity;  //capacity
};

Step 2: write constructor and destructor

vector(int capacity) :capacity(capacity)
{
	if (capacity > 0)
	{
		base = new int[capacity];
		this->capacity = capacity;
		this->size = 0;
	}
}
~vector()
{
	if (base)
	{
		delete[] base;
		base = nullptr;
	}
}

Step 3: write two magic oil functions

bool empty() const
{
	return size == 0;
}
int getsize()const
{
	return size;
}

Step 4: implement subscript access

int& operator[](int index)
{
	if (index >= 0 && index < capacity)
	{
		return base[index];
	}
}

Step 5: then realize dynamic array growth

void push_back(int data)
{
	if (size == capacity)
	{
		int* newbase = new int[capacity * 2];
		this->capacity *= capacity;
		for (int i = 0; i < size; i++)
		{
			newbase[i] = base[i];
		}
		delete[] base;
		base = newbase;
	}
	base[size++] = data;
}

Finally, write some debugging code and try it!

int main()
{
	vector a(3);
	for (int i = 0; i < 5; i++)
	{
		a.push_back(i);
		cout << a[i] << " ";
	}
	cout << endl << a.getsize();
}

As you can see, the result is the same as we want!

2. Packing

It's relatively simple here. Paste the code directly, and the comments have been added to the code.

class Int
{
public:
	Int(int num) :num(num) {}
	int& data() 
	{
		return num;                    //get data
	}
	string tostr() 
	{
		return to_string(num);         //Convert int to string type
	}
	//Arithmetic operator overloading
	Int operator+(const Int& value)    //+Method of overloading member functions in object addition classes
	{
		return Int(this->num + value.num);
	}
	//Friend overloading: operand = = number of parameters of overloaded function
	friend Int operator-(const Int& one, const Int& two) 
	{		
		return Int(one.num - two.num);
	}
	Int operator+=(const int& value)  //Original value + = value of type int
	{
		return Int(this->num + value);
	}
	Int operator+=(const Int& value)  //The original value + = an object forms an overload | the parameter types are inconsistent   
	{
		return Int(this->num + value.num);
	}
 
	Int operator++(int) 
	{
		return Int(this->num++);
	}
	Int operator++() 
	{
		return Int(++this->num);
	}  
    //Bitwise operator overloading
	Int operator&(const Int& value) 
	{
		return Int(this->num & value.num);
	}
	bool operator!() 
	{
		return !this->num;
	}
    //minus sign
	Int operator-() 
	{
		return Int(-this->num);
	}
    //Stream overload
	friend ostream& operator<<(ostream& out, const Int& object) 
	{
		out << object.num << endl;
		return out;
	}
	friend  istream& operator>>(istream& in, Int& object) 
	{
		in >> object.num;
		return in;
	}
    //Address character overload
	int* operator&() 
	{
		return &this->num;  //You don't need to return an object, just return an address of type int *
	}
    //Relational operator overloading
	bool operator>(const Int& object) 
	{
		return this->num > object.num;
	}
 
protected:
	int num;    //Data or int type
};

3. Other heavy loads

Reload ():

class Function
{
	typedef void(*PF)();
public:
	Function(PF pf) :pf(pf){}
	void operator()()
	{
		pf();
	}
protected:
	PF pf;
};
void print()
{
	cout << "Reload successful";
}
int main()
{
	Function f(print);
	f();
}

Overload - >:

class A
{
public:
	A(string name,int age):name(name),age(age){}
	void print()
	{
		cout << name << " " << age << endl;
	}
protected:
	string name;
	int age;
};
class Auto_ptr
{
public:
	Auto_ptr(int* ptr):ptr(ptr){}
	Auto_ptr(A* pa):pa(pa){}
	int& operator*()
	{
		return *ptr;
	}
	A* operator->()
	{
		return pa;
	}
	~Auto_ptr()
	{
		if (ptr)
		{
			delete ptr;
			ptr = nullptr;
		}
		if (pa)
		{
			delete pa;
			pa = nullptr;
		}
	}
protected:
	int* ptr;
	A* pa;
};
int main()
{
	Auto_ptr ptr(new int(18));
	cout << *ptr << endl;
	Auto_ptr ptra(new A("Blue ", 20));
	ptra->print();
}

Well, that's all for today's study!

Keywords: C++ Back-end

Added by xinnex on Fri, 25 Feb 2022 15:06:01 +0200