Three major features of C++ object-oriented--inheritance

1. The concept and definition of inheritance

1.1 The concept of inheritance

Inheritance (inheritance) mechanism is the most important means for object-oriented programming to make code reusable. It allows programmers to expand and add functions on the basis of maintaining the characteristics of the original class. The new class generated in this way is called a derived class. or subclasses. Inheritance presents the hierarchical structure of object-oriented programming, reflecting the cognitive process from simple to complex. The reuse we have been exposed to before is function reuse, and inheritance is the reuse of class design levels.

class Person
{
    
    
public:
	void func()
	{
    
    
		cout << "func" << endl;
	}
public:
	string _name = "张三";
};
class Student : public Person
{
    
    
public:
	//继承后父类的Person的成员(成员函数+成员变量)都会变成子类Student的一部分。
	//即子类现在也拥有成员变量_name和成员函数func,可以通过子类对象直接访问
	int age=20;
};
int main()
{
    
    
	Person p;
	Student stu;
	cout << stu._name << endl;
	return 0;
}

1.2 Inheritance definition

1.2.1 Definition format

insert image description here

1.2.2 Inheritance and access qualifiers

Inheritance mode: public inheritance, protected inheritance, private inheritance.
Access qualifiers: public access, protected access, private access.

1.2.3 Changes in access methods of inherited base class members

insert image description here
In total, there are nine combinations of inheritance and access qualifiers.
To sum up, there are two points:
1. No matter what inheritance method is used by the subclass to inherit the private members of the parent class, it is invisible.
2. In other cases, the access qualifier and the inheritance method with less permission are used as the access permission of the member variable inherited by the subclass from the parent class.

In addition, there are several supplementary points:
1. The private members of the base class are invisible no matter how they are inherited in the derived class. Invisibility here means that the private members of the base class are still inherited into the derived class object, but the derived class object is grammatically restricted from being able to access it no matter inside or outside the class.
2. The private members of the base class cannot be accessed in the derived class. If the base class members do not want to be accessed directly outside the class, but need to be accessible in the derived class, they can be defined as protected. So protected member qualifiers appear because of inheritance.
3. When using the keyword class, the default inheritance method is private, and when using struct, the default inheritance method is public, but it is better to write out the inheritance method explicitly.
4. In practice, public inheritance is generally used, because private inheritance and protected inheritance can only be used inside subclasses, and the extension and maintenance are not very easy.

Two, parent class and subclass object assignment conversion

Objects of derived classes can be assigned to objects of base classes/pointers of base classes/references of base classes. This assignment we call slicing or cutting. It means to cut the part of the parent class in the subclass and assign it to the past.
The parent class object cannot be assigned to the subclass object, because the members of the subclass itself do not exist in the parent class, so they cannot be assigned.
insert image description here

class Person
{
    
    
public:
	void func()
	{
    
    
		cout << "func" << endl;
	}
public:
	string _name;
	int _id;
	int _age;
};
class Student : public Person
{
    
    
public:
	int _No;
};
int main()
{
    
    
	Person p;
	Student stu;
	
	//子类对象可以直接赋值给父类对象/指针/引用
	p = stu;
	Person* pp = &stu;
	Person& rp = stu;

	return 0;
}

3. Scope in inheritance

  1. In the inheritance system, the base class and the derived class have independent scopes.
  2. There are members with the same name in the subclass and the parent class, and the subclass members will block the direct access of the parent class to the members with the same name. This situation is called hiding or redefinition. (In a subclass member function, it can be explicitly accessed using base::base member)
  3. It should be noted that if it is the hiding of a member function, only the same function name is required to constitute hiding.
  4. Note that in practice it is best not to define members with the same name in the inheritance hierarchy.
  5. Hiding is different from the previous function overloading. Function overloading requires the same scope, and the parameter types or numbers of the overloaded functions are different, but hiding only requires the same function name.
class A
{
    
    
public:
	int _num = 10;
};

class B :public A
{
    
    
public:
	int _num = 20;
};

int main()
{
    
    
	//A中的_num和B中的_num构成隐藏(重定义),
	B b;
	//就近原则访问B类自己的_num成员,
	cout << b._num << endl;
	//如果想访问A中的_num,需要指定类域
	cout << b.A::_num << endl;

	//A中的func和B中的func构成隐藏
	b.func();
	//要想用b对象调用A中的func函数,需要指定类域
	b.A::func();
	return 0;
}

insert image description here

Fourth, the default member function of the derived class

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

  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 has no default constructor, the call must be explicit during the initializer 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 in this way, the order in which derived class objects are cleaned up first and then base class members is guaranteed.
  5. Derived class object initialization calls the base class construction first and then calls the derived class construction.
  6. The derived class object destructor cleanup calls the derived class destructor first and then adjusts the base class destructor.
  7. Because some subsequent scene destructors need to be rewritten, one of the conditions for rewriting is that the function name is the same. Then the compiler will perform special processing on the destructor name and process it as destructor(), so when the parent class destructor does not add virtual, the subclass destructor and the parent class destructor form a hidden relationship.
class A
{
    
    
public:

	//构造函数
	A(int num)
		:_num(num)
	{
    
    
		cout << "A(int num)" << endl;
	}

	//拷贝构造
	A(const A& a)
		:_num(a._num)
	{
    
    
		cout << "A(const A& a)" << endl;
	}

	//赋值重载
	A& operator=(const A& a)
	{
    
    
		if (this != &a)
		{
    
    
			_num = a._num;
			cout << "operator=(const A& a)" << endl;
		}
		return *this;
	}

	//析构函数
	~A()
	{
    
    
		cout << "~A()" << endl;
	}

	void func()
	{
    
    
		cout << "funcA" << endl;
	}
public:
	int _num = 10;
};

class B :public A
{
    
    
public:

	//构造函数
	B(int num)
		//调用父类的构造函数
		:A(num)
		,_num(num)
	{
    
    
		cout << "B(int num)" << endl;
	}

	//拷贝构造
	B(const B& b)
		//调用父类的拷贝构造函数
		:A(b)
		,_num(b._num)
	{
    
    
		cout << "B(const B& b)" << endl;
	}

	//赋值重载函数
	B& operator=(const B& b)
	{
    
    
		if (this != &b)
		{
    
    
			//调用父类的赋值重载函数,参数b不用处理,因为子类对象可以直接赋
			//值给父类对象/引用/指针
			A::operator=(b);
			_num = b._num;
			cout << "operator=(const B& b)" << endl;
		}
		return *this;
	}

	//析构函数
	~B()
	{
    
    
		//编译器自动先调用子类的析构函数析构子类对象自己的成员,
		// 再自动调用父类的析构函数析构从父类继承过来的那部分成员
		cout << "~B()" << endl;
	}

	void func()
	{
    
    
		cout << "funcB" << endl;
	}
public:
	int _num = 20;
};

int main()
{
    
    
	B b(10);

	B bb(b);

	b = bb;

	return 0;
}

5. Inheritance and friendship

Friend relationships cannot be inherited, that is to say, base class friends cannot access subclass private and protected members.

6. Inheritance and static members

If the base class defines a static static member, there is only one such member in the entire inheritance system. No matter how many subclasses are derived, there is only one static member instance.
You can use this feature to calculate how many objects are defined in the derived class.

class A
{
    
    
public:
	A()
	{
    
    
		count++;
	}
	static int countkid()
	{
    
    
		return count;
	}
public:
	static int count;
};
int A::count = 0;

class B :public A
{
    
    };

int main()
{
    
    
	int i = 0;
	for (int i = 0; i < 10; i++)
	{
    
    
		//定义子类B的对象时一定会调用父类A的构造函数,
		//故可以在A的构造函数里统计定义了多少个子类对象
		B b;
	}
	cout << A::countkid() << endl;

	return 0;
}

7. Complex diamond inheritance and diamond virtual inheritance

Single inheritance: When a subclass has only one direct parent class, the inheritance relationship is called single inheritance.
insert image description here
Multiple inheritance: When a subclass has two or more direct parent classes, the inheritance relationship is called multiple inheritance.
insert image description here
Diamond inheritance: Diamond inheritance is a special case of multiple inheritance.
insert image description here
insert image description here
Virtual inheritance can solve the problem of ambiguity and data redundancy of diamond inheritance. As in the inheritance relationship above, using virtual inheritance when B and C inherit A can solve the problem. It should be noted that virtual inheritance should not be used in other places.

Diamond inheritance object storage model:

//多继承

class A
{
    
    
public:
	int _a;
};
class B : public A
{
    
    
public:
	int _b;
};
class C : 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;
}

insert image description here

Virtual inheritance object storage model:

//虚拟继承
class A
{
    
    
public:
	int _a;
};
class B : virtual public A
{
    
    
public:
	int _b;
};
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;
}

insert image description here
The virtual inheritance of single inheritance also uses the same storage model, and also handles the object model of virtual inheritance by storing the offset of the virtual base table pointer. as follows:
insert image description here

insert image description here

8. Summary and reflection on inheritance

1. It is said that the syntax of C++ is very complicated. In fact, multiple inheritance is a manifestation. With multiple inheritance, there is diamond-shaped inheritance, and with diamond-shaped inheritance, there is diamond-shaped virtual inheritance, and 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 are problems in complexity and performance.

2. Multiple inheritance can be considered as one of the defects of C++. Many later OO languages ​​do not have multiple inheritance, such as Java.

3. Inheritance and combination
Public inheritance is an is-a relationship. That is to say, every derived class object is a base class object. If every student is a person.

Composition is a has-a relationship. Assuming B composes A, there is an A object inside each B object. Like heads and noses, each head has a nose.

Prefer object composition over class inheritance.

Inheritance allows you to define derived class implementations in terms of base class implementations. This kind of reuse by generating derived classes is often called white-box reuse. The term "white box" is relative to visibility: in inheritance, the internal details of the base class are visible to subclasses. Inheritance destroys the encapsulation of the base class to a certain extent, and the change of the base class has a great impact on the derived class. The dependency relationship between the derived class and the base class is very strong, and the coupling degree is high.

Object composition is an alternative to class inheritance for reuse. New and more complex functions 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 objects are invisible. Objects only appear as "black boxes".

There is no strong dependency relationship between composite classes, and the degree of coupling is low. Preferring object composition helps you keep each class encapsulated.

In practice, use as many combinations as possible. The coupling degree of the combination is low, and the code maintainability is good. However, inheritance is also useful. Some relationships are suitable for inheritance, so use inheritance. In addition, to achieve polymorphism, inheritance is also necessary. The relationship between classes can use inheritance or combination, just use combination.

Nine, answer three questions


1. What is diamond inheritance? What is the problem with diamond inheritance?
Diamond-shaped inheritance means that a subclass has multiple parent classes, and the parent classes have a common parent class. At this time, diamond-shaped inheritance is formed. Diamond inheritance has data redundancy and ambiguity problems.

2. What is rhombus virtual inheritance? How to solve data redundancy and ambiguity?
The keyword virtual is added to the inheritance of the "waist" of multiple inheritance to form virtual inheritance. Virtual inheritance is to put the inherited members in a common position, and only one copy of the same members is placed; at the same time, a virtual object is stored in the object. Base table pointer, when accessing an inherited member, find the offset of the member relative to the virtual base table pointer through the virtual base table pointer, so as to find the member that needs to be accessed.

3. What is the difference between inheritance and composition? When to use inheritance? When to use combination?
Inheritance: every derived class object is a base class object, composition: every B object has an A object.
Inheritance is used when it only conforms to the characteristics of inheritance, and inheritance must be used when polymorphism is used; combination is used when it only meets the characteristics of combination; combination can be used when both inheritance and combination can be used. Because the combination has the characteristics of high cohesion and low coupling.

The above is all the content I want to share with you today, have you learned it? If you feel that it is helpful, please like it and follow it. We will continue to update C++ related knowledge in the future. See you in the next issue! ! ! ! ! ! !

Guess you like

Origin blog.csdn.net/weixin_70056514/article/details/131880287