[C++] 初识多态

标题:[C++] 多态

@水墨不写bug



目录

(一)什么是多态

(二)什么是虚函数

 (1)虚函数的重写:

 I,协变(基类 和 派生类 虚函数的返回值类型不同)

II,析构函数的重写(基类 和 派生类 析构函数名不同)

(三)C++11 新增的 override 和 final 

 (四)重载,重写(覆盖),隐藏(重定义)的辨析

(五)抽象类

 


(一)什么是多态

        在继承体系中,不同的class实例化的对象调用同一个函数,产生了不同的行为。

实现多态的条件:

        通过基类的指针或者引用调用重写过的虚函数。

class Person
{
public:
	virtual void buyticket()
	{
		cout << "100¥" << endl;
	}
};
class VIP : public Person
{
public:
	virtual void buyticket()
	{
		cout << "50¥" << endl;
	}
};
void Enter(Person& p)
{
	p.buyticket();
}
int main()
{
	Person p1;
	VIP vp1;
	Enter(p1);
	Enter(vp1);
	return 0;
}

       ( 生活中,想要入场需要买票,普通人Person买票需要100元;VIP买票需要50元)

        在上例中,基类中buyticket()函数前 声明有 “virtual” ,是虚函数。在派生类VIP中,我们重写了虚函数buyticket()。在main函数中,通过一个Enter函数(形参为基类的引用)调用buyticket()函数,满足了上述两个条件:

1. 必须通过基类的指针或者引用调用虚函数
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

        满足上述两个条件,就构成多态。 


(二)什么是虚函数

        在class中,被 virtual 修饰的member function 就是虚函数(virtual function)。

 (1)虚函数的重写:

        虚函数的重写(覆盖):基类中声明了一个虚函数,派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
        在派生类中,重写虚函数前面可以不加“virtual”,但是为了规范,建议加上“virtual”:

class Person
{
public:
	virtual void buyticket()
	{
		cout << "100¥" << endl;
	}
};
class VIP : public Person
{
public:

    //可以不加virtual
    void buyticket()
	{
		cout << "50¥" << endl;
	}
/*
	virtual void buyticket()
	{
		cout << "50¥" << endl;
	}
*/
};

虚函数重写的例外:

        1.协变

        2.析构函数的重写***(重要)

 I,协变(基类 和 派生类 虚函数的返回值类型不同)

        派生类重写虚函数时,和基类的返回值类型不同,但是两个虚函数返回值的类型必须是 拥有继承关系的类:

        基类返回基类指针,派生类返回派生类指针。

class A
{};
class B : public A
{};
class Person
{
public:
	virtual A* GetObj()
	{
		cout << "100¥" << endl;
		return new A;
	}
};
class VIP : public Person
{
public:
	virtual B* GetObj()
	{
		cout << "50¥" << endl;
		return new B;
	}
};

II,析构函数的重写(基类 和 派生类 析构函数名不同)

        基类的析构函数建议设置为虚函数,目的是为了多态做准备。如果基类的析构函数是虚函数,此时派生类的析构函数如果有定义,无论是否加上virtual,都与基类的析构函数构成重写。

为什么呢?

        析构函数是类内部特殊处理的一类成员函数,在编译时,析构函数的名称会被编译器统一处理为destructor,这就为重写奠定基础。

class Person {
public:
    virtual ~Person() {cout << "~Person()" << endl;}
};
class Student : public Person {
public:
    virtual ~Student() { cout << "~Student()" << endl; }
};
/*只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函
数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。*/
int main()
{
    Person* p1 = new Person;
    Person* p2 = new Student;

    delete p1;
    delete p2;

    return 0;
}

输出:

~Person()

~Student()
~Person()

(三)C++11 新增的 override 和 final 

override和final是对虚函数的实现与否进行限制的关键字。

1. final:修饰虚函数,表示该虚函数不能再被重写
2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

 (四)重载,重写(覆盖),隐藏(重定义)的辨析

        重载:在同一个作用域中,如果两个或者多个函数的函数名相同,形参列表不同,这些函数就构成重载。

 比如:

//这两个函数构成重载
long long Add(int x, long y) 
{ 
	return x + y; 
}
long long Add(int x, int y) 
{ 
	return x + y; 
}

形参列表不同指的是:1)形参的数目不同,2)形参的类型不同,3)形参的顺序不同

        需要注意:仅仅返回值类型不同,不构成函数重载


         重写(覆盖):在继承关系中,派生类的虚函数与基类的虚函数完全相同(函数名,形参列表,返回值都相同(协变除外)),这两个函数构成重写(覆盖)。(重写之所以叫做“重写”“覆盖”,在后面多态的原理会让你有深刻的理解)

比如: 

class Person
{
public:
	virtual void  buyticket()
	{
		cout << "100¥" << endl;
	}
};
class VIP : public Person
{
public:
	virtual void buyticket()
	{
		cout << "50¥" << endl;
	}
};

        重定义(隐藏):在继承关系中,派生类的函数与基类的函数名相同(仅仅是函数名称相同),并且不构成重写(覆盖),就是重定义(隐藏)。

比如:

// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
{
public:
	void fun()
	{
		cout << "func()" << endl;
	}
};
class B : public A
{
public:
	void fun(int i)
	{
		cout << "func(int i)->" << i << endl;
	}
};

        由于B类的func和A类的func构成隐藏关系:B类实例化的对象默认调用的是fun(int i), 而不是fun() 。


(五)抽象类

        在虚函数的函数头后面加上“ =0 ”,则这个函数就被声明为纯虚函数,含有纯虚函数的类称为抽象类(也叫接口类),抽象类不能实例化出对象

        抽象类的派生类在没有重写纯虚函数的情况下也不能实例化出对象。只有重写纯虚函数,派生类才能实例化出对象。纯虚函数的声明规范了派生类必须重写,纯虚函数也体现了接口继承


class PC
{
public:
	//纯虚函数
	virtual void Band()=0;
};
class Thinkbook : public PC
{
public:
	virtual void Band()
	{
		cout << "Thinkbook" << endl;
	}
};
class Huawei : public PC
{
	virtual void Band()
	{
		cout << "Huawei" << endl;
	}
};
int main()
{
    //纯虚函数不能实例化出对象
	PC p;                //(错误)
	Thinkbook tb;        //(正确)
	Huawei hw;           //(正确)
	return 0;
}

什么是接口继承?

        普通函数继承是一种实现继承,派生类继承了基类函数,可以调用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是重写,构成多态,继承的是接口。所以如果不实现多态,不要把函数定义为虚函数。

看了下面这道题,你会对接口继承有更深的理解:

// 以下程序输出结果是什么()
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;
}
//A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确

程序输出结果是什么()
 

        A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确

 稍加分析,你会发现这道题不同寻常:

        B对象调用A类成员函数,实际是B*赋值给A的this,再调用test(),test()内调用func():基类指针调用重写后的虚函数,构成多态,于是你会选择D选项;

        但是你要明白一个事实:接口继承会保留原函数的接口(体现接口继承)——走原函数的函数头,函数体部分根据多态,走重写后的部分函数体。

        所以这道题的正确选项应该是B。


完~

未经作者同意禁止转载

  • 25
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水墨不写bug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值