C++:多态

目录

一、多态的概念

二、多态的定义及实现

1、多态的构成条件

2、虚函数

3、虚函数的重写

3.1、协变:

3.2、析构函数重写:

4、override 和 final 关键字

5、重载、覆盖、隐藏

三、抽象类

1、接口继承

2、实现继承


 

一、多态的概念

顾名思义,多态就是多种形态,举个例子:比如说买票这个行为,当普通人买票时是全价票,学生买票时是半价票,军人买票时是优先买票。也就是说一件事,每个不同的类去做,会发生不一样的行为称之为多态。

二、多态的定义及实现

1、多态的构成条件

1、多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如 Student 继承了Person。 Person 对象买票全价, Student 对象买票半价。
2、必须通过基类的指针或者引用调用虚函数。
3、被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。

2、虚函数

虚函数就是被virtual修饰的类成员函数成为虚函数。

class Person{
public:
    virtual void BuyTicket() 
    {
        cout << "全价票" << endl;
    }
}

3、虚函数的重写

当派生类中和基类有一个完全相同的虚函数。派生类和基类都得写才标准。
class Person {
public:
    virtual void BuyTicket()
    {
        cout << "买票-全价" << endl;
    }
};
 
class Student : public Person {
public:
    virtual void BuyTicket()
    {
        cout << "买票-半价" << endl;
    }
 
    //void BuyTicket() { cout << "买票-半价" << endl}
};
 
void Func(Person& p)
{
    p.BuyTicket();
}
 
int main()
{
    Person ps;
    Func(ps);
 
    Student st;  
    Func(st);
 
    return 0;
}

3.1、协变:

派生类重写基类虚函数时,与基类虚函数返回的类型不同。

3.2、析构函数重写:

如果基类的析构函数是虚函数,那么派生类的析构函数默认会和基类的析构函数构成重写,虽然函数名不同,但是编译器进行编译后,把析构函数统一处理为destructor。

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "全价" << endl;
	}
	virtual Person* F()
	{
		return new Person;
	}
	virtual ~Person()
	{
		cout << "~Person" << endl;
	}
};
class Student : public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "半价" << endl;
	}
	virtual Student* F()
	{
		return new Student;
	}
	virtual ~Student()
	{
		cout << "~Student" << endl;
	}
};
void Test(Person& p)
{
	p.BuyTicket();
}
int main()
{
	Person p;
	Student s;
	Test(p);
	Test(s);
	return 0;
}析构了两次~Person

 

class Person {
public:
	~Person()
	{
		cout << "~Person()" << endl;
	}
};

class Student : public Person {
public:
	~Student()
	{
		cout << "~Student()" << endl;
	}
};

int main()
{
	Person* p1 = new Person;
	Person* p2 = new Student;

	delete p1;
	delete p2;

	return 0;
}

为什么会调用两次~Person()? 而且没有调用~Student()。子类的指针赋给父类指针时,会发生切割。p2 指针只会指向属于父类的那一部分,子类的析构函数自然也就没有赋值过去。所以调用的是父类的析构函数, 并不能正确的释放掉动态开辟的空间。只有派生类 Student 的析构函数重写了 Person 的析构函数,下面的 delete 对象调用析构函数,才能构成多态,才能保证 p1 和 p2 指向的对象正确的调用析构函数。
修改后:
class Person {
public:
    virtual ~Person()
    {
        cout << "~Person()" << endl;
    }
};
 
class Student : public Person {
public:
    virtual ~Student()
    {
        cout << "~Student()" << endl;
    }
};
 
int main()
{
    Person* p1 = new Person;
    Person* p2 = new Student;
 
    delete p1;
    delete p2;
 
    return 0;
}

4、override 和 final 关键字

C++11中, 用来辅助进行虚函数多态的多种复杂情况,避免出现疏忽而导致错误的情况出现。

override : 检查派生类虚函数是否重写了基类的某个虚函数,如果没有就会报错。

class Person
{
public:
	/*virtual*/ void BuyTicket()
	{
		cout << "全价票" << endl;
	}
};
class Student : public Person
{
public:
	virtual void BuyTicket() override//报错
	{
		cout << "半价票" << endl;
	}
};
class Child : public Student
{
public:
	virtual void BuyTicket() override
	{
		cout << "半价票" << endl;
	}
};

final : 修饰虚函数,表示这个虚函数不能被重写。

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "全价票" << endl;
	}
};
class Student : public Person
{
public:
	virtual void BuyTicket() final
	{
		cout << "半价票" << endl;
	}
};
class Child : public Student
{
public:
	virtual void BuyTicket()//
	{
		cout << "半价票" << endl;
	}
};

5、重载、覆盖、隐藏

1.重载指的是,函数名在一个作用域,并且函数名相同,参数不同的情况,那么这两个函数就构成了函数重载,编译器在进行处理的时候会根据参数形成不同的函数表。

2.重写指的是,两个函数在基类和派生类的作用域下,前提是函数名、参数、返回值都一样的情况下,如果是虚函数,那么就构成了重写,其中子类可以不写virtual,可以理解为虚函数的属性被从基类中继承了下来,但是并不推荐这样写,其中要注意特殊情况,比如协变和析构函数的情况。

3.隐藏指的是,两个函数在基类和派生类的作用域下,当函数名相同的时候,如果不符合重写的定义那么就是重定义了,比如在继承中见到的很多种情况。

三、抽象类

抽象类的定义:在虚函数后面写上=0,就是纯虚函数,有纯虚函数的类就是抽象类,特点是不能实例化出一个具体的对象派生类也是不能,只有在重写了虚函数,才能实例化,纯虚函数体现了派生类要重写的这个原则,同时也体现了接口继承的概念。

基类 - 抽象类 - 不能实例化出对象
class Car
{
public:
    virtual void Drive() = 0; 纯虚函数,不需要实现它
};
 
派生类
class Benz :public Car
{
public:
    virtual void Drive() 必须重写基类虚函数,派生类才能实例化出对象
    {
        cout << "Benz-舒适" << endl;
    }
};
 
class BMW :public Car
{
public:
    virtual void Drive()
    {
        cout << "BMW-操控" << endl;
    }
};
 
int main()
{
    基类是抽象类,不能实例化出对象,但可以定义基类指针,用来实现多态
    Car* pBenz = new Benz;
    pBenz->Drive();
 
    Car* pBMW = new BMW;
    pBMW->Drive();
 
    return 0;
}

1、接口继承

接口继承(Interface Inheritance)是指从一个纯虚基类(pure virtual base class)继承而来,目的是为了实现一个类的接口,使得派生类必须实现该接口中定义的所有纯虚函数。接口继承的主要目的是实现类的接口复用,它并不关心实现细节。在接口继承中,派生类只需要实现基类中定义的纯虚函数,不需要关心基类中其他的数据和函数。

class Shape
{
public:
	virtual void draw() = 0;//纯虚函数
};
class Cirrle : public Shape
{
public:
	void draw() override
	{
		//实现圆形的绘画
	}
};
class Squre : public Shape
{
	void draw() override
	{
		//实现矩形的绘画
	}
};

2、实现继承

实现继承(Implementation Inheritance)是指从一个普通的基类(非纯虚基类)继承而来,目的是为了实现基类中已有的函数或数据。实现继承的主要目的是实现代码复用,它关心基类中的实现细节。在实现继承中,派生类会继承基类中所有的成员函数和数据成员,并且可以重写这些函数以改变它们的行为。

class Person
{
public:
	virtual void Say()
	{
		cout << "Person" << endl;
	}
};
class Student : public Person
{
public:
	virtual void Say() override
	{
		cout << "Student" << endl;
	}
};

int main()
{
	Person p;
	Student s;
	s.Say();
	return 0;
}

下章节介绍多态的原理!如有不正之处,希望大家私信我去改正! 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值