多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。
继承中构成多态的两个条件
1. 必须通过基类的指针或者引用调用虚函数。
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。
虚函数:即被virtual修饰的类成员函数称为虚函数。
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用。
class person2
{
public:
virtual person2* fun()
{
cout << "person" << endl;
return nullptr;
}
};
class stu2 :public person2
{
public:
virtual stu2* fun()
{
cout << "stu" << endl;
return nullptr;
}
};
void test1_1(person *p)
{
p->fun(); // 与类型无关,与指向的对象有关,指向那个对象,调用它的虚函数
}
void test1_2(stu*s)
{
}
void test1_3(person&p)
{
p.fun();
}
void test1_4(person p)
{
p.fun();
}
void test1()
{
person p1;
stu s2;
// 传指针
test1_1(&p1);
test1_1(&s2); // 子类可以转为父类
// 在继承中提到--> person* p = &s2 ,p的虚函数表与stu类型的变量是一样的,他们调用的函数自然一样
// test1_2(&p1); //无法转化类型
//传引用 ---> 底层也是传指针-->指针指向的类型不变
test1_3(p1);
test1_3(s2);
//传值
test1_4(p1);
test1_4(s2); // 传值对象就变为person类型
// 发生切片和虚函数表重置为基类的虚函数表,失去多态性,接口改变了对象,失去意义
}
虚函数重写的两个例外
1. 协变(基类与派生类虚函数返回值类型不同) 派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。
2. 析构函数的重写(基类与派生类析构函数的名字相同) 如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字, 都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同, 看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。
class person2
{
public:
virtual person2* fun()
{
cout << "person" << endl;
return nullptr;
}
};
class stu2 :public person2
{
public:
virtual stu2* fun()
{
cout << "stu" << endl;
return nullptr;
}
};
class A{};
class B:public A{};
//只要返回类型构成父子关系
class person3
{
public:
virtual A* fun()
{
cout << "person" << endl;
return nullptr;
}
};
class stu3 :public person3
{
public:
virtual B* fun()
{
cout << "stu" << endl;
return nullptr;
}
};
test()
{
person2 p2;
stu2 s2;
// 传指针
test2_1(&p2); // -->person
test2_1(&s2); // -->stu
person3 p3;
stu3 s3;
test3_1(&p3); // -->person
test3_1(&s3); // -->stu
}
析构重写
class person4
{
public:
virtual ~person4()
{
cout << "virtual ~person4()" << endl;
}
};
class stu4 :public person4
{
public:
virtual ~stu4() // 析构函数名被处理成destructor-->同名
{
cout << "virtual ~stu4()" << endl;
}
};
test()
{
//析构函数
person4 p4;
stu4 s4;
test4_1(&p4); // -->virtual ~person4()
test4_1(&s4); //virtual ~stu4() -->virtual ~person4()
// 这是测试,不能对一块空间析构两次(会自动调用)
// 自动调用
// virtual ~stu4() -- > virtual ~person4()
// virtual ~person4()
}
C++11 override 和 final
1. final:修饰虚函数表示该虚函数不能再被重写。
2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
class A
{
public:
virtual void fun(int a, int b)final {};
};
class B:public A
{
public:
// virtual void fun(int a, int b) {}; // 无法重写,A中的fun()有final修饰
virtual void fun1() {};
};
class C:public B
{
public:
//virtual void fun2()override {}; //如果没有重写B中的某个函数就报错
};
抽象类 纯虚函数
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
class D
{
public:
virtual void fun() = 0; // 纯虚函数 // 只要声明,不需要实现
};
class E :public D
{
public:
int _e;
};
class F :public D
{
public:
virtual void fun() {}; // 将纯虚函数重写
};
void test5()
{
// E e; 无法实例化对象
F f; // 重写纯虚函数就可以实例化了
}