[C++]16:多态

多态

  • 1.多态的定义和实现
    • 1.多态的概念:
    • 2.虚函数和虚函数的重写:
      • 1.虚函数的重写:
      • 2.协变:子类和父类中虚函数的返回值不同(意义不大)
      • 3.析构函数的重写:
      • 4.一个题目:
      • 5.普通调用 and 多态调用:
      • 6.C++11 新增语法 final 和 override
        • 1.final
        • 2.override:
      • 7.重载 && 重写 && 隐藏(重定义)
  • 2.抽象类:
    • 1.抽象类概念:
    • 2.接口继承和实现继承:
  • 3.多态的原理:
    • 1.虚函数表:
    • 2.虚函数的重写是一种==覆盖==:
    • 3.动态绑定+静态绑定
  • 4.单继承和多继承中的虚函数表:
    • 1.单继承---->虚函数表:
    • 2.多继承---->虚函数表:
    • 3.多继承---->菱形继承和菱形虚拟继承
      • 1.菱形继承
      • 2.菱形虚拟继承
  • 5.继承和多态常见的面试题目:

1.多态的定义和实现

1.多态的概念:

同一个行为不同类型的对象去做会产生不同的结果

比如下面的例子:正常人座公交车投币和刷卡,学生会刷学生卡,老人刷老年卡----->同一个买票乘车的行为–>不同对象(学生or普通人or老人)—>不同结果(滴 学生卡 / 投币 or 刷卡 / 滴 老年卡)

[C++]16:多态_第1张图片
[C++]16:多态_第2张图片

2.虚函数和虚函数的重写:

1.使用virtual修饰的类的成员函数就是虚函数。

观察主函数:
1.必须通过父类的指针和引用调用父类的这个虚函数。
2.调用的必须是一个虚函数,并且子类要对基类的虚函数进行重写。

1.虚函数的重写:

1.虚函数之间的重写需要满足三同:返回值 函数名称 参数。
2.重写:子类中去—>相同声明不同定义的虚函数.
3.不同对象可以通过同一个父类对象的指针或者引用调用重写完成的虚函数.
4.看似调用的是父类的成员函数但是实际上调用的是重写的子类的虚函数。

[C++]16:多态_第3张图片

2.协变:子类和父类中虚函数的返回值不同(意义不大)

1,要求是父子类关系的指针或者引用类型的返回:
2.成员函数:调用对象不是自己或者自己的父类。
3.返回的是其他的父子类关系对象。

namespace sfpy {
	class A {};
	class B : public A {};
	class C : public A {};


	class preson_bus {
	public:
		virtual void ticket()
		{
			cout << "投币 or 刷卡" << endl;
		}

		//1.协变
		virtual A* ret_1() { return new A; }
		virtual A& ret_2(A& a) { return a; }
	};
	class student :public preson_bus {
	public:
		virtual void ticket()
		{
			cout << "滴 学生卡" << endl;
		}
		virtual B* ret_1(){ return new B; }
		virtual B& ret_2(B& b){ return b; }
	};
	class oldman :public preson_bus {
	public:
		virtual void ticket()
		{
			cout << "滴 老年卡" << endl;
		}
		virtual C* ret_1() { return new C; }
		virtual C& ret_2(C& c) { return c; }
	};
}

3.析构函数的重写:

1.重写需要满足三同,对于析构函数来说(~函数名称)。
2.父类的析构函数加上virtual对于子类函数只要有定义就已经完成了析构函数的重写.
3.这个时候就有一个问题了:子类和父类的析构函数的名称是不是不一样,为什么构成析构呢?因为析构函数的名称统一被修改为destructor,实际上还是满足虚函数重写的规则。

namespace sfpy {
	class preson_bus {
	public:
		virtual void ticket()
		{
			cout << "投币 or 刷卡" << endl;
		}

		preson_bus()
		{}

		//1.协变
		virtual A* ret_1() { return new A; }
		virtual A& ret_2(A& a) { return a; }

		//2.析构函数的重写:

		virtual ~preson_bus()
		{
			cout << "~preson_bus()" << endl;
		}
	};
	class student :public preson_bus {
	public:
		virtual void ticket()
		{
			cout << "滴 学生卡" << endl;
		}
		student()
			:_name("xxxxxx")
		{}

		virtual B* ret_1(){ return new B; }
		virtual B& ret_2(B& b){ return b; }

		virtual ~student()
		{
			cout << "~student()" << endl;
		}
	protected:
		string _name;
	};
	class oldman :public preson_bus {
	public:
		virtual void ticket()
		{
			cout << "滴 老年卡" << endl;
		}

		oldman() 
			:_name("xxxxxx")
		{}
		virtual C* ret_1() { return new C; }
		virtual C& ret_2(C& c) { return c; }

		virtual ~oldman()
		{
			cout << "~oldman()" << endl;
		}
	protected:
		string _name;
	};
}

内容补充:
1.父类中的析构函数前面加了virtual后面子类中的析构函数前面可以不需要加virtual(建议virtual都去加上)。
2.不需要显示的去使用析构函数,在函数结束后自动的调用析构先父后子。

[C++]16:多态_第4张图片

4.一个题目:

class A
{
public:
	virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
	virtual void test() { func(); }
};

class B : public A
{
public:
	void func(int val = 0) 
	{
		std::cout << "B->" << val << std::endl; 
	}
};

int main(int argc, char* argv[])
{
	B* p = new B;
	p->test();
	return 0;
}

[C++]16:多态_第5张图片

5.普通调用 and 多态调用:

1.普通调用:对象,对象指针,对象引用,都可以去调用相关成员的函数和变量。
2.多态调用:子类对象的(指针或者引用)赋值给父类对象通过父类对象(指针或者引用)调用对应已经重写好的虚函数。

6.C++11 新增语法 final 和 override

1.final

实现一个类不可以被继承:

方法一:
1.父类构造私有化,子类实例化不了对象。

方法二:
1.使用final修饰类,如果类要继承在编译的时候就报错。

[C++]16:多态_第6张图片
2.final修饰父类的virtual成员函数可以让它不可以被重写:

[C++]16:多态_第7张图片

2.override:

1.override加在子类中的虚函数(){} 中可以检查当前虚函数是否重写,如果没有重写就编译报错。

namespace sfpy {
	class preson_bus{
	public:
		virtual void ticket ()
		{
			cout << "投币 or 刷卡" << endl;
		}

		preson_bus()
		{}


		//2.析构函数的重写:

		virtual ~preson_bus()
		{
			cout << "~preson_bus()" << endl;
		}
	};
	class student :public preson_bus {
	public:
		virtual void ticket() override
		{
			cout << "滴 学生卡" << endl;
		}
		student()
			:_name("xxxxxx")
		{}


		virtual ~student()
		{
			cout << "~student()" << endl;
		}
	protected:
		string _name;
	};
	class oldman :public preson_bus {
	public:
		virtual void ticket() override
		{
			cout << "滴 老年卡" << endl;
		}

		oldman()
			:_name("xxxxxx")
		{}

		virtual ~oldman()
		{
			cout << "~oldman()" << endl;
		}
	protected:
		string _name;
	};
}

7.重载 && 重写 && 隐藏(重定义)

1,重载:两个函数在同一个作用域 && 函数名相同参数不同。
2.重写:
2-1:两个函数分别在父类和子类中。
2-2:三同(协变除外)
2-3:两个函数必须是虚函数。
3.隐藏(重定义)
3-1:两个函数分别在父类和子类中。
3-2:函数名相同。
3-3:子类和父类的同名函数不是重写就是隐藏!

2.抽象类:

1.抽象类概念:

1.给虚函数后面加上一个=0,虚函数变成了纯虚函数。
2.包涵纯虚函数的类被称为抽象类,抽象类不可以实例化出对象。
3.继承抽象类,子类要完成一个对纯虚函数的一个重写,完成重写后才可以实例化子类对象。
4.规定类派生类必须进行重写。

namespace sfpy {
	class preson_bus {
	public:
		//纯虚函数:
		virtual void ticket()=0
		{}
		//2.析构函数的重写:
		virtual ~preson_bus()
		{
			cout << "~preson_bus()" << endl;
		}
	};

	class student :public preson_bus {
	public:
		virtual void ticket() override
		{
			cout << "滴 学生卡" << endl;
		}
		student()
			:_name("xxxxxx")
		{}


		virtual ~student()
		{
			cout << "~student()" << endl;
		}
	protected:
		string _name;
	};
	class oldman :public preson_bus {
	public:
		virtual void ticket() override
		{
			cout << "滴 老年卡" << endl;
		}

		oldman()
			:_name("xxxxxx")
		{}

		virtual ~oldman()
		{
			cout << "~oldman()" << endl;
		}
	protected:
		string _name;
	};
}

2.接口继承和实现继承:

1.普通函数的继承是实现继承,继承父类的实现,继承的是整体的内容。
2.虚函数的继承是接口继承,继承父类的接口,重写父类的内容。不进行多态调用,没有必要定义虚函数,没有必要进行重写。

3.多态的原理:

1.虚函数表:

namespace sfpy {
	class Base{
	public:
		virtual void fun1()
		{
			cout << "fun1()" << endl;
		}
	protected:
		int _a;
	};
}

[C++]16:多态_第8张图片
[C++]16:多态_第9张图片

1.存在一个vfptr的函数指针数组的指针。
2.vfptr就指向一个虚函数表。

namespace sfpy {
	class Base{
	public:
		virtual void func1()
		{
			cout << "Base_1()" << endl;
		}
		virtual void func2()
		{
			cout << "Base_2()" << endl;
		}
	protected:
		int _a;
	};
	class Base_1 : public Base {
	public:
		virtual void func1()
		{
			cout << "Base_1_1()" << endl;
		}
		virtual void func2()
		{
			cout << "Base_1_2()" << endl;
		}
	protected:
		int _a;
	};

}

[C++]16:多态_第10张图片

2.虚函数的重写是一种覆盖

[C++]16:多态_第11张图片

3.动态绑定+静态绑定

1.静态绑定:在编译的时候函数就已经编译完成,确定了函数调用的行为。
2.动态绑定:在程序运行的时候提供对象的类型去确定具体调用的行为同时调用对应的函数的过程。

4.单继承和多继承中的虚函数表:

1.单继承---->虚函数表:

补充:
1.虚函数表存在常量区。
2.相同类型的对象他们的虚表是相同的。
3.虚表在编译的时候就产生了,虚表指针是在初始化列表之前产生的。

2.多继承---->虚函数表:


namespace sfpy {
	class B {
	public:
		virtual void func1()
		{
			cout << "B::func1()" << endl;
		}
	protected:
		int _b = 1;
	};
	class C {
	public:
		virtual void func1()
		{
			cout << "C::func1()" << endl;
		}
	protected:
		int _c = 2;
	};
	class D : public B , public C{
		virtual void func1()
		{
			cout << "D::func1()" << endl;
		}
	protected:
		int _d = 3;
	};
}


[C++]16:多态_第12张图片

[C++]16:多态_第13张图片

3.多继承---->菱形继承和菱形虚拟继承

1.菱形继承

1.A作为公共的父类存在数据冗余和二义性的问题!
2.这个时候就需要菱形虚拟继承。

namespace sfpy {
	class A {
	public:
		virtual void func1()
		{
			cout << "B::func1()" << endl;
		}
	protected:
		int _a = 1;
	};
	class B : public A{
	public:
		virtual void func1()
		{
			cout << "B::func1()" << endl;
		}
	protected:
		int _b = 2;
	};
	class C : public A {
	public:
		virtual void func1()
		{
			cout << "C::func1()" << endl;
		}
	protected:
		int _c = 3;
	};
	class D : public B , public C{
		virtual void func1()
		{
			cout << "D::func1()" << endl;
		}
	protected:
		int _d = 4;
	};
}

[C++]16:多态_第14张图片

2.菱形虚拟继承

namespace sfpy {
	class A {
	public:
		virtual void func1()
		{
			cout << "B::func1()" << endl;
		}
	protected:
		int _a = 1;
	};
	class B : virtual public A {
	public:
		virtual void func1()
		{
			cout << "B::func1()" << endl;
		}
	protected:
		int _b = 2;
	};
	class C : virtual public A {
	public:
		virtual void func1()
		{
			cout << "C::func1()" << endl;
		}
	protected:
		int _c = 3;
	};
	class D : public B, public C {
		virtual void func1()
		{
			cout << "D::func1()" << endl;
		}
	protected:
		int _d = 4;
	};
}

[C++]16:多态_第15张图片

5.继承和多态常见的面试题目:

  1. inline函数可以是虚函数吗?
    答:可以,不过编译器就忽略inline属性,这个函数就不再是inline,因为虚函数要放到虚表中去。

  2. 静态成员可以是虚函数吗?
    答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。

  3. 构造函数可以是虚函数吗?
    答:不能,因为对象中的虚函数表指针是在构造函数初始化列表
    阶段才初始化的。

  4. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?
    答:可以,并且最好把基类的析构函数定义成虚函数。

  5. 对象访问普通函数快还是虚函数更快?
    答:首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。

你可能感兴趣的:(c++,多态)