C + + classes and objects (medium)

               

catalogue

Class

Constructor

characteristic

Its characteristics are as follows

Compiler default generated constructor

Piecemeal knowledge

Parameterless constructor call details

     ​​​​

The following conflict scenarios may exist for all default and parameterless constructors

reflection

Destructor

characteristic

Compiler default generated destructor

copy constructor

features

The copy constructor should refer to the passed parameter

Compiler default generated copy constructor

Copy constructor details

Detail 1

Overload assignment operator

Operator overloading

be careful:

characteristic

Default generated assignment operator overload

Operator overload details

const member

const modifier class member function

reflection

Can const objects call non const member functions? may not

Can non const objects call const member functions? sure

Can other non const member functions be called in const member functions? may not

Can other const member functions be called in non const member functions? sure

Overload of address fetching and const address fetching operators

Application scenario

Class

If a class has no members, it is referred to as an empty class. Is there nothing in the empty class? No, any class will automatically generate the following six default member functions without writing.

class Date {};

Constructor

The constructor is a special member function with the same name as the class name. When creating a class type object, it is automatically called by the compiler to ensure that each data member has an appropriate initial value and is called only once in the life cycle of the object.

characteristic

Constructors are special member functions. It should be noted that although the name of constructors is called constructors, it should be noted that the main task of constructors is not to open space to create objects, but to initialize objects.

Its characteristics are as follows

1. The function name is the same as the class name.
2. No return value.
3. When the object is instantiated, the compiler automatically calls the corresponding constructor.
4. The constructor can be overloaded.

Take chestnuts for example:

class Date
{
public:
	//Parameterless constructor
	Date()
	{
		cout << "Date()" << endl;
	};
	//Constructor with arguments
	Date(int year, int month, int day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1; //Call parameterless
	Date d2;//Call parameterized
	return 0;
}

We can see the definition methods of the above two constructors. Let's test whether it will automatically call the corresponding constructor when we define an object.

Look at the results:

We found that when we create an object, we automatically call the constructor for initialization. This can avoid the error caused by forgetting to call initialization when using the corresponding interface when writing the program.

Compiler default generated constructor

If the constructor is not explicitly defined in the class, the C + + compiler will automatically generate a parameterless default constructor. Once the user explicitly defines the constructor, the compiler will no longer generate it.

class Date
{
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d;
	return 0;
}

In this case, we do not define a constructor, but the system will generate a constructor by default. Let's see how the constructor generated by default handles built-in types through the debugging window.

We found that the three built-in types are random values, which shows that the constructor generated by the system by default does not handle built-in types. Is it really useless? But it handles custom types.  

Take chestnuts for example:

class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
private:
	int _a;
};
class B
{
private:
	int _b;
	A _aa;
};
int main()
{
	B b;
	return 0;
}

In the above code, we don't show the defined constructor in custom type B. let's see how the system generated constructor handles custom type A?

The operation is as follows:

The constructor generated by the compiler in class B automatically calls the constructor in custom type A. in fact, the constructor generated by the compiler is still useful.

Piecemeal knowledge

The default constructor (which can be called without passing parameters) includes: parameterless, full default, and we didn't write it automatically generated by the compiler.

Note: parameterless constructors, all default constructors, and constructors generated by the compiler without writing them can be considered as default member functions.

Note that the constructor of this date class does not belong to the default constructor:

	Date(int year , int month , int day = 2)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}

Parameterless constructor call details

     ​​​

If you create an object through a parameterless constructor, the object does not need to be followed by parentheses, otherwise it becomes a function declaration.

The following conflict scenarios may exist for all default and parameterless constructors

class Date
{
public:
	//Parameterless constructor
	Date()
	{
		cout << "Date()" << endl;
	};
	//Constructor with parameters (full default)
	Date(int year = 2022, int month = 2, int day = 2)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d;
	return 0;
}

Let's look at the results:

At this time, it is not clear when the object d calls the constructor, because the two constructors conflict when calling.  

reflection

About the default member function generated by the compiler, many children's shoes will have doubts: if we do not implement the constructor, the compiler will generate the default constructor. But it seems that the default constructor is useless? The d object calls the compiler generated default constructor, but the d object year/month/_day is still a random value. That is to say, the default constructor generated by the compiler is useless here??


Answer: C + + divides types into built-in types (basic types) and custom types. A built-in type is a type whose syntax has already been defined: for example, int/char, A custom type is a type defined by ourselves using class/struct/union. If you look at the following program, you will find that the compiler will generate a default constructor for custom type members_ t calls its default member function.

Destructor

concept

Through the study of constructors, we know how an object comes from and how that object doesn't come from?
Destructor: Contrary to the constructor function, the destructor does not complete the destruction of objects, and the destruction of local objects is completed by the compiler. When the object is destroyed, it will automatically call the destructor to clean up some resources of the class.

characteristic

Destructors are special member functions.

Its characteristics are as follows:

1. The destructor name is preceded by the character ~.
2. No parameter, no return value.
3. A class has only one destructor. If not explicitly defined, the system will automatically generate a default destructor.
4. At the end of the object life cycle, the C + + compilation system automatically calls the destructor.

Take chestnuts for example:

class Stack
{
public:
	Stack( int capacity = 4)
	{
		 _size = 0;
		 _capacity = 4;
		 _a = (int*)malloc(sizeof(int) * capacity);
	}
	~Stack()
	{
		free(_a);
		_a = nullptr;
		cout << "~Stack()" << endl;
	}
private:
	int _capacity;
	int _size;
	int* _a;
};
int main()
{
	Stack st;
	return 0;
}

The destructor does not need a class like date because it has few resources to clean up. However, classes like the above require destructors to clean up the space created on the heap. The advantage is that we clean up the relevant resources in time after the object is destroyed to avoid memory leakage.

Let's run:

After the current scope ends, return 0; Call the corresponding destructor to complete the corresponding resource cleanup.  

Compiler default generated destructor

Will the destructor automatically generated by the compiler accomplish something? In the following program, we will see that the default destructor generated by the compiler will call its destructor for self-defined type members.

Take chestnuts for example:

class A
{
public:
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};
class B
{
private:
	int _b;
	A _aa;
};
int main()
{
	B b;
	return 0;
}

In the above code, the destructor corresponding to the definition of user-defined type B is not displayed. At this time, does the destructor generated by the compiler in B work on user-defined type A? Let's verify it.

Operation results:

We found that the destructor generated by the compiler in sub custom B will destroy the object to be destroyed_ AA (custom type) calls the corresponding destructor to clean up the corresponding resources.  

copy constructor

Constructor: there is only a single formal parameter, which is a reference to the object of this class type (commonly used const decoration). It is automatically called by the compiler when creating a new object with an existing class type object.

features

The copy constructor is also a special member function with the following characteristics:
1. Copy constructor is an overloaded form of constructor.
2. There is only one parameter of the copy constructor, and the parameter must be passed by reference. Using the value passing method will cause infinite recursive calls.

Take chestnuts for example:

class Date
{
public:
	Date(int year = 2022, int month = 2, int day = 2)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2022, 2, 3);
	Date d2(d1);
	d2.Print();
	return 0;
}

In the above code, we use the initialized object D1 (through copy construction) to create a new object d2. Let's print the value of d2 to determine whether the construction is successful.

The results are as follows:

We found that d2 was copied and constructed successfully.

Next, let's think about why copy constructors use reference passing parameters?

The copy constructor should refer to the passed parameter

Here, remember a sentence: the copy structure should first pass parameters, and the value passing and parameter passing is another copy structure.

At this time, it will continue to recurse, resulting in an endless loop, so the copy structure must be passed to and received by reference!

Compiler default generated copy constructor

If the definition is not displayed, the system generates the default copy constructor. The default copy constructor object completes the copy in byte order according to memory storage. This kind of copy is called shallow copy, or value copy.

Take chestnuts for example:

class Date
{
public:
	Date(int year = 2022, int month = 2, int day = 2)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2022, 2, 3);
	Date d2(d1);
	d2.Print();
	return 0;
}

The above code does not show the write copy structure. At this time, the compiler will generate one by default. Copy construction by shallow copy.

Run as follows, the copy constructor generated by the compiler by default will also meet the copy constructor of classes such as date class (we don't need to implement it ourselves):

Note: the copy constructor generated by the compiler by default is constructed by copying in byte order. For classes like Stack, shallow copy cannot be used. At this time, deep copy is required to implement the copy constructor.  

Because every time a class like Satck completes a shallow copy, there will be two or more objects with two or more pointers pointing to the space on the same heap. Before the object is destroyed, the destructor will destruct the same space twice or more, causing the program to crash. As for deep copy, I won't explain it here. In future blogs, I will introduce and implement deep copy in combination with specific scenes.

Copy constructor details

Detail 1

Copy constructor is a special constructor, so you must implement the constructor when you define the copy constructor, otherwise the compiler will not generate it by default. At this time, the program will have problems.

Take chestnuts for example:

Overload assignment operator

Operator overloading

In order to enhance the readability of the code, C + + introduces operator overloading. Operator overloading is a function with special function name, and also has its return value type, function name and parameter list. Its return value type and parameter list are similar to ordinary functions.
The function name is: the keyword operator is followed by the operator symbol that needs to be overloaded.
Function prototype: return value type operator operator (parameter list).

be careful:

1. You cannot create a new operator by concatenating other symbols: for example, operator@
2. Overloaded operators must have an operand of class type or enumeration type
3. The meaning of operators used for built-in types cannot be changed. For example, the meaning of built-in integer +
4. When an overloaded function is a member of a class, its formal parameters appear to be 1 less than the number of operands
The operator has a default parameter this, which is limited to the first parameter
5,.* ,:: , sizeof ,?: ,. Note that the above five operators cannot be overloaded. This often appears in written multiple-choice questions.

Take chestnuts for example:

Overload an assignment construct:

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2022, 2, 2);
	Date d2;
	d2 = d1;
	d2.Print();
	return 0;
}

The operation results are as follows:

We find that the assignment of d1 to d2 is successful, and the operator overloading greatly improves the readability of the code.  

characteristic

Assignment operators mainly have four points:
1. Parameter type
2. Return value
3. Check whether you assign values to yourself
4. Return to * this
5. If a class does not explicitly define the overload of assignment operator, the compiler will also generate one to complete the byte order value copy of the object

Default generated assignment operator overload

Take chestnuts for example:

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2022, 2, 2);
	Date d2;
	d2 = d1;
	d2.Print();
	return 0;
}

At this time, we don't show the overload of write assignment operator. When we use it, the compiler will automatically generate one to complete the shallow copy.

There is no problem for classes like date, but for classes like Stack, there will be problems like copy structure. At this time, deep copy should be applied to solve the problem. As for deep copy, it will be introduced in detail in combination with specific scenarios in the future.

The result of the above code is as follows:

Operator overload details

When implementing operator overloading in a class, we should pay attention to the left and right operands. Usually, the hidden this pointer is the left operand and the rest are the right operand.

For example, in the date class, such operator overloading:

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	bool operator == (const Date& d)
	{
		return _year == d._year
		&& _month == d._month
		&&      _day == d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

We will use d2 = = d1 in comparison; At this time, d2 is used as the left operand and d1 is used as the right operand for comparison. d2 is passed to the this pointer at this time.

                              

const member

const modifier class member function

The class member function modified by const is called const member function. Const modifies the class member function and actually modifies the implicit this pointer of the member function, indicating that no member of the class can be modified in the member function.

void Display() const will be converted into void Display(const Date* const this) by the compiler, which means that neither this nor the content pointed to by this can be changed.

Take chestnuts for example:

Note: const modification can make the code more rigorous. If it is a function that does not modify the content pointed by this pointer, try to add this pointer to modify it.

reflection

1. Can const objects call non const member functions? may not
2. Can non const objects call const member functions? sure
3. Can other non const member functions be called in const member functions? may not
4. Can other const member functions be called in non const member functions? sure

Can const objects call non const member functions? may not

Can non const objects call const member functions? sure

Can other non const member functions be called in const member functions? may not

Can other const member functions be called in non const member functions? sure

Summary: const members can only call const members; Non const members can call either non const or const #.

Overload of address fetching and const address fetching operators

These two default member functions generally do not need to be redefined, and the compiler will generate them by default.

If you want to reload, the code is as follows:

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date* operator&()
	{
		return this;
	}
	const Date* operator&() const
	{
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

Run the following:

Each object will call the corresponding address overload.

Application scenario

Generally, these two operators do not need to be overloaded. They can be overloaded by using the default address generated by the compiler. Only in special cases, they need to be overloaded. For example, if you want others to get the specified content:

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date* operator&()
	{
		return nullptr;
	}
	const Date* operator&() const
	{
		return nullptr;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	cout << &d1 << endl;
	const Date d2;
	cout << &d2 << endl;
	return 0;
}

The experimental results are as follows:

If we don't want others to view the address of the object, we can do the above operations.  

Keywords: C++ Back-end

Added by marcela1637 on Thu, 03 Feb 2022 02:34:32 +0200