c++ primer阅读笔记-15章-2

15.2. 定义基类和派生类

15.2.1. 定义基类

1、新的部分是 protected 访问标号以及对析构函数和 net_price 函数所使用的保留字 virtual。(析构函数为虚函数的作用???

基类成员函数

2、保留字 virtual 的目的是启用动态绑定。成员默认为非虚函数,对非虚函数的调用在编译时确定。为了指明函数为虚函数,在其返回类型前面加上保留字 virtual。除了构造函数之外,任意非 static 成员函数都可以是虚函数。保留字只在类内部的成员函数声明中出现,不能用在类定义体外部出现的函数定义上。

访问控制和继承

3、在基类中,public 和 private 标号具有普通含义:用户代码可以访问类的 public 成员而不能访问 private 成员,private 成员只能由基类的成员和友元访问。派生类对基类的 public 和 private 成员的访问权限与程序中任意其他部分一样:它可以访问 public 成员而不能访问 private 成员。

4、protected 成员可以被派生类对象访问但不能被该类型的普通用户访问。

15.2.2. protected 成员

1、派生类只能通过派生类对象访问其基类的 protected 成员,派生类对其基类类型对象的 protected 成员没有特殊访问权限。例如,假定 Bulk_item 定义了一个成员函数,接受一个 Bulk_item 对象的引用和一个 Item_base 对象的引用,该函数可以访问自己对象的 protected 成员以及 Bulk_item 形参的 protected 成员,但是,它不能访问 Item_base 形参的 protected 成员。

   void Bulk_item::memfcn(const Bulk_item &d, const Item_base &b)
     {
         // attempt to use protected member
         double ret = price;   // ok: uses this->price
         ret = d.price; // ok: uses price from a Bulk_item object
         ret = b.price; // error: no access to price from an Item_base
     }

d.price 的使用正确,因为是通过 Bulk_item 类型对象引用 price;b.price 的使用非法,因为对 Base_item 类型的对象没有特殊访问访问权限。

15.2.3. 派生类

1、为了定义派生类,使用类派生列表指定基类

2、一般而言,派生类只(重)定义那些与基类不同或扩展基类行为的方面。

派生类和虚函数

3、尽管不是必须这样做,派生类一般会重定义所继承的虚函数。派生类没有重定义某个虚函数,则使用基类中定义的版本。

4、派生类中虚函数的声明(第 7.4 节)必须与基类中的定义方式完全匹配,但有一个例外:返回对基类型的引用(或指针)的虚函数。派生类中的虚函数可以返回基类函数所返回类型的派生类的引用(或指针)。

5、一旦函数在基类中声明为虚函数,它就一直为虚函数,派生类无法改变该函数为虚函数这一事实。派生类重定义虚函数时,可以使用 virtual 保留字,但不是必须这样做。

6、C++ 语言不要求编译器将对象的基类部分和派生部分和派生部分连续排列,因此,图 15.1 是关于类如何工作的概念表示而不是物理表示。

7、已定义的类才可以用作基类。这一限制的原因应该很容易明白:每个派生类包含并且可以访问其基类的成员,为了使用这些成员,派生类必须知道它们是什么。这一规则暗示着不可能从类自身派生出一个类

8、如果需要声明(但并不实现)一个派生类,则声明包含类名但不包含派生列表

15.2.4. virtual 与其他成员函数


1、C++ 中的函数调用默认不使用动态绑定。要触发动态绑定,满足两个条件:第一,只有指定为虚函数的成员函数才能进行动态绑定,成员函数默认为非虚函数,非虚函数不进行动态绑定;第二,必须通过基类类型的引用或指针进行函数调用。要理解这一要求,需要理解在使用继承层次中某一类型的对象的引用或指针时会发生什么。

2、因为每个派生类对象都包含基类部分,所以可将基类类型的引用绑定到派生类对象的基类部分,也可以用指向基类的指针指向派生类对象。

3、因为可以使用基类类型的指针或引用来引用派生类型对象,所以,使用基类类型的引用或指针时,不知道指针或引用所绑定的对象的类型:基类类型的引用或指针可以引用基类类型对象,也可以引用派生类型对象。无论实际对象具有哪种类型,编译器都将它当作基类类型对象。将派生类对象当作基类对象是安全的,因为每个派生类对象都拥有基类子对象。而且,派生类继承基类的操作,即,任何可以在基类对象上执行的操作也可以通过派生类对象使用。

4、基类类型引用和指针的关键点在于静态类型(在编译时可知的引用类型或指针类型)和动态类型(指针或引用所绑定的对象的类型这是仅在运行时可知的)可能不同。

5、将基类类型的引用或指针绑定到派生类对象对基础对象(派生类对象)没有影响,对象本身不会改变,仍为派生类对象。对象的实际类型可能不同于该对象引用或指针的静态类型,这是 C++ 中动态绑定的关键(???)。

6、引用和指针的静态类型与动态类型可以不同,这是 C++ 用以支持多态性的基石。

7、另一方面,对象是非多态的——对象类型已知且不变。对象的动态类型总是与静态类型相同,这一点与引用或指针相反。运行的函数(虚函数或非虚函数)是由对象的类型定义的。

8、只有通过引用或指针调用,虚函数才在运行时确定。只有在这些情况下,直到运行时才知道对象的动态类型

覆盖虚函数机制

9、在某些情况下,希望覆盖虚函数机制并强制函数调用使用虚函数的特定版本,这里可以使用作用域操作符。只有成员函数中的代码才应该使用作用域操作符覆盖虚函数机制

10、为什么会希望覆盖虚函数机制?最常见的理由是为了派生类虚函数调用基类中的版本。在这种情况下,基类版本可以完成继承层次中所有类型的公共任务,而每个派生类型只添加自己的特殊工作。

11、派生类虚函数调用基类版本时,必须显式使用作用域操作符。如果派生类函数忽略了这样做,则函数调用会在运行时确定并且将是一个自身调用,从而导致无穷递归

虚函数与默认实参

12、像其他任何函数一样,虚函数也可以有默认实参。通常,用在给定调用中的默认实参值(如果有),该值将在编译时确定。如果一个调用省略了具有默认值的实参,则所用的值由调用该函数的类型定义,与对象的动态类型无关。通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值,如果通过派生类的指针或引用调用虚函数,则默认实参是在派生类的版本中声明的值。

13、在同一虚函数的基类版本和派生类版本中使用不同的默认实参几乎一定会引起麻烦。如果通过基类的引用或指针调用虚函数,但实际执行的是派生类中定义的版本,这时就可能会出现问题。在这种情况下,为虚函数的基类版本定义的默认实参将传给派生类定义的版本,而派生类版本是用不同的默认实参定义的。

15.2.5. 公用、私有和受保护的继承

1、对类所继承的成员的访问由基类中的成员访问级别派生类派生列表中使用的访问标号共同控制。

2、每个类控制它所定义的成员的访问。派生类可以进一步限制但不能放松对所继承的成员的访问。

3、基类本身指定对自身成员的最小访问控制。如果成员在基类中为 private,则只有基类和基类的友元可以访问该成员。派生类不能访问基类的 private 成员,也不能使自己的用户能够访问那些成员。如果基类成员为 public 或 protected,则派生列表中使用的访问标号决定该成员在派生类中的访问级别:

a):如果是公用继承,基类成员保持自己的访问级别:基类的 public 成员为派生类的 public 成员,基类的 protected 成员为派生类的 protected 成员。

b):如果是受保护继承,基类的 public 和 protected 成员在派生类中为 protected 成员。

c):如果是私有继承,基类的的所有成员在派生类中为 private 成员。

4、派生访问标号还控制来自非直接派生类的访问。

5、public 派生类继承基类的接口,它具有与基类相同的接口。设计良好的类层次中,public 派生类的对象可以用在任何需要基类对象的地方。使用 private 或 protected 派生的类不继承基类的接口,相反,这些派生通常被称为实现继承。派生类在实现中使用被继承但继承基类的部分并未成为其接口的一部分。

6、如果进行 private 或 protected 继承,则基类成员的访问级别在派生类中比在基类中更受限:

 class Base {
     public:
         std::size_t size() const { return n; }
     protected:
         std::size_t n;
     };
     class Derived : private Base { . . . };

派生类可以恢复继承成员的访问级别,但不能使访问级别比基类中原来指定的更严格或更宽松

在这一继承层次中,size 在 Base 中为 public,但在 Derived 中为 private。为了使 size 在 Derived 中成为 public,可以在 Derived 的 public 部分增加一个 using 声明。如下这样改变 Derived 的定义,可以使 size 成员能够被用户访问,并使 n 能够被从 Derived 派生的类访问:

    class Derived : private Base {
     public:
        // maintain access levels for members related to the size of the object
        using Base::size;
     protected:
         using Base::n;
         // ...
      };

正如可以使用 using 声明(第 3.1 节)从命名空间使用名字,也可以使用 using 声明访问基类中的名字,除了在作用域操作符左边用类名字代替命名空间名字之外,使用形式是相同的。

默认继承保护级别

1、默认继承访问级别根据使用哪个保留字定义派生类也不相同。使用 class 保留字定义的派生默认具有 private 继承,而用 struct 保留字定义的类默认具有 public 继承。有一种常见的误解认为用 struct 保留字定义的类与用 class 定义的类有更大的区别。唯一的不同只是默认的成员保护级别和默认的派生保护级别,没有其他区别。

15.2.6. 友元关系与继承

1、友元关系不能继承。基类的友元对派生类的成员没有特殊访问权限。如果基类被授予友元关系,则只有基类具有特殊访问权限,该基类的派生类不能访问授予友元关系的类。
 
2、如果派生类想要将自己成员的访问权授予其基类的友元,派生类必须显式地这样做:基类的友元对从该基类派生的类型没有特殊访问权限。同样,如果基类和派生类都需要访问另一个类,那个类必须特地将访问权限授予基类的和每一个派生类。

15.2.7. 继承与静态成员

1、如果基类定义 static 成员(第 12.6 节),则整个继承层次中只有一个这样的成员。无论从基类派生出多少个派生类,每个 static 成员只有一个实例。static 成员遵循常规访问控制:如果成员在基类中为 private,则派生类不能访问它。假定可以访问成员,则既可以通过基类访问 static 成员,也可以通过派生类访问 static 成员。一般而言,既可以使用作用域操作符也可以使用点或箭头成员访问操作符。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值