虚函数
在C++中,基类会将子类不做改变就能直接继承的函数与需要子类覆盖自己实现的函数区别对待,对于后者,我们通常称为虚函数
1:成员函数可以被子类直接继承,不重写的话对于每个子类来说该成员函数都是相同的 string isbn() const; 2:虚函数通常声明为virtural,绝大多数情况下需要子类重写,因为编译器执行的是动态绑定,只有在运行时才能确定是调用的哪个版本的虚函数 virtual double net_price(size_t i) const;
动态绑定
使用基类的指针或者引用访问虚函数会发生动态绑定,根据动态类型来决定去调用哪个子类的该虚函数
定义基类
protected:只能被自身和子类访问
protected: double price;
private:只能在类的内部被访问
private: string bookNo;
public:可以在类的外部被访问,具有公共访问权限
public: string isbn() const{ return bookNo; }
定义构造函数和析构函数:
Quote(const string bookNO,double price):bookNo(bookNO),price(price){} Quote() = default; ~Quote() = default;
完整的基类:
class Quote { protected: double price; private: string bookNo; public: Quote(const string bookNO,double price):bookNo(bookNO),price(price){} Quote() = default; ~Quote() = default; //普通成员函数 string isbn() const{ return bookNo; } //虚函数 virtual double net_price(size_t i) const{ return i*price; } };
定义派生类(子类)
如果一个派生类是公有的,则基类的公有接口也是派生类的公有接口
C++的规则是每个类负责各自的接口,要想与对象交互必须使用该类的接口,即使是该基类的派生类
每个类控制自己的成员初始化过程,所以派生类会调用基类的构造函数去初始化他的基类成员,接下来按照声明的顺序去初始化派生类的非基类成员
Book_Quote(const string bookNO,double price,double discount = 0.8,int maxCount = 100): Quote(bookNO,price),discount(discount),max_cou(maxCount){}
我们尽管不能在派生类中直接访问基类的私有成员,但是可以通过基类的公有成员函数访问其私有成员
cout << isbn() << endl; //isbn()是基类定义的公有接口,返回私有属性bookNo的值
完整的派生类:
class Book_Quote : public Quote { private: size_t max_cou; double discount; public: Book_Quote() = default; Book_Quote(const string bookNO,double price,double discount = 0.8,int maxCount = 100): Quote(bookNO,price),discount(discount),max_cou(maxCount){} ~Book_Quote() = default; //override指示该函数是继承自基类的虚函数,编译器会进行强制检查 double net_price(size_t n) const override{ cout << isbn() << endl; //这里是正确的,因为isbn()是公有接口,但是不能直接访问private的bookNo if(n>max_cou){ return n * price * discount * discount; }else{ return n * price * discount; } } };
我们可以使用final阻止继承
class Person final{/**/}; //final修饰符决定了Person类不能当作基类被继承
静态类型和动态类型
引入:
void getInfo(const Quote &q){ cout << q.net_price(100) << endl; //动态绑定 } int main(int argc, char const *argv[]) { Book_Quote bq("Thinking in JAVA",100,0.8,100); getInfo(bq); }
结果:
Thinking in JAVA //静态类型是Quote,但是动态绑定根据动态类型(传入的实参)调用了Book_Quote的虚函数 100 0.8
这里的q的静态类型是Quote,但是他的动态类型是由传入的实参Quote决定的,如果传入的是Book_Quote,会发生Book_Quote–>Quote的隐式类型转换,所以q的动态类型是Book_Quote,真正调用的虚函数也是Book_Quote的虚函数
如果q既不是引用也不是指针,则它的静态类型和动态类型都是Quote(派生类到基类的隐式类型转换只是相对于引用和指针来说的)
基类到派生类不会发生隐式类型转换
在我们将一个派生类对象赋值给一个基类或者对其初始化赋值的时候,只有该派生类中的基类部分被拷贝/移动或赋值,派生类其余的部分被忽略,之后调用虚函数不会发生动态绑定,只会执行基类的虚函数(这个是编译器就会确定的)
动态绑定的基础是动态类型(运行期确定),且只有在通过指针或者引用调用虚函数的时候才会发生
引用与指针的静态类型和动态类型可能不同正是C++支持多态性的根本
动态绑定只是针对于虚函数而已,对于其他的成员函数在编译器已经确定,不会动态绑定,只会调用基类的函数,而不会调用派生类可能重写后的函数。
如果某次函数调用使用了默认实参,则实参值由本次调用的静态类型决定,即使派生类中的默认实参可能和基类中的不同
回避虚函数机制(使用作用域运算符强制执行某个类的虚函数)——>相当于JAVA中的super(指明调用父类中的该虚函数)
例如:我们调用派生类的虚函数分别显示基类和派生类的数据成员
class Quote { private: string bookNo; public: /××///省略了一些没有必要的信息 virtual void debug() const { cout << bookNo << endl; } }; class Book_Quote : public Quote { private: size_t max_cou; double discount; public: void debug() const override { Quote::debug(); //利用作用域运算符强制调用Quote的debug()虚函数 cout << max_cou << " " << discount << "\n"; } }; void getInfo(const Quote &q){ q.debug(); //动态绑定 } int main(int argc, char const *argv[]) { Book_Quote bq("Thinking in JAVA",100,0.8,100); getInfo(bq); }
结果:
Thinking in JAVA //尽管执行了动态绑定,但依旧打印出了基类中的私有数据成员 100 0.8
含有(或继承自抽象基类但并没有override)纯虚函数的类叫做抽象基类,只负责定义接口,而不允许创建该类的对象,类似于JAVA的abstract抽象类,是用来实现模板方法设计模式的经典案例
class Disc_Quote : public Quote{ public: double net_price(size_t n) const = 0; //另虚函数=0声明一个纯虚函数,=0只能出现在类内部的虚函数声明语句后,不允许在类内部定义纯虚函数 };
如果Disc_Quote的派生类没有覆盖纯虚函数net_price,则该派生类也是抽象基类
再次郑重声明:
即使派生类没有增加新的数据成员(和基类相同),也必须在构造函数初始化的时候调用基类的构造函数来进行初始化,每个类必须由自己控制数据成员的初始化过程
C++面向对象(一)
最新推荐文章于 2023-07-05 13:08:02 发布