[C++] Three major features --- Detailed explanation of inheritance

Table of contents

1. Concept and definition of inheritance

1.1 The concept of inheritance

1.2 Inheritance definition

1.2.1 Define format

1.2.2 Inheritance relationship and access qualifiers

1.2.3 Changes in access methods of inherited base class members

2. Assignment and conversion of base class and derived class objects

3. Scope in inheritance

4.Default member functions of derived classes

5. Inheritance and friends

6. Inheritance and static members

7. Complex diamond inheritance and diamond virtual inheritance

8. Summary and reflection on inheritance


1. Concept and definition of inheritance

1.1 The concept of inheritance

The inheritance mechanism is the most important means for object-oriented programming to makecode reusable. It allows programmers to /span> and add functions, thus generating a new class, which is called a derived class. Inheritance presents the hierarchical structure of object-oriented programming and embodies the cognitive process from simple to complex. In the past, most of the reuse we came into contact with was function reuse, and inheritance is reuse at the class design level. Expand on the basis of maintaining the characteristics of the original class

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

};

//继承方式可以不写,class默认private继承,struct默认是public
class Student : public Person
{

protected:
	int _stuid;//学号

};

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

1.2 Inheritance definition

1.2.1 Define format

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

1.2.2 Inheritance relationship and access qualifiers
 

1.2.3 Changes in access methods of inherited base class members

Class members/inheritance methods public inheritance protected inheritance private inheritance
public members of base class public members of derived classes protected members of derived classes
members
private
members of derived classes
protected
member of base class
protected members of derived classes
members
protected members of derived classes
members
private
members of derived classes
private member of base class
member
Not visible in derived classes Not visible in derived classes Not visible in derived classes

The most commonly used method is shared public inheritance.

Summarize:

  1. The private members of the base class are not visible in the derived class no matter how they are inherited. The invisible here means that the private members of the base 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. Private members of the base class cannot be accessed in the derived class. If the base class member does not want to be accessed directly outside the class, but needs to be accessible in the derived class, it is defined as protected. It can be seen that the protected member qualifier appears due to inheritance.
  3. In fact, if we summarize the above table, we will find that the private members of the base class are not visible in the subclass. The access method of other members of the base class in the subclass == Min (member's access qualifier in the base class, inheritance method), public > protected > private.
  4. 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.
  5. In practical applications, public inheritance is generally used, and protected/private inheritance is rarely used. The use of protected/private inheritance is also not recommended, because members inherited from protected/private can only be used in derived classes. In practice, Extension and maintainability are not strong

2. Assignment and conversion of base class and derived class objects

  • Derived class objects can be assigned to base class objects/base class pointers/base 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.
  • Base class objects cannot be assigned to derived class objects.
  • A pointer or reference from a base class can be assigned to a pointer or reference from a derived class through cast. But a base class pointer must be safe only when it points to a derived class object. If the base class here is a polymorphic type, you can use dynamic_cast of RTTI (RunTimeType Information) to identify and perform safe conversion. (ps: We will explain this in a later article, let’s learn about it first)
     

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;
}

The subclass object/pointer is assigned to the reference of the parent class object/parent class pointer, and no temporary object is generated in the middle.

int main()
{
	//相近类型可以隐式类型转换
	//int int* 

	double d = 2.2;
	int i = d;//中间产生临时变量,具有常性
	const int& r = d;//不加const会报错,r是临时变量的别名

	Student s;
	Person p = s;//中间也会产生临时变量
	Person& rp = s;//特殊处理
	//public继承,父类和子类是一个is-a 的关系
	//子类对象赋值给父类对象/父类指针的引用,我们认为是天然的,中间不产生临时对象
	//这个叫做父子类复制兼容的规则(切割/切片)

	//父不能给子类

	string str = "xxxx";
	const string& str1 = "xxxx";//自定义类型也是有常性的

	return 0;
}

3. Scope in inheritance

  1. In the inheritance system, base classes and derived classes have independent scopes.
  2. When 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, also called redefinition. (In subclass member functions, you can use base class::base class member to access explicitly)
  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.
class Person
{
public:
	void func()
	{
		cout << "func()" << endl;
	}
protected:
	string _name = "西兰花";
	int _num = 111;
};

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

	void Print()
	{
		cout << "姓名" << _name << endl;
		cout << "学号" << _num << endl;//就近原则访问自己的
		cout << "学号" << Person::_num << endl;//指定作用域
	}
protected:
	int _num = 999;//可以与父亲成员变量相同
};

//父类和子类可以有同名成员,因为它们是独立作用域
//默认情况直接访问是子类的,子类同名成员隐藏了父类同名成员
//继承中,同名的成员函数,函数名相同就构成隐藏,不管参数和返回值
//函数重载要求在同一作用域里面

int main()
{
	Student s1;
	s1.Print();

	s1.func(1);//调用自己成员函数
	s1.Person::func();//调用父类同名成员函数

	return 0;
}

4.Default member functions of derived classes

There are 6 default member functions. "Default" means that if we don't write them, the compiler will automatically generate one for us. So how are these member functions generated in the derived class?

  1. The derived class'sconstructor must call the base class's constructor to initialize that part of the members of the base class. If we don't write, the compiler will automatically call the default base class constructor. If the base class does not have a default constructor, it must be called explicitly during the initialization list stage 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, the base class constructor is called first and then the derived class constructor is called.
  6. To clean up the destructor of derived class objects, first call the destructor of the derived class and then call the destructor of the base class.
  7. Because the destructors in some subsequent polymorphic scenarios need 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 process it into destructor(), so when the parent class destructor does not add virtual,the subclass destructor and the parent class destructor The constructor constitutes a hidden relationship, you just need to know it first.

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

	Person(const Person& person)
	{
		_name = person._name;
		_num = person._num;
		cout << "Person(const Person& person)" << endl;
	}

	Person& operator=(const Person& person)
	{
		cout << "Person& operator=(const Person& person)" << endl;
		_name = person._name;
		_num = person._num;
		return *this;
	}

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

protected:
	string _name = "西兰花";
	int _num = 111;
};

class Student : public Person
{
public:
	Student(const char* name,int id)//先父后子,按声明的顺序走
	:Person(name)//父类成员当成一个整体,调用父类的构造函数
		,_id(id)
	{
	}

	Student(const Student& s)
		:Person(s)//虽然传的是子类,会发生隐式类型转换
		,_id(s._id)
	{
		cout << "Student(const Student& s)" << endl;
	}

	Student& operator=(const Student& s)//与父类构成隐藏关系,优先调用自己的
	{ 
		cout << "Student& operator=(const Student& s)" << endl;
		if (&s != this)
		{
			Person::operator=(s);//显示调用
			_id = s._id;
		}
		return *this;
	}

	//由于多态的原因,析构函数同一会被处理成destructor
	//父子类的析构函数构成隐藏
	~Student()
	{
		//Person::~Person();//这里需要指定 ,但是不需要我们自己调用,
                                //编译器会在派生类析构后自动调用
		cout << "~Student()" << endl;
	}
	//构造先父后子
	//析构先子后父,自己调用不能保证先子后父
	//因为子类析构中可能访问父类成员,先析构父类存在风险
	//为了保证析构安全,先子后父,
	//父类析构函数不需要显示调用,子类析构函数结束时会自动调用父类析构
	//保证先子后父
protected:
	int _id;
};

int main()
{
	Student s1("张三",18);
	Student s2(s1);//我们不写编译器自动生成,把父类当成一个整体,调用父类的拷贝构造函数,

	Student s3("李四",19);
	s1 = s3;//我们不写编译器自动生成,把父类当成一个整体,调用父类的赋值运算符重载,子类内置类型值拷贝,自定义类型调用拷贝构造函数
	return 0;
}

5. Inheritance and friends

Friend relationships cannot be inherited, which means that base class friends cannot access private and protected members of subclasses.

6. Inheritance and static members

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.
 

7. Complex diamond inheritance and diamond virtual inheritance

Single inheritance:When a subclass has only one direct parent class, this inheritance relationship is called single inheritance

Multiple inheritance:When a subclass has two or more direct parent classes, this inheritance relationship is called multiple inheritance

 Diamond inheritance: Diamond inheritance is a special case of multiple inheritance.

Problems with 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.

Code example: 

//菱形继承
class Person
{
public:
	string _name; // 姓名
};
class Student : public Person
{
protected:
	int _num; //学号
};
class Teacher : public Person
{
protected:
	int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
	string _major; // 主修课程
};
void Test()
{
	// 这样会有二义性无法明确知道访问的是哪一个
	Assistant a;
	//a._name = "peter";
	a.Teacher::_name = "西老师";
	a.Student::_name = "西同学";
	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决

}

Virtual inheritance can solve the problems of ambiguity and data redundancy 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.

//菱形继承
class Person
{
public:
	string _name; // 姓名
};
class Student : virtual public Person
{
protected:
	int _num; //学号
};
class Teacher : virtual public Person
{
protected:
	int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
	string _major; // 主修课程
};
void Test()
{
	
	Assistant a;
    //虚继承之后是同一份,Teacher与Student中是同一份,改一个都改。可以通过调试查看
    //可以简单理解为存的是引用
	a.Teacher::_name = "西老师";
	a.Student::_name = "西同学";
	
	a._name = "西兰花";
}

The principle of virtual inheritance to solve data redundancy and ambiguity

In order to study the principle of virtual inheritance, we give a simplified diamond inheritance system, and then use memory window to observe the model of object members .

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;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;

	return 0;
}

No virtual inheritance is used:

The following figure is the memory object member model of diamond inheritance, viewed through the debug memory window: here you can see data redundancy, there are two copies of A.

Use virtual inheritance:

The following figure is the memory object member model of diamond virtual inheritance: Here we can analyze that A is placed at the bottom of the object composition in the D object. This A belongs to both B and C, so how do B and C go What about finding the public 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. The following A can be found through the offset

The following is the abovePrinciple explanation of diamond virtual inheritance of Person relationship:

8. Summary and reflection on inheritance

  • 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 (object-oriented) languages ​​do not have multiple inheritance, such as Java.

Inheritance and composition

  1. Public inheritance is ais-a relationship. That is to say, every derived class object is a base class object.
  2. Combination is ahas-a relationship. Suppose B combines A, and there is an A object in every B object.
  3. Prefer object composition over class inheritance.
  4. 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 dependence between derived classes and base classes is very strong and the degree of coupling is high.
  5. Object composition is an alternative to class inheritance for reuse. 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 the combined classes, and the degree of coupling is low. Preferring object composition helps you keep each class encapsulated.
  6. Actually try to 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.

Thought questions:

  1. What is diamond inheritance? What's the problem with diamond inheritance?
  2. What is diamond virtual inheritance? How to resolve data redundancy and ambiguity
  3. What is the difference between inheritance and composition? When to use inheritance? When to use combination?

This article is over!

Guess you like

Origin blog.csdn.net/qq_72916130/article/details/134788799