C++的基本特点:抽象封装继承和多态
抽象通过类的定义来实现;
封装实现了访问控制;
继承实现了代码复用;
而多态实现了软件开发的终极目标。框架写好,功能就可以在已有的框架下不断增加。实现80年代写的代码21世纪20年代仍然能够使用。
我们先来看一个程序:
#include<iostream>
using namespace std;
class Parent{
public:
Parent(int a)
{
this->a=a;
cout << "Parent a"<<a<<endl;
}
void print()//子类的和父类的函数名字一样,若想让子类的同名函数覆盖基类的同名函数,那么必须要让基类的函数变成虚函数。
{
cout << "Parenta : "<<a<<endl;
}
private:
int a;
};
class Child:public Parent
{
public:
Child(int b):Parent(10)
{
this ->b=b;
cout << "Child b"<<b<<endl;
}
void print()
{
cout << "Child b : "<<b<<endl;
}
private:
int b;
};
void howToPrint(Parent *base)
{
base ->print();
//一种调⽤用语句有多种表现形态...
}
void howToPrint2(Parent &base)
{
base.print();
}
int main(void)
{
Parent *base = NULL;
Parent p1(20);
Child c1(30);
base = &p1;
base->print(); //执⾏行⽗父类的打印函数。输出20
base = &c1;
/*
编译器认为最安全的做法是编译到⽗父类的print函数,
因为父类和子类肯定都有相同的print函数
*/
base -> print(); //这里虽然基类的指针指向的派生类的对象,但是该指针调用的函数却是基类中的同名函数。所以仍输出10
Parent &base2 = p1;
base2.print();//执行父类的打印函数。输出20
Parent &base3 = c1;
base3.print();//执⾏行谁的函数?父类的引用引用了子类对象。但是调用的却是基类的同名函数。输出10
//函数调用
howToPrint(&p1);//20
howToPrint(&c1);//10
howToPrint2(p1);//20
howToPrint2(c1);//10
return 0;
}
在这个程序里,最大的问题是:当使用基类的指针指向子类的对象时,若基类和子类中存在同名函数,使用基类指针调用该同名函数,编译器会无脑将基类的同名函数返回给你。(你在子类对象中重写的该同名函数毫无用处。。)但是你用子类实例化一个对象,用该对象调用同名函数,然后发现,调用的就是重定义的子类中的同名函数。。。
那么问题来了:编译器的做法不是我们期望的,如何使用基类指针和引用去调用整个家族类中的成员函数呢?
我们希望:
- 如果父类指针指向的是父类对象则调用父类中定义的函数;
- 如果父类指针指向的是子类对象则调用子类中定义的重写函数;
这就是多态要解决的问题。
什么是多态?
由继承产生的相关但不同的类实例化的对象对同一消息有不同的相应。这个不同是通过重定义或者重写成员函数实现的。简单来说,就是在基类中有一个成员函数A;我在派生类中定义一个同名的成员函数A。现在有两个情况,第一种情况是基类中的这个A函数不是虚函数,那么我在派生类中定义A函数的行为就叫重定义;如果基类中的这个A是虚函数,那么我在派生类中定义A函数的行为叫做重写。重定义保证了子类对象调用A函数为子类的A函数;重写保证了用基类指针指向子类对象时,用基类指针调用的A函数为子类中的A函数。这就是重定义和重写的概念和区别。
所以,重写是多态的前提之一。多态实现的前提
多态实现的前提是赋值兼容规则。
赋值兼容规则确定了子类对象能够当做基类对象使用,基类指针能够指向子类对象;基类引用能够引用子类对象。总之,想要实现多态有三个条件必须满足:
- 继承
- 子类中重写父类中的虚函数
- 使用基类指针(或引用)指向子类对象
实现一个简单的多态
hero vs monster#include <iostream> using namespace std; class Hero{ private: double ad; public: Hero(){ this->ad = 10; } virtual double getAD(){ return this->ad; } }; class HeroII :public Hero{ private: double ad; public: HeroII(){ this->ad = 100; } virtual double getAD(){ return this->ad; } }; class HeroIII :public HeroII{ private: double ad; public: HeroIII(){ this->ad = 1000; } virtual double getAD(){ return this->ad; } }; class Monster{ private: double ad; public: Monster(){ this->ad = 101; } double getAD(){ return this->ad; } }; void play(Hero * h, Monster * m){ //一个接口实现了多个结果 if (h->getAD() > m->getAD()){ cout << "Hero win!!!" << endl; } else{ cout << "Hero die. Monster win." << endl; } } int main(){ Hero h; HeroII h2; HeroIII h3; Monster m; play(&h, &m); cout << "----------------" << endl; play(&h2, &m); cout << "----------------" << endl; play(&h3, &m); cout << "----------------" << endl; return 0; }
动态联编和静态联编
什么是静态联编?
什么是动态联编?
虚析构函数
#include <iostream> using namespace std; namespace A{ class A{ public: A(){ cout << "A() ..." << endl; } ~A(){ cout << " ~A() ..." << endl; } }; class B:public A{ public: B(){ cout << "B() ..." << endl; } ~B(){ cout << " ~B() ..." << endl; } }; class C:public B{ public: C(){ cout << "C() ..." << endl; } ~C(){ cout << " ~C() ..." << endl; } }; } namespace B{ class A{ public: A(){ cout << "A() ..." << endl; } virtual ~A(){ cout << " ~A() ..." << endl; } }; class B :public A{ public: B(){ cout << "B() ..." << endl; } virtual ~B(){ cout << " ~B() ..." << endl; } }; class C :public B{ public: C(){ cout << "C() ..." << endl; } virtual ~C(){ cout << " ~C() ..." << endl; } }; } int main(){ A::A * a = new A::C; delete a; //我们用基类指针指向子类,当我们delete基类指针时,想调用析构函数把子类和基类均析构掉。但是这里只调用了基类的析构函数,为什么呢? //因为析构函数也算是成员函数,基类的成员函数不定义为虚函数就无法重写,就无法实现多态。 //想要实现析构函数的多态,就需要将析构函数定义为虚函数。 cout << "---------------------" << endl; B::A * a2 = new B::C; delete a2; //完美实现 new delete return 0; }
重载 重写 重定义的区别
- 重载(添加)
- 命名空间相同
- 函数名相同
- 函数返回类型相同
- 参数列表不同
- 重写(覆盖)
- 命名空间不同
- 函数名相同
- 函数返回类型相同
- 参数列表相同
- 必须加virtual关键字
- 重定义(隐藏)
- 命名空间不同
- 函数名相同
- 函数返回类型相同
- 参数列表相同
- 没有virtual关键字
- 重载(添加)