以下代码在vs2019下的x86程序中,涉及指针为4bytes。
多态:多种形态。具体一些,就是不同的对象去做同样的行为,会产生出不同的状态。
例如:买车票的时候,当你是学生的时候,可能就会半价;当你是其他人的时候可能就是全价了。
//举例,这个代码可以暂时不用看懂
class Person
{
public:
virtual void BuyTicket()
{
cout << "普通票---全价" << endl;
}
};
class Student : public Person
{
virtual void BuyTicket()
{
cout << "学生票---半价" << endl;
}
};
void func(Person& p)
{
p.BuyTicket();
}
int main(void)
{
Person p1;
Student s1;
//不同的对象做同一件事,得到的状态不同
func(p1);
func(s1);
return 0;
}
多态分为:静态的多态、动态的多态。
静态的多态:是在编译时实现的。例如函数重载,看上去像是在调用同一个函数有着不同的行为。
动态的多态:是在运行时实现的。基类的指针或者引用去调用同一个函数,传不同类型的对象会调用不同的函数。
1、多态是基于继承的,只有在有继承关系的体系中才能构成多态。
2、必须通过基类的指针或者引用去调用虚函数。
3、被调用的成员必须是虚函数,且派生类必须对基类的虚函数进行重写。
虚函数:被virtual
关键字修饰的非静态类成员。(ps:在返回值前面加virtual
)
虚函数的重写:派生类中有一个和基类基本相同的函数,这里的基本相同指,同样是虚函数并且返回值、函数名、参数都相同。而函数体内的内容不同,类似于将这个函数重新改写了,这种情况称为派生类的虚函数重写了基类的虚函数。
构成多态的一个例外:返回值不同,其他条件满足,并且这两个函数的返回值是继承关系(父子关系)的指针或者引用,这个时候也构成多态。这种情况称为协变
//协变也构成多态
//协变:返回值可以不同,但是必须是继承关系(父子关系)
class Person
{
public:
virtual Person* BuyTicket()
{
cout << "普通票---全价" << endl;
return nullptr;
}
};
class Student : public Person
{
virtual Student* BuyTicket() //派生类中可以不是虚函数
{
cout << "学生票---半价" << endl;
return nullptr;
}
};
void func(Person& p)
{
p.BuyTicket();
}
int main(void)
{
Person p1;
Student s1;
func(p1);
func(s1);
return 0;
}
构成多态的另一个例外:基类成员是虚函数、而派生类成员可以不是虚函数,也就是不用virtual修饰,但是其他条件还是需要满足的。这样也构成多态,虽然派生类成员没有被virtual修饰,但它是先继承了基类虚函数的属性,再完成的重写,那么它也算是虚函数。
class Person
{
public:
virtual void BuyTicket()
{
cout << "普通票---全价" << endl;
}
};
class Student : public Person
{
void BuyTicket() //派生类可以不是虚函数
{
cout << "学生票---半价" << endl;
}
};
void func(Person& p)
{
p.BuyTicket();
}
int main(void)
{
Person p1;
Student s1;
func(p1);
func(s1);
return 0;
}
存在着一个场景,析构函数必须是虚函数:动态申请基类、派生类的对象,如果都交给了基类指针管理,那么析构函数需要是虚函数。
//存在着一种场景,析构函数必须是函数
//基类、派生类对象,都交给了基类指针管理。
class Person
{
public:
virtual void BuyTicket()
{
cout << "普通票---全价" << endl;
}
~Person()
{
cout << "~Person" << endl;
}
};
class Student : public Person
{
virtual void BuyTicket()
{
cout << "学生票---半价" << endl;
}
~Student()
{
cout << "~Student" << endl;
}
};
void func(Person& p)
{
p.BuyTicket();
}
int main(void)
{
//派生类对象交给了基类指针管理
Person* p2 = new Person;
Person* s2 = new Student;
delete p2;
delete s2;
return 0;
}
运行结果:p2和s2都是调用基类的析构函数去清理资源。
派生类对象地址赋值给基类指针,虽然被切片了,但还是需要调用派生类自己的析构函数去清理资源。所以这并没有正确地调用析构函数,我们需要的是,S2调用Student自己的析构函数。析构函数的函数名,编译时会被统一处理成destructor
,并且它不需要参数和返回值,所以认为析构函数函数名、参数、返回值相同。那要使得析构函数构成多态,那么可以在用virtual修饰这两个析构函数,或者修饰基类的析构函数就可以了。
class Person
{
public:
virtual void BuyTicket()
{
cout << "普通票---全价" << endl;
}
virtual ~Person() //析构函数修饰成虚函数
{
cout << "~Person" << endl;
}
};
class Student : public Person
{
virtual void BuyTicket()
{
cout << "学生票---半价" << endl;
}
~Student()
{
cout << "~Student" << endl;
}
};
void func(Person& p)
{
p.BuyTicket();
}
int main(void)
{
//派生类对象交给了基类指针管理
Person* p2 = new Person;
Person* s2 = new Student;
delete p2;
delete s2;
return 0;
}
这里调用了两次基类的析构函数,是因为,派生类的析构函数调用之后,会自动调用一次基类的析构函数去析构被继承下来的基类部分。
final
:修饰虚函数,表示该虚函数不能被重写。
修饰方法:在基类虚函数参数括号后,函数体之前使用。
//关键字final
class Car
{
public:
virtual void Drive() final
{}
};
class Benz :public Car
{
public:
virtual void Drive()
{ cout << "Benz-舒适" << endl; }
};
【补充】
使用final
修饰类,那么会使得该类不能被继承。修饰方法:在基类类名之后,类体之前使用。
//关键字final
class Car final
{
public:
virtual void Drive()
{}
};
class Benz :public Car
{
public:
virtual void Drive()
{ cout << "Benz-舒适" << endl; }
};
override
:修饰虚函数,检查派生类的虚函数是否重写了基类的某个虚函数,如果没有重写编译报错。
修饰方式:在派生类虚函数参数括号后面,函数体之前使用。
//关键字override
//使用override修饰派生类的虚函数,判断该虚函数是否重写,如果没有重写则会编译报错
class Car
{
public:
virtual void Drive()
{}
};
class Benz :public Car
{
public:
virtual void Drive(int i) override
{
//cout << "Benz-舒适" << endl;
}
};
重载:
1、两个函数在同一作用域中
2、要求函数名相同。参数个数、类型或者顺序不同
重写(覆盖):
1、两个函数在不同的作用域。一个在基类的作用域、一个在父类的作用域。
2、函数名、参数、返回值都必须相同(协变除外,返回值可以不同,但是一定是父子关系)
3、两个函数都是虚函数,或者基类中的那个函数是虚函数。
重定义(隐藏)
1、两个函数在不同的作用域。一个在基类的作用域、一个在父类的作用域
2、两个函数的函数名、参数相同
3、两个基类和派生类的同名函数不构成重写就是重定义