classes and objects
6 default functions of the class
In the data structure chapter, we use C language to write linked lists, stacks, queues, etc., each of which must be initialized and destroyed. In c++, in order to avoid these repeated operations and other problems, 6 functions are introduced. (the most important are the first four)
1. Constructor
1. Easy to use
Pay attention to the name here, construction is not creating objects, constructors are used for initialization. The destructor is not destroying the object, but to clean up the work.
This function replaces the initialization function. Its function is similar to the initialization function but the difference is that this function will be called automatically without writing codes such as d1.init().
2. Constructor overloading
If we don't want to initialize day, we can write another constructor.
3. Default constructor
The answer is yes, because every class creation will generate a constructor by default, so what is the printed result here?
What's going on here? It seems that the random value compiler does nothing. Actually not.
Next define a type. And debug to see how their values change.
If you don't know much about debugging here, you can take a look at this blog portal . We can see that it is not a random value, but the function automatically initializes the memory to cccccccc when the stack frame is created. For details, please refer to this blog portal . And we can see that our custom type will also be initialized to the same value. (ps: Some compilers will be initialized to 0, but this belongs to the optimization of the compiler, and c++ has no clear regulations, especially for versions above vs2019, it will be very strange here).
Let's look at a question
These are two constructors, which constitute overloading in syntax (if you don't know about overloading, you can read this portal ). However, since there is no need to pass parameters when calling, there will actually be conflicts, so this way of writing is actually not allowed.
Summary: The default constructor can be called without passing parameters.
2. Destructor
This is a stack, the front is a constructor to replace the initialization, and the back is a destructor to replace the original destroy to prevent memory leaks. Both functions are called automatically, so we can directly operate on the stack. .
In this way, you no longer have to worry about forgetting to initialize and destroy the data structure when using it.
3. Copy construction
For example, I want to initialize d2 with the initial value of d1
What should be noted here is why do we use references to pass parameters in the constructor? In fact, this is related to a rule of C++. In C++, if the parameter is a built-in type, then passing by value is a direct copy. If the passed parameter is a custom type, then passing the value is to call the copy construction to complete. So if we don't quote the symbol, then it will keep copying, resulting in an infinite loop.
Next, we will not write the copy structure to see if it can be copied normally.
It seems that the copy function generated by itself can complete the copy, and there is no need to write it yourself. In fact, it is not, because the default copy is simply copied by byte, so some more complex structures cannot be copied. For example, I want to copy a stack below.
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;
size_t _capacity;
};
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2(s1);
return 0;
}
We can see that it is indeed copied, but this will cause the system to crash. In fact, the problem lies in _array. Shallow copy is to copy the value. There is no problem with _size and _capacity, but _array is a pointer. After copying to s2, the two pointers point to the same space. The biggest problem is that when destructing, s1 is destructed once, and s2 is destructed again, causing the same space to be destructed twice, which leads to a system crash.
Therefore, we cannot let two pointers point to the same space, we must write a deep copy by ourselves.
Let’s take a brief look here, and I will explain it in detail later (because there are many scenes)
Four. Assignment operator overloading function
1. Operator overloading
one example
We know that for built-in types (such as int, double...) you can directly use > or <, = to compare, but what if it is a custom type? In c, we can only write a comparison function to judge by ourselves, for example as follows:
But this is not intuitive enough in c++, because if the function name is a popular name like Func, it will be a headache for people who read the code. So is there any way to directly write such obvious code as d1<d2? This depends on operator overloading.
You only need to add an operator and < symbol to the original judgment function to judge whether d1<d2.
There is another problem here, what if I set _year... to private? Private ones cannot be accessed externally, so we need to create functions in the class.
There may be doubts here, there should be two parameters, why only pass one parameter? This is because there is an invisible parameter this pointer, which is also a parameter.
2. Assignment operator overloading function
Note that this is distinguished from copy construction, which uses an existing object to initialize another object. And assignment function overloading is a copy between two objects that already exist.
This seems simple, and indeed it is the simplest case, but there are still some problems here. When assigning a built-in type, we can write a=b=c=4, so can the custom type also be written as d1=d2=d3?
What's going on?
Therefore, in order to be able to assign values continuously, we need the return value of d2 to be d2, so that the assignment can be completed successfully.
This is successful but not perfect, because in the previous reference, it was said that copying by value consumes too much ( portal ), so use references as much as possible when you can use references (why can you use references here? Because we return *this, That is, d2, then the return value always exists before d2 is destroyed)
So, in fact, if we don't write the default generated by the compiler here, it can still be compiled and passed. Of course, this is only for objects that are all built-in types and all custom types, such as Date and queue here, but you need to write them yourself if you have both, like the stack.
3. Date addition
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//private:
int _year;
int _month;
int _day;
int GetDay(int year, int month)
{
static int Month[13] = {
0,31,28,30,31,30,31,31,30,31,30,31 };
if (month == 2 && (year % 400 == 0) || (year % 4 == 0 && year % 100 != 0))
return 29;
return Month[month];
}
Date& operator+(int day)
{
_day += day;
while (_day > GetDay(_year, _month))
{
_day -= GetDay(_year, _month);
++_month;
if (_month == 13)
{
_month = 1;
++_year;
}
}
return *this;
}
};
int main()
{
Date d1(2023,4,29);
d1 + 3;
printf("%d %d %d", d1._year, d1._month, d1._day);
return 0;
}
You can find a problem, that is, we did get the desired result, but we changed the value of d1, so strictly speaking, what we achieve here is actually +=. So next, modify the code so that it only implements addition.
We need the same data as d1 but do not change d1, so we need to perform copy construction.
It should be noted that here tmp is temporarily copied out of scope and will be destroyed, so we cannot use references to avoid garbled characters when returning.
4. Pre ++ and Post ++
Addition is divided into preposition and postposition. In C language, we did not make a distinction, because the efficiency of the two additions is almost the same in the built-in type, but there is a problem in the custom type of C++. The pre-++ is the value after returning ++, and the post-++ is the value before returning ++, so two kinds of ++ need to use two operators to overload the function.
5. << Overload
In C++, there is an operator called stream insertion, which can automatically recognize the type.
How exactly does it work? In fact, it is not mysterious, it is just simple operator overloading. cout actually exists in an ostream class, and this class is in the iostream header file, so we only need to include this header file, and then expand the header file to use cout.
So how does cout automatically identify the type? In fact, it is just using multiple function overloads.
cout can automatically recognize that the built-in type is implemented in the library itself, so we have to overload it ourselves if we want to recognize the custom type.
It is a very simple overload, but it is obviously wrong, why? This is because cout is a binary operator. The first parameter is the left operand, and the second parameter is the right operand. If written here, cout is the first parameter, and d1 is the second parameter. In the member function To use overloading, you must first convert it to d1.oprator<<(cout), which is obviously impossible to pass parameters in this way (in short, Date takes up the first parameter by default). And << does not need a return value, so the return type can be changed to void.
Writing in this way can fulfill our expectations, but it does not conform to our usage habits, so can we write it as cout<<d1? It is not possible, but this can only modify the << in the ostream class in the library. So we can't write << as a member function, because it doesn't conform to our usage habits.
Write << as a global function
In this way, we can freely adjust our parameters when written as a global function, but there is still a problem that if _year, _month... are all private and cannot be accessed directly, there are two ways to solve this problem.
1. Add a function to the class to return data directly (commonly used in Java)
2. Friend function
It is very simple to use, just declare in the class and add a friend in front, so that the function can access the private of the class.
What if I want continuous inflow?
Like the previous continuous assignment, only one return value is required.
6. const members
As an example
I defined a print function in the class and called it with two objects.
Here d1 can be called but d2 can't, why? This is because const is added in front of d2, the parameter passed by d2 is const Date , and the parameter in the Print function is Date , so passing it in the past is a privilege amplification, and naturally it cannot be compiled. So how to solve this problem? **
There is a very simple method, which is to change the parameters in the Print function to const Date*. But we can't change it directly, because this is a hidden parameter and cannot be modified directly. So c++ has introduced a new way to add const outside the brackets.
Pay special attention to the const here to modify *this. So const is only applicable to member functions that will not change (such as +=, -= that need to change the value cannot be used)