List of articles
The default member function of the class
Any class , In the absence of an explicit definition , The compiler will automatically generate 6 Default member functions .
Constructors 、 Destructor 、 Copy structure and Assignment overload Is the focus of our study .
Constructors
Many times, creating an object requires initialization . stay C In language , We will write an initialization function and call it , A little bit of a problem , And it's easy to forget to call .C++ The constructor of makes up for this hole .
Constructor is special Member functions , Its task is to initialize objects , When an object is instantiated The compiler will Automatically call The corresponding constructor .
its features as follows :
- Function name And Class name identical .
- No return value .
- Constructors Can overload .
For the following date classes , Two constructors are written here
class Date
{
public:
Date() // Constructors
{
_year = 1;
_month = 1;
_day = 1;
}
Date(int year, int month, int day) // Constructor overload
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
void test1()
{
Date d1;// The constructor automatically calls at this point
d1.Print();// result :1-1-1
Date d2(1919, 8, 10);// The second constructor was called
d2.Print();// result :1919-8-10
}
: Be careful : To call a parameterless constructor , Must be like the above , It's written in Date d1;
, It can't be written Date d1();
, This becomes a function declaration .
You can also use the default parameters , With this constructor , Neither of the above two is needed .
Be careful : Full default constructors and nonparametric constructors constitute function overloads that can exist at the same time , But it cannot be called without parameters , Because there will be ambiguity .
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
- Concept : Both the parameterless constructor and the full default constructor are called Default constructor , Because they can be called without parameters . also The default constructor can only have one . Default constructor Are all default member functions .
Compiler generated default constructor :
If we don't explicitly define a constructor , The compiler will automatically generate a parameterless default constructor , Once explicitly defined , The compiler no longer generates .
- Be careful :
Default generated constructor about Built in type members are not processed , about Only the member variables of the custom type will be processed
Built in type / Basic types : such as int/char/double/ The pointer …
Custom type :class/struct The type of definition
Here's a definition of A
class ,Date
Class A
A member variable of type ,Date
Class, we don't write constructors , See what happens .
class A
{
public:
A()
{
cout << "A()" << endl;
_a = 0;
}
private:
int _a;
};
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
A _aa;
};
void test1()
{
Date d1;
d1.Print();
// result :
//A()
//-858993460--858993460--858993460
}
In the end, we found out ,A
The default constructor of the class is called ,_a
Is initialized to 0,Date
The other three built-in type member variables of the class are random values .
The way to handle a custom type member variable is to call its default constructor .
summary : If all the members in a class are custom types , We can use the default generated constructor , If there are members of built-in types , Or you need to explicitly pass parameter initialization , Then you have to implement your own constructor .
This is actually C++ A flaw in design , To make up for it ,C++11 Default values for built-in type member variables are supported , For the compiler generated default constructor .
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year = 1; // The default value
int _month = 1;
int _day = 1;
A _aa;
};
void test1()
{
Date d1;
d1.Print();
}
// result :
//A()
//1-1-1
Destructor
Destructors are also special member functions , Its function is the opposite of constructor , It's in Automatically call... When the object is destroyed , Complete some of the class Resources to clean up Work .
characteristic :
- The destructor name is
~
+ Class name . - No parameter, no return value .
- A class has only one destructor , If not explicitly defined , The system will automatically generate the default destructor
class Date
{
public:
~Date() // Destructor
{
cout << "~Date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
void test1()
{
Date d1;
}
// result :~Date()
Actually, the date class has no resources to clean up . There is a need to clean up resources for dynamic space allocation , Otherwise, memory leakage will occur , For example, the stack we learned before
stay C++ The constructor and destructor in are written as follows :
class Stack
{
public:
Stack(int capacity = 10) // Constructors
{
_a = (int*)malloc(sizeof(int) * capacity);
assert(_a);
_top = 0;
_capacity = capacity;
}
~Stack() // Destructor
{
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
- Little knowledge : Destructors are called in the reverse order of constructors , Because the object is defined first and then destroyed , The object defined after is destroyed first .
Let's verify , Use this Stack
Class defines two objects respectively , Initialize the incoming different _capacity
Distinguish , Print when destructor is called _capacity
To see the sequence of calls .
class Stack
{
public:
Stack(int capacity = 10) // Constructors
{
_a = (int*)malloc(sizeof(int) * capacity);
assert(_a);
_top = 0;
_capacity = capacity;
}
~Stack() // Destructor
{
cout << _capacity << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
void test2()
{
Stack s1(1);
Stack s2(2);
}
// result :
//2
//1
The result is indeed the reverse of the order in which the objects are constructed .
- Like constructors , The destructor generated by the compiler by default does not handle the built-in type member variables , Only the user-defined type member variables are processed .
copy constructor
Copy structure , That is to construct an object that is the same as an existing object .
We don't write , You can use the compiler generated copy constructor directly :
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;
};
void test3()
{
Date d1(1919, 8, 10);// Construct and initialize d1
Date d2(d1);// use d1 To copy d2
d2.Print();// Print d2
}
// result :1919-8-10
characteristic :
- The copy construct is an overloaded form of the constructor .
- The copy construct has only one reference to pass parameters , Using value passing calls will cause infinite recursive calls .
Explicit writing , Be sure to use reference to pass parameters :
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
: If you are not right d
Make changes , Then it is suggested to add const
. Because the permission can be reduced , Can't zoom in , This ensures const
Modified variables can also be passed as parameters .
Why does calling by value cause infinite recursion ?
answer : For built-in types , We know that its value passing calls will produce additional copies . So for custom types , Its value passing calls need to copy the structure , The copy structure itself needs to be called by value , So there is infinite recursion . No additional copies are required to pass parameters by reference , This problem will not occur .
Be careful : To the following Stack class , Using the default copy structure directly can cause problems .
class Stack
{
public:
Stack(int capacity = 10) // Constructors
{
_a = (int*)malloc(sizeof(int) * capacity);
assert(_a);
_top = 0;
_capacity = capacity;
}
~Stack() // Destructor
{
cout << _capacity << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
void test2()
{
Stack s1(1);
Stack s2(s1);
}
This involves the problem of deep and shallow copy , The default copy constructor is shallow copy , It's similar to using memcpy Directly copy the value of this object to another object , That results in two objects _a
The pointer points to the same space , The two destructors that are then called make _a
The space pointed to was released twice , And then cause the program to crash .
This requires us to implement a deep copy constructor ourselves .
- For built-in types, complete a shallow copy , For members of custom types , Will go to the copy structure of this member .
Operator overloading
characteristic
Built in types can be used directly <
,>
,==
And so on , What if the custom type also wants to use these ?C++ Operator overloading of solves this problem .
Operator overloading is a kind of Special functions , Its implementation requires keywords operator
The function prototype : return type
operator
+ Operator to overload ( parameter list );
operator
+ The operator to be overloaded is the function name of the function .The number of arguments is determined by the operands of the operator .
The return value is the result of the operator operation .
If yes Date Class overload ==
:
If the operator overload is written outside the class , Just like this. :
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
So here comes the question , Because you want to access the private members in the class , You must make the data in the class public , Or provide a function interface to access private members , Or set up friends , These methods are either too complex or break the encapsulation , It's not right .
Let's set the member variable to public first :
void test4()
{
Date d1(1919, 8, 10);
Date d2(1919, 8, 10);
if (operator==(d1, d2))//①
{
cout << "operator==(d1, d2)" << endl;
}
if (d1 == d2)//②
{
cout << "d1 == d2" << endl;
}
}
// result :
//operator==(d1, d2)
//d1 == d2
: The above two forms of invocation are equivalent , But for readability we must choose ②.
Actually It is best to write operator overloading directly in the class :
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
This is still two parameters , Because there is an implied this The pointer .
Invocation time d1 == d2
Equivalent to d1.operator==(d2)
Be careful : If the above two overloaded functions exist at the same time , The compiler will call the... In the class first .
heavy load <
:
bool operator<(const Date& d)
{
return ((_year < d._year)
|| (_year == d._year && _month < d._month)
|| (_year == d._year && _month == d._month && _day < d._day));
}
Be careful :
- You cannot create a new operator by concatenating other symbols : Such as operator@
- Overloaded operator has at least one custom type parameter , We cannot overload built-in types directly .
- Operator overloading as a member function will appear to be one less parameter , In fact, there is an implied this The pointer .
.*
::
sizeof
?:
.
this 5 Operators cannot be overloaded : Interviews often test
Assignment overload
Copy construction is to initialize another object to be created with an existing object , Assignment overload is the assignment between two existing objects .
Assignment overloading is one of the default member functions of a class .
Date Class, we explicitly write :
Be careful :
- The assignment of a built-in type has a return value , Because continuous assignment is supported , Such as
i = j = k
,k
Assign a value toj
You should return toj
, thenj
Can be assigned toi
- Prevent yourself from assigning values to yourself
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
We don't write , The assignment overload automatically generated by the compiler will complete the shallow copy .
const member
characteristic
Let's start with the following program
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
void func(const Date& d)
{
d.Print(); // Report here : Object contains and members function "Date::Print" Incompatible type qualifier
}
void test5()
{
Date d(1919, 8, 10);
func(d);
}
Why is it wrong ?
It's because this The pointer appears Permission amplification , because d
By const
modification ,&d
The type of const Date*
and this The default pointer type is Date* const
, The content pointed to by the pointer becomes changeable , It is the enlargement of authority .
As mentioned in the previous article , Implicit in the parameter this Pointers we can't write explicitly , So where to add this const Well ?
We can add it directly after the member function :
void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
such this The type of pointer becomes const Date* const
Be careful :
- It is recommended to add... To all member functions that do not modify member variables
const
, So ordinary objects andconst
Object can call . - If the declaration and definition are separated , Statements and definitions should be added
const
const
Member functions cannot call other nonconst
Member functions
Overload the address operator
Write explicitly :
Date* operator&()
{
return this;
}
const Date* operator&() const
{
return this;
}
We usually don't write these two by ourselves , The compiler generated by itself is enough .
thank