[C + +] object oriented encapsulation

2, Encapsulation (Part 2)

4.1 object arrays and object members

(1) Object array

Many times, we need not only one object, but a group of objects. For example, if there are 50 students in a class, we can use the object array.

[object array code practice]
Title: define a Coordinate class whose data members contain abscissa and ordinate. Instantiate an object array with length of 3 from the stack and heap respectively, assign values to the elements in the array respectively, and finally traverse the two arrays.

Header file:

class Coordinate{
public:
    Coordinate();
    ~Coordinate ();
public:
    int m_iX;
    int m_iY;
};

Source program:

#include<iostream>
#include<stdlib.h>
#include"Coordinate.h"

using namespace std;

/* object array
/* requirement
      1. Define Coordiante class
      2. Data member: m_iX,m_iY
      3. Instantiate an object array of length 3 from the stack and heap respectively
      4. Assign separate values to the elements in the array
      5. Traverse two arrays
/* *****************************************/


Coordinate::Coordinate(){
    cout <<"Coordinate()"<<endl;
}

Coordinate::~Coordinate (){
    cout <<"~Coordinate()"<< endl;
}


int main(){
    Coordinate coor[3]; //Instantiate an array of objects from the stack
    coor[0].m_iX =3;
    coor[0].m_iY =5;


    Coordinate *p =new Coordinate[3];
    p->m_iX = 7; //If you write p directly, it means that it is the first element
    p[0].m_iY =9; //Equivalent to p - > m_ iY = 9

    p++; //Move the pointer back one position to point to the second element
    p->m_iX = 11;
    p[0].m_iY = 13; //Here, P points to the second element, and p[0] is the current element, which is equivalent to p - > M_ iY = 13

    p[1].m_iX = 15;//Abscissa of the third element
    p++; //Move the pointer back one position to point to the third element
    p[0].m_iY = 17;//Here, P points to the third element, and p[0] is the current element, which is equivalent to p - > M_ iY = 17


    for(int i = 0; i < 3; i++){
        cout <<"coor_X: "<< coor[i].m_iX <<endl;
        cout <<"coor_Y: "<< coor[i].m_iY <<endl;
    }
    for(int j = 0; j < 3; j++){
        //If the above p has not been operated by + +, you can poll according to the following
        //cout <<"p_X: " << p[i].m_iX <<endl;
        //cout <<"p_Y: " << p[i].m_iY <<endl;
        //However, we have done two + + operations on p above. In fact, p has pointed to the third element, which should be operated as follows
        cout <<"p_X: "<< p->m_iX <<endl;
        cout <<"p_Y: "<< p->m_iY <<endl;
        p--;
    }

    //After three cycles, p points to an illegal memory. Instead of deleting it directly, p should point to an element we applied for, as shown below
    p++; //So p points to the memory we applied for
    delete []p;
    p = NULL;


    system("pause");
    return 0;
}

result:

Coordinate()
Coordinate()
Coordinate()
Coordinate()
Coordinate()
Coordinate()
coor_X: 3
coor_Y: 5
coor_X: -858993460
coor_Y: -858993460
coor_X: -858993460
coor_Y: -858993460
p_X: 15
p_Y: 17
p_X: 11
p_Y: 13
p_X: 7
p_Y: 9
~Coordinate()
~Coordinate()
~Coordinate()
Please press any key to continue. . .

From the running results, the first thing to see is to print out six lines of "coordinating()", which is because the object array with length of 3 is instantiated from the stack and the object array with length of 3 is instantiated from the heap respectively. The default constructor must be called every time an object is instantiated.

Finally, only three lines of "~ Coordinate()" are printed. Is it just that the destructor is called when the object instantiated from the heap is destroyed, and the destructor is also called when the object instantiated from the stack is destroyed
——When the object instantiated from the stack is destroyed, the system will automatically reclaim memory, that is, automatically call the destructor. Only when we follow the prompt "please press any key to end". After pressing any key, the screen will flash. In the process of flashing, three lines of "~ Coordinate()" will appear, but it is not easy for us to see.

(2) Object member

The data members of the above-mentioned classes are all basic data types. For example, for the automobile class, we only declare the number of car wheels, which is obviously not enough, because the wheel itself is an object, and there are objects such as engine and seat on the car.

The following rectangular coordinates, starting point A and ending point B, need to define such line segment class and point coordinate class

[object member code practice]

Define two classes:

(1) Coordinate type: Coordinate
Data member: abscissa m_iX, ordinate m_iY

Member functions: constructors, destructors, and encapsulation functions of data members

(2) Segment class: Line
Data member: point A m_coorA, point B m_coorB

Member functions: constructor, destructor, data member encapsulation function, information printing function

Header file coordinate h:

class Coordinate{
public:
    Coordinate();
    ~Coordinate();
    void setX(int x);
    int getX();
    void setY(int y);
    int getY();
private:
    int m_iX;
    int m_iY;
};

Header file line h:

#include "Coordinate.h"

class Line{
public:
    Line();
    ~Line();
    void setA(int x, int y);
    void setB(int x, int y);
    void printInfo();
private:
    Coordinate m_coorA;
    Coordinate m_coorB;
};

Source program coordinate cpp:

#include <iostream>
#include "Coordinate.h"
using namespace std;

Coordinate::Coordinate (){
    cout <<"Coordinate()"<<endl;
}

Coordinate::~Coordinate (){
    cout <<"~Coordinate()"<<endl;
}
void Coordinate::setX(int x){
    m_iX = x;
}
int Coordinate::getX(){
    return m_iX;
}
void Coordinate::setY(int y){
    m_iY = y;
}
int Coordinate::getY(){
    return m_iY;
}

Source program line h:

#include<iostream>
#include "Line.h"
//#include "Coordinate.h"

using namespace std;

Line::Line(){
    cout <<"Line()"<< endl;
}
Line::~Line(){
    cout <<"~Line()"<< endl;
}
void Line::setA(int x, int y){
    m_coorA.setX(x);
    m_coorA.setY(y);    
}
void Line::setB(int x, int y){
    m_coorB.setX(x);
    m_coorB.setY(y);
}
void Line::printInfo(){
    cout << "(" << m_coorA.getX() <<","<< m_coorA.getY()<< ")" <<endl;
    cout << "(" << m_coorB.getX() <<","<< m_coorB.getY()<< ")" <<endl;
}

Source demo cpp:

//Let's first instantiate an object of line segment class, as follows
#include <iostream>
#include "Line.h"

using namespace std;

int main(){
    Line *p = new Line();
    delete p;
    p = NULL;
    system("pause");
    return 0;
}

From the running results, the constructor of the coordinate class is called twice in succession, and then the constructor of the line segment class is called once, which means that the objects of the two coordinate classes are created first. The objects of the two coordinate classes are point A and point B, and then the line segment object is called. The line segment object is created after the initialization of point A and point B.

When destroying, the destructor of line segment class is called first, and then the destructor of coordinate class is called twice in succession. It can be seen that the process of creating and destroying object members is just the opposite, which also verifies our previous conclusions.

As a line segment, we hope that the starting point and ending point of the line segment have been determined when the line segment is created. In order to achieve this goal, we often hope that the constructor of line segment class has parameters, and this parameter can be passed to these two points in the future, so we can further improve this program.

4.2 deep copy and shallow copy

In the encapsulation (Part 1), I learned the declaration method and automatic call time of the copy constructor, but how to implement the copy constructor? It can be divided into deep copy and shallow copy.

[chestnut 1] member variable has no pointer

  • In the above Chestnut: a data member (m_iCount) is defined in the class (Array) of an Array defined, and a constructor is defined, in which the initial value of 5 is assigned to the data member.
  • A copy constructor is also defined. The copy constructor is implemented in this way: the passed in parameter is arr, and the data type of this parameter is also an Array class object, so it must also contain the data member M_ Icount, where m is the data member of arr_ Icount is assigned to its own M_ icount.
  • When we use Array arr1 to instantiate an arr1, we will call the constructor of arr1, that is, the data member m in arr1_ Icount is assigned an initial value of 5.
  • When we use Array arr2 = arr1, that is, we use arr1 to initialize arr2 - when instantiating arr2, we will call its copy constructor. The parameter arr in the copy constructor is actually arr1. When the code is implemented, it is equivalent to the data member m of arr1_ Icount is assigned to the data member m of arr2_ icount.

[chestnut 2] member variable has one more pointer


In this example, we add a new data member, which is an int pointer m_pArr, which applies for a section of memory from the heap in the constructor and points to the applied section of memory. The size of memory is m_icount.

(1) When we use Array arr1 to instantiate an arr1, we will call the constructor of arr1, that is, the data member m in arr1_ Icount is assigned an initial value of 5.

(2) When we use Array arr2 = arr1, that is, we use arr1 to initialize arr2. At this time, when instantiating arr2, we will call its copy constructor, so we will use the data member m of arr1_ Icount is assigned to the data member m of arr2_ Icount, the data member m of arr1_ Parr is assigned to the data member m of arr2_ pArr.

  • In these two examples, there is a common feature, that is, only the values of data members are simply copied. We call this copy mode shallow copy.

  • But for the first example, there is no problem using shallow copy to implement the copy constructor, while for the second example, there must be a problem - after shallow copy, The pointer in object arr1 and the pointer in object arr2 are bound to point to the same block of memory (because we assign the data member m_pArr of arr1 to the data member m_pArr of arr2). Here, it is assumed that the address pointed to is 0x00FF00 (as shown in the figure below).

  • At this time, if we first give m of arr1_ Parr is assigned some values, that is, some values are written in this memory, and then we give m of arr1_ When ARP is rewritten, the memory will be overwritten by ARP_ Some values assigned by parr.
  • The more serious problem is that when we destroy the object of arr1, we will release m in order to avoid memory leakage_ The memory pointed to by parr. If we have released this memory, when we destroy the object of arr2, we will release m in arr2 in the same way_ The memory pointed to by the pointer Parr is equivalent to that the same memory is released twice, resulting in an error.
  • Therefore, we hope that the work done by the copy constructor here, the pointers of two objects should point to two different memories. When copying, we do not simply copy the address of the pointer, but copy each element in the memory pointed to by the pointer in turn. (as shown below)


In order to achieve the above effect, the following modifications are made:

The difference between this code and the previous code is that it copies the constructor, where m_pArr is not directly assigned to m in arr_ Parr, but allocate a section of memory first (PS: whether this section of memory is allocated successfully or not is not judged here, because this is not the focus here). The focus is the following for loop statement. We should put m in arr_ Every element of Parr is copied to the current M_ The corresponding memory pointed to by parr.

Summary: when copying objects, instead of simply copying values, the data in the heap is also copied (deep copy).

[practice of deep and shallow code copying]

  • Define an Array class.
    • Data member: m_iCount
    • Member functions: constructor, copy constructor, destructor
    • Encapsulation function of data member
    • It is required to experience the shallow copy principle through this example

Header file array h

class Array{
public:
	//Constructor
    Array();
	//copy constructor 
    Array(const Array &arr);
    ~Array();
    void setCount(int count);
    int getCount();
private:
	//Unique member variable
    int m_iCount;
};

And the array The CPP source program is:

#include"Array.h"
#include<iostream>
using namespace std;

Array::Array(){
    cout <<"Array()"<< endl;
}
//copy constructor 
Array::Array(const Array &arr){
    m_iCount = arr.m_iCount;
    cout <<"Array(const Array &arr)"<<endl;
}
Array::~Array(){
    cout <<"~Array()"<< endl;
}
void Array::setCount(int count){
    m_iCount = count;
}
int Array::getCount(){
    return m_iCount;
}

main. The CPP source program is:

#include<iostream>
#include<stdlib.h>
#include"Array.h"

using namespace std;

int main(){
    Array arr1;
    arr1.setCount(5);

	//Instantiate arr2 through Arr1
    Array arr2(arr1); 
	
    cout <<"arr2.m_iCount"<<" "<< arr2.getCount() << endl;

    system("pause");
    return 0;
}

  • Add a data member based on 1: m_pArr
    • And increase m_pArr address viewing function
    • At the same time, transform the constructor, copy constructor and destructor
    • It is required to understand the principle and necessity of deep copy through this example

The modified chestnuts are as follows:
Array.h add data member m_iCount:

class Array{
public:
	//Constructor
    Array(int count);
	//copy constructor 
    Array(const Array &arr);
    ~Array();
    void setCount(int count);
    int getCount();

	//Add view address function
	void printAddr();
private:
	//Unique member variable
    int m_iCount;
	
	//New data member: m_pArr
	int *m_pArr;
};

Source program array CPP, please note that here, let's try the copy constructor with shallow copy first:

#include"Array.h"
#include<iostream>
using namespace std;

Array::Array(int count){
	m_iCount = count;
	//Allocate memory on heap
	m_pArr = new int[m_iCount];
}
Array::Array(const Array &arr){
    m_iCount = arr.m_iCount;
    m_pArr = arr.m_pArr;//Let's first use the shallow copy implementation to see what the consequences will be?
    cout <<"Array(const Array &arr)"<<endl;
}
//Destructor
Array::~Array(){
	//delete array pointer
    delete []m_pArr;
    m_pArr = NULL;
    cout <<"~Array()"<< endl;
}
void Array::setCount(int count){
    m_iCount = count;
}
int Array::getCount(){
    return m_iCount;
}
void Array::printAddr(){
    cout <<"m_pArr The values are:"<< m_pArr << endl;
}

Source program main CPP. Here, arr2 is instantiated through Arr1:

#include<iostream>
#include<stdlib.h>
#include"Array.h"

using namespace std;

int main(){
    //Array arr1;
    //Modified chestnuts
	Array arr1(5);
	
	//Delete this sentence this time
	//arr1.setCount(5);
	
	//Instantiate arr2 through Arr1
    Array arr2(arr1); 
	
    //cout <<"arr2.m_iCount"<<" "<< arr2.getCount() << endl;
	cout << "arr1 in";
	arr1.printAddr();
	cout << "arr2 in";
	arr2.printAddr();

    system("pause");
    return 0;
}

Obviously, it can be found that after the copy constructor is executed, the m of arr1 and arr2_ The value of Parr is the same, that is, two pointers point to the same address (memory), and the same block of memory will be released twice during destruct, resulting in an error.

The reason why there is an error in the above log is that the program finally adds system("pause"); If you continue to press any key, the following error will be reported, that is, the destructor ~ Array() is executed only once.

For the deep copy method, you need to allocate a section of memory for the current pointer in the copy constructor, and then copy the memory of the corresponding position of the incoming object to the newly applied section of memory. Code changed to deep copy (only need to modify constructor and copy constructor):

Array::Array(int count)
{
    m_iCount = count;
    m_pArr = new int[m_iCount];
    for(int i =0; i < m_iCount; i++)
    {
        m_pArr[i] = i;
    }
    cout <<"Array()"<< endl;
}
Array::Array(const Array &arr)
{
    m_iCount = arr.m_iCount;
    m_pArr = new int[m_iCount];
    for(int i = 0; i < m_iCount; i++)
    {
        m_pArr[i] = arr.m_pArr[i];
    }
    cout <<"Array(const Array &arr)"<< endl;
}

And after pressing any key this time, the program did not collapse:

4.3 object pointer and object member pointer

(1) Object pointer


Defines a Coordinate class, which has two data members (one represents abscissa and one represents ordinate). After we define this class, we can instantiate it. If we want to instantiate this object in the heap, it should be as follows:

After instantiating an object through the new operator (the object will execute its constructor), the object pointer p will point to the object. Our focus is to explain the relevant location of p and this object in memory and the corresponding relationship between them.

When we instantiate an object in this way, its essence is to allocate a space in memory, in which the abscissa (m_iX) and ordinate (m_iY) are stored. At this time, M_ The address of IX should be consistent with the address saved by P, that is, P points to the first element (m_iX) of the object. If you want to use p to access this element, it is very simple. You can access it in this way (P - > m_iX or P - > m_iY). You can also add * before p to make the pointer become an object, and then use the dot (.) To access relevant data members (such as (* P) m_ iY).

Note: the new operator here can automatically call the constructor of the object, while malloc in C language only allocates memory and does not automatically call the constructor.

(2) Object pointer code practice

Define the Coordinate class:

  • Data member: m_iX and m_iY
  • Declare an object pointer and manipulate the object through the pointer
  • Calculate the sum of two points, abscissa and ordinate

(3) Object member pointer

Object member means that as an object, it becomes a data member of another class. As for the object member pointer, the object pointer becomes the data member of another class.

(4) Object member pointer in memory


When instantiating the line object, two pointers (m_pCoorA and m_pCoorB) will also be defined. Since both pointers are pointer types, they will occupy four basic memory units. If we apply for memory from the heap through operators like new in the constructor and instantiate two Coordinate objects, these two Coordinate objects are in the heap, not in the line object, so when we used sizeof just now, we can only get 8, because M_ Pcoora occupies 4 basic memory units, M_ Pcoorb occupies four basic memory units, and the two Coordinate objects on the right are not in the memory of line. When we destroy the line object, we should also release the memory in the heap first, and then release the line object.

(5) Object member pointer code practice

Define two classes:
Coordinate class: coordinate
Data member: m_iX and m_iY
Member functions: constructor, Xigou function, data member encapsulation function

Segment class: Line
Data member: point A pointer m_pCoorA, point B pointer m_pCoorB
Member functions: constructor, destructor, information printing function

The change between here and before is that the member in the Line class is the Coordinate class object, and here is the Coordinate class object pointer.

#include"Coordinate.h"

class Line
{
public:
    Line(int x1, int y1, int x2, int y2);
    ~Line();
    void printInfo();
private:
    Coordinate *m_pCoorA;
    Coordinate *m_pCoorB;
};

4.4 this pointer

this pointer is a pointer to its own data.

Every object in C + + can access its own address through this pointer. This pointer is the implicit parameter of all member functions, so it can be used to point to the calling object inside the member function.

PS: friend functions do not have this pointer, because friend functions are not members of a class. Only member functions have this pointer.

#include <iostream>
 
using namespace std;
 
class Box{
   public:
      // Constructor definition
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
      }
      double Volume(){
         return length * breadth * height;
      }
      int compare(Box box){
         return this->Volume() > box.Volume();
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};
 
int main(void){
   Box Box1(3.3, 1.2, 1.5);    // Declare box1
   Box Box2(8.5, 6.0, 2.0);    // Declare box2
 
   if(Box1.compare(Box2)){
      cout << "Box2 is smaller than Box1" <<endl;
   }
   else{
      cout << "Box2 is equal to or larger than Box1" <<endl;
   }
   return 0;
}




[this pointer code practice]
Define an Array class:
Data member: m_iLen indicates the length of the array
Member functions: constructor # destructor # M_ Encapsulation function of iLen
Information printing function printInfo

4.5 const advanced

The const section can be reviewed [C + + basic] reference usage and const constant reference.

4.6 constant pointer and constant reference

(1) Object reference and object pointer

Let's take a look at the following chestnuts. There are three member functions in the class, of which printInfo() function is a constant member function. When implementing, you also need to add const keyword after printInfo function to modify:

class Coordinate{
public:
	Coordinate(int x, int y);
	int getX();
	int getY();
	//Constant member function
	void printInfo() const;
private:
	int m_iX;
	int m_iY;
};

Add const after the definition of member function:
1. A mechanism of C + + makes the permission of the function read-only, that is, it cannot change the value of member variables.

2. At the same time, if an object is const, it only has the right to call const function, because the member variable cannot be changed.

For example, a class FooClass is defined:

class FooClass{
public:
    void Foo(){ /*...*/}
private:
    /*...*/
};

//Create A constant object A of FooClass in the program and try to call this member function Foo()
const FooClass A; 
A.Foo();

Calling the member function Foo() on A will make an error!

A is a const object, but Foo() can only be used for non const objects. When defining the member function Foo(), it is obvious that the object calling it cannot be written into the formal parameter list and declared as a const, such as Foo(const FooClass* this). How can Foo() be used for const objects? Is to add a const declaration to Foo(), as follows:

class FooClass{
public:
    void Foo() const{ /*...*/}
private:
    /*...*/
};

At this time, the member function can be called for const A created earlier.

In fact, the member function Foo() has an implicit formal parameter this, which is a pointer to its own object, but it cannot be explicitly used in the formal parameter list of Foo(). Adding const indicates that the object this points to is const object.

Of course, the member function with const declaration cannot modify the member in the object calling it (except the member declared as mutable).

(2) Constant references and pointers to objects

Reference

[1] https://www.zhihu.com/question/27860418

Keywords: C++ Back-end

Added by martins on Sun, 27 Feb 2022 06:00:56 +0200