C + + learning diary - polymorphism, pure virtual functions and abstract classes, virtual destruct and pure virtual destruct, file operation

Posted by poisa on Wed, 19 Jan 2022 22:19:51 +0100

1, Basic concepts 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

Dynamic polymorphism: derived classes and virtual functions implement runtime polymorphism,

Virtual is added before the function, which is called virtual function

What is the difference between static polymorphism and dynamic polymorphism?

Static polymorphic function address is early binding ------ > the function address is determined in the compilation stage

The dynamically polymorphic function address is late binding ------ > the function address is determined at the run stage

#include <iostream>
using namespace std;

//polymorphic
//Design animals
class Animal{
public:
	//Add virtual before the function header	
	//virtual function
	virtual void speak()
	{
		cout << "Animals are talking" << endl;
	}
};

//Design cats
class Cat:public Animal{
public:
	//The subclass must override the virtual function name of the parent class, and the parameter list is exactly the same.
	virtual void speak()
	{
		cout << "The kitten is talking" << endl;
	}
};

//Design dogs
class Dog:public Animal{
public:
	//The subclass must override the virtual function name of the parent class, and the parameter list is exactly the same.
	virtual void speak()
	{
		cout << "The dog is talking" << endl;
	}
};

//Function to execute speech
//Because the address is bound early, the address has been determined at the compilation stage.

void dospeak(Animal &animal)  //This animal sometimes represents a kitten and sometimes a dog.
{
	//1. It has been bound long ago, so it has been determined during compilation that this speech is the speech in animal, so no matter what is passed, it will call the speech () of animal.
	//animal.speak();

	//2. If you want the kitten or dog to talk, don't bind the dead animal and speak in the compilation stage, but wait until the runtime to call the speak() of what is passed;
	//How to tell the compiler to bind late?  
	//		Add a keyword called virtual in front of the function header
	//After adding virtual, is there any change?
	//      animal will not be bound with speak() at the compilation stage, but will be bound according to what is passed in when it is called.
	//      At this time, the speak function is a variety of forms. 
	animal.speak();
}

int main()
{
	Cat c;  //Instantiate a specific cat
	dospeak(c);

	Dog d;   //Instantiate a specific dog
	dospeak(d);

	return 0;
}

Summary:
Dynamic polymorphism meets the following conditions:
1. Inheritance relationship.
2. The subclass must override the virtual function of the parent class.

Dynamic polymorphism usage:
The parent class pointer or reference points to the object of the child class

   Animal &animal = c;
An object of a parent class that references a child class

2, Polymorphic case 1 - computer class.
Using common writing method and polymorphic technology respectively, a calculator class is designed to realize the operation of two operands.

#include <iostream>
using namespace std;

//Common writing.
/*
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 extend new functions, you need to modify the source code.
		//In real development, the principle of opening and closing is advocated.
		//Opening / closing principle: open the extension and close the modification.
	}

	int m_Num1;  //Operand 1
	int m_Num2;  //Operand 2
};

void test01()
{
	Calculator c;  //Instantiate a concrete calculator through a computer class.
	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 writing.
//Benefits of polymorphism:
//1. Clear organizational structure
//2. Strong readability
//3. It is very convenient for early and later maintenance

//Conclusion: polymorphism is advocated in C + +, because polymorphism has many advantages. 

//Design calculator base class.
class Calculator{
public:
	//virtual function
	virtual int getResult()
	{

	}

	int m_Num1;
	int m_Num2;
};

//Design addition class.
class AddCalculator:public Calculator{
public:
	//Subclass overrides parent virtual function
	virtual int getResult()
	{
		return m_Num1 + m_Num2;
	}
};

//Design subtraction class.
class SubCalculator:public Calculator{
public:
	//Subclass overrides parent virtual function
	virtual int getResult()
	{
		return m_Num1 - m_Num2;
	}
};

//Design multiplication classes.
class MulCalculator:public Calculator{
public:
	//Subclass overrides parent virtual function
	virtual int getResult()
	{
		return m_Num1 * m_Num2;
	}
};

void test02()
{	
	//Polymorphic usage condition: the parent class pointer / reference points to the object of the child class.
	Calculator *c = new AddCalculator;
	c->m_Num1 = 10;
	c->m_Num2 = 10;

	cout << c->m_Num1 << " + " << c->m_Num2 << " = " << c->getResult() << endl;
	delete c;

	c = new SubCalculator;
	c->m_Num1 = 10;
	c->m_Num2 = 10;

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

	c = new MulCalculator;
	c->m_Num1 = 10;
	c->m_Num2 = 10;

	cout << c->m_Num1 << " * " << c->m_Num2 << " = " << c->getResult() << endl;
	delete c;
}

int main()
{
	//Common writing.
	//test01();
	
	//Polymorphic writing.
	test02();

	return 0;
}

3, Pure virtual functions and abstract classes.
In polymorphism, the implementation of the virtual function of the parent class is meaningless, mainly calling the content rewritten by the subclass.
Therefore, we can write the virtual function of the parent class as a pure virtual function.
Pure virtual function syntax: virtual return value type function name (parameter list) = 0;
When there are pure virtual functions in a class, the class is called an abstract class.

/ / abstract class features:
    //1. Cannot instantiate object.
    //2. The subclass of an abstract class must override the pure virtual function in the parent class, otherwise it also belongs to an abstract class.

#include <iostream>
using namespace std;

//Pure virtual functions and abstract classes
class Base{
public:
	//The implementation of parent virtual function is meaningless and can be modified to pure virtual function.
	//Pure virtual function syntax: virtual return value type function name (parameter list) = 0;
	virtual void func() = 0;   // Pure virtual function

	//As long as there is a pure virtual function in a class, this class is called an abstract class.
	//Abstract class features:
	//1. Unable to instantiate object.
	//2. The subclass of an abstract class must override the pure virtual function in the parent class, otherwise it also belongs to an abstract class.
};

class Son:public Base{
public:
	//Since the subclass does not override the pure virtual function of the parent class, the subclass also belongs to the abstract class.
	virtual void func()
	{
		cout << "Son func Call of" << endl;
	}
};

int main()
{
	/*
	Son s;
	Base &b = s;
	b.func();
	*/

	//1. Abstract classes cannot instantiate objects.
	//Base b;
	//new Base;
	
	//2. If the subclass does not override the pure virtual function of the parent class, it is also an abstract class.
	Son s;
	//new Son;

	return 0;
}

4, Polymorphism case 2. -- making drinks.
Case description:
The general process of making drinks is: boiling water - brewing - pouring into the cup - adding accessories
Use polymorphic technology to realize this case, provide abstract production of drinks, and provide subclasses to make coffee and tea.

For example:
Make coffee:
1. Boiled farmer spring
2. Brew coffee
3. Pour into a glass
4. Add sugar and milk

make tea
1. Boiled mineral water
2. Brewing tea
3. Pour into a small tea cup
4. Add lemon

#include <iostream>
using namespace std;

//Making beverage cases
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;

	//Making beverage functions
	void makeDrink()
	{
		Boil();
		Brew();
		PourInCup();
		Putsomething();
	}
};

//Making coffee
class Coffee:public AbstractDrinking{
public:
	virtual void Boil()
	{
		cout << "Boiled farmer spring" << endl;
	}

	virtual void Brew()
	{
		cout << "Brew coffee" << endl;
	}

	virtual void PourInCup()
	{
		cout << "Pour into a glass" << endl;
	}

	virtual void Putsomething()
	{
		cout << "Add sugar and milk" << endl;
	}
};

class Tea:public AbstractDrinking{
public:
	virtual void Boil()
	{
		cout << "Boiled mineral water" << endl;
	}

	virtual void Brew()
	{
		cout << "Brewing tea" << endl;
	}

	virtual void PourInCup()
	{
		cout << "Pour into a small tea cup" << endl;
	}

	virtual void Putsomething()
	{
		cout << "Add lemon" << endl;
	}
};

void doWork(AbstractDrinking *abs)   //  AbstractDrinking *abs = new Coffee
{
	abs->makeDrink();
	delete abs;
}

int main()
{
	doWork(new Coffee);

	cout << "-------------------" << endl;

	doWork(new Tea);

	return 0;
}

5, Virtual destruct and pure virtual destruct.
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: modify the destructor in the parent class to virtual destructor or pure virtual destructor.

Virtual destructor and pure virtual destructor have the following commonalities:
1) You can solve the problem of releasing subclass objects from parent class pointers.
2) All need to have 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 ()
{

}
 

#include <iostream>
using namespace std;

//Virtual destruct and pure virtual destruct
class Animal{
public:
	//Constructor
	Animal()
	{
		cout << "Animal Call of constructor for" << endl;
	}

	//Pure virtual function
	//virtual void speak() = 0;

	//Destructor  
	//Solution: modify the destructor of the parent class to virtual destructor or pure virtual destructor.
	/*virtual ~Class name ()
	{

	}
	*/
	
	virtual ~Animal()   //Virtual destructor - > then the Animal class is an ordinary class that can instantiate objects.
	{
		cout << "Animal Call of destructor" << endl;
	}
	

	/*Pure virtual deconstruction
	virtual ~Class name () = 0;
	Class name:: ~ class name ()
	{

	}
	*/

	//virtual ~Animal() = 0;  // Pure virtual destructor - > then the animal class is an abstract class and cannot instantiate an object.
};

/*
Animal::~Animal()
{
	cout << "Animal Call of destructor "< < endl;
}
*/

class Cat:public Animal{
public:
	//Constructor
	Cat(string name)
	{
		cout << "Cat Call of constructor for" << endl;
		m_Name = new string(name);
	}

	//virtual void speak()
	//{
		//cout << *m_ Name < < kitten talking < < endl;
	//}

	~Cat()
	{
		if(m_Name != NULL)
		{
			cout << "Cat Call of destructor" << endl;
			delete m_Name;
			m_Name = NULL;
		}
	}

	string *m_Name;
};

int main()
{
	//Animal a;  // Because the animal class has a pure virtual destructor, it is an abstract class. If the object cannot be instantiated, an error will be reported.
	//new Animal;
	
	Animal a;

	//The parent class pointer points to the child class object
	Animal *animal = new Cat("Tom");
	//animal->speak();
	delete animal;

	return 0;
}

Summary:
Remember, if any attribute in the subclass is opened to the heap, remember to modify the destructor of the parent class to virtual destructor or pure virtual destructor.

Summary:
Virtual function , pure virtual function , virtual destruct , pure virtual destruct
Not an abstract class , an abstract class , not an abstract class , an abstract class
You can instantiate an object, you can't instantiate an object, you can instantiate an object, and you can't instantiate an object

6, File operation.
1. Why learn file manipulation?
The data generated when the program runs belongs to temporary data, which will be released once the program runs.
Data can be persisted through files.
C + + operations on files need to include header files #include < fstream >

2. File type.
There are two kinds.
1) Text file.
The file is stored in the computer as ASCII code of text.

2) Binary file.
Files are stored in the computer in binary form of text, and users generally can't read them directly.

3. Operation file.
1) ofstream - > write operation
2) ifstream - > read operation
3) fstream - > read / write operation

7, Text file. -- write file.
To write a file:
1. Contains header files.
   #include <fstream>

2. Create a flow object.
   ofstream ofs; / / instantiate an object of a write operation class

3. Open the file.
   ofs.open("file path", opening method);

4. Write data.
Ofs < < "written data";

5. Close the file.
   ofs.close();

8, What are the opening methods?
ios::in: opens a file for reading.
ios::out: open a file to write to.
ios::ate initialization location: end of file
ios::app: write files in append mode
ios::trunc if the file exists, delete it first and then create it
ios::binary mode

Note: the file opening method can be used in conjunction with the "|" operator.
For example: write files in binary mode
      ios::binary | ios::out
 

#include <iostream>
using namespace std;
#include <fstream>

//Write operation of text file

int main()
{
	//1. Include header file.
	//2. Create a flow object.
	ofstream ofs;

	//3. Specify the opening method.
	ofs.open("example.txt",ios::out);

	//4. Write data
	ofs << "full name:Guan Guoyuan" << endl;
	ofs << "Gender:male" << endl;
	ofs << "Age:18" << endl;

	//5. Close the file.
	ofs.close();

	return 0;
}

result:
Create a new file in the current path called example txt
Contents:
Name: Guan Guoyuan
Gender: Male
Age: 18
 

9, Text file. -- read the file.
Reading a file is very similar to writing a file, but there are many more ways to read than to write.
The steps of reading files are as follows:
1. Contains header files.
   #include <fstream>

2. Create a flow object.
   ifstream ifs;

3. Open the file and specify how to open it.
   ifs.open("file path", opening method);

4. Read data
There are four ways to read data.

5. Close the file.
   ifs.close();
 

#include <iostream>
using namespace std;
#include <fstream>

//Text file read operation

int main()
{
	//1. Include header file.
	//2. Create a flow object.
	ifstream ifs;

	//3. Open the file and specify the opening method.
	ifs.open("example1.txt",ios::in);
	if( !ifs.is_open() )
	{
		cout << "File open failed!" << endl;
		return -1;
	}

	//4. Read data. (four ways)
	/* First kind 
	char buf[1024] = {0};
	while( ifs >> buf )
	{
		cout << buf << endl;
	}
	*/

	/* Second 
	char buf[1024] = {0};
	while( ifs.getline(buf,sizeof(buf)) )
	{
		cout << buf << endl;
	}
	*/

	/* Third 
	string buf;
	while( getline(ifs,buf) )
	{
		cout << buf << endl;
	}
	*/

	/* The fourth is to read by character
	char c;
	while(   (c = ifs.get()) != EOF )   //EOF: end of file   As long as it is not read to the end of the file, it is always read.
	{ 
		cout << c;
	}
	*/

	ifs.close();

	return 0;
}

10, Binary file-- Write operation.
Read and write files in binary mode. Remember to specify the opening mode: ios::binary

Writing files in binary mode mainly uses the stream object to call the member function write
Function prototype: ostream & write (const char * buffer, int len);

Parameters:
buffer: character pointer pointing to a legal space in memory.
len: number of bytes

#include <iostream>
using namespace std;
#include <fstream>

//Binary file write file.
class Person{
public:
	char m_Name[64];  //full name
	int m_Age;        //Age
};

int main()
{	
	//1. Include header file.
	//2. Create a flow object.
	//ofstream ofs;

	//3. Open the file in binary mode.
	//ofs.open("person.txt",ios::out | ios::binary);
	ofstream ofs("person.txt",ios::out | ios::binary);

	//4. Write data.
	Person p = {"Zhang San",18};
	ofs.write( (const char *)&p , sizeof(Person) );

	//5. Close the file.
	ofs.close();

	return 0;
}

11, Binary file-- Read the file.
Reading files in binary mode mainly uses the stream object to call the member function read
Function prototype: istream & read (char * buffer, int len);

Parameters:
buffer: character pointer pointing to a legal space in memory.
len: number of bytes read.

#include <iostream>
using namespace std;
#include <fstream>

//Binary file write file.
class Person{
public:
	char m_Name[64];  //full name
	int m_Age;        //Age
};


int main()
{
	//1. Include header file
	//2. Create a flow object.
	ifstream ifs;

	//3. Open file
	ifs.open("person.txt",ios::in | ios::binary);
	if( !ifs.is_open() )
	{
		cout << "File open failed" << endl;
		return -1;
	}

	//4. Read the document.
	Person p;
	ifs.read((char *)&p,sizeof(Person));

	cout << "full name:" << p.m_Name << " Age:" << p.m_Age << endl;

	//5. Close the file.
	ifs.close();

	return 0;
}

Topics: C++