多态
概念
多态:多种形态。去完成某个行为,不同的对象会产生不同的结果。
这里以买票为例,学生买票,成人买票,军人买票。多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。Student继承了 Person。Person对象买票全价,Student对象买票半价。
构成多态的条件
1.通过父类的指针或引用调用虚函数。
2.虚函数重写。
虚函数
在函数声明的返回值前加virtual 就是虚函数
必须是成员函数才可以带virtual
class Person
{
public:
virtual void BuyTicket() { cout << "买票-全价" << endl;}
};
当一个类的某个函数被说明为 virtual ,则在该类的所有派生类中的同原型函数都是虚函数
虚函数的重写
子类中有一个跟父类完全相同的虚函数(返回值类型、函数名字、参数列表完全相同)。
注:不符合重写才是隐藏关系。
class Person
{
public:
virtual void Buyticket()
{
cout<<"买票--全价"<<endl;
}
};
class Student :public Person
{
public:
virtual void Buyticket()
{
cout<<"买票--半价"<<endl;
}
};
void Func(Person& p)
{
p.Buyticket();
}
int main()
{
Person a;
Student b;
Func(a);
Func(b);
return 0;
}
虚函数重写的两个特例
1.如果父基的析构函数为虚函数,此时子类析构函数只要定义,无论是否加virtual关键字, 都与父类的析构函数构成重写。编译器对析构函数做了特殊处理,统一析构函数名字为destructor。
子类虚函数不加virtual,依旧构成重写。(ps:最好加上)
析构函数重写的意义
释放父类指针时能正确释放子类对象,指向的对象是谁,就调用谁的析构函数。
class Person {
public:
virtual ~Person() {cout << "~Person()" << endl;}
};
class Student : public Person {
public:
virtual ~Student() { cout << "~Student()" << endl; }
};
2.重写的协变。返回值可以不同,但必须是父子关系的指针或引用。任意父子关系即可
class A{};
class B:public A{};
class Person
{
public:
virtual A* func()
{
return new A;
}
}
class Student:public Person
{
public:
virtual B* func()
{
return new B;
}
}
多态考题
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;
}
这里输出结果为 B->1
首先p是一个指向B对象的指针,调用test函数 由于test没有参数(默认有this指针),相当于p指针传给this指针,而p是一个B对象的指针,所以调用了B的func()函数,打印了B->
至于val,虚函数重写是接口继承,重写实现。即{ }内的内容是重写的 { }外继承了A中的func()
所以val初始化为了1,打印B->1
class A
{
public:
virtual void func(int val){ std::cout << "A->" << val << std::endl; }
void test(){ func(1); }
};
class B : public A
{
public:
void func(int val){ std::cout << "B->" << val << std::endl; }
};
int main(int argc, char* argv[])
{
A*p = new B;
p->test();
return 0;
}
这里输入依旧为B->1 因为p是指向B对象的指针,A*仅是改变了该指针的访问形式。对象是谁,就去调用谁的虚函数。
求输出结果
using namespace std;
class A{
public:
A(char *s) { cout<<s<<endl; }
~A(){}
};
class B:virtual public A
{
public:
B(char *s1,char*s2):A(s1) { cout<<s2<<endl; }
};
class C:virtual public A
{
public:
C(char *s1,char*s2):A(s1) { cout<<s2<<endl; }
};
class D:public B,public C
{
public:
D(char *s1,char *s2,char *s3,char *s4)
:B(s1,s2)
,C(s1,s3)
,A(s1)
{
cout<<s4<<endl;
}
};
int main() {
D *p=new D("class A","class B","class C","class D");
delete p;
return 0;
}
按照声明顺序去调用。A虽然没有显示的被继承,但按理来说A是最先被继承的。A的初始化里面B有,C也有,但在D中A是独一份的,所以应该由D来调用A的初始化以避免二义。A初始化之后,按照继承顺序先构造B,再构造C,此时的两者中的A就不会在初始化了。
答案是 class A class B class C class D
多态简答
一、inline函数可以是虚函数吗?
内敛函数没有地址,在调用处展开,多态是运行起来后去虚函数表里去找,内联函数在运行前就确定了,所以多态调用时inline失效。但是inline只是建议,当普通调用时,inline依然生效。
二、静态成员函数可以是虚函数吗?
不能。静态成员函数没有this指针。可以通过类域调用。静态成员函数可以认为是在编译时决议,虚函数是为了实现多态,多态都是运行时去找虚表决议的。
三、构造函数可以是虚函数吗?
不能。虚表是编译时就生成好的。对象中的虚表指针都是在构造函数初始化列表才初始化的。虚表没初始化好,做不了多态。拷贝构造也是构造,也不能是虚函数。
四、赋值运算符重载可以是虚函数吗?
可以。但是如果派生类中使用赋值操作符,传入的参数一定是派生类对象。那参数就不同,构不成函数重写。所以可以但没用。
五、析构函数可以是虚函数吗?
可以。而且最好把基类的析构函数定义成虚函数。
六、对象访问普通函数快还是虚函数更快?
首先如果是普通对象,是一样快的。如果是指针 对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函 数表中去查找。
七、虚函数表是在什么阶段生成的?存在于哪里?
编译阶段就生成好的,存在代码段。构造函数初始化列表阶段初始化的是虚函数表指针,对象中存的也是虚函数表指针。
重载、隐藏、重写
重载的构成条件:在同一个作用域下的函数,函数名相同,参数列表不同构成重载;
隐藏的构成条件:派生类中含有与基类同名的方法,编译器会优先选择使用派生类中同名方法,看上去派生类将基类的同名方法隐藏。隐藏也叫重定义。
重写构成条件:
- 方法A在基类中是虚函数
- 子类中存在一个与父类的那个虚函数返回值、参数列表相同的函数B,则父类虚函数A被子类继承后被B函数覆盖。
- 子类方法B此时也是一个虚函数
编译错误
b.f()函数需要参数 ,虽然基类的f()函数不需要参数,但是被隐藏了。