[c++ basics] (4) default member function | operator overloading | const member of class and object (medium) class

Shizhen2022-06-23 18:02:56

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 .

img

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 :

  1. Function name And Class name identical .
  2. No return value .
  3. 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;
}
  1. 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 .

  1. 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

  1. The destructor name is ~+ Class name .
  2. No parameter, no return value .
  3. 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;
};

  1. 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 .


  1. 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

  1. The copy construct is an overloaded form of the constructor .
  2. 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 .

  1. 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 to j You should return to j, then j Can be assigned to i
  • 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 and const Object can call .
  • If the declaration and definition are separated , Statements and definitions should be added const
  • const Member functions cannot call other non const 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
Similar articles