Getting to Know C++ - Classes and Objects (Part 1, Part 1)

Table of contents 

The 6 default member functions of the class

Constructor 

concept

characteristic

destructor 

concept

characteristic

copy construction

concept 

feature

operator overloading

 concept

unfinished continue... 


Note that the code of this article does not add the following content by default

#include <iostream>

using  std::endl;
using  std::cout;

The 6 default member functions of the class

If there are no members in a class, it is simply called an empty class.
Is there really nothing in the empty class? No, when any class does not write anything, the compiler will automatically generate the following 6 default members
function.
Default member function: The member function generated by the compiler without explicit implementation by the user is called the default member function.

The following is an empty class, 

class Date {};

Constructor 

concept

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

characteristic

        The constructor is a special member function. It should be noted that although the name of the constructor is called construction, the main task of the constructor 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. The compiler automatically calls the corresponding constructor when the object is instantiated .
4. The constructor can be overloaded.

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//构造函数是可以写多个的
	//Date()	//因为构造函数支持函数重载,所以可以合并到一起
	//{
	//	_year = 1;
	//	_month = 1;
	//	_day = 1;
	//}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2022, 10, 8);     // 调用带参的构造函数
	Date d2(2022, 10, 9);
	Date d3;	//不可以加 ()  // 调用无参构造函数


	//无参的不要像如下这样子写
	//Date d4();	//这样子在VS里面会报警告,但是在别的编译器中可能会直接报错
	//Date func();	//上面这样子写就类似于函数的声明了,会出错的

	d1.Print();
	d2.Print();
	d3.Print();

	return 0;
}

Constructor usage

        When in use, the compiler will automatically call the constructor to do some work, which can avoid many errors caused by forgetting, such as forgetting Inint ( initialization)  , forgetting destroy (release memory) 

5. If no constructor is explicitly defined in the class, the C++ compiler will automatically generate a default constructor with no parameters. Once
Compilers explicitly defined by the user will no longer be generated.
6. Regarding the default member functions generated by the compiler, many children's shoes will have doubts: if the constructor is not implemented, the compiler will
Generate a default constructor. But it seems that the default constructor is useless? The d object invokes the compiler-generated default
The constructor is recognized, but the d object _year/_month/_day is still a random value. That is to say, here the compiler generates
The default constructor is useless? ?
Answer: C++ divides types into built-in types ( basic types ) and custom types . Built-in types are data classes provided by the language
Type, such as: int/char... , the custom type is the type we define ourselves using class/struct/union , etc., see
In the following program, you will find that the default constructor generated by the compiler will call its default constructor members for custom type members
function.

Built-in types: int / char / double ... pointers (pointers of custom types are also counted)

Custom types: class / struct ... Stack / queue / Person 

The built-in type does not handle, and the custom type will call its default constructor

Analyze the code below

class A
{
public:
	A()
	{
		_a = 0;
		cout << "A()构造函数" << endl;
	}
private:
	int _a;
};

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;      // 日

	// 自定义类型
	A _aa;
};

int main()
{
	Date d1;
	d1.Print();

	return 0 ;
}

        Through the above code, it is found that the built-in type will call the default constructor, but it will not be processed (initialized), because the custom type will call the constructor

class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack(int capacity = 4)" << endl;

		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}

		_top = 0;
		_capacity = capacity;
	}

	~Stack()    //这里是一个析构函数, 函数名与类名一致,但是前面需要加上 ~(C语言中的取反)
	{
		cout << "~Stack()" << endl;

		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

	void Push(int x)
	{
		// ....
		// 扩容
		_a[_top++] = x;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};

class MyQueue {
public:
	void push(int x)
	{
		_pushST.Push(x);
	}

	Stack _pushST;    //自动调用了 Stack 里面的构造函数
	Stack _popST;

	//size_t _size;
};

int main()
{

	MyQueue mq;
	mq.push(1);
	mq.push(2);

	return 0;
}

        When there is a default constructor and the requirements are met, there is no need to write another function, and it can be called directly (the destructor will be introduced below)

     

Based on the above inconveniences, it was optimized in C++11 later, and a patch was applied to directly give the default value (default value)

Note: In C++11, a patch has been patched for the defect of non-initialization of built-in type members, namely: built-in type member variables are in
Default values ​​can be given when declaring in a class.

Then with this patch, the above code can be written as follows

        Note that these are all default values, not initialization, and it does not open up space for it. The above described it as a blueprint, which has methods but no entities.

        The same feature that conforms to the default value, when there is no given value, it will be initialized directly with the default value, and when there is a value, the default value will not be used, as follows, it will not be initialized to 0, while all initialized to 1

7. Both the parameterless constructor and the default constructor are called default constructors, and there can only be one default constructor.
Note: No-argument constructors, full default constructors, and constructors that we did not write by default generated by the compiler can all be considered
is the default constructor.

destructor 

concept

 Through the study of the previous constructor, we know how an object came about, and how did that object disappear?
         Destructor: Contrary to the function of the constructor, the destructor does not complete the destruction of the object itself, and the local object destruction is done by the compiler. When the object is destroyed, it will automatically call the destructor to complete the cleanup of resources in the object.

characteristic

A destructor is a special member function whose characteristics are as follows:

1. The name of the destructor is to add characters before the class name ~
2. No parameters and no return value type.
3. A class can only have one destructor. If not explicitly defined, the system will automatically generate a default destructor.
     Note: Destructors cannot be overloaded
4. When the object life cycle ends, the C++ compilation system automatically calls the destructor
The following is the use of a destructor
typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	void Push(DataType data)
	{
		_array[_size] = data;
		_size++;
	}

	~Stack()    //析构函数,在对象生命周期结束时自动调用
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	int _capacity = 4;
	int _size = 0;
};

void TestStack()
{
	Stack s;
	s.Push(1);
	s.Push(2);
}


int main()
{
	TestStack();

	return 0;
}

5. Regarding the destructor automatically generated by the compiler, will something be done? As we will see in the following program, the compiler
A generated default destructor that calls its destructor on members of a custom type.

//析构函数 补
class Time
{
public:
	~Time()
	{
		cout << "~Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};

int main()
{
	Date d;
	return 0;
}

The output of the above is as follows

In the main method, no object of the Time class is directly created at all. Why is the destructor of the Time class called at the end?
        Because: the Date object d is created in the main method, and d contains 4 member variables, among which _year, _month, and _day are built-in type members, and resource cleaning is not required when destroying. Finally, the system can directly reclaim its memory ; and _t is an object of the Time class, so when d is destroyed, the _t object of the Time class contained in it must be destroyed, so the destructor of the Time class must be called.
        However: the destructor of the Time class cannot be called directly in the main function. What actually needs to be released is the Date class object, so the compiler will call the destructor of the Date class. If Date is not explicitly provided, the compiler will give the Date class Generate a default destructor, the purpose is to call the destructor of the Time class inside it, that is, when the Date object is destroyed, it is necessary to ensure that each custom object inside it can be destroyed correctly and the Time class is not directly called in the main function Destructor, but explicitly call the default destructor generated by the compiler for the Date class.
        Note: the destructor of that class is called when an object of that class is created, and the destructor of that class is called when an object of that class is destroyed.

Example case (from Topic  232. Implementing Queues with Stacks - LeetCode )

    Of course, sometimes there is no need to write a destructor, because the compiler will automatically generate it when there is no destructor. For example, there is no need to write the date class above. It depends on what you need to use. 

copy construction

concept 

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

        Construction means initialization, then copy construction means copy initialization

        Some places call it copy constructor

feature

The copy constructor is also a special member function with the following characteristics : 
1. The copy constructor is an overloaded form of the constructor .
2. There is only one parameter of the copy constructor and it must be a reference to a class type object . If the value-passing method is used, the compiler will report an error directly, because it will cause infinite recursive calls.

        The built-in type (provided by the compiler, the compiler knows the built-in type well) so there is no copy constructor, but the custom type (compare copy) needs a copy constructor to help complete

The following code

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	Date(const Date& d)	//使用 &(无限循环访问构造函数) 并且要加上 const (防止写反,导致被反向赋值)
	{
		cout << "Date 拷贝构造" << endl;

		_year = d._year;
		_month = d._month;
		_day = d._day;

		// 形参加const,防止写反了,下面问题就可以检查出来
		//d._day = _day;
	}

private:
	int _year;
	int _month;
	int _day;
};

void Func1(Date d)
{
	cout << "Func1()" << endl;
}

void Func2(Date& d)
{
	cout << "Func2()" << endl;
}

int main()
{
	Date d1(2022, 9, 22); // 构造 - 初始化
	Func1(d1);
	Func2(d1);

	// 拷贝一份d1
	//Date d2(d1); // 拷贝构造 -- 拷贝初始化    会调用拷贝构造函数

	return 0;
}

Without & will call the constructor infinitely

Without const, the following errors are prone to occur

        Another thing to note is that the & here can theoretically be replaced by pointers, but using it will make the code look untidy. More importantly, using pointers to complete the operation is no longer a copy construction. , should call it the constructor

        

3. If it is not explicitly defined (write it yourself), the compiler will generate a default copy constructor. The default copy constructor object is copied in byte order according to memory storage. This kind of copy is called shallow copy, or value copy.
Note: In the default copy constructor generated by the compiler, built-in types are directly copied in bytes, while custom
A defined type is copied by calling its copy constructor.
4. The default copy constructor generated by the compiler can already copy byte-ordered values . Do I need to explicitly implement it myself?
Of course classes like the Date class are unnecessary.
     It still depends on the situation (whether it is a custom type or a built-in type)
The following situation

shallow copy
// 这里会发现下面的程序会崩溃掉?这里就需要写一个深拷贝去解决。

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
			_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}


	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size = 0;
	size_t _capacity = 0;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);

	Stack s2(s1);	//浅拷贝

	return 0;
}

        It will be found that the program crashes (the destructor destroys the same space twice), here, according to the nature of the stack, the latter definition will be destructed first (last in, first out), s2 will be destructed first, and then s1 will be destructed . However, since the destructor automatically generated by the compiler is a shallow copy, the two pointers point to the same space, so the program will crash directly

deep copy

//深拷贝
typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 4)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		_array[_size] = data;
		_size++;
	}

	Stack(const Stack& st)
	{
		_array = (DataType*)malloc(sizeof(DataType) * st._capacity);
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		memcpy(_array, st._array, sizeof(DataType) * st._size);	//利用memcpy进行拷贝,利用 st._size(数据的个数)更好点

		_size = st._size;
		_capacity = st._capacity;
	}

	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size = 0;
	size_t _capacity = 0;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);

	Stack s2(s1);	//深拷贝
	s2.Push(10);

	return 0;
}

Surveillance found that the two are no longer pointing to the same space, and the two do not interfere with each other 

Summary: If there is no resource application involved in the class, the copy constructor can be written or not; once resource application is involved
, the copy constructor must be written, otherwise it is a shallow copy.

5. Typical calling scenarios of copy constructor:
1. Create a new object using an existing object
2. The function parameter type is a class type object
3. The return value type of the function is a class type object

        Therefore, in order to improve the efficiency of the program, when passing parameters to general objects, try to use the reference type as much as possible, and when returning, use references as much as possible according to the actual scene.        

operator overloading

The next article will mention assignment operator overloading

 concept

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

Notice:

1. You cannot create new operators by connecting other symbols: such as operator@
2. Overloaded operators must have a class type parameter (operators that cannot change built-in types)
3. The meaning of the operator used for built-in types cannot be changed, for example: the built-in integer + cannot change its meaning
4. When overloaded as a class member function, its formal parameters seem to be 1 less than the number of operands , because the first parameter of the member function is implicit
Hidden this
.* :: sizeof ?: .  Note that the above 5 operators cannot be overloaded. This often appears in written multiple choice questions.  

The first one is relatively rare and is an ancient operator, but it still needs to be paid attention to

The following is an operator overloading

//运算符的重载

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	// 我们不写,编译器生成的默认拷贝构造函数
//private:
	int _year;
	int _month;
	int _day;
};

bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}

int main()
{
	Date d1(2022, 9, 22);
	Date d2(2023, 1, 1);

	//d1 > d2;
	cout << (d1 == d2) << endl; // 转换成operator==(d1, d2);

	d1 == d2;
	// 也可以显示调用,不过一般不这样 ,上面的与下面的写法是一样的,相同的意思
	operator==(d1, d2);

	return 0;
}

But it's still bad, and it's bad behavior to make private variables public in order to use operator overloading.

To do this we need to put the function into the class, but we need to make a little modification because of the existence of the this pointer

If you put it in directly without modification, the compiler will report the following error 

After the modification is as follows

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		//_year 前面默认有一个 this指针,下面同理 
			&& _month == d._month
			&& _day == d._day;
	}

	// 我们不写,编译器生成的默认拷贝构造函数
private:    //可以被类里面的成员函数访问了
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2022, 9, 22);
	Date d2(2023, 1, 1);

	cout << (d1 == d2) << endl; // 编译器会转换成 d1.operator == (d2),所以在使用的时候要注意运算符的优先级(这里用括号括起来)

	d1 == d2;
	// 也可以显示调用,不过一般不这样写 ,上面的与下面的写法是一样的,相同的意思
	cout << (d1.operator == (d2)) << endl; // 

	return 0;
}

    To sum up, you must not forget the this pointer. It can be said that any member function (including constructors) contains a hidden this pointer, except for the following static member functions

It was a bit too long, so I wrote it in parts

unfinished continue... 

Guess you like

Origin blog.csdn.net/weixin_67595436/article/details/127210298