欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[C++]牢记这7个要点,掌握继承和多态将变得非常简单

程序员文章站 2022-07-11 11:41:15
...

[C++]牢记这7个要点,掌握继承和多态将变得非常简单

一:c++中的继承

之前我妈所接触到的复用更多的都是函数复用,而继承则是对类层次的复用。它允许在保持原有类的特性基础上进行扩展,增加功能,产生新的类,称之为派生类(子类),原本的类则称之为基类(父类)。

1. 继承及访问权限

class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
public:
	int _id = 1;
// proteced: 类外不可见,类内部和子类的可见
protected:
	string _name = "peter"; // 姓名
private:
	int _age = 18; // 年龄
};

class Student : public Person
{
public:
	void show()
	{
		cout << _name << "\t" << _id << endl;
		//父类private成员在子类中不可见, 但是子类确实有父类的私有成员
		//cout << _age << endl;
	}
public:
	int _num = 2020;
};

继承基类成员后访问方式的变化:
[C++]牢记这7个要点,掌握继承和多态将变得非常简单
[C++]牢记这7个要点,掌握继承和多态将变得非常简单

  1. 友元关系不能继承,也就是说父母的朋友不一定是我们的朋友,是不能够相互访问的。
  2. 基类定义了static静态成员,则整个继承体系里面只有一个这样的成员,无论派生多少个子类,都只会存在这有一个。
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; // 研究科目


test:
	Person p;
	Student s;
	Graduate g;
	p._count = 1;
	s._count = 2;
	g._count = 3;
	Person::_count = 4;
	Student::_count = 5;
	Graduate::_count = 6;
	cout << &p._count << endl;//6
	cout << &s._count << endl;//6
	cout << &g._count << endl;//6
};

  1. 优先使用对象组合,而不是类继承,类继承一定程度破坏了基类的封装,也被人们称之为白箱复用,而对象组合则是黑箱复用,组合类之间没有较强的依赖性,耦合度低,代码的维护性较好。

2. 切片操作

把派生类中父类那部分切割出来进行赋值。
[C++]牢记这7个要点,掌握继承和多态将变得非常简单
[C++]牢记这7个要点,掌握继承和多态将变得非常简单

3. 同名隐藏及默认成员函数

同名隐藏:

  1. 父类和子类中有同名的成员,子类只能够直接看到自己的成员
  2. 如果需要访问父类同名的成员,则需要加上父类的作用域
  3. 不同的作用域下,含有同名成员的话,当前作用域下的成员就会隐藏其他作用域下的同名成员,这不仅仅是继承体系独有的
    成员变量隐藏:成员变量的名称相同
  • 函数隐藏:只要函数名相同,就会构成函数隐藏,和参数无关–>这种情况一般是发生在不同作用域的父类和子类中(因此我们在继承体系中最好不要定义同名成员)
  • 函数重载:在同一个作用域中,函数名相同,但参数不同的情况。

默认成员函数
[C++]牢记这7个要点,掌握继承和多态将变得非常简单
[C++]牢记这7个要点,掌握继承和多态将变得非常简单

  1. 派生类对象初始化先调用基类构造,再调派生类构造
  2. 派生类对象析构清理先调用派生类析构,再调用基类的析构

4. 菱形继承

  1. 单继承:一个子类只有一个父类
    [C++]牢记这7个要点,掌握继承和多态将变得非常简单
  2. 多继承:一个子类有两个或以上直接父类
    [C++]牢记这7个要点,掌握继承和多态将变得非常简单
  3. 菱形继承:多继承的一种特殊情况
    [C++]牢记这7个要点,掌握继承和多态将变得非常简单
    菱形继承存在着冗余性和二义性
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 _majorCourse; // 主修课程
};

void test()
{
	Assistant ass;
	ass._name = "1";//无法知道它访问的是那个,或造成二义性
	cout << sizeof(ass) << endl;
	ass.Student::_name = "1";//因此即使是指定父类的话,但是却无法解决掉数据冗余的问题
	ass.Teacher::_name = "2";

}

为了更好解决菱形继承的二义性和数据冗余问题,可以使用虚拟继承的方式来进行解决(虚拟继承不能够在其他地方使用)virtual

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

[C++]牢记这7个要点,掌握继承和多态将变得非常简单

二:多态

不同的对象执行同一种行为时所产生的不同状态,被人们称之为多态。(比如我们抢红包,同样的点击动作,出现的金额是不同的)

  1. 如果是非多态的话,则需要看它的类型
  2. 多态的话,则只需要看实际指向的那个实体

1. 多态的实现条件

  1. 继承
  2. 父类定义虚函数,子类重写了父类虚函数
  3. 调用虚函数的类型必须为指针/引用,一般都为父类指针/引用
    必须同时满足以上条件,三者条件缺一不可
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl;}
};

重写

  1. 重写: 派生类中有一个跟基类完全相同的虚函数(返回值类型,函数名,参数列表完全相同)当然其中也有列外的存在,就是协变函数重写和析构函数的重写
  • 协变: 返回值类型可以不同,但是返回值类型必须是父子关系的指针/引用
  • 析构函数的重写:虽然函数名不相同,看似违背重写规则,但在编译后的析构函数却是同一个,因此无论子类析构函数是否加virtual关键字,都与父类析构函数构成重写
虚函数的重写
class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }

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

class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }

	virtual ~Student() { cout << "~Student()" << endl; }
}


协变函数重写
	virtual B& BuyTicket() {
		cout << "买票-半价" << endl;
		return B();
	}
析构函数的重写

  1. 派生类的虚函数即使是不加virtual关键字,也是可以构成重写,因为继承后基类的虚函数被继承下来了,不建议这样使用
  2. 如果父类函数加了virtual声明,则子类接口完全一致的函数,即使不加virtual的声明,也具有虚函数的属性,建议一般对于所有虚函数,都加上virtual。

2. 检测重写 抽象类

C++给我们提供了override和final两个关键字,可以帮助我们检测是否重写

  1. final:修饰虚函数,表示改虚函数不能被继承
class Car
{
public:
	virtual void Drive() final {}//使用final定义的函数不能被重写,体现实现继承
};
class Benz :public Car
{
public:
	virtual void Drive() {cout << "Benz-舒适" << endl;}
};
  1. override:检查派生类虚函数是否重写了基类某个虚函数,强制子类重写父类的某一个虚函数,如果没有则重写编译报错
class Car{
public:
	virtual void Drive(){}
};
class Benz :public Car {
public:
	virtual void Drive() override {cout << "Benz-舒适" << endl;}//体现接口继承
};

重载,重写,隐藏的对比:
[C++]牢记这7个要点,掌握继承和多态将变得非常简单
3. 抽象类(纯虚函数)
在虚函数的后面写上= 0,则整个函数为纯虚函数(纯虚函数没有函数体),包含纯虚函数的类也被称之为抽象类,不能实例化对象。

class Car
{
public:
virtual void Drive() = 0;
};

3. 多态的原理

class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
};

sizeof(Base)=4

这也就意味着在类中,存放着一个_vfptr指针放在对象的前面,我们将这个指针叫做虚函数表指针。一个含有虚函数的类中都至少有一个虚函数表指针,因为虚函数的地址要被放到虚函数表(虚表)中。
[C++]牢记这7个要点,掌握继承和多态将变得非常简单

class Base
{
public:
	

	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}
	
	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
	
	void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
private:
	int _b = 1;
};
class Derive : public Base
{
public:
	//虚表:存放函数指针的数组
	//     
	virtual void Func1()
	{
		cout << "Derive::Func1()" << endl;
	}
private:
	int _d = 2;
};

void test()
{
	Base b;
	Derive d;

	Base& rb = b;
	Base& rd = d;

	rb.Func1();
	rd.Func1();
	rd.Func2();

	d.Func1();
}

[C++]牢记这7个要点,掌握继承和多态将变得非常简单

4. 单继承和多继承的虚函数表(重要)

  1. 单继承状态下的虚表
class Base
{
public:
	

	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}
	
	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
	
	void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
private:
	int _b = 1;
};
class Derive : public Base
{
public:
	//虚表:存放函数指针的数组
	//     
	virtual void Func1()
	{
		cout << "Derive::Func1()" << endl;
	}
private:
	int _d = 2;
};

[C++]牢记这7个要点,掌握继承和多态将变得非常简单
2. 多继承中的虚函数表


class Base1 {
public:
	virtual void func1() { cout << "Base1::func1" << endl; }
	virtual void func2() { cout << "Base1::func2" << endl; }
private:
	int b1;
};
class Base2 {
public:
	virtual void func1() { cout << "Base2::func1" << endl; }
	virtual void func2() { cout << "Base2::func2" << endl; }
private:
	int b2;
};

class Derive : public Base1, public Base2 {
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
private:
	int d1;
};

//定义 函数指针: void 函数()
typedef void(*vfPtr)();

void doVF(vfPtr* vftable)
{
	cout << "虚表地址:" << vftable << endl;
	//nullptr: 结束
	for (int i = 0; vftable[i] != nullptr; ++i)
	{
		//获取当前虚表位置的函数指针
		vfPtr func = vftable[i];
		//执行指向的函数
		func();
	}
}

void test()
{
	Base1 b;
	Base2 b2;
	Derive d;
	cout << "Base1:" << &b << endl;
	vfPtr* vftable = (vfPtr*)(*((int*)&b));
	doVF(vftable);

	cout << "Base2:" << &b2 << endl;
	vftable = (vfPtr*)(*((int*)&b2));
	doVF(vftable);
	
	//访问Derive的第一个虚表
	vftable = (vfPtr*)(*((int*)&d));
	cout << "Derive first vftable:" << &d << endl;
	doVF(vftable);

	//访问Derive的第二个虚表: 地址偏移
	vftable = (vfPtr*)(*((int*)((char*)&d + sizeof(Base1))));
	cout << "Derive second vftable:" << (int*)((char*)&d + sizeof(Base1)) << endl;
	doVF(vftable);
}

[C++]牢记这7个要点,掌握继承和多态将变得非常简单
总结:
[C++]牢记这7个要点,掌握继承和多态将变得非常简单