[Advanced Road to C++] Classes and Objects (Part 2)

Preface

Six default member functions

Insert image description here

1. Constructor - completes the initialization of object member variables
2. Destructor - completes the release of space (mainly the heap)
3. Copy construction - uses an initialized object to initialize another object that is being initialized
4. Assignment Overloading - assigning an initialized object to another initialized object
5. Address overloading - taking the address of an object without const
6. Const modified address overloading - adding const to an object The object takes the address

  • Why is it called the default member function?
  • Because even an empty class will automatically generate these six functions (default) in the class . If these functions are defined , they can only be defined in the class, or declared inside the class, and defined outside the class using scope qualifiers (members ).
  • Special note: The six default member functions generated by the compiler are public (required) .

1. Constructor

  • In order to make up for the shortcomings of the C language - for example, when writing bracket matching , we will notice that we may accidentally forget to initialize the two stacks . With the constructor, this problem is well solved.

nature

  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. Constructors can be overloaded.

default constructor

Example code:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace::std;
class Date
{
    
    
public:
	Date(int year = 1, int month = 1, int day = 1)//类名就是函数名,无返回值
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	Date(int year, int month = 1, int day = 1)//构成重载
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
    
    
	Date A;//自动调用析构函数
	//Date A();
	return 0;
}

Debugging results:
Insert image description here

  • Many viewers will ask again - shouldn't calling a function under full default conditions be written as the code commented below?
  • At first glance, it seems yes. What if we change the form - Date func(); Does it look like a function declaration? Then why not write it like this? In fact, it is to distinguish it from the function declaration!
  • It’s not enough to get here, let’s continue the analysis!

  • Since function parameters can be overloaded, can we write it like this?
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace::std;
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;
	}
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
    
    
	//Date A;
	return 0;
}
  • It is supported syntactically, but if you release the commented code and then compile it, an error will be reported because you don't know which constructor to call, so it is possible, but it is not supported .

  • Since it is a default member function, the compiler should generate one for us without writing it.
#include<iostream>
using namespace::std;
class Date
{
    
    
public:
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
    
    
	Date A;
	//Date A();
	return 0;
}

Illustration:
Insert image description here

  • Why is this? The compiler didn't even complete the initialization for us.
  • The syntax only explains a little bit about the compiler-generated constructor:
  • 1. Do not process built-in types - the built-in types here refer to the types that the compiler originally has
  • Note: Some compilers will still handle it, but we will treat it as if it does not.
  • 2. Call the default constructor of the custom type

  • So if we want to initialize the variables of the object without writing a constructor?
  • C++11 introduced that we can use default values ​​​​for class variables.
#include<iostream>
using namespace::std;
class Date
{
    
    
public:
private:
	int _day = 1;
	int _month = 1;
	int _year = 1;
};
int main()
{
    
    
	Date A;
	return 0;
}

Debugging results:
Insert image description here

  • Obviously the compiler helps us initialize the object.

  • Summarize:
  • 1. The default constructor refers to a function that can be called without passing parameters.
  • 2. The default constructors we currently know are either completely default, parameterless, or generated by the compiler.

Constructor (needs to pass parameters)

using namespace::std;
class Date
{
    
    
public:
	Date(int year, int month, int day)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	Date()
	{
    
    
		_year = 1;
		_month = 1;
		_day = 1;
	}
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
    
    
	Date A(2023,5,3);//这就跟函数调用差不多了,就多了个类型。
	return 0;
}

Debugging results:
Insert image description here

  • Summarize:
    1. Under normal circumstances, built-in types generally need to write constructors and cannot be generated by the compiler.
    1. For custom types, we can consider not writing a constructor and letting the compiler generate it, which is essentially using the default constructor of the custom type.
    1. Member variables can be given default values.

2. Destructor

  • If you are writing bracket matching and initialization problems that are not easy to make mistakes, then the destruction on return will have a high probability of making mistakes. However, you can pass this programming problem even if you do not destroy it, but as a qualified programmer, how can Can you let it go?
  • Therefore, the destructor is to solve this problem and allow the program to automatically release space (mainly the heap) when it is destroyed.

Definition of destructor: The destructor corresponds to the constructor. The constructor is automatically called when the object is created. The destructor does not complete the destruction of the object. The local object destruction is completed by the compiler. When the object is destroyed, the destructor will be automatically called to complete some resource cleanup work of the class.

  • Say important things three times:
    1. The destructor does not complete the destruction of the object, but only cleans up the internal resources (heap space).
    1. The destructor does not complete the destruction of the object, but only cleans up the internal resources (heap space).
    1. The destructor does not complete the destruction of the object, but only cleans up the internal resources (heap space).

nature

    1. Function name: ~ + class name
    1. No parameters, no return value, does not constitute overloading
    1. A class has one and only one destructor . If not explicitly defined, the system will automatically generate a default destructor .
    1. Automatically called at the end of the object's life cycle .

We hand-write the initialization and release of a stack

#include<iostream>
using namespace::std;
class Stack
{
    
    
public:
	Stack(int capacity = 4)
	{
    
    
		cout << "Stack()" << endl;
		int* tmp = (int*)malloc(sizeof(int) * capacity);
		if (tmp == NULL)
		{
    
    
			perror("malloc fail");
			exit(-1);

		}
		else
		{
    
    
			_arr = tmp;
			_top = 0;
			_capacity = capacity;
		}
	}
	~Stack()
	{
    
    
		cout << "~Stack()" << endl;
		free(_arr);
		_arr = NULL;
	}
private:
	int* _arr;
	int _top;
	int _capacity;
};
int main()
{
    
    
	Stack stack1;
	return 0;
}

When we create a stack object, the initialization of the object will be completed, and the heap space will be released when the main function ends.

Check the results of debugging:
Insert image description here
at this time, the mainj function is one step away from ending:
Insert image description here
continue debugging:
Insert image description here
continue to execute the program until the object is released.

default destructor

  • Will the destructor generated by default release the dynamically applied resources?
#include<iostream>
using namespace::std;
class Stack
{
    
    
public:
	Stack(int capacity = 4)
	{
    
    
		cout << "Stack()" << endl;
		int* tmp = (int*)malloc(sizeof(int) * capacity);
		if (tmp == NULL)
		{
    
    
			perror("malloc fail");
			exit(-1);

		}
		else
		{
    
    
			_arr = tmp;
			_top = 0;
			_capacity = capacity;
		}
	}
private:
	int* _arr;
	int _top;
	int _capacity;
};
int main()
{
    
    
	Stack stack1;
	return 0;
}

debug:
Insert image description here

  • It can be seen that after returning, the dynamically applied resources are not released.
  • Therefore: the default constructor does not handle built-in types

  • What about custom types?

We define a pair:

#include<iostream>
using namespace::std;
class Stack
{
    
    
public:
	Stack(int capacity = 4)
	{
    
    
		cout << "Stack()" << endl;
		int* tmp = (int*)malloc(sizeof(int) * capacity);
		if (tmp == NULL)
		{
    
    
			perror("malloc fail");
			exit(-1);

		}
		else
		{
    
    
			_arr = tmp;
			_top = 0;
			_capacity = capacity;
		}
	}
	~Stack()
	{
    
    
		cout << "~Stack()" << endl;
		free(_arr);
		_arr = NULL;
	}
private:
	int* _arr;
	int _top;
	int _capacity;
};

class MyQueue
{
    
    
	Stack stack_push;
	Stack stack_pop;
};
int main()
{
    
    
	MyQueue Q;
	return 0;
}

Then debug:
Insert image description here

  • It can be seen that the destructor generated by default will release the resources of its members, which is essentially calling the destructor of its custom member.
  • Therefore: the objects are all custom members, and the destructor generated by default will call the destructor of its custom members to complete the release and cleanup of resources.

  • Summarize:
  • Default generated destructor:
    1. Built-in types are not processed.
    1. For a custom type, its destructor will be called.

So when to write a destructor? When is there no need to write a destructor?

  • The object has resources that need to be cleaned up, and the resources are managed by built-in types . In this case, we need to write our own destructor.
  • The object has resources that need to be cleaned up, but the resources are managed by custom types , so there is no need to write them in this case .
  • The object has no resources to clean up , and there is no .

practise

  • The calling sequence of destruction and construction of objects with different life cycles and scopes
class A
{
    
    
public:
	A()
	{
    
    
		cout << "A()" << endl;
	}
	~A()
	{
    
    
		cout << "~A()" << endl;
	}
};
class B
{
    
    
public:
	B()
	{
    
    
		cout << "B()" << endl;
	}
	~B()
	{
    
    
		cout << "~B()" << endl;
	}
};


class C
{
    
    
public:
	C()
	{
    
    
		cout << "C()" << endl;
	}
	~C()
	{
    
    
		cout << "~C()" << endl;
	}
};

class D
{
    
    
public:
    D()
	{
    
    
		cout << "D()" << endl;
	}
	~D()
	{
    
    
		cout << "~D()" << endl;
	}
};
class E
{
    
    
public:
	E()
	{
    
    
		cout << "E()" << endl;
	}
	~E()
	{
    
    
		cout << "~E()" << endl;
	}
};

A a;
B b;
int main()
{
    
    
	C c;
    D d;
	static E e;
	return 0;
}

Here we list three types of objects - global objects, local objects, and static-modified local objects.
Without further ado, let’s start debugging:
Insert image description here

  • At this point we can see that a calls its constructor at the beginning of the program and constructs it in the order of the statements .
  • Therefore: the construction of the global object takes precedence and follows the order of statements.
    Continue debugging:

Insert image description here

  • From this, it can be judged that the order of constructor calls of local objects is after that of global objects, and they are also constructed in the order of statements.
  • Therefore: the priority of the constructor: global is greater than local, and follows the order of statements.
  • Note: Local variables modified by static are also local variables.

Continue debugging:
Insert image description here

  • It can be seen that the calling order of the destructor of a local object without static modification is opposite to the construction order , and the destructor is called first than the static object .

Then debug until the program ends:

Insert image description here

  • As can be seen
  1. The destructor static is released after the local and in the reverse order of the constructor.
  2. Global objects are released last and in the reverse order of constructors.

  • Summarize:
  1. The order in which constructors are called is based on the order of statements.
  2. The global is greater than the local
  3. The order in which the destructor is called is the opposite of the order in which the constructor is called.
  4. Priority: local objects without static > local objects with static > global objects

3. Copy constructor

  • In our daily use, if we need to modify an object without destroying the original object, we need to make a separate copy - a bit like the effect of post ++, so how to copy it?
  • At this time we introduce the copy constructor - the copy constructor is an overloaded form of the constructor.

Basic properties:

  1. There is only a single parameter . This formal parameter is a reference to an object of this class (一般常用const修饰).
  2. No return value
  3. The class name is the same as the function name
  4. Automatically called by the compiler when creating a new object from a type object of an existing class .
  • Explanation: The copy construction completes the assignment operation of an initialized object to another object that is being initialized .

Parameters must be references

  • Why do formal parameters have to be references rather than copies?

We analyze it according to the syntax: it is automatically called by the compiler when creating a new object using an existing class type object (calling condition of copy construction).

Code:

#include<iostream>
using namespace::std;
class Date
{
    
    
public:
	//构造函数
	Date(int year = 1949, int month = 10, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造
	Date(const Date& A)
	{
    
    
		_year = A._year;
		_month = A._month;
		_day = A._day;
    }
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
    
    

	Date d;
	Date B(d);
	return 0;
}

If you remove the & in the copy constructor, compile it.
Insert image description here

  • It can be seen that mandatory checking will be performed on the syntax and errors will be reported.
  • But what if we analyze it assuming no errors are reported?

Insert image description here

  • It can be seen that if we analyze it this way, it will only go farther and farther in the opposite direction (only recursive but not recursive).

  • Also look at the code above: add a paragraph
void Fun(Date A)
{
    
    

}
  • When we call this function, the conditions for copying are also met.
  • Therefore: copy construction will also be called when passing parameters.

Default copy construction

Shallow copy

#include<iostream>
using namespace::std;
class Date
{
    
    
public:
	//构造函数
	Date(int year = 1949, int month = 10, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
    
    
	Date d;
	Date B(d);
	return 0;
}

To debug:
Insert image description here

  • It can be seen that the task is completed, does this mean that we do not need to write a copy structure?
  • No, copy is also divided into deep copy and shallow copy
  • The default generated version only completes the shallow copy , so what is the deep copy?

deep copy

Give another code:

#include<iostream>
using namespace::std;
class Stack
{
    
    
public:
	Stack(int capacity = 4)
	{
    
    
		cout << "Stack()" << endl;
		int* tmp = (int*)malloc(sizeof(int) * capacity);
		if (tmp == NULL)
		{
    
    
			perror("malloc fail");
			exit(-1);

		}
		else
		{
    
    
			_arr = tmp;
			_top = 0;
			_capacity = capacity;
		}
	}
	void PushBack(int data)
	{
    
    
		_arr[_top++] = data;

	}
	~Stack()
	{
    
    
		cout << "~Stack()" << endl;
		free(_arr);
		_arr = NULL;
	}
private:
	int* _arr;
	int _top;
	int _capacity;
};
int main()
{
    
    
	Stack s1;
	Stack s2(s1);
	return 0;
}

Debugging:
Insert image description here
It simply copied the address, but the task was still not completed.
Diagram of this code implementation:
Insert image description here

Diagram of what should be implemented:
Insert image description here
What we implement in this way is called deep copy.

How to achieve:

  • With malloc and memcpy
    code:
#include<iostream>
using namespace::std;
class Stack
{
    
    
public:
	Stack(int capacity = 4)
	{
    
    
		cout << "Stack()" << endl;
		int* tmp = (int*)malloc(sizeof(int) * capacity);
		if (tmp == NULL)
		{
    
    
			perror("malloc fail");
			exit(-1);

		}
		else
		{
    
    
			_arr = tmp;
			_top = 0;
			_capacity = capacity;
		}
	}
	Stack(const Stack& A)
	{
    
    
		int* tmp = (int*)malloc(sizeof(int) * A._capacity);
		if (tmp == NULL)
		{
    
    
			perror("malloc fail");
			exit(-1);

		}
		else
		{
    
    
			memcpy(tmp, A._arr, sizeof(int) * A._capacity);
			_arr = tmp;
			_top = A._top;
			_capacity = A._capacity;
		}
	}
	void PushBack(int data)
	{
    
    
		_arr[_top++] = data;

	}
	~Stack()
	{
    
    
		cout << "~Stack()" << endl;
		free(_arr);
		_arr = NULL;
	}
private:
	int* _arr;
	int _top;
	int _capacity;
};
int main()
{
    
    
	Stack s1;
	Stack s2(s1);
	return 0;
}

Debug it:
Insert image description here

  • From this our deep copy is simply completed.
    Let's go back and write a function.
#include<iostream>
using namespace::std;
class Stack
{
    
    
public:
	Stack(int capacity = 4)
	{
    
    
		cout << "Stack()" << endl;
		int* tmp = (int*)malloc(sizeof(int) * capacity);
		if (tmp == NULL)
		{
    
    
			perror("malloc fail");
			exit(-1);

		}
		else
		{
    
    
			_arr = tmp;
			_top = 0;
			_capacity = capacity;
		}
	}
	Stack(const Stack& A)
	{
    
    
		int* tmp = (int*)malloc(sizeof(int) * A._capacity);
		if (tmp == NULL)
		{
    
    
			perror("malloc fail");
			exit(-1);

		}
		else
		{
    
    
			memcpy(tmp, A._arr, sizeof(int) * A._capacity);
			_arr = tmp;
			_top = A._top;
			_capacity = A._capacity;
		}
	}
	void PushBack(int data)
	{
    
    
		_arr[_top++] = data;

	}
	~Stack()
	{
    
    
		cout << "~Stack()" << endl;
		free(_arr);
		_arr = NULL;
	}
private:
	int* _arr;
	int _top;
	int _capacity;
};
Stack Func()
{
    
    
	static Stack A;
	return A;
}
int main()
{
    
    
	Stack A = Func();
	return 0;
}
  • Here we write a function, and what is returned here is a deep copy of the A object in the function.
  • Assumption: If _arr is opened very large, then our overhead will be very large, so it is usually returned by reference, and the parameters of the function are generally references. The purpose is to avoid space overhead.

Custom type

The object is a copy constructor of a custom type. How to copy it?

class Date
{
    
    
public:
	//构造函数
	Date(int year = 1949, int month = 10, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造
	Date(const Date& A)
	{
    
    
		_year = A._year;
		_month = A._month;
		_day = A._day;
    }
private:
	int _day;
	int _month;
	int _year;
};
class Dates
{
    
    
	Date A;
	Date B;
};
int main()
{
    
    

	Dates A;

	Dates B(A);

	return 0;
}

debug:
Insert image description here

  • Therefore: the copy constructor generated by default will call the copy constructor of its type to copy.

  • Summarize:
    1. Deep copy requires us to write copy constructor.
    1. When making a shallow copy or all members are custom types, we don’t need to write a copy constructor.
    1. Copy construction is to complete the copy of an initialized object to another uninitialized object.
    1. The formal parameters of copy construction must be references, and copy construction is an overloading of the constructor.

4. Assignment operator overloaded function

  • Before talking about assignment operators, we must understand what operator overloaded functions are.

C++To enhance code readabilityOperator overloading is introduced, operator overloading isHave special function namesfunction, alsohas its return value typeFunction name and parameter list, its return value type and parameter list are similar to those of ordinary functions.

Basic Features

  • Purpose: Enhance code readability
  • Basic Features
  1. Have return type, parameters and return value
  2. The function name is operate+operator (must already exist!)

Notice:

  1. *.—— Never used
  2. :: ——Scope qualifier
  3. sizeof - find the type size
  4. ?: ——Ternary operator
  5. .
  • The above five operators cannot be overloaded.

Global operator overloaded functions

  • Because we need to access members, we have to make the member variables public in order to make them global.
    Code:
#include<iostream>
using namespace::std;
class Date
{
    
    
public:
	//构造函数
	Date(int year = 1949, int month = 10, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造
	Date(const Date& A)
	{
    
    
		_year = A._year;
		_month = A._month;
		_day = A._day;
     }
//private:
	int _day;
	int _month;
	int _year;
};
bool operator== (const Date& A, const Date& B)
{
    
    
	return A._day == B._day
		&& A._month == B._month
		&& A._year == B._year;
}
int main()
{
    
    
	Date A;
	Date B;
	cout <<( A == B )<< endl;
	//这里括号不可以省去,因为流插入的优先级比较高所以我们需要加括号让表达式先计算。
	return 0;
}

operation result:
Insert image description here

  • The returned bool value is 1, so true and therefore equal.

  • The whole thing is obviously not very good. Is there any way to write down part of it?

  • The answer is yes, wouldn't it be better to write it in the class?


Local operator overloading functions

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace::std;
class Date
{
    
    
public:
	//构造函数
	Date(int year = 1949, int month = 10, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造
	Date(const Date& A)
	{
    
    
		_year = A._year;
		_month = A._month;
		_day = A._day;
     }
	bool operator== (const Date& B)
	{
    
    
	return _day == B._day
		&& _month == B._month
		&& _year == B._year;
	}
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
    
    
	Date A;
	Date B;
	cout <<( A == B )<< endl;
	//A==B 其实本质上就是A.operator==(B)
	return 0;
}

Let’s list the overloaded functions here separately:

	bool operator== (const Date& B)
	//其实就是bool operator== (Date *const this,const Date& B)
	{
    
    
	return _day == B._day
		&& _month == B._month
		&& _year == B._year;
	}
  • For example, if A==B is required here , then it is equivalent to A.operator==(B)
  • On the surface, one parameter is missing, which is actually the this pointer.
  • Therefore: to compare n operands we only need to pass in n-1 parameters.

Implementation of pre-++ and post-++

  • At this point we have a certain understanding of some operators, but how should we implement post-++ and pre-++?
  • Since ++ is a unary operator and only operates on one object, the parameters of the implemented function have no or only an implicit this pointer. This is the condition for our distinction. One with a parameter is postfix ++, and one without is prefix ++.
    Implementation code:
#include<iostream>
using namespace::std;
class Date
{
    
    
public:
	//构造函数
	Date(int year = 1949, int month = 10, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造
	Date(const Date& A)
	{
    
    
		_year = A._year;
		_month = A._month;
		_day = A._day;
     }
	Date& operator++(int)//加一个参数以示区分,别的到没什么用处,
	{
    
    
		_day++;
		return *this;
	}
	Date operator++()
	{
    
    
		Date tmp(*this);
		_day++;
		return tmp;
	}
private:
	int _day;
	int _month;
	int _year;
};

assignment operator overloaded function

  • Note: The assignment operator overloaded function is the default member function. If you don’t write it, the compiler will automatically generate one, but the other operator overloaded functions are not default member functions.
#include<iostream>
using namespace::std;
class Date
{
    
    
public:
	//构造函数
	Date(int year = 1949, int month = 10, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造
	Date(const Date& A)
	{
    
    
		_year = A._year;
		_month = A._month;
		_day = A._day;
     }
	Date& operator=(const Date& d)
	{
    
    
		if (this != &d)
		{
    
    
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
private:
	int _day;
	int _month;
	int _year;
};

int main()
{
    
    
	Date A;
	Date B(2023,5,3);
	A = B;
	return 0;
}
  • *this as return value
  • 1. This object is still out of scope.
  • 2. The return type is recommended to be a reference

The return type of the assignment operator is the lvalue itself, so it is more appropriate for us to return a reference to reduce space overhead.
When the value is assigned to itself, this operation does not need to be performed, and the result is the same.

  • The difference between assignment operator overloading and copy constructor:
    1. Assignment operator overloading - assignment operation between two initialized objects
    1. Copy constructor c - the operation of one initialized object on another object being initialized.

Look at the code below:

#include<iostream>
using namespace::std;
class Date
{
    
    
public:
	//构造函数
	Date(int year = 1949, int month = 10, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造
	Date(const Date& A)
	{
    
    
		_year = A._year;
		_month = A._month;
		_day = A._day;
     }
	Date& operator=(const Date& d)
	{
    
    
		if (this != &d)
		{
    
    
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
    
    
	Date A;
	Date B = A;//这个代码不是赋值重载而是拷贝构造
	//这是已初始化的对象对另一个正在初始化的对象的赋值——拷贝构造。
	return 0;
}
  • Date B = A here ; pay extra attention when you see it - this is calling the copy constructor

How to prove:

  • Looking at the assembly code,
    Insert image description here
    the same function is called here - since the following is a copy constructor, it can be inferred that the above code is also a copy constructor .

Date class (exercise)

  • If you are interested, you can implement the period class:
    Here is the code for the date class first
#include<stdbool.h>
#include<assert.h>
#include<iostream>
using namespace::std;
class Date
{
    
    

public:
	// 获取某年某月的天数
	bool is_leap_year(int year)
	{
    
    
		if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
		{
    
    
			return true;
		}
		return false;
	}
	int GetMonthDay(int year, int month)
	{
    
    
		int day[13] = {
    
     0,31,28,31,30,31,30,31,31,30,31,30,31 };
		if (is_leap_year(year) && month == 2)
		{
    
    
			return 29;
		}
		else
		{
    
    
			return day[month];
		}
	}
	//检查一下输入或者赋值的时候是否日期非法
	bool is_legal_Date()
	{
    
    
		if (_month >= 1 && _month <= 12 && _day >= 1 && _day <= GetMonthDay(_year,_month))
		{
    
    
			return true;
		}
		else
		{
    
    
			return false;
		}
	}
	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	// 拷贝构造函数
	Date(const Date& d)
	{
    
    
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	// 赋值运算符重载
	Date& operator=(const Date& d)
	{
    
    
		_year = d._year;
		_month = d._month;
		_day = d._day;
		return *this;
	}
	// 析构函数
	~Date()
	{
    
    
		;
	}
	// 日期+=天数
	Date& operator+=(int day)
	{
    
    
		if (day < 0)
		{
    
    
			return *this -= -day;
		}
		_day += day;

		while (_day >= GetMonthDay(_year, _month))
		{
    
    
			int tmp = GetMonthDay(_year, _month);
			_day -= tmp;
			_month += 1;
			if (_month == 13)
			{
    
    
				_year += 1;
				_month = 1;
			}
		}
		return *this;
	}
	// 日期+天数
	Date operator+(int day)
	{
    
    
		Date tmp(*this);
		tmp += day;
		return tmp;
	}
	// 日期-天数
	Date operator-(int day)
	{
    
    
		Date tmp(*this);
		tmp._day -= day;
		while (tmp._day <= 0)//等于0不能忘了
		{
    
    
			int tmp1 = GetMonthDay(tmp._year, tmp._month - 1);
			if (tmp._month == 1)
			{
    
    
				tmp1 = GetMonthDay(tmp._year, 12);
			}
			tmp._day += tmp1;
			tmp._month--;
			if (tmp._month == 0)
			{
    
    
				tmp._year--;
				assert(tmp._year);
				tmp._month = 12;
			}
		}
		return tmp;
	}
	// 日期-=天数
	Date& operator-=(int day)
	{
    
    
		if (day < 0)
		{
    
    
			return *this += -day;
		}
		*this = *this - day;
		return *this;
	}
	// 前置++
	Date& operator++()
	{
    
    
		*this += 1;
		return *this;
	}
	// 后置++
	Date operator++(int)
	{
    
    
		Date tmp(*this);
		*this += 1;
		return tmp;
	}
	// 后置--
	Date operator--(int)
	{
    
    
		Date tmp(*this);
		*this -= 1;
		return tmp;
	}
	// 前置--
	Date& operator--()
	{
    
    
		*this -= 1;
		return *this;
	}
	// >运算符重载
	bool operator>(const Date& d)
	{
    
    
		if (_year > d._year)
		{
    
    
			return true;
		}
		else if (_year == d._year && _month > d._month)
		{
    
    
			return true;
		}
		else if (_year == d._year && _month == d._month && _day > d._day)
		{
    
    
			return true;
		}
		else
		{
    
    
			return false;
		}
	}
	// ==运算符重载
	bool operator==(const Date& d)
	{
    
    
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}
	// >=运算符重载
	bool operator >= (const Date& d)
	{
    
    
		return *this == d || *this > d;
	}
	// <运算符重载
	bool operator < (const Date& d)
	{
    
    
		return !(*this >= d);
	}
	// <=运算符重载
	bool operator <= (const Date& d)
	{
    
    
		return !(*this > d);
	}
	// !=运算符重载
	bool operator != (const Date& d)
	{
    
    
		return !(*this == d);
	}
	// 日期-日期 返回天数
	int operator-(const Date& d)
	{
    
    
		int day = 0;
		if (*this >= d)
		{
    
    
			Date tmp(d);
			while (*this != tmp)
			{
    
    
				++tmp;
				day++;
			}
			return day;
		}
		else
		{
    
    
			Date tmp(*this);
			while (tmp != d)
			{
    
    
				++tmp;
				day++;
			}
			return -day;
		}
	}
private:
	int _year;
	int _month;
	int _day;
};

5. Get address and reload

const member

Let’s take a look at this piece of code:

#include<iostream>
using namespace::std;
class Date
{
    
    
public:
	void Print()
	{
    
    
		cout << _year << _month << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;

};
int main()
{
    
    
	const Date A;
	A.Print();
	return 0;
}

Error reason:
Insert image description here

  • Analysis: This pointer is of type int *const this. Do you remember the const modification rules?
  • Example:
		定义一个变量int * const this
		因为:const具有就近原则
		所以:放在thisthis本身不能修改
		补充:放在*前说明*this不能被修改

Let’s look at the type of parameter we passed in - const Date (indicating that Date cannot be modified)

  • When converted into a pointer, *this cannot be modified, so it should be - const int* const this.
  • Why write it like this? ——Permissions cannot be enlarged, only reduced or panned .
  • Since - this pointer cannot be displayed, where should this const be added?
  • Grandmaster put it like this:
	void Print() const
	{
    
    
		cout << _year << _month << _day << endl;
	}
  • Why put it like this? Who in the family understands?
  • Guess: Maybe the founder couldn't think of a place to put it. . . .

Applicable scene:

    1. The passed parameter object has const modification.
    1. As long as the member variables are not modified inside the function.

Get address reload

class Date
{
    
    
public :
	Date* operator&()
	{
    
    
		return this ;
	}
private :
	int _year ; // 年
	int _month ; // 月
	int _day ; // 日
};

6. Const address reloading

class Date
{
    
    
public :

	const Date* operator&()const
	{
    
    
	return this ;
	}
private :
	int _year ; // 年
	int _month ; // 月
	int _day ; // 日
};
  • illustrate:
  1. Generally, it is enough for us to directly use the one generated by the default compiler .
  2. There are very few usage scenarios - just understand it

Guess you like

Origin blog.csdn.net/Shun_Hua/article/details/130474309