[C++] Basic knowledge of inheritance once again

Table of contents

1. Concept

2. Inheritance definition

1. Inheritance format 

2. The relationship between access qualifiers and inheritance methods

3. Changes in access methods inherited from parent class members

summary: 

3. Assignment and transformation of parent class and subclass objects

4. Inheritance scope

1.Features 

2. Test questions

5. Different default member functions for derived classes

1.Constructor

2.Copy construction

3. Assignment symbol overloading

4. Destructor

5. Summary

6. Friendship and inheritance

7. Inheritance and static members

2. Thinking: How to make a class that cannot be inherited

8. Diamond inheritance and virtual diamond inheritance

1. The problem of diamond inheritance

2. Virtual inheritance solution

3. Low-level details of virtual inheritance 

9. Inheritance and combination

Summarize:

Conclusion


1. Concept

The inheritance mechanism is the most important means of object-oriented programming to make code reusable . It allows programmers to extend and add functions while maintaining the characteristics of the original class , thus generating new classes, called derived classes. Inheritance presents the hierarchical structure of object-oriented programming and reflects the cognitive process from simple to complex. In the past, the reuse we came into contact with was function reuse, and inheritance was reuse at the class design level .

The one based on the original class is generally called the base class (base_class). I think it is easier to understand that it is called the parent class .
Run the following code
#include <iostream>
#include <string>

using namespace std;

class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "peter"; // 姓名
	int _age = 18;
};

class Student : public Person
{
protected:
	int _stuid; // 学号
};

class Teacher : public Person
{
protected:
	int _jobid; // 工号
};

int main()
{
	Student s;
	Teacher t;
	s.Print();
	t.Print();
	t.Print();
	return 0;
}

After inheritance, the members (member functions + member variables) of the parent class's Person will become part of the subclass. This reflects that Student and Teacher reuse members of Person. Next, we use the monitoring window to view the Student and Teacher objects, and we can see the reuse of variables. You can see the reuse of member functions by calling Print .

2. Inheritance definition

1. Inheritance format 

Below we see that Person is the parent class, also called the base class. Student is a subclass, also called a derived class  

2. The relationship between access qualifiers and inheritance methods

The default inheritance method when  using the keyword class is private , and the default inheritance method when using struct is public , but it is best to write the inheritance method explicitly.

3.  Inherit changes in access methods of parent class members

summary: 

1. The private members of the parent class are invisible in the derived class no matter how they are inherited. Invisible here means that the private members of the parent class are still inherited into the derived class object, but the syntax restricts the derived class object from accessing it whether inside or outside the class .
2. Although the private members of the parent class are inherited in the derived class, the private members of the parent class are not visible (due to permission issues). If the members of the parent class do not want to be directly accessed outside the class, but they need to be accessible in the derived class, It is defined as protected. It can be seen that the protected member qualifier appears because of inheritance .

 3. Subclass access permission = the minimum permission of the modifier and inheritance mode. For example: the modifier is private, the inheritance mode is public, and the final permission is private. Permissions from large to small are: public > protected > private

4. In practical applications, public inheritance is generally used, and protected/private inheritance is rarely used. The use of protected/private inheritance is not recommended, because members inherited from protected/private can only be used in derived classes. In practice, expansion and maintainability are not strong. So basically, the common way to deal with inheritance: public . Handle member variables, functions: use public / protected

3. Assignment and transformation of parent class and subclass objects

Subclass objects can be assigned to parent class objects/parent class pointers/parent class references . There is a vivid term here called slicing or cutting . It means cutting off the parent class part of the derived class and assigning it to it.
class Person
{
protected:
	string _name; // 姓名
	string _sex;
	int _age; // 年龄
};
class Student : public Person
{
public:
	int _No; // 学号
};

void Test()
{
	Student sobj;
	// 1.子类对象可以赋值给父类对象/指针/引用
	Person pobj = sobj;
	Person* pp = &sobj;
	Person& rp = sobj;

	//2.父类对象不能赋值给子类对象
	// sobj = pobj;


	// 3.父类的指针可以通过强制类型转换赋值给子类的指针
	pp = &sobj;
	Student * ps1 = (Student*)pp; // 这种情况转换时可以的。
	ps1->_No = 10;

	// 父类利用指针强转给子类指针
	pp = &pobj;
	Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
	ps2->_No = 10;   
}

we can find out that:

1. Parent class objects cannot be assigned to subclass objects.

2. The parent class object address is forcibly converted to the subclass address, and eventually an out-of-bounds error will occur when the subclass accesses its new members.

4. Inheritance scope

1.Features 

1. In the inheritance system, base classes and derived classes have independent scopes . (So ​​even if the parent and child classes have functions with the same name, it is not called function overloading because they are not in the same scope)
2. If there are members with the same name in the subclass and the parent class, the subclass members will block the parent class from direct access to the members with the same name. This situation is called hiding , or redefinition. (In subclasses, you can use base class::base class members to explicitly access parent class members )
3. It should be noted that if it is the hiding of a member function, only the function names need to be the same to constitute hiding .
4. Note that in practice it is best not to define members with the same name in the inheritance system .

2. Test questions

class A
{
public:
 void fun(int i)
 {
 cout << "func()" << endl;
 }
};

class B : public A
{
public:
 void fun(int i)
 {
 A::fun();
 cout << "func(int i)->" << i << endl;
 }
};

void Test()
{
 B b;
 b.fun();
};
// 不定向选择:
// A: 子父func构成重载
// B: 子父func构成隐藏
// C: 程序报错
// D:以上都不对

Answer: BC. B. First, the child and parent classes have member functions with the same name, which constitutes hiding . C, because the subclass hides the function with the same name of the parent class, the matching fun function cannot be found, so an error is reported.

5. Different default member functions for derived classes

 This is the experimental code for this time. After this study, we will supplement this derived class.

class Person
{
public:
	Person(const char* name = "peter")
		: _name(name)
	{
		cout << "Person()" << endl;
	}

	Person(const Person& p)
		: _name(p._name)
	{
			cout << "Person(const Person& p)" << endl;
	}

	Person& operator=(const Person& p)
	{
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;

		return *this;
	}

	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name; // 姓名
};

class Student : public Person
{
public:
	// 构造函数
protected:
	int _num; //学号
};

void Test()
{
	Student a;
	int x = 1;
}

int main()
{
	Test();
	return 0;
}

1.Constructor

 Let's not write the constructor first. What will happen if we look at the default constructor?

 It's obvious: with the default constructor, the parent class will call its own constructor; the subclass will also call its own constructor. If there is no constructor, the compiler will automatically generate a constructor. (In short: each constructs its own) and the construction order is parent class first -> subclass.

    // 构造函数
	Student(const char* name ,const int num = 0)
		:Person(name)
		,_num(num)
	{}

2.Copy construction

 Idea: Parent class data, through implicit conversion of the parent class.

    // 拷贝构造
	Student(const Student& s1)
		:_num(s1._num)
		, Person(s1)    // 通过子类向父类的强转化
	{
		cout << "Student(const Student& )" << endl;
	}

3. Assignment symbol overloading

 Idea: Call the parent class assignment symbol overload, and then assign values ​​to the subclass member variables.

    // 赋值符号重载
	Student& operator=(const Student& s1)
	{
		cout << "operator=" << endl;
		if (this != &s1)
		{
			Person::operator=(s1);
			_num = s1._num;
		}	
		return *this;
	}

 Supplement: If the subclass member variables do not require deep copying , there is actually no need to write copies and assignment symbols to overload functions, because the parent class has these two functions.

4. Destructor

 There is nothing to say about this. The parent and child classes each call their own destructors.

Destruction sequence: The subclass is destructed first, and then the parent class destructor is called after the subclass destruction is completed.

5. Summary

1. The constructor of the derived class must call the constructor of the base class to initialize that part of the members of the base class. If the base class does not have a default constructor, it must be called explicitly during the initialization list phase of the derived class constructor.
2. The copy constructor of the derived class must call the copy constructor of the base class to complete the copy initialization of the base class.
3. The operator= of the derived class must call the operator= of the base class to complete the copy of the base class.
4. The destructor of the derived class will automatically call the destructor of the base class to clean up the base class members after being called. Because this can ensure that the derived class object first cleans up the derived class members and then cleans up the base class members in order.
5. When initializing a derived class object, first call the base class constructor and then the derived class constructor.
6. When destructing and cleaning up the derived class object, first call the destructor of the derived class and then call the destructor of the base class.
7. Because the destructor in some subsequent scenarios needs to be rewritten, one of the conditions for rewriting is that the function names are the same (we will explain this later). Then the compiler will perform special processing on the destructor name and change it to destructor(). Therefore, when the parent class destructor does not add virtual, the subclass destructor and the parent class destructor form a hidden relationship.

6. Friendship and inheritance

If you are unfamiliar with Youyuan, you can read this article by the little blogger to recall: Detailed explanation of C++ classes and objects (Part 2) - Use code to practice functions_Huaguoshan~~Programmer's Blog-CSDN Blog

Conclusion: Derived classes cannot inherit the friend relationship of the parent class. In other words, friend functions or friend classes cannot directly obtain private or protected member variables and functions of the derived class .  

Sample code: 

class Student;
class Person
{
public:
 friend void Display(const Person& p, const Student& s);
protected:
 string _name; // 姓名
};
class Student : public Person
{
protected:
 int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{
 cout << p._name << endl;
 cout << s._stuNum << endl;
}
void main()
{
 Person p;
 Student s;
 Display(p, s);
}

Friend relationships cannot be inherited , so if a friend relationship is needed in a derived class, the friend relationship needs to be re-declared

7. Inheritance and static members

Detailed explanation of basic knowledge about static member variables and functions in C++ classes and objects (Part 2) - Use code to practice functions_Huaguoshan~~Programmer's Blog-CSDN Blog

Phenomenon conclusion: If the base class defines static members, there will be only one such member in the entire inheritance system . No matter how many subclasses are derived, there is only one static member instance.

Sample code:

class Person
{
public:
	Person() { ++_count; }
protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};

int Person::_count = 0;

class Student : public Person
{
protected:
	int _stuNum; // 学号
};

class Graduate : public Student
{
protected:
 string _seminarCourse; // 研究科目
};

void TestPerson()
{
	Student s1;
	Student s2;
	Student s3;
	Graduate s4;
	cout << " Person接口 人数 :" << Person::_count << endl;
	Student::_count = 666; // 在Student类,访问Person类中静态成员变量,并且设置该静态成员变量
	cout << " Person接口 人数 :" << Person::_count << endl;
	cout << " Student接口 人数 :" << Student::_count << endl;
	cout << " Graduate接口 人数 :" << Graduate::_count << endl;
}

2. Thinking: How to make a class that cannot be inherited

 Idea: privatize one of the constructor and destructor.

class  Person
{
public:
	~Person()	
	{}
private:
	Person()
	{}
    
	int _age = -1;
	char* _name;
};

class Student : public Person
{
private:
	int _score;
};


void TestPerson()
{
	Student a;
    Person  a1; // 当然,父类自己也实例不了对象了
}

No, this way the subclass cannot adjust the parent class. However, at the same time, the parent class itself cannot instantiate objects. You really made me cry to death.

Solution: Create a static member function so that the object can be instantiated outside the class, thus bypassing the system calling the constructor when creating the object.

    // 单纯的成员函数也不行,因为对象都没有你告诉咋调函数。
	static Person create_Person()
	{
		return Person();
	}

8. Diamond inheritance and virtual diamond inheritance


1. The problem of diamond inheritance

From the object member model construction below, we can see that diamond inheritance has problems with data redundancy and ambiguity . There will be two copies of the Person member in the Assistant object . this will
Causes memory waste! ! !

2. Virtual inheritance solution

Virtual inheritance can solve the ambiguity and data redundancy problems of diamond inheritance. As shown in the inheritance relationship above, using virtual inheritance when Student and Teacher inherit Person can solve the problem. It should be noted that virtual inheritance should not be used elsewhere .

Experimental code:

class A
{
public:
	int _a;
};

// class B : public A
class B : virtual public A
{
public:
	int _b;
};

// class C : public A
class C : virtual public A
{
public:
	int _c;
};

class D : public B, public C
{
public:
	int _d;
};

int main()
{
	D d;
	d.B::_a = 1;
	cout << &(d.B::_a) << endl;

	d.C::_a = 2;
	cout << &(d.C::_a) << endl;

	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

Virtual diamond inheritance allows a class to inherit from multiple base classes through virtual inheritance. Only one common base class sub-object will be retained, avoiding the occurrence of multiple identical base class sub-objects.

No, data redundancy:

3. Low-level details of virtual inheritance 

 

It can be analyzed here that in the D object, A is placed at the bottom of the object composition. This A belongs to both B and C. So how do B and C find the common A?
Here is a table pointed to by the two pointers B and C. These two pointers are called virtual base table pointers, and these two tables are called virtual base tables . The offset stored in the virtual base table. A below can be found by the offset .  

 Knowledge of memory endianness will be mentioned here. You can refer to this article [C Language] Integration, floating point data storage, big and small endian. Full of details! ! _Little endian floating point array_Huaguoshan~~Programmer’s Blog-CSDN Blog

 Principle simplified diagram:

 Supplement: If there are multiple objects of the same virtual diamond inheritance class, the offset tables they access are the same set. For example: D is a class inherited from virtual diamond, then D d1, d2, d3..., d1, d2, d3 all access the same set of offset tables. From here we can also find that in order to solve the problem of data redundancy and ambiguity, virtual diamond inheritance needs to access the offset table, but there is no doubt that this will cause performance loss.

4. The diamond shape is inherited to Xiaobai who doesn’t know whether to live or die, which is a little shocking!

 Q: Please explain the printing order

using namespace std;
class A {
public:
	A(const char* s) { cout << s << endl; }
	~A() {}
};

class B :virtual public A
{
public:
	B(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
};

class C :virtual public A
{
public:
	C(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
};

class D :public B, public C
{
public:
	D(const char* s1, const char* s2, const char* s3, const char* s4) : B(s1, s2), C(s1, s3), A(s1)
	{
		cout << s4 << endl;
	}
};
 

int main() 
{
	D* p = new D("class A", "class B", "class C", "class D");
	delete p;
	return 0;
}

Analysis: First, the constructor constructs the parent class first, so class A is constructed first , and then according to the inheritance order

Then comes the Class B construct, then Class C, and finally Class D. Class A is only called once during the entire process. (When I first saw it, people were a little confused) 

9. Inheritance and combination

Inheritance is a relationship in which one class (called a child or derived class) can inherit the properties and methods of another class (called a parent or base class).

Composition is another type of relationship in which one class (called a container class) contains objects of another class (called a member class). A container class uses the properties and methods of a member class by creating an object of the member class.

(Just like the vector class in STL we learned before, the container contains the string class)

Summarize:

  Many people say that C++ syntax is complicated, but in fact multiple inheritance is a manifestation of it. With multiple inheritance , there is diamond inheritance, and with diamond inheritance, there is diamond virtual inheritance. The underlying implementation is very complicated. Therefore, it is generally not recommended to design multiple inheritance, and you must not design diamond inheritance. Otherwise, there will be problems in complexity and performance. Multiple inheritance can be considered one of the shortcomings of C++. Many later OO languages ​​do not have multiple inheritance, such as Java.

1. Inheritance allows you to define the implementation of a derived class based on the implementation of the base class. This kind of reuse by generating derived classes is often called white-box reuse. The term "white box" refers to visibility: in inheritance, the internal details of a base class are visible to subclasses. Inheritance destroys the encapsulation of the base class to a certain extent, and changes in the base class have a great impact on the derived class. The dependency between derived classes and base classes is very strong and the degree of coupling is high.
2. Object composition is another reuse option besides class inheritance. New and more complex functionality can be obtained by assembling or combining objects. Object composition requires that the objects being composed have well-defined interfaces. This style of reuse is called black-box reuse because the internal details of the object are not visible. Objects only appear as "black boxes". There is no strong dependency between combined classes, and the degree of coupling is low . Preferring object composition helps you keep each class encapsulated .
3. Actually use as many combinations as possible . The combination has low coupling and good code maintainability. However, inheritance also has its place. Some relationships are suitable for inheritance, so use inheritance. In addition, inheritance is also necessary to achieve polymorphism. The relationship between classes can be inherited or combined, so just use combination .

Conclusion

   That’s it for this section. Thank you for browsing. If you have any suggestions, please leave them in the comment area. If you bring something to your friends, please leave a like. Your likes and attention will become a blog post . Main motivation for creation

Guess you like

Origin blog.csdn.net/qq_72112924/article/details/132723309