4, Classes and objects

Posted by krystof78 on Mon, 28 Feb 2022 02:17:14 +0100

4 classes and objects

The three characteristics of C + + object-oriented are encapsulation, inheritance and polymorphism

C + + believes that everything is an object, and the object has its properties and behavior

For example:

People can be objects. Their attributes include name, age, height, weight... And their behaviors include walking, running, jumping, eating, singing

Cars can also be used as objects. Their properties include tires, steering wheel, lights... And their behaviors include carrying people, playing music, playing air conditioning

Objects with the same properties can be abstractly called classes. People belong to humans and cars belong to cars

4.1 packaging

4.1.1 significance of packaging

Encapsulation is one of the three characteristics of C + + object-oriented

Meaning of encapsulation:

  • Take attributes and behaviors as a whole to express things in life
  • Control attributes and behaviors with permission

Packaging significance I:

When designing classes, attributes and behaviors are written together to represent things

Syntax: class name {access rights: attribute / behavior};

**Example 1: * * design a circle class to calculate the circumference of a circle

Example code:

//PI
const double PI = 3.14;

//1. Meaning of encapsulation
//Attributes and behaviors as a whole are used to express things in life

//Encapsulate a circle class to find the circumference of the circle
//Class stands for designing a class, followed by the class name
class Circle
{
public:  //Access rights public rights

	//attribute
	int m_r;//radius

	//behavior
	//Gets the circumference of the circle
	double calculateZC()
	{
		//2 * pi  * r
		//Gets the circumference of the circle
		return  2 * PI * m_r;
	}
};

int main() {

	//Through the circle class, create the object of the circle
	// c1 is a concrete circle
	Circle c1;
	c1.m_r = 10; //Assign a value to the radius of the circle object

	//2 * pi * 10 = = 62.8
	cout << "The circumference of the circle is: " << c1.calculateZC() << endl;

	system("pause");

	return 0;
}

Example 2: design a student class. The attributes include name and student number. You can assign values to the name and student number, and display the student's name and student number

Example 2 Code:

//Student class
class Student {
public:
	void setName(string name) {
		m_name = name;
	}
	void setID(int id) {
		m_id = id;
	}

	void showStudent() {
		cout << "name:" << m_name << " ID:" << m_id << endl;
	}
public:
	string m_name;
	int m_id;
};

int main() {

	Student stu;
	stu.setName("Demacia");
	stu.setID(250);
	stu.showStudent();

	system("pause");

	return 0;
}

Packaging Significance 2:

When designing classes, attributes and behaviors can be controlled under different permissions

There are three types of access rights:

  1. Public public permission
  2. Protected protected permissions
  3. Private private rights

Example:

//Three permissions
//Public permission public can be accessed inside the class and outside the class
//protected permission can be accessed inside the class, but not outside the class
//Private permission private can be accessed inside the class, but not outside the class

class Person
{
	//Name public authority
public:
	string m_Name;

	//Vehicle protection authority
protected:
	string m_Car;

	//Private permission of bank card password
private:
	int m_Password;

public:
	void func()
	{
		m_Name = "Zhang San";
		m_Car = "Tractor";
		m_Password = 123456;
	}
};

int main() {

	Person p;
	p.m_Name = "Li Si";
	//p.m_Car = "Benz"// Cannot access outside the protection permission class
	//p.m_Password = 123; // Cannot access outside private permission class

	system("pause");

	return 0;
}

4.1.2 differences between struct and class

In C + +, the only difference between struct and class is that the default access permissions are different

difference:

  • The default permission of struct is public
  • class default permission is private
class C1
{
	int  m_A; //The default is private permissions
};

struct C2
{
	int m_A;  //The default is public permission
};

int main() {

	C1 c1;
	c1.m_A = 10; //Error, access is private

	C2 c2;
	c2.m_A = 10; //Correct, access is public

	system("pause");

	return 0;
}

4.1.3 set member property to private

Advantage 1: set all member properties to private, and you can control the read and write permissions yourself

Advantage 2: for write permission, we can detect the validity of data

Example:

class Person {
public:

	//The name setting is readable and writable
	void setName(string name) {
		m_Name = name;
	}
	string getName()
	{
		return m_Name;
	}


	//Get age 
	int getAge() {
		return m_Age;
	}
	//Set age
	void setAge(int age) {
		if (age < 0 || age > 150) {
			cout << "You old goblin!" << endl;
			return;
		}
		m_Age = age;
	}

	//Lover set to write only
	void setLover(string lover) {
		m_Lover = lover;
	}

private:
	string m_Name; //Readable and writable name
	
	int m_Age; //Read only age

	string m_Lover; //Only lovers
};


int main() {

	Person p;
	//Name settings
	p.setName("Zhang San");
	cout << "full name: " << p.getName() << endl;

	//Age setting
	p.setAge(50);
	cout << "Age: " << p.getAge() << endl;

	//Lover settings
	p.setLover("Cangjing");
	//Cout < < lover: "< p.m_ Lover << endl;  // Write only attributes, not read

	system("pause");

	return 0;
}

Exercise case 1: designing a cube class

Design cube class (Cube)

Find the area and volume of the cube

The global function and member function are used to judge whether the two cubes are equal.

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-cz6wSFxn-1645785708629)(assets/1545533548532.png)]

Exercise case 2: relationship between point and circle

Design a Circle class and a Point class to calculate the relationship between points and circles.

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-fHFFdv3W-1645785708631)(assets/1545533829184.png)]

4.2 initialization and cleaning of objects

  • We don't need to delete some basic information in our life when we buy electronic products
  • The object-oriented in C + + comes from life. Each object will also have initial settings and settings for cleaning data before object destruction.

4.2.1 constructors and destructors

Object initialization and cleanup are also two very important security issues

An object or variable has no initial state, and the consequences of its use are unknown

Similarly, after using an object or variable, it will also cause some security problems if it is not cleaned up in time

c + + uses constructors and destructors to solve the above problems. These two functions will be automatically called by the compiler to complete object initialization and cleaning.

The initialization and cleanup of objects are what the compiler forces us to do, so if we don't provide construction and destructor, the compiler will provide

The constructor and destructor provided by the compiler are empty implementations.

  • Constructor: its main function is to assign value to the member attribute of the object when creating the object. The constructor is automatically called by the compiler without manual call.
  • Destructor: the main function is that the system will automatically call and perform some cleaning work before the object is destroyed.

Constructor syntax: class name () {}

  1. Constructor, no return value and no void
  2. The function name is the same as the class name
  3. Constructors can have arguments, so overloading can occur
  4. When the program calls the object, it will automatically call the construction without manual call, and it will only be called once

Destructor syntax: ~ class name () {}

  1. Destructor, no return value, no void
  2. The function name is the same as the class name, and the symbol is added before the name~
  3. Destructors cannot have parameters, so overloading cannot occur
  4. The program will automatically call the destructor before the object is destroyed. There is no need to call it manually, and it will only be called once
class Person
{
public:
	//Constructor
	Person()
	{
		cout << "Person Constructor call for" << endl;
	}
	//Destructor
	~Person()
	{
		cout << "Person Destructor call for" << endl;
	}

};

void test01()
{
	Person p;
}

int main() {
	
	test01();

	system("pause");

	return 0;
}

4.2.2 classification and calling of constructors

Two classification methods:

According to parameters, it can be divided into parametric structure and nonparametric structure

By type, it can be divided into ordinary structure and copy structure

Three call modes:

Bracket method

Display method

Implicit transformation method

Example:

//1. Constructor classification
// According to the classification of parameters, it is divided into parametric and nonparametric constructions. Nonparametric construction is also called default constructor
// According to the type, it can be divided into ordinary structure and copy structure

class Person {
public:
	//Parameterless (default) constructor
	Person() {
		cout << "non-parameter constructor !" << endl;
	}
	//Parameterized constructor
	Person(int a) {
		age = a;
		cout << "Parameterized constructor!" << endl;
	}
	//copy constructor 
	Person(const Person& p) {
		age = p.age;
		cout << "copy constructor !" << endl;
	}
	//Destructor
	~Person() {
		cout << "Destructor!" << endl;
	}
public:
	int age;
};

//2. Call of constructor
//Call parameterless constructor
void test01() {
	Person p; //Call parameterless constructor
}

//Call the constructor with parameters
void test02() {

	//2.1 bracket method, commonly used
	Person p1(10);
	//Note 1: calling the parameterless constructor cannot be parenthesized. If it is, the compiler considers it a function declaration
	//Person p2();

	//2.2 explicit method
	Person p2 = Person(10); 
	Person p3 = Person(p2);
	//Writing Person(10) alone means that the anonymous object is destructed immediately after the end of the current line

	//2.3 implicit transformation method
	Person p4 = 10; // Person p4 = Person(10); 
	Person p5 = p4; // Person p5 = Person(p4); 

	//Note 2: anonymous objects cannot be initialized with copy constructors. The compiler considers them object declarations
	//Person p5(p4);
}

int main() {

	test01();
	//test02();

	system("pause");

	return 0;
}

4.2.3 call timing of copy constructor

In C + +, there are usually three situations when copying constructor calls

  • Initialize a new object with an already created object
  • Value is passed to function parameters
  • Returns a local object as a value

Example:

class Person {
public:
	Person() {
		cout << "non-parameter constructor !" << endl;
		mAge = 0;
	}
	Person(int age) {
		cout << "Parameterized constructor!" << endl;
		mAge = age;
	}
	Person(const Person& p) {
		cout << "copy constructor !" << endl;
		mAge = p.mAge;
	}
	//Destructor is called before releasing memory.
	~Person() {
		cout << "Destructor!" << endl;
	}
public:
	int mAge;
};

//1. Initialize a new object with an already created object
void test01() {

	Person man(100); //The p object has been created
	Person newman(man); //Call copy constructor
	Person newman2 = man; //copy construction 

	//Person newman3;
	//newman3 = man; // Instead of calling the copy constructor, the assignment operation
}

//2. The method of value transfer is to transfer values to function parameters
//Equivalent to Person p1 = p;
void doWork(Person p1) {}
void test02() {
	Person p; //non-parameter constructor 
	doWork(p);
}

//3. Return local objects as values
Person doWork2()
{
	Person p1;
	cout << (int *)&p1 << endl;
	return p1;
}

void test03()
{
	Person p = doWork2();
	cout << (int *)&p << endl;
}


int main() {

	//test01();
	//test02();
	test03();

	system("pause");

	return 0;
}

4.2.4 constructor calling rules

By default, the c + + compiler adds at least three functions to a class

1. Default constructor (no parameters, empty function body)

2. Default destructor (no parameters, empty function body)

3. The default copy constructor copies the value of the attribute

Constructor calling rules are as follows:

  • If the user defines a parameterized constructor, c + + will no longer provide a default parameterless construct, but will provide a default copy construct

  • If you define a copy constructor, c + + will not provide another constructor

Example:

class Person {
public:
	//Parameterless (default) constructor
	Person() {
		cout << "non-parameter constructor !" << endl;
	}
	//Parameterized constructor
	Person(int a) {
		age = a;
		cout << "Parameterized constructor!" << endl;
	}
	//copy constructor 
	Person(const Person& p) {
		age = p.age;
		cout << "copy constructor !" << endl;
	}
	//Destructor
	~Person() {
		cout << "Destructor!" << endl;
	}
public:
	int age;
};

void test01()
{
	Person p1(18);
	//If you do not write a copy construct, the compiler will automatically add a copy construct and do a shallow copy operation
	Person p2(p1);

	cout << "p2 Your age is: " << p2.age << endl;
}

void test02()
{
	//If the user provides a parameter construct, the compiler will not provide a default construct, but a copy construct
	Person p1; //At this time, if the user does not provide the default structure, an error will occur
	Person p2(10); //User provided parameters
	Person p3(p2); //At this time, if the user does not provide a copy structure, the compiler will provide it

	//If the user provides a copy construct, the compiler does not provide other constructors
	Person p4; //At this time, if the user does not provide the default structure, an error will occur
	Person p5(10); //At this time, if the user does not provide a parameter, an error will occur
	Person p6(p5); //Users provide their own copy structure
}

int main() {

	test01();

	system("pause");

	return 0;
}

4.2.5 deep copy and shallow copy

Deep and shallow copy is not only a classic interview question, but also a common pit

Shallow copy: a simple assignment copy operation

Deep copy: reapply space in the heap area for copy operation

Example:

class Person {
public:
	//Parameterless (default) constructor
	Person() {
		cout << "non-parameter constructor !" << endl;
	}
	//Parameterized constructor
	Person(int age ,int height) {
		
		cout << "Parameterized constructor!" << endl;

		m_age = age;
		m_height = new int(height);
		
	}
	//copy constructor   
	Person(const Person& p) {
		cout << "copy constructor !" << endl;
		//If you do not use deep copy to create new memory in the heap, it will lead to the problem of repeatedly freeing the heap caused by shallow copy
		m_age = p.m_age;
		m_height = new int(*p.m_height);
		
	}

	//Destructor
	~Person() {
		cout << "Destructor!" << endl;
		if (m_height != NULL)
		{
			delete m_height;
		}
	}
public:
	int m_age;
	int* m_height;
};

void test01()
{
	Person p1(18, 180);

	Person p2(p1);

	cout << "p1 Age: " << p1.m_age << " Height: " << *p1.m_height << endl;

	cout << "p2 Age: " << p2.m_age << " Height: " << *p2.m_height << endl;
}

int main() {

	test01();

	system("pause");

	return 0;
}

Conclusion: if the attribute is opened in the heap area, you must provide your own copy constructor to prevent the problems caused by shallow copy

4.2.6 initialization list

effect:

C + + provides the syntax to initialize the list of attributes

Syntax: constructor (): Property 1 (value 1), property 2 (value 2) {}

Example:

class Person {
public:

	Traditional initialization
	//Person(int a, int b, int c) {
	//	m_A = a;
	//	m_B = b;
	//	m_C = c;
	//}

	//Initialization list mode
	Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
	void PrintPerson() {
		cout << "mA:" << m_A << endl;
		cout << "mB:" << m_B << endl;
		cout << "mC:" << m_C << endl;
	}
private:
	int m_A;
	int m_B;
	int m_C;
};

int main() {

	Person p(1, 2, 3);
	p.PrintPerson();


	system("pause");

	return 0;
}

4.2.7 class objects as class members

A member in a C + + class can be an object of another class, which we call an object member

For example:

class A {}
class B
{
    A a;
}

Class B has object A as A member and A as an object member

Then, when creating B objects, which is the order of construction and Deconstruction of A and B?

Example:

class Phone
{
public:
	Phone(string name)
	{
		m_PhoneName = name;
		cout << "Phone structure" << endl;
	}

	~Phone()
	{
		cout << "Phone Deconstruction" << endl;
	}

	string m_PhoneName;

};


class Person
{
public:

	//The initialization list tells the compiler which constructor to call
	Person(string name, string pName) :m_Name(name), m_Phone(pName)
	{
		cout << "Person structure" << endl;
	}

	~Person()
	{
		cout << "Person Deconstruction" << endl;
	}

	void playGame()
	{
		cout << m_Name << " use" << m_Phone.m_PhoneName << " Brand mobile phone! " << endl;
	}

	string m_Name;
	Phone m_Phone;

};
void test01()
{
	//When a member in a class is an object of another class, we call it an object member
	//The order of construction is: call the construction of object members first, and then call the construction of this class
	//The tectonic sequence is opposite to that of the structure
	Person p("Zhang San" , "Apple X");
	p.playGame();

}


int main() {

	test01();

	system("pause");

	return 0;
}

4.2.8 static members

Static members are called static members by adding the keyword static before member variables and member functions

Static members are divided into:

  • Static member variable
    • All objects share the same data
    • Allocate memory at compile time
    • In class declaration, out of class initialization
  • Static member function
    • All objects share the same function
    • Static member functions can only access static member variables

Example 1: static member variables

class Person
{
	
public:

	static int m_A; //Static member variable

	//Characteristics of static member variables:
	//1 allocate memory during compilation
	//2. Declaration within class and initialization outside class
	//3 all objects share the same data

private:
	static int m_B; //Static member variables also have access rights
};
int Person::m_A = 10;
int Person::m_B = 10;

void test01()
{
	//There are two ways to access static member variables

	//1. Through object
	Person p1;
	p1.m_A = 100;
	cout << "p1.m_A = " << p1.m_A << endl;

	Person p2;
	p2.m_A = 200;
	cout << "p1.m_A = " << p1.m_A << endl; //Share the same data
	cout << "p2.m_A = " << p2.m_A << endl;

	//2. By class name
	cout << "m_A = " << Person::m_A << endl;


	//cout << "m_B = " << Person::m_ B << endl; // Private access not available
}

int main() {

	test01();

	system("pause");

	return 0;
}

Example 2: static member function

class Person
{

public:

	//Static member function features:
	//1 programs share a function
	//2 static member functions can only access static member variables
	
	static void func()
	{
		cout << "func call" << endl;
		m_A = 100;
		//m_B = 100; // Error, non static member variables cannot be accessed
	}

	static int m_A; //Static member variable
	int m_B; // 
private:

	//Static member functions also have access rights
	static void func2()
	{
		cout << "func2 call" << endl;
	}
};
int Person::m_A = 10;


void test01()
{
	//There are two ways to access static member variables

	//1. Through object
	Person p1;
	p1.func();

	//2. By class name
	Person::func();


	//Person::func2(); // Private access not available
}

int main() {

	test01();

	system("pause");

	return 0;
}

4.3 C + + object model and this pointer

4.3.1 member variables and member functions are stored separately

In C + +, member variables and member functions in a class are stored separately

Only non static member variables belong to objects of a class

class Person {
public:
	Person() {
		mA = 0;
	}
	//Non static member variables occupy object space
	int mA;
	//Static member variables do not occupy object space
	static int mB; 
	//Functions do not occupy object space, and all functions share a function instance
	void func() {
		cout << "mA:" << this->mA << endl;
	}
	//Static member functions also do not occupy object space
	static void sfunc() {
	}
};

int main() {

	cout << sizeof(Person) << endl;

	system("pause");

	return 0;
}

4.3.2 this pointer concept

Through 4.3.1, we know that in C + +, member variables and member functions are stored separately

Each non static member function will only produce a function instance, that is, multiple objects of the same type will share a piece of code

So the question is: how does this piece of code distinguish that object from calling itself?

c + + solves the above problems by providing special object pointer and this pointer. This pointer points to the object to which the called member function belongs

this pointer is a pointer implied in every non static member function

this pointer does not need to be defined and can be used directly

Purpose of this pointer:

  • When a formal parameter has the same name as a member variable, it can be distinguished by this pointer
  • Return the object itself in the non static member function of the class. You can use return *this
class Person
{
public:

	Person(int age)
	{
		//1. When a formal parameter has the same name as a member variable, it can be distinguished by this pointer
		this->age = age;
	}

	Person& PersonAddPerson(Person p)
	{
		this->age += p.age;
		//Returns the object itself
		return *this;
	}

	int age;
};

void test01()
{
	Person p1(10);
	cout << "p1.age = " << p1.age << endl;

	Person p2(10);
	p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
	cout << "p2.age = " << p2.age << endl;
}

int main() {

	test01();

	system("pause");

	return 0;
}

4.3.3 null pointer accessing member functions

C + + hollow pointer can also call member functions, but pay attention to whether this pointer is used

If this pointer is used, it needs to be judged to ensure the robustness of the code

Example:

//Null pointer access member function
class Person {
public:

	void ShowClassName() {
		cout << "I am Person class!" << endl;
	}

	void ShowPerson() {
		if (this == NULL) {
			return;
		}
		cout << mAge << endl;
	}

public:
	int mAge;
};

void test01()
{
	Person * p = NULL;
	p->ShowClassName(); //Null pointer, member functions can be called
	p->ShowPerson();  //However, if this pointer is used in the member function, it is not allowed
}

int main() {

	test01();

	system("pause");

	return 0;
}

4.3.4 const modifier member function

Constant function:

  • After adding const to the member function, we call it a constant function
  • Member properties cannot be modified within a constant function
  • After the keyword mutable is added to the member attribute declaration, it can still be modified in the constant function

Constant object:

  • Add const before declaring an object to call it a constant object
  • Constant objects can only call constant functions

Example:

class Person {
public:
	Person() {
		m_A = 0;
		m_B = 0;
	}

	//The essence of this pointer is a pointer constant, and the pointer cannot be modified
	//If you want the value pointed to by the pointer to be immutable, you need to declare a constant function
	void ShowPerson() const {
		//const Type* const pointer;
		//this = NULL; // Cannot modify the pointer pointing to Person* const this;
		//this->mA = 100; // However, the data of the object pointed to by this pointer can be modified

		//const modifies the member function, which indicates that the data of the memory space pointed to by the pointer cannot be modified, except for the variable modified by mutable
		this->m_B = 100;
	}

	void MyFunc() const {
		//mA = 10000;
	}

public:
	int m_A;
	mutable int m_B; //Modifiable variable
};


//const modifier object
void test01() {

	const Person person; //const object   
	cout << person.m_A << endl;
	//person.mA = 100; // A constant object cannot modify the value of a member variable, but it can be accessed
	person.m_B = 100; //However, mutable can be modified to modify member variables for constant objects

	//Constant object access member function
	person.MyFunc(); //Constant objects cannot call const functions

}

int main() {

	test01();

	system("pause");

	return 0;
}

4.4 friends

In life, your home has a public living room and a private bedroom

All guests in the living room can enter, but your bedroom is private, that is, only you can enter

But you can also allow your best friend and best friend to go in.

In the program, some private properties also want to be accessed by special functions or classes outside the class, so you need to use friend technology

The purpose of a friend is to let a function or class access private members in another class

The keyword of friend is friend

Three implementations of friends

  • Global function as friend
  • Class as friend
  • Member function as friend

4.4.1 global functions as friends

class Building
{
	//Tell the compiler that the goodGay global function is a good friend of the Building class and can access the private content in the class
	friend void goodGay(Building * building);

public:

	Building()
	{
		this->m_SittingRoom = "a living room";
		this->m_BedRoom = "bedroom";
	}


public:
	string m_SittingRoom; //a living room

private:
	string m_BedRoom; //bedroom
};


void goodGay(Building * building)
{
	cout << "Good friends are visiting: " << building->m_SittingRoom << endl;
	cout << "Good friends are visiting: " << building->m_BedRoom << endl;
}


void test01()
{
	Building b;
	goodGay(&b);
}

int main(){

	test01();

	system("pause");
	return 0;
}

4.4.2 making friends

class Building;
class goodGay
{
public:

	goodGay();
	void visit();

private:
	Building *building;
};


class Building
{
	//Tell the compiler that goodGay class is a good friend of Building class and can access the private content of Building class
	friend class goodGay;

public:
	Building();

public:
	string m_SittingRoom; //a living room
private:
	string m_BedRoom;//bedroom
};

Building::Building()
{
	this->m_SittingRoom = "a living room";
	this->m_BedRoom = "bedroom";
}

goodGay::goodGay()
{
	building = new Building;
}

void goodGay::visit()
{
	cout << "Good friends are visiting" << building->m_SittingRoom << endl;
	cout << "Good friends are visiting" << building->m_BedRoom << endl;
}

void test01()
{
	goodGay gg;
	gg.visit();

}

int main(){

	test01();

	system("pause");
	return 0;
}

4.4.3 member functions as friends

class Building;
class goodGay
{
public:

	goodGay();
	void visit(); //Only use the visit function as a good friend of Building, and you can send and access private content in Building
	void visit2(); 

private:
	Building *building;
};


class Building
{
	//Tell the compiler that the visit member function in the goodGay class is a good friend of Building and can access private content
	friend void goodGay::visit();

public:
	Building();

public:
	string m_SittingRoom; //a living room
private:
	string m_BedRoom;//bedroom
};

Building::Building()
{
	this->m_SittingRoom = "a living room";
	this->m_BedRoom = "bedroom";
}

goodGay::goodGay()
{
	building = new Building;
}

void goodGay::visit()
{
	cout << "Good friends are visiting" << building->m_SittingRoom << endl;
	cout << "Good friends are visiting" << building->m_BedRoom << endl;
}

void goodGay::visit2()
{
	cout << "Good friends are visiting" << building->m_SittingRoom << endl;
	//Cout < < good friends are visiting < < building - > m_ BedRoom << endl;
}

void test01()
{
	goodGay  gg;
	gg.visit();

}

int main(){
    
	test01();

	system("pause");
	return 0;
}

4.5 operator overloading

Operator overloading concept: redefine the existing operators and give them another function to adapt to different data types

4.5.1 overloading of plus operator

Function: realize the operation of adding two user-defined data types

class Person {
public:
	Person() {};
	Person(int a, int b)
	{
		this->m_A = a;
		this->m_B = b;
	}
	//The member function implements the overloading of the + operator
	Person operator+(const Person& p) {
		Person temp;
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.m_B;
		return temp;
	}


public:
	int m_A;
	int m_B;
};

//Global function implementation + operator overload
//Person operator+(const Person& p1, const Person& p2) {
//	Person temp(0, 0);
//	temp.m_A = p1.m_A + p2.m_A;
//	temp.m_B = p1.m_B + p2.m_B;
//	return temp;
//}

//Operator overloading can cause function overloading 
Person operator+(const Person& p2, int val)  
{
	Person temp;
	temp.m_A = p2.m_A + val;
	temp.m_B = p2.m_B + val;
	return temp;
}

void test() {

	Person p1(10, 10);
	Person p2(20, 20);

	//Member function mode
	Person p3 = p2 + p1;  //Equivalent to P2 operaor+(p1)
	cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;


	Person p4 = p3 + 10; //Equivalent to operator+(p3,10)
	cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;

}

int main() {

	test();

	system("pause");

	return 0;
}

Summary 1: the operators of expressions with built-in data types cannot be changed

Summary 2: do not abuse operator overloading

4.5.2 shift left operator overload

Function: you can output custom data types

class Person {
	friend ostream& operator<<(ostream& out, Person& p);

public:

	Person(int a, int b)
	{
		this->m_A = a;
		this->m_B = b;
	}

	//The member function can't realize P < < cout, which is not the effect we want
	//void operator<<(Person& p){
	//}

private:
	int m_A;
	int m_B;
};

//Global function to achieve left shift overload
//There can only be one ostream object
ostream& operator<<(ostream& out, Person& p) {
	out << "a:" << p.m_A << " b:" << p.m_B;
	return out;
}

void test() {

	Person p1(10, 20);

	cout << p1 << "hello world" << endl; //Chain programming
}

int main() {

	test();

	system("pause");

	return 0;
}

Conclusion: overloading the shift left operator and cooperating with friends can output user-defined data types

Overloaded increment operator

Function: realize your own integer data by overloading the increment operator

class MyInteger {

	friend ostream& operator<<(ostream& out, MyInteger myint);

public:
	MyInteger() {
		m_Num = 0;
	}
	//Front++
	MyInteger& operator++() {
		//First++
		m_Num++;
		//Return again
		return *this;
	}

	//Postposition++
	MyInteger operator++(int) {
		//Return first
		MyInteger temp = *this; //Record the current value of itself, and then add 1 to its own value, but the previous value is returned to return before + +;
		m_Num++;
		return temp;
	}

private:
	int m_Num;
};


ostream& operator<<(ostream& out, MyInteger myint) {
	out << myint.m_Num;
	return out;
}


//Pre + + first + + and then return
void test01() {
	MyInteger myInt;
	cout << ++myInt << endl;
	cout << myInt << endl;
}

//Post + + return first and then++
void test02() {

	MyInteger myInt;
	cout << myInt++ << endl;
	cout << myInt << endl;
}

int main() {

	test01();
	//test02();

	system("pause");

	return 0;
}

Summary: the pre increment returns the reference and the post increment returns the value

4.5.4 overload of assignment operator

The c + + compiler adds at least four functions to a class

  1. Default constructor (no parameters, empty function body)
  2. Default destructor (no parameters, empty function body)
  3. The default copy constructor copies the value of the attribute
  4. The assignment operator operator =, copies the value of the attribute

If there are attributes in the class pointing to the heap area, the problem of deep and shallow copy will also occur during assignment

Example:

class Person
{
public:

	Person(int age)
	{
		//Opening up age data to the heap area
		m_Age = new int(age);
	}

	//Overload assignment operator 
	Person& operator=(Person &p)
	{
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
		//The code provided by the compiler is a shallow copy
		//m_Age = p.m_Age;

		//Provide deep copy to solve the problem of shallow copy
		m_Age = new int(*p.m_Age);

		//Return to itself
		return *this;
	}


	~Person()
	{
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
	}

	//Age pointer
	int *m_Age;

};


void test01()
{
	Person p1(18);

	Person p2(20);

	Person p3(30);

	p3 = p2 = p1; //Assignment operation

	cout << "p1 Your age is:" << *p1.m_Age << endl;

	cout << "p2 Your age is:" << *p2.m_Age << endl;

	cout << "p3 Your age is:" << *p3.m_Age << endl;
}

int main() {

	test01();

	//int a = 10;
	//int b = 20;
	//int c = 30;

	//c = b = a;
	//cout << "a = " << a << endl;
	//cout << "b = " << b << endl;
	//cout << "c = " << c << endl;

	system("pause");

	return 0;
}

4.5.5 overloading of relational operators

Function: overloads the relational operator, allowing two user-defined type objects to be compared

Example:

class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	};

	bool operator==(Person & p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	bool operator!=(Person & p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
			return false;
		}
		else
		{
			return true;
		}
	}

	string m_Name;
	int m_Age;
};

void test01()
{
	//int a = 0;
	//int b = 0;

	Person a("Sun WuKong", 18);
	Person b("Sun WuKong", 18);

	if (a == b)
	{
		cout << "a and b equal" << endl;
	}
	else
	{
		cout << "a and b Unequal" << endl;
	}

	if (a != b)
	{
		cout << "a and b Unequal" << endl;
	}
	else
	{
		cout << "a and b equal" << endl;
	}
}


int main() {

	test01();

	system("pause");

	return 0;
}

4.5.6 function call operator overloading

  • The function call operator () can also be overloaded
  • Because the method used after overloading is very similar to the call of function, it is called imitation function
  • Imitation function has no fixed writing method and is very flexible

Example:

class MyPrint
{
public:
	void operator()(string text)
	{
		cout << text << endl;
	}

};
void test01()
{
	//Overloaded () operators are also called functors
	MyPrint myFunc;
	myFunc("hello world");
}


class MyAdd
{
public:
	int operator()(int v1, int v2)
	{
		return v1 + v2;
	}
};

void test02()
{
	MyAdd add;
	int ret = add(10, 10);
	cout << "ret = " << ret << endl;

	//Anonymous object call  
	cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}

int main() {

	test01();
	test02();

	system("pause");

	return 0;
}

4.6 succession

Inheritance is one of the three characteristics of object-oriented

There are special relationships between some classes, such as the following figure:

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-uMDYpzJ9-1646010240157)(assets/1544861202252.png)]

We found that when defining these classes, the lower level members not only have the commonalities of the upper level, but also have their own characteristics.

At this time, we can consider using inheritance technology to reduce duplicate code

4.6.1 basic syntax of inheritance

For example, we can see that in many websites, there are public headers, public bottoms, and even public lists on the left. Only the central content is different

Next, we use the common writing method and the inheritance writing method to realize the content in the web page, and take a look at the significance and benefits of inheritance

Common implementation:

//Java page
class Java 
{
public:
	void header()
	{
		cout << "Home page, open class, login, registration...(Common head)" << endl;
	}
	void footer()
	{
		cout << "Help center, communication and cooperation, station map...(Common bottom)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(Public classification list)" << endl;
	}
	void content()
	{
		cout << "JAVA Subject video" << endl;
	}
};
//Python page
class Python
{
public:
	void header()
	{
		cout << "Home page, open class, login, registration...(Common head)" << endl;
	}
	void footer()
	{
		cout << "Help center, communication and cooperation, station map...(Common bottom)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(Public classification list)" << endl;
	}
	void content()
	{
		cout << "Python Subject video" << endl;
	}
};
//C + + page
class CPP 
{
public:
	void header()
	{
		cout << "Home page, open class, login, registration...(Common head)" << endl;
	}
	void footer()
	{
		cout << "Help center, communication and cooperation, station map...(Common bottom)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(Public classification list)" << endl;
	}
	void content()
	{
		cout << "C++Subject video" << endl;
	}
};

void test01()
{
	//Java page
	cout << "Java The download video page is as follows: " << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "--------------------" << endl;

	//Python page
	cout << "Python The download video page is as follows: " << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "--------------------" << endl;

	//C + + page
	cout << "C++The download video page is as follows: " << endl;
	CPP cp;
	cp.header();
	cp.footer();
	cp.left();
	cp.content();

}

int main() {

	test01();

	system("pause");

	return 0;
}

Inheritance implementation:

//Public page
class BasePage
{
public:
	void header()
	{
		cout << "Home page, open class, login, registration...(Common head)" << endl;
	}

	void footer()
	{
		cout << "Help center, communication and cooperation, station map...(Common bottom)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(Public classification list)" << endl;
	}

};

//Java page
class Java : public BasePage
{
public:
	void content()
	{
		cout << "JAVA Subject video" << endl;
	}
};
//Python page
class Python : public BasePage
{
public:
	void content()
	{
		cout << "Python Subject video" << endl;
	}
};
//C + + page
class CPP : public BasePage
{
public:
	void content()
	{
		cout << "C++Subject video" << endl;
	}
};

void test01()
{
	//Java page
	cout << "Java The download video page is as follows: " << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "--------------------" << endl;

	//Python page
	cout << "Python The download video page is as follows: " << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "--------------------" << endl;

	//C + + page
	cout << "C++The download video page is as follows: " << endl;
	CPP cp;
	cp.header();
	cp.footer();
	cp.left();
	cp.content();


}

int main() {

	test01();

	system("pause");

	return 0;
}

Summary:

Benefits of inheritance: you can reduce duplicate code

class A : public B;

Class A is called a subclass or derived class

Class B is called a parent or base class

Members in derived classes consist of two parts:

One is inherited from the base class, and the other is added by itself.

Inherited from the base class shows its commonness, while the new members reflect its individuality.

4.6.2 inheritance mode

Inheritance syntax: class subclass: inheritance method parent class

There are three inheritance methods:

  • Public succession
  • Protect inheritance
  • Private inheritance

Example:

class Base1
{
public: 
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

//Public succession
class Son1 :public Base1
{
public:
	void func()
	{
		m_A; //Access to public permissions
		m_B; //Access protected permissions
		//m_C; // Inaccessible
	}
};

void myClass()
{
	Son1 s1;
	s1.m_A; //Other classes can only access public permissions
}

//Protect inheritance
class Base2
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
class Son2:protected Base2
{
public:
	void func()
	{
		m_A; //Access protected permissions
		m_B; //Access protected permissions
		//m_C; // Inaccessible
	}
};
void myClass2()
{
	Son2 s;
	//s.m_A; // Inaccessible
}

//Private inheritance
class Base3
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
class Son3:private Base3
{
public:
	void func()
	{
		m_A; //Access to private rights
		m_B; //Access to private rights
		//m_C; // Inaccessible
	}
};
class GrandSon3 :public Son3
{
public:
	void func()
	{
		//Son3 is a private inheritance, so the properties of inherited son3 cannot be accessed in GrandSon3
		//m_A;
		//m_B;
		//m_C;
	}
};

4.6.3 object model in inheritance

Question: which members inherited from the parent class belong to the subclass object?

Example:

class Base
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C; //Private members are just hidden, but they will continue
};

//Public succession
class Son :public Base
{
public:
	int m_D;
};

void test01()
{
	cout << "sizeof Son = " << sizeof(Son) << endl;
}

int main() {

	test01();

	system("pause");

	return 0;
}

Use tools to view:

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-byISm7aM-1646010240160)(assets/1545881904150.png)]

After opening the tool window, locate the drive letter of the current CPP file

Then enter: cl /d1 reportSingleClassLayout to view the file name of the class name

The effect is as follows:

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-Uj2FU5Sd-1646010240160)(assets/1545882158050.png)]

Conclusion: the private members in the parent class are also inherited by the child class, but they can't be accessed after being hidden by the compiler

4.6.4 construction and deconstruction sequence in inheritance

After the subclass inherits from the parent class, when the subclass object is created, the constructor of the parent class will also be called

Question: who comes first and who comes second in the construction and deconstruction order of parent and child classes?

Example:

class Base 
{
public:
	Base()
	{
		cout << "Base Constructor!" << endl;
	}
	~Base()
	{
		cout << "Base Destructor!" << endl;
	}
};

class Son : public Base
{
public:
	Son()
	{
		cout << "Son Constructor!" << endl;
	}
	~Son()
	{
		cout << "Son Destructor!" << endl;
	}

};


void test01()
{
	//In inheritance, the constructor of the parent class is called first, and then the constructor of the child class is called. The deconstruction order is opposite to that of the construction
	Son s;
}

int main() {

	test01();

	system("pause");

	return 0;
}

Summary: in inheritance, the constructor of the parent class is called first, and then the constructor of the child class is called. The deconstruction order is opposite to that of the construction

4.6.5 handling method of inheriting members with the same name

Question: when there are members with the same name in the subclass and parent class, how can you access the data with the same name in the subclass or parent class through the subclass object?

  • Members with the same name of the subclass can be accessed directly
  • Scope is required to access the member with the same name as the parent class

Example:

class Base {
public:
	Base()
	{
		m_A = 100;
	}

	void func()
	{
		cout << "Base - func()call" << endl;
	}

	void func(int a)
	{
		cout << "Base - func(int a)call" << endl;
	}

public:
	int m_A;
};


class Son : public Base {
public:
	Son()
	{
		m_A = 200;
	}

	//When a subclass has a member function with the same name as the parent class, the subclass will hide all versions of the member function with the same name in the parent class
	//If you want to access the member function with the same name hidden in the parent class, you need to add the scope of the parent class
	void func()
	{
		cout << "Son - func()call" << endl;
	}
public:
	int m_A;
};

void test01()
{
	Son s;

	cout << "Son Lower m_A = " << s.m_A << endl;
	cout << "Base Lower m_A = " << s.Base::m_A << endl;

	s.func();
	s.Base::func();
	s.Base::func(10);

}
int main() {

	test01();

	system("pause");
	return EXIT_SUCCESS;
}

Summary:

  1. Subclass objects can directly access members with the same name in subclasses
  2. The subclass object plus scope can access the member with the same name as the parent class
  3. When a subclass has a member function with the same name as the parent class, the subclass will hide the member function with the same name in the parent class, and the scope can access the function with the same name in the parent class

4.6.6 processing method of inheriting static members with the same name

Question: how do static members with the same name in inheritance access on subclass objects?

Static members and non static members have the same name and are handled in the same way

  • Members with the same name of the subclass can be accessed directly
  • Scope is required to access the member with the same name as the parent class

Example:

class Base {
public:
	static void func()
	{
		cout << "Base - static void func()" << endl;
	}
	static void func(int a)
	{
		cout << "Base - static void func(int a)" << endl;
	}

	static int m_A;
};

int Base::m_A = 100;

class Son : public Base {
public:
	static void func()
	{
		cout << "Son - static void func()" << endl;
	}
	static int m_A;
};

int Son::m_A = 200;

//Member property with the same name
void test01()
{
	//Access by object
	cout << "Access via object: " << endl;
	Son s;
	cout << "Son  lower m_A = " << s.m_A << endl;
	cout << "Base lower m_A = " << s.Base::m_A << endl;

	//Access by class name
	cout << "Access by class name: " << endl;
	cout << "Son  lower m_A = " << Son::m_A << endl;
	cout << "Base lower m_A = " << Son::Base::m_A << endl;
}

//Member function with the same name
void test02()
{
	//Access by object
	cout << "Access via object: " << endl;
	Son s;
	s.func();
	s.Base::func();

	cout << "Access by class name: " << endl;
	Son::func();
	Son::Base::func();
	//In case of the same name, the subclass will hide all the member functions with the same name in the parent class and need to add scope access
	Son::Base::func(100);
}
int main() {

	//test01();
	test02();

	system("pause");

	return 0;
}

Summary: static members with the same name are handled in the same way as non static members, except that there are two access methods (through object and class name)

4.6.7 multi inheritance syntax

C + + allows a class to inherit multiple classes

Syntax: class subclass: inheritance method parent class 1, inheritance method parent class 2

Multiple inheritance may cause members with the same name to appear in the parent class, which needs to be distinguished by scope

Multi inheritance is not recommended in the actual development of C + +

Example:

class Base1 {
public:
	Base1()
	{
		m_A = 100;
	}
public:
	int m_A;
};

class Base2 {
public:
	Base2()
	{
		m_A = 200;  //Start with m_B will not be a problem, but if it is changed to mA, there will be ambiguity
	}
public:
	int m_A;
};

//Syntax: class subclass: inheritance method parent class 1, inheritance method parent class 2 
class Son : public Base2, public Base1 
{
public:
	Son()
	{
		m_C = 300;
		m_D = 400;
	}
public:
	int m_C;
	int m_D;
};


//Multi inheritance is easy to produce the situation of members with the same name
//By using the class name and scope, you can distinguish which members of the base class are called
void test01()
{
	Son s;
	cout << "sizeof Son = " << sizeof(s) << endl;
	cout << s.Base1::m_A << endl;
	cout << s.Base2::m_A << endl;
}

int main() {

	test01();

	system("pause");

	return 0;
}

Summary: in multi inheritance, if the parent class has the same name, the scope should be added when the child class is used

4.6.8 diamond inheritance

Diamond inheritance concept:

Two derived classes inherit the same base class

Another class inherits two derived classes at the same time

This inheritance is called diamond inheritance, or diamond inheritance

Diamond inheritance problem:

  1. Sheep inherits animal data, and camel also inherits animal data. When grass mud horse uses data, it will produce ambiguity.
    
  2. Grass Mud Horse inherits two copies of data from animals. In fact, we should be clear that we only need one copy of this data.
    

Example:

class Animal
{
public:
	int m_Age;
};

//After adding the virtual keyword before inheritance, it becomes virtual inheritance
//At this time, the public parent class Animal is called the virtual base class
class Sheep : virtual public Animal {};
class Tuo   : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};

void test01()
{
	SheepTuo st;
	st.Sheep::m_Age = 100;
	st.Tuo::m_Age = 200;

	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
	cout << "st.Tuo::m_Age = " <<  st.Tuo::m_Age << endl;
	cout << "st.m_Age = " << st.m_Age << endl;
}


int main() {

	test01();

	system("pause");

	return 0;
}

Summary:

  • The main problem caused by diamond inheritance is that subclasses inherit two copies of the same data, resulting in waste of resources and meaninglessness
  • Using virtual inheritance can solve the problem of diamond inheritance

4.7 polymorphism

4.7.1 basic concept of polymorphism

Polymorphism is one of the three characteristics of C + + object-oriented

Polymorphisms fall into two categories

  • Static polymorphism: function overloading and operator overloading belong to static polymorphism and reuse function names
  • Dynamic polymorphism: derived classes and virtual functions implement runtime polymorphism

Difference between static polymorphism and dynamic polymorphism:

  • Statically polymorphic function address early binding - the function address is determined in the compilation stage
  • Dynamic polymorphic function address late binding - determine the function address at the run-time

The following is a case study

class Animal
{
public:
	//The Speak function is an imaginary function
	//The virtual keyword is added in front of the function to become a virtual function, so the compiler cannot determine the function call when compiling.
	virtual void speak()
	{
		cout << "Animals are talking" << endl;
	}
};

class Cat :public Animal
{
public:
	void speak()
	{
		cout << "The kitten is talking" << endl;
	}
};

class Dog :public Animal
{
public:

	void speak()
	{
		cout << "The dog is talking" << endl;
	}

};
//If we want to pass in any object, we will call the function of any object
//If the function address can be determined at the compilation stage, then static binding
//If the function address can only be determined at the running stage, it is dynamic binding

void DoSpeak(Animal & animal)
{
	animal.speak();
}
//
//Polymorphism meets the following conditions: 
//1. There is an inheritance relationship
//2. Subclasses override virtual functions in the parent class
//Polymorphic use:
//A parent class pointer or reference points to a child class object

void test01()
{
	Cat cat;
	DoSpeak(cat);


	Dog dog;
	DoSpeak(dog);
}


int main() {

	test01();

	system("pause");

	return 0;
}

Summary:

Polymorphic condition

  • There is an inheritance relationship
  • Subclasses override virtual functions in the parent class

Polymorphic service conditions

  • Pointer to parent or child class

Rewriting: when the return value type of a function is exactly the same as the parameter list of the function name, it is called rewriting

4.7.2 polymorphic case I - calculator

Case description:

Using common writing method and polymorphic technology, a calculator class is designed to realize the operation of two operands

Advantages of polymorphism:

  • Clear code organization
  • Strong readability
  • It is conducive to the expansion and maintenance in the early and later stages

Example:

//Common implementation
class Calculator {
public:
	int getResult(string oper)
	{
		if (oper == "+") {
			return m_Num1 + m_Num2;
		}
		else if (oper == "-") {
			return m_Num1 - m_Num2;
		}
		else if (oper == "*") {
			return m_Num1 * m_Num2;
		}
		//If you want to provide new operations, you need to modify the source code
	}
public:
	int m_Num1;
	int m_Num2;
};

void test01()
{
	//General implementation test
	Calculator c;
	c.m_Num1 = 10;
	c.m_Num2 = 10;
	cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;

	cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;

	cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
}



//Polymorphic implementation
//Abstract calculator class
//Advantages of polymorphism: the code organization structure is clear and readable, which is conducive to early and later expansion and maintenance
class AbstractCalculator
{
public :

	virtual int getResult()
	{
		return 0;
	}

	int m_Num1;
	int m_Num2;
};

//Addition calculator
class AddCalculator :public AbstractCalculator
{
public:
	int getResult()
	{
		return m_Num1 + m_Num2;
	}
};

//Subtraction calculator
class SubCalculator :public AbstractCalculator
{
public:
	int getResult()
	{
		return m_Num1 - m_Num2;
	}
};

//Multiplication calculator
class MulCalculator :public AbstractCalculator
{
public:
	int getResult()
	{
		return m_Num1 * m_Num2;
	}
};


void test02()
{
	//Create addition calculator
	AbstractCalculator *abc = new AddCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;  //When you run out, remember to destroy it

	//Create subtraction calculator
	abc = new SubCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;  

	//Create multiplication calculator
	abc = new MulCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;
}

int main() {

	//test01();

	test02();

	system("pause");

	return 0;
}

Conclusion: C + + development advocates the use of polymorphism to design program architecture, because polymorphism has many advantages

4.7.3 pure virtual functions and abstract classes

In polymorphism, the implementation of virtual functions in the parent class is meaningless, which is mainly the content rewritten by calling subclasses

Therefore, virtual functions can be changed to pure virtual functions

Pure virtual function syntax: virtual return value type function name (parameter list) = 0;

When there are pure virtual functions in a class, this class is also called an abstract class

Abstract class features:

  • Cannot instantiate object
  • Subclasses must override pure virtual functions in abstract classes, otherwise they also belong to abstract classes

Example:

class Base
{
public:
	//Pure virtual function
	//As long as there is a pure virtual function in a class, it is called an abstract class
	//Abstract classes cannot instantiate objects
	//Subclasses must override pure virtual functions in the parent class, otherwise they also belong to abstract classes
	virtual void func() = 0;
};

class Son :public Base
{
public:
	virtual void func() 
	{
		cout << "func call" << endl;
	};
};

void test01()
{
	Base * base = NULL;
	//base = new Base; //  Error, abstract class cannot instantiate object
	base = new Son;
	base->func();
	delete base;//Remember to destroy
}

int main() {

	test01();

	system("pause");

	return 0;
}

4.7.4 case 2 - making drinks

Case description:

The general process of making drinks is: boiling water - brewing - pouring into the cup - adding accessories

This case is realized by using polymorphic technology, providing Abstract production of beverage base class and subclass production of coffee and tea

Example:

//Abstract making drinks
class AbstractDrinking {
public:
	//Boil water
	virtual void Boil() = 0;
	//Brew
	virtual void Brew() = 0;
	//Pour into a cup
	virtual void PourInCup() = 0;
	//Add excipients
	virtual void PutSomething() = 0;
	//Specified process
	void MakeDrink() {
		Boil();
		Brew();
		PourInCup();
		PutSomething();
	}
};

//Making coffee
class Coffee : public AbstractDrinking {
public:
	//Boil water
	virtual void Boil() {
		cout << "Boiled farmer spring!" << endl;
	}
	//Brew
	virtual void Brew() {
		cout << "Brew coffee!" << endl;
	}
	//Pour into a cup
	virtual void PourInCup() {
		cout << "Pour the coffee into the cup!" << endl;
	}
	//Add excipients
	virtual void PutSomething() {
		cout << "Add milk!" << endl;
	}
};

//Making tea
class Tea : public AbstractDrinking {
public:
	//Boil water
	virtual void Boil() {
		cout << "Boiled tap water!" << endl;
	}
	//Brew
	virtual void Brew() {
		cout << "Brewing tea!" << endl;
	}
	//Pour into a cup
	virtual void PourInCup() {
		cout << "Pour the tea into the cup!" << endl;
	}
	//Add excipients
	virtual void PutSomething() {
		cout << "Add medlar!" << endl;
	}
};

//Business function
void DoWork(AbstractDrinking* drink) {
	drink->MakeDrink();
	delete drink;
}

void test01() {
	DoWork(new Coffee);
	cout << "--------------" << endl;
	DoWork(new Tea);
}


int main() {

	test01();

	system("pause");

	return 0;
}

4.7.5 virtual deconstruction and pure virtual deconstruction

When polymorphism is used, if an attribute in a subclass is opened to the heap, the parent class pointer cannot call the destructor code of the subclass when it is released

Solution: change the destructor in the parent class to virtual destructor or pure virtual destructor

The commonness between virtual destruct and pure virtual destruct:

  • You can solve the problem of releasing subclass objects from parent class pointers
  • All need specific function implementation

Difference between virtual destruct and pure virtual destruct:

  • If it is a pure virtual destructor, this class belongs to an abstract class and cannot instantiate an object

Virtual destructor syntax:

virtual ~ class name () {}

Pure virtual destructor syntax:

virtual ~ class name () = 0;

Class name:: ~ class name () {}

Example:

class Animal {
public:

	Animal()
	{
		cout << "Animal Constructor call!" << endl;
	}
	virtual void Speak() = 0;

	//The destructor plus the virtual keyword becomes a virtual destructor
	//virtual ~Animal()
	//{
	//	Cout < < "animal virtual destructor call!"<< endl;
	//}


	virtual ~Animal() = 0;
};

Animal::~Animal()
{
	cout << "Animal Pure virtual destructor call!" << endl;
}

//Like a class containing ordinary pure virtual functions, a class containing pure virtual destructors is also an abstract class. Cannot be instantiated.

class Cat : public Animal {
public:
	Cat(string name)
	{
		cout << "Cat Constructor call!" << endl;
		m_Name = new string(name);
	}
	virtual void Speak()
	{
		cout << *m_Name <<  "The kitten is talking!" << endl;
	}
	~Cat()
	{
		cout << "Cat Destructor call!" << endl;
		if (this->m_Name != NULL) {
			delete m_Name;
			m_Name = NULL;
		}
	}

public:
	string *m_Name;
};

void test01()
{
	Animal *animal = new Cat("Tom");
	animal->Speak();

	//Releasing through the parent class pointer may cause the subclass objects to be unclearly cleaned, resulting in memory leakage
	//How? Add a virtual destructor to the base class
	//The virtual destructor is used to release subclass objects through the parent class pointer
	delete animal;
}

int main() {

	test01();

	system("pause");

	return 0;
}

Summary:

​ 1. Virtual destruct or pure virtual destruct is used to solve the problem of releasing subclass objects through the parent class pointer

​ 2. If there is no heap data in the subclass, it can not be written as virtual destruct or pure virtual destruct

​ 3. Classes with pure virtual destructors are also abstract classes

4.7.6 case 3 - computer assembly

Case description:

The main components of the computer are CPU (for calculation), graphics card (for display) and memory module (for storage)

Encapsulate each part into an abstract base class, and provide different manufacturers to produce different parts, such as Intel manufacturers and Lenovo manufacturers

Create a computer class, provide functions that let the computer work, and call the interface of each part

During the test, three different computers were assembled to work

Example:

#include<iostream>
using namespace std;

//Abstract CPU class
class CPU
{
public:
	//Abstract computing function
	virtual void calculate() = 0;
};

//Abstract graphics card class
class VideoCard
{
public:
	//Abstract display function
	virtual void display() = 0;
};

//Abstract memory module class
class Memory
{
public:
	//Abstract storage function
	virtual void storage() = 0;
};

//Computer
class Computer
{
public:
	Computer(CPU * cpu, VideoCard * vc, Memory * mem)
	{
		m_cpu = cpu;
		m_vc = vc;
		m_mem = mem;
	}

	//Functions that provide work
	void work()
	{
		//Make the part work and call the interface
		m_cpu->calculate();

		m_vc->display();

		m_mem->storage();
	}

	//Provide destructor to release 3 computer parts
	~Computer()
	{

		//Release CPU parts
		if (m_cpu != NULL)
		{
			delete m_cpu;
			m_cpu = NULL;
		}

		//Release graphics card parts
		if (m_vc != NULL)
		{
			delete m_vc;
			m_vc = NULL;
		}

		//Release memory module parts
		if (m_mem != NULL)
		{
			delete m_mem;
			m_mem = NULL;
		}
	}

private:

	CPU * m_cpu; //Part pointer of CPU
	VideoCard * m_vc; //Graphics card part pointer
	Memory * m_mem; //Memory module part pointer
};

//Specific manufacturer
//Intel manufacturer
class IntelCPU :public CPU
{
public:
	virtual void calculate()
	{
		cout << "Intel of CPU Start counting!" << endl;
	}
};

class IntelVideoCard :public VideoCard
{
public:
	virtual void display()
	{
		cout << "Intel Your graphics card is starting to show!" << endl;
	}
};

class IntelMemory :public Memory
{
public:
	virtual void storage()
	{
		cout << "Intel My memory module is beginning to store!" << endl;
	}
};

//Lenovo manufacturer
class LenovoCPU :public CPU
{
public:
	virtual void calculate()
	{
		cout << "Lenovo of CPU Start counting!" << endl;
	}
};

class LenovoVideoCard :public VideoCard
{
public:
	virtual void display()
	{
		cout << "Lenovo Your graphics card is starting to show!" << endl;
	}
};

class LenovoMemory :public Memory
{
public:
	virtual void storage()
	{
		cout << "Lenovo My memory module is beginning to store!" << endl;
	}
};


void test01()
{
	//First computer parts
	CPU * intelCpu = new IntelCPU;
	VideoCard * intelCard = new IntelVideoCard;
	Memory * intelMem = new IntelMemory;

	cout << "The first computer starts working:" << endl;
	//Create first computer
	Computer * computer1 = new Computer(intelCpu, intelCard, intelMem);
	computer1->work();
	delete computer1;

	cout << "-----------------------" << endl;
	cout << "The second computer starts working:" << endl;
	//Second computer assembly
	Computer * computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);;
	computer2->work();
	delete computer2;

	cout << "-----------------------" << endl;
	cout << "The third computer starts working:" << endl;
	//Third computer assembly
	Computer * computer3 = new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemory);;
	computer3->work();
	delete computer3;

}

Topics: C++ Qt