c++编程思想---第14章 继承和组合

1,实现代码重用两种方式

组合:我简单的在新类中创建一个已存在的类的对象。因为新类是由已存在类的对象组合而成,称之为组合。 这样就可以把已存在类的功能加到了新的类中去。

继承:扩展父类。

2,基类的private成员,只能通过基类提供的接口来访问。

3,调用基类的函数,隐藏的或者覆盖的可以通过作用域运算符来访问。

4,重载

  (1)相同的范围(在同一个类中)

  (2)函数名字相同

  (3)参数不同

  (4)virtual 关键字可有可无

覆盖(派生类函数覆盖基类函数)

  (1)不同的范围(分别位于派生类与基类)

  (2)函数名字相同

  (3)参数相同

  (4)基类函数必须有 virtual 关键字

隐藏(派生类的函数屏蔽了与其同名的基类函数)只要子类自己定义了一个和基类重名的函数(无论返回值,参数列表是否相同),只要名字相同,基类的版本都是会被隐藏的。

  (1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无 virtual 关键字,基类的函数将被隐藏(注意别与重载混淆)

  (2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有 virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)

5,初始化列表的作用:初始化类的成员有两种方式,一是使用初始化列表,二是在构造函数体内进行赋值操作。使用初始化列表主要是基于性能问题,对于内置类型,如int, float等,使用初始化类表和在构造函数体内初始化差别不是很大,但是对于类类型来说,最好使用初始化列表,为什么呢?使用初始化列表少了一次调用默认构造函数的过程,这对于数据密集型的类来说,是非常高效的。未使用初始化列表的情况是:当类的有成员变量时,而且该成员变量是类class类型的,编译器会先调用默认的构造函数,然后调用赋值的函数,进行复制操作。影响效率。

6,除了性能问题之外,有些时场合初始化列表是不可或缺的,以下几种情况时必须使用初始化列表:

  (1)常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面

  (2)引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面

  (3)没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。

7,C++的一个强化机制,在没有构建完基类、成员对象(class型的,内建类型)时,是没有办法进入该类的构造函数的,为了防止在构造函数中调用成员对象内的函数,父类的函数。先调用基类的构造函数,然后调用成员对象的构造函数,然后进入自己的构造函数。

8,伪构造函数语法 int i(100);

9,构造函数的调用顺序是从根往下顺序构造,析构函数是从下往根的逆序方向调用。构造和析构的方向是正好相反的。多个类成员变量的构造顺序不是按照初始化列表的顺序进行的,是根据在类中声明的位置进行先后调用的。

10,编译器自动生成的有

  (1)默认构造函数。只要自己定义了一个构造函数(无论什么构造函数)编译器就不会自动生成了。

  (2)拷贝构造函数。

  (3)赋值运算符 =。

  (4)析构函数

11,不能够被继承的函数有

  (1)构造函数。因为它的作用是针对自己的那一层的。

  (2)析构函数。因为它的作用也是针对自己的那一层的。

  (3)“=”,赋值运算符,子类会有一个默认的赋值运算符,然后会把父类的赋值运算符给隐藏掉了。

12,静态成员函数与非静态成员函数的共同点

  (1)他们均可以被继承。

  (2)如果我们定义了一个静态成员函数,则它基类中同名的函数也是会被隐藏的。

13,静态成员函数不能为虚函数。因为他没有this指针。

14,组合:为产生新功能,车的例子,车是有很多个零部件构成的。has-a的关系

15,继承:使用旧的接口,定义新功能。is-a的关系。决定什么时候用组合还是构造,还有一个判断点是,考虑是否需要用到向上类型转换。

16,三种继承权限:

  public 继承

  protect 继承

  private 继承

  组合结果:

  基础类中     继承方式     子类中

  public & public继承 => public
  public & protected继承 => protected 
  public & private继承 => private

  protected & public继承 => protected
  protected & protected继承 => protected 
  protected & private继承 => private

  private & public继承 => 子类无权访问
  private & protected继承 => 子类无权访问
  private & private继承 => 子类无权访问

由以上组合结果可以看出()这些权限下的成员到了子类中,但是子类的成员,权限是由继承方式来决定的。

  (1)、public继承不改变基类成员的访问权限
  (2)、private继承使得基类所有成员在子类中的访问权限变为private
  (3)、protected继承将基类中public成员变为子类的protected成员,其它成员的访问 权限不变。
  (4)、基类中的private成员不受继承方式的影响,子类永远无权访问。

17,我们对C++类三种方式控制权限总结如下:

访问权限 public protected private
对本类 可见 可见 可见
对子类 可见 可见 不可见
对外部(调用方) 可见 不可见 不可见

18,私有继承,当与运行时类型识别相连时,私有继承特别复杂,应注意。(后面章节有学习到)

19,私有继承成员公有化(仅限于基类的成员函数是public、protected的权限下,private是不可以的,实验过了)

class Car
{
public :
    Car(){}

    void hello()
    {
        qDebug() << "hello !";
    }

protected:
    void haha()
    {
        qDebug() << "haha !";
    }
};

class BMW : private Car
{
public :

    using Car::hello;
    using Car::haha;

};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Car car;

    BMW bmw;
    bmw.hello();
    bmw.haha();

    return a.exec();
}

20,多重继承存在争议(后续学习)。

21,向上类型转换:子类到父类是安全的。向上类型转换失去子类独有的成员变量(除子类重写的虚函数外)。

Child child;

Parent * parent = &child;

这时parent只能操作child的中有的属于基类的部分(重写的虚函数除外)。

22,另一个经常发生的和继承有关的类似问题是在实现派生类的拷贝构造函数时。看看下面这个构造函数,其代码和上面刚讨论的类似:

class base
{
public:
  base(int initialvalue = 0): x(initialvalue)
  {
      qDebug() << "i am base constructor !";
  }
  base(const base& rhs): x(rhs.x)
  {
      qDebug() << "i am base copy-constructor !";
  }

private:
  int x;
};

class derived: public base
{
public:
  derived(int initialvalue = 1) : base(initialvalue), y(initialvalue)
  {
      qDebug() << "i am derived constructor !";
  }
  derived(const derived& rhs) : y(rhs.y)
  {
      qDebug() << "i am derived copy-constructor !";
  }                    // 错误的拷贝构造函数,需要在初始化列表中显示的调用父类的拷贝构造函数。这时调用的是基类的默认的构造函数,只要不是我们显示调用哪个构造函数,默认调用的都是默认的构造函数。             //如果子类的这个拷贝构造函数注掉的话,编译器会自动生成一个拷贝构造函数,该构造函数会调用基类的拷贝构造函数。

private:
  int y;
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    derived child1;

    derived child2 = child1;

    return a.exec();
}

没有注掉子类的拷贝构造函数的结果:
i am base constructor !
i am derived constructor !
i am base constructor !
i am derived copy-constructor !注掉之后的结果是:
i am base constructor !
i am derived constructor !
i am base copy-constructor !可以看出来默认生成的拷贝构造函数是可以调用基类的拷贝的构造函数,如果自己声明了拷贝构造函数,则必须在初始化列表中显示的调用基类的拷贝构造函数,不然默认调用的都是默认的构造函数。

类derived展现了一个在所有c++环境下都会产生的bug:当derived的拷贝创建时,没有拷贝其基类部分。当然,这个derived对象的base部分还是创建了,但它是用base的缺省构造函数创建的,成员x被初始化为0(缺省构造函数的缺省参数值),而没有顾及被拷贝的对象的x值是多少!

为避免这个问题,derived的拷贝构造函数必须保证调用的是base的拷贝构造函数而不是base的缺省构造函数。这很容易做,只要在derived的拷贝构造函数的成员初始化列表里对base指定一个初始化值:

class derived: public base {
public:
  derived(const derived& rhs): base(rhs), y(rhs.y) {}

  ...

};

现在,当用一个已有的同类型的对象来拷贝创建一个derived对象时,它的base部分也将被拷贝了。

如果子类没有实现拷贝构造函数,编译器会自动生成一个拷贝构造函数,它将首先自动地调用基类的拷贝函数,然后再是个成员对象的拷贝构造函数(或者在内建类型上执行位拷贝)。

结论:必须记住无论何时我们在自己创建了自己的拷贝构造函数的时候,都要正确地调用基类的拷贝构造函数(就像编译器做的那样)。

时间: 2024-10-14 04:18:21

c++编程思想---第14章 继承和组合的相关文章

Java编程思想——第14章 类型信息(二)反射

六.反射:运行时的类信息 我们已经知道了,在编译时,编译器必须知道所有要通过RTTI来处理的类.而反射提供了一种机制——用来检查可用的方法,并返回方法名.区别就在于RTTI是处理已知类的,而反射用于处理未知类.Class类与java.lang.reflect类库一起对反射概念进行支持,该类库包含Field.Method以及Constructor(每个类都实现了Member接口).这些类型是由JVM运行时创建的,用来表示未知类种对应的成员.使用Constructor(构造函数)创建新的对象,用ge

java编程思想笔记(第一章)

Alan Kay 第一个定义了面向对象的语言 1.万物皆对象 2.程序是对象的集合,他们彼此通过发送消息来调用对方. 3.每个对象都拥有由其他对象所构成的存储 4.每个对象都拥有其类型(TYpe) 5.某一特定类型的所有对象都可以接收同样的消息. Booch提出一种更简洁的描述: 对象拥有状态(state) 行为(behavior) 和标识(identity) 每个对象都有一个接口 每个对象都属于定义了特性和行为的某个类(特性可以理解为属性的状态,行为可以理解为method) 在面向对象的程序设

Java编程思想——第17章 容器深入研究(two)

六.队列 排队,先进先出.除并发应用外Queue只有两个实现:LinkedList,PriorityQueue.他们的差异在于排序而非性能. 一些常用方法: 继承自Collection的方法: add 在尾部增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常 remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常 element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementExce

Java编程思想笔记(第二章)

第二章  一切都是对象 尽管Java是基于C++的,但相比之下,Java是一种更纯粹的面向对象程序设计语言. c++和Java都是杂合型语言(hybird language) 用引用(reference)操作对象 类似遥控器(引用)来操作电视(对象) 在Java中你可以创建一个引用,但是没有与任何对象关联,比如: String s; 这个时候如果用则会报错.安全的做法是: 创建一个引用的同时并进行初始化 String s="1111"; 必须由你创建所有对象 New关键字的意思是给我一

Java 编程思想 第五章 ----初始化与清理(1)

从今天开始每天一小时的java 编程思想的阅读和编码,其实就是把书上的代码抄下来. 5.5 清理:终结处理和垃圾回收 初始化和清理工作同等重要,但是清理工作却被常常忘记,但是在使用对象之后,对对象弃之不顾的做法并不是很安全.Java有自己的垃圾回收器负责回收无用的对象占据的内存资源.但也有特殊情况:假定你的内存区域不是用new获得的,这是无法用垃圾回收器释放所以java中允许在类中定义一个名为 finalize()的方法.       工作原理: 一旦垃圾回收器准备好释放对象占用的存储空间,将首

Java编程思想 第七章

7.1 组合语法 1)组合即 将对象引用置于新类中 2)每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个String而你却只有一个对象时,该方法便会被调用. 3)初始化一个类中的对象引用有如下四种方式: 1.在定义对象的地方初始化,,意味着总能在调用构造器之前被初始化 2.在类的构造器中 3.就在正要使用这些对象之前,这种叫惰性初始化,这种可以减少额外的负担 4.使用实例初始化 class Soap { private String s; Soap() { print

Windows核心编程:第14章 探索虚拟内存

Github https://github.com/gongluck/Windows-Core-Program.git //第14章 探索虚拟内存.cpp: 定义应用程序的入口点. // #include "stdafx.h" #include "第14章 探索虚拟内存.h" int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lp

java 编程思想 一 第二章(对象)

上班之余发现有很多空闲时间,享受生活 又觉得有点空虚,而且对自己的基础知识总觉得掌握的不是很牢固,有点似懂非懂的感觉,近来刚好有时间,所以就考虑继续学习,然后再经过考虑和各大博主推荐选择了<java编程思想>这本书,在此分享学习心得跟大家共勉,也算是对自己的监督吧.(本内容需要有一定的基础才能看,类似于基础回顾,强化理解,新手可能有些地方不太能听懂) 一.什么是对象? 这并不是我们男女朋友那种对象哈哈. 简言之:万事万物皆对象. 个人理解:我们所要处理的事务或者建立的某种模型的抽象总结.具体就

Java编程思想:第7章 复用类

复用代码是Java众多引人注目的功能之一.但仅仅能复制代码并对之加以改变是不够的,还需要做更多的事情. 复用代码的两种形式: 1.组合,新类中产生现有类对象 2.继承,用现有类型创建新类型 7.1组合语法 7.2继承语法 7.2.1初始化基类 当创建了一个导出类的对象时,该对象包含了一个基类的子对象.这个子对象与你用基类直接创建的对象是一样的(Java会自动在导出类构造器里插入对基类构造器的调用,基类只含带参构造需要自己用super调用,不可省略),二者区别在于继承时基类的子对象包装在导出类对象