C++笔记-虚函数

  1. 对虚函数的调用可能在运行时才被解析。当某个虚函数通过指针或引用调用时,编译器产生的代码直到运行时才能确定该用哪个版本的函数。
  2. 另一方面,对于非虚函数的调用在编译时进行绑定。通过对象进行的函数(虚函数或非虚函数)调用也在编译时绑定。
  3. 如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致。
  4. 在某些情况下,我们希望执行某个特定版本的虚函数,需使用作用域运算符明确指出。
  5. 含有(或者未经覆盖直接继承)纯虚函数的类是抽象基类。抽象基类负责定义接口,我们不能创建抽象基类的对象。纯虚函数在类的内部声明,函数体如果需要定义则在类外,声明时在分号前带上声明符(=0)。
//用于保存折扣和购买量的类,派生类使用这些数据可以实现不同的价格策略
class Disc_quote : public Quote
{
public:
    Disc_quote() = default;
    Disc_quote(const string& book, double price, std::size_t qty, double disc) : 
        Quote(book, price), quantity(qty), discount(disc) { }
    double net_price(std::size_t) const = 0;  //声明为纯虚函数
proteced:
    std::size_t quaninty = 0;
    double discount = 0.0;
};

//接下来定义派生类Bulk_quote
//当同一书籍的销售量超过某个值时,启用折扣
//折扣的值为小于1的正小数
class Bulk_quote : public Disc_quote
{
public:
    Bulk_quote() = default;
    Bulk_quote(const string& book, double price, std::size_t qty, double disc) : 
        Disc_quote(book, price, qty, disc) { }
    //覆盖基类中的函数版本以实现一种新的折扣策略
    double net_price(std::size_t) const override;
};
#####注意#####
派生类构造函数只初始化它的直接基类,因此Bulk_quote类构造函数传入四个参数后通过构造Disc_quote来初始化。但是Disc_quote为抽象基类,不能定义它的对象。
  1. 在Quote的继承体系中增加Disc_quote类时重构的一个典型示例。重构负责重新设计类的体系以便将操作和/或数据从一个类移动到另一个类中。
  2. 访问控制与继承
1. 和私有成员类似,protected成员对于类的用户来说是不可访问的
2. 和公有成员类似,protected成员对于派生类和友元来说是可访问的
3. 派生类的成员或者友元只能通过派生类对象来访问基类的受保护成员。派生类对于一个基类对象中的受保护成员没有任何访问权限。
//举个例子
class Base
{
protected:
    int prot_mem;   //protected成员
};
class Sneaky : public Base
{
    friend void clobber(Sneaky&);   //能访问Sneaky::prot_mem
    friend void clobber(Base&);     //不能访问Base::prot_mem
    int j;      //j在不声明时默认为private
};
void clobber(Sneaky& s)
{
    s.j = s.prot_mem = 0;   //正确:clobber能访问Sneaky对象的private和protected成员
}
void clobber(Base& b)
{
    b.prot_mem = 0; //错误:clobber不能访问Base的protected成员
}

一个类对其继承的成员访问控制可以在两方面实行:
在基类中该成员的访问说明符;
或在派生类的派生列表中的访问说明符。
class Base
{
public:
    void pub_mem();
protected:
    int prot_mem;
private:
    char pirv_mem;
};
struct  Pub_Derv : public Base
{
    //派生类能访问protected成员
    int f() { return prot_mem; }
    //错误:private成员对于派生类而言不可访问
    char g() { return pirv_mem; }
};
struct Priv_Derv : private Base
{
    //private不影响派生类的访问权限
    int f1() const { return prot_mem; }
};
#Note#
    对基类成员的访问权限只与基类中的访问说明符有关。与派生列表的访问说明符无关。
    派生访问说明符的目的是控制派生类用户(包括派生类的派生类在内)对于基类成员的访问权限。
Pub_Derv d1;        //继承自Base的成员是public的
Priv_Derv d2;       //继承自Base的成员是private的
d1.pub_mem();       //正确:pub_mem在派生类中是public的
d2.pub_mem();       //错误:pub_mem在派生类中是private的
对于私有成员,类的对象无权访问,只能通过其成员函数来访问。

在三种不同方式的继承下,派生类对原来基类中的成员访问的方式也不一样,具体如下:
1>公有继承中:
  (1)基类的共有成员就相当于是派生类的公有成员,也就是说派生类可以像访问自身公有成员一样访问从基类继承的公有成员。
  (2)基类的保护成员就相当于是派生类的保护成员,即,派生类可以像访问自身的保护成员一样,访问基类的保护成员。
  (3)对于基类的私有成员,派生类内部成员是无法直接访问的,派生类使用者也无法通过派生类对象直接访问。
2>受保护继承中:
  (1)基类的公有成员和保护成员都相当于派生类的保护成员,派生类可以通过自身的成员函数或其子类的成员函数访问它们。
  (2)对于基类的私有成员,无论派生类内部成员或派生类的对象都无法直接访问。
3>私有继承中:
  (1)基类公有成员和受保护成员都相当于派生类的私有成员,派生类只能通过自身的成员函数访问他们。
  (2)对于基类的私有成员,无论派生类内部成员或派生类的对象都无法直接访问
  1. 派生类向基类转化的可访问性
假定D继承自B:
1. 只有当D公有继承B时,用户代码才能使用派生类向基类的转换。
2. 无论D以何种关系继承B,D的成员函数和友元都能使用派生类向基类的转化。
3. 如果D继承自B是public或protected,则D的派生类的成员和友元可以使用D向B的类型转化
#Tip#
    对于代码中的某个给定节点来说,如果基类的公有成员是可访问的,则派生类向基类类型转化也是可行的。
  1. 友元关系是不能继承的;每个类负责控制各自成员的访问权限。
  2. 有时候我们需要改变派生类继承的某个名字的访问级别,通过使用using声明
class Base
{
public:
    std::size_t size() const { return n; }
protected:
    std::size_t n;
};
class Derived : private Base    //此处使用私有继承
{
public:
    using Base::size;
protected:
    using Base::n;
};
由于Derived使用私有继承,所以继承来的size和n(在默认情况下)是Derived的私有成员。
但是使用using声明改变了其可访问性,Derived的用户可以使用size,Derived的派生类可以使用n
#Note#
    派生类只能对它可以访问的成员使用using声明,基类的private则不能访问和声明
  1. 默认的继承保护级。struct和class关键字定义的类具有不同的默认访问说明符。
class Base { ... }
struct D1 : Base { ... }        //默认public继承
class D2 : Base { ... }         //默认private继承
因此在继承关系中最好在派生列表中显式写出访问控制符。

#Note#
    派生类的成员将隐藏基类的同名成员,即使两个成员的形参列表不同
struct Base
{
    Base() mem(0) { }
protected:
    int mem;
};
struct Derived : Base
{
    Derived(int i) : mem(i) { }
    int get_mem() { return mem; }
protected:
    int mem;
};
Derived d(42);
cout << d.get_mem() <<endl; //42
或者你也可以通过作用域符来指明 return Base::mem
#Tip#
    但是除了虚函数的覆盖,派生类中最好不要重用其他基类中的名字。

名字查找顺序:
    假定调用p->mem()或obj.mem()
    首先确定p或obj的类型。因为我们调用的是一个成员,所以该类型必然是类类型
    然后再p或obj的静态类型中查找mem。如果没有,则从直接基类开始再继承链中查找。如果一直没找到,则报错
    假如找到了,则进行常规的类型检查和访问权限检查
    假设调用合法,则依据调用的是否为虚函数而产生不同的代码
  1. 在搜索成员时,名字查找先于类型检查。因此基类与派生类中的虚函数必须有相同的形参列表。假如基类与派生类的虚函数接受的实参不同,我们就无法通过基类的引用或者指针调用派生类的虚函数。
struct Base
{
    int memfcn();
};
struct Derived : Base
{
    int memfcn(int);    //同名,隐藏了基类的memfcn
};
Derived d; 
Base b;
b.memfcn();         //正确:调用Base::memfcn()
d.memfcn(10);       //正确:调用Derived::memfcn()
d.memfcn();         //错误:参数列表为空的memfcn被隐藏了
d.Base::memfcn();   //正确:调用Base::memfcn()
class Base
{
public:
    virtual int fcn();      //基类定义虚函数
};
class D1 : public Base
{
public:
    int fcn(int);       //形参列表与基类不一致,因此不是改写基类的虚函数
                        //实际上,D1将用有两个名为fcn的函数
    virtual void f2();  //定义的一个新的虚函数,在Base中不存在
};
class D2 : public D1
{
public:
    int fcn(int);       //这是一个非虚函数,隐藏了D1::fcn(int)
    int fcn();          //覆盖了Base的虚函数
    void f2();          //覆盖了D1的虚函数f2
};
D1的fcn没有覆盖Base的虚函数,原因时它们的形参列表不一致。
即,D1的fcn将隐藏Base的fcn。D1有两个名为fcn的函数,一个为虚函数,一个为非虚函数。
Base b;
D1 d1;
D2 d2;
Base *pb1 = &b, *pb2 = &d1, *pb3 = &d2;
pb1->fcn();     //虚调用,将在运行时调用Base::fcn()
pb2->fcn();     //虚调用,将在运行时调用Base::fcn()
pb3->fcn();     //虚调用,将在运行时调用D2::fcn()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值