C++ primer笔记----类继承

1、面向对象程序设计的三个基本概念:数据抽象、继承和动态绑定(核心概念)
数据抽象:将类的接口与实现分离
继承:我们可以定义与其他类相似但完全不相同的新类
动态绑定:在使用这些彼此相似的类时,在一定程度上忽略他们的区别,统一使用它们的对象

2、类的继承构成一种层次关系,在层次根部的为基类,其他类则直接或者间接的从基类中继承而来,称为派生类,基类负责定义在层次关系中所有类共有的数据成员,而派生类定义各自特有的成员

3、对于某些函数,基类希望它的派生类各自定义适合其自身的版本,基类会将该函数声明为虚函数,而派生类必须在其内部对所有重新定义的虚函数进行声明,若不加virtual关键字,可以在其后加上override显式声明改写基类的虚函数

4、派生类必须通过使用类派生列表明确指出它是从哪个基类中继承而来的:冒号+(访问限定符:public、protected、private)基类列表

5、动态绑定,函数的运行版本由实参决定也被称为运行时绑定:在使用基类的引用调用一个虚函数时将会发生动态绑定;当我们使用指针或者引用调用虚函数时,将调用动态绑定

6、基类通常应该定义一个虚析构函数,即使该函数不执行任何操作也是如此!

7、关键词virtual只能出现在类内的声明函数语句之前,而不能用于类外部的函数定义,在派生类中相应的函数将隐式的是虚函数(不加virtual的情况)

8、非虚函数,其解析过程将发生在编译时而非运行时

9、protected受保护的成员,基类希望它的派生类有权访问该成员,同时禁止其他用户访问,而private即使是其派生类也不能访问

10、派生类必须将其继承来的成员函数中需要覆盖的那些重新声明;大多数类只继承自一个类,被称作“单继承”。

11、C++允许派生类显式地注明它使用某个成员函数覆盖了它继承的虚函数,具体做法是在形参列表后面、或者在const成员函数的const关键字后面、或者在引用成员函数的引用限定符后面添加一个关键字override。

12、派生类拥有的成员包括自己定义的成员和继承自基类的相关成员,但是这两部分并不一定是连续存储的;
我们可以把派生类的对象当成基类的对象来使用,也可以将基类的指针或引用绑定到派生类对象中的基类部分上;

13、继承的关键:派生类中有基类的对应组成部分。

14、派生类需要使用基类的构造函数来初始化它的基类部分—每个类控制自己的初始化过程,派生类的构造函数同样是通过构造函数初始化列表将实参传递给基类的构造函数,再由基类的构造函数,完成其基类部分的初始化;

15、首先初始化基类的部分,然后按照声明的顺序依次初始化派生类的成员;派生类可以访问基类的公有成员和受保护成员;派生类的作用域嵌套在基类的作用域之内,所以对于一个派生类的成员,其使用派生类成员和积累成员的方式相同—最好使用各自的接口。

16、若基类中定义了静态成员static,则在整个继承体系中只存在该成员的唯一定义,并且只有一份实例;派生类的声明只能包含类名,不能包含它的派生列表:一条声明语句的目的是令程序知晓某个名字的存在以及该名字表示一个怎样的实体,派生列表以及其定义的相关细节必须与类的主体一起出现。

17、若要将一个类当作基类来使用,那么这个类就必须是已经定义过的,仅仅声明是不可以的,因为派生类需要知道其基类到底是什么,所以类不能派生其本身,最终的派生类将包含它的直接基类的子对象以及每个间接基类的子对象;C++11新标准,防止类被继承可以在其类名后加final,表示其不能作为基类

18、通常情况下,如果我们想使用指针或者引用绑定一个对象,则指针或者引用的类型需要和对象的类型一致或者可进行const转换,但是存在继承关系的类是一个重要的例外:我们可以将基类的指针或者引用绑定到派生类的对象上:这就意味着,当我们使用基类的指针或者引用时,我们并不知道该指针或引用所绑定的对象的真实类型,该对象可能是基类的对象,也可能是派生类的对象。

19、当我们使用一个变量或者表达式时,我们需要将其静态类型和动态类型相互区分开,表达式的静态类型是在编译时是已知的,它是变量声明时的类型或者表达式生成的类型,其动态类型是变量或者表达式表示内存的对象的类型,知道运行时才可知,即如item对象,静态类型为Quote&,动态类型则依赖于item所绑定的实参,直到函数运行时才可知;如果表达式既不是指针也不是引用,则其动态类型和静态类型会一直绑定在一起

19、之所以存在派生类到基类的类型转换是因为派生类之中含有基类的部分,但是基类中并不含有派生类中的成员,所以一个基类对象既可能是以独立的形式存在,也可能是派生类对象的一部分,所以不存在从基类到派生类之间的自动类型转换(可以将派生类转化为基类)

20、派生类到基类的类型转换只针对与引用或者指针的类型,其他类型是不支持的,即对象之间不存在类型转换;当我们用一个派生类的对象给一个基类对象初始化或者赋值时,只有其基类的部分被拷贝、移动或者赋值,它的派生类部分将会被忽略掉。

21、关键点:存在继承关系的类型之间的转换规则:
1:从派生类像基类类型转换只有对指针或者引用类型有效
2:基类到派生类不存在隐式类型转换
3:派生类到基类的类型转换也可能会由于访问限制而变得不可行

22、当我们使用引用或者指针调用一个虚成员函数时才会执行动态绑定,因为我们知道在程序运行时才知道到底调用了哪个版本的虚函数,所以所有虚函数都必须有定义

23、引用和指针的静态类型与动态类型不同的这一事实是C++语言支持多态性的根本所在(必须是虚函数)

24、一个派生类的函数成员如果覆盖了基类的继承而来的虚函数,则它的形参类型必须与被它覆盖的基类函数完全一致,返回类型也必须相匹配,但当类的虚函数的返回类型是类本身的指针或者引用时,返回类型可以不同;

25、派生类如果定义了一个与基类虚函数同名函数,但参数列表不相同的话,仍然是合法行为,编译器会认为该函数与基类虚函数是相互独立的,但这往往是把形参列表弄错了的错误,编译器发现不了,所以C++11有一个好东西,在其后加上override表示其要对基类的函数进行覆盖,若未覆盖,编译器报错,我们可以发现自己的错误。如果函数定义成final,则之后任何尝试覆盖该函数的操作都将引发错误。

26、虚函数可以有默认实参,若函数调用了默认实参,则实参值由静态类型决定,所以基类和派生类中定义的默认实参最好一致;某些情况下,我们不希望进行动态绑定,我们可使用作用域运算符强行指定其执行哪个版本,进行回避虚函数—一般情况下是成员函数中的代码才需要以防止自己调用自身造成无限循环

27、protected成员:类的对象不可访问,派生类的成员和友元可访问;派生类的成员只能通过派生类对象来访问基类的受保护成员,也就是说派生类不能访问基类对象的受保护成员;派生类的成员和友元只能访问派生类对象中的基类的受保护部分,对于普通的基类对象的受保护成员不具有特殊的访问权限。

28、公有继承,继承自基类的成员在派生类中遵循原有的访问说明符,私有继承,继承而来的基类成员在派生类中变为private,变为私有,派生类的对象都不可以访问其本身的私有成员,但私有继承不影响派生类本身的访问权限,它同样不可以使用基类的私有成员。

29、友元关系不能继承,每个类负责自己控制自己成员的访问权限;当一个类将另一个类作为友元函数时,这种友元关系只对作出声明的类有效,对于原来的类来说,其友元的基类或派生类不具备特殊访问能力;

30、通过在类的内部使用using声明改变派生类继承的某个名字的访问权限,using语句中名字的访问权限由该using出现之前的访问说明符决定;如果出现在类的private部分,则该名字只能被类的成员和友元访问;如果出现在public的部分,则类的所有用户都能访问它;如果出现在protected部分,则改名字对于成员、友元和派生类都是可访问的;派生类只能为那些它可以访问的名字提供using声明;

31、struct默认公有继承,private默认私有继承,但是我们最好将其显式的表明

32、 假定B继承自A,无论B以什么方式继承A,B的成员函数和友元都能使用派生类到基类的转换
B继承A的方式是公有或者保护,则B的派生类的成员或友元可以使用B到A的转换,若为私有则不行
B继承A的方式是公有的,用户的代码才能使用B到A的(派生类到基类)的转换,保护或者私有方式则不行

33、当类之间存在继承关系时,派生类的作用域嵌套在其基类的作用域之内,如果一个名字在派生类作用域中找不到定义,则编译器会继续在外层基类中寻找;

34、派生类可以重用定义在其直接基类或者间接基类中的名字,此时定义在内层作用域的名字将隐藏外层作用于中的名字,但是我们可以使用作用域运算符来访问使用隐藏的基类成员;除了覆盖虚函数,派生类最好不要重用其他基类中的名字

35、函数的调用及解析过程
1:确定其静态类型,必然是一个类类型
2:在其静态类型的类中查找该成员,找不到,则向外层的基类移动,再没有,编译器报错
3:找到了该成员,进行常规的类型检查,编译器再根据其是否是虚函数产生不同的代码,若是虚函数且为引用或者指针类型的调用,则需要进行动态绑定,编译器产生的运行代码在运行时将决定到底运行该虚函数的哪个版本,依据其动态类型;若不是虚函数或是没有指针引用调用,则产生常规调用

36、内层作用域中的函数不会重载外层作用域中的函数,所以派生类成员若有名字相同,即使其形参列表不一致,基类成员也会被隐藏掉—名字查找优先于类型检查;

37、基类和派生类的虚函数参数列表必须相同,并且可以通过基类的作用域运算符调用基类的虚函数,若派生类的与基类虚函同名的成员参数列表与虚函数不同,那么派生类中的成员并没有覆盖基类的相应虚函数,因为形参列表不一致,派生类将拥有两个同名成员。

38、虚析构函数:可以动态分配继承体系中的对象,如果我们需要删除一个指向派生类对象的基类指针,就需要虚析构函数,这样可以确保delete基类指针时将运行正确的虚构函数版本(动态绑定虚析构函数);

39、基类需要一个虚析构函数产生的影响:一个类定义了析构函数,即使它通过=default的形式生成合成的版本,编译器也不会为这个类合成移动操作;

40、如果基类中的默认构造函数、拷贝构造函数、拷贝赋值运算符或是析构函数被删除或是不可访问,则其派生类中的相应成员是被删除的;
如果在基类中有一个不可访问或删除掉的虚构函数,则派生类中合成的默认和拷贝构造函数将是被删除的;

41、大多数基类都会有一个虚析构函数,因此基类通常不会含有合成的移动操作,派生类中也如此,如果我们确实需要移动的操作,我们需要自行首先在基类中进行定义;

42、派生类的构造函数不仅要初始化自己的成员,还负责初始化派生类对象的基类部分,而派生类对象的基类部分是自动销毁的,派生类的析构函数只负责销毁自身派生类的成员,但是拷贝和移动操作,都会包含基类的部分;

43、定义派生类的拷贝和移动构造函数,需要在其初始值列表中显式的调用其基类的拷贝或移动构造函数,否则的话,派生类对象的基类部分会被默认初始化(派生类中定义拷贝和移动赋值运算符也是一样,需要在函数体中调用基类的相应成员)

44、对象的销毁顺序正好和其创建的顺序相反,派生类的部分先被销毁,基类部分后被销毁

45、如果构造函数或者析构函数调用了某个虚函数,那么我们应该执行与构造函数和析构函数所属类类型相对应的虚函数版本

46、C++11新标准,派生类可以直接重用基类的构造函数,一个类只能”继承“它的直接基类的构造函数,并且不能继承默认、拷贝、移动构造函数,若派生类中没有这些构造函数,编译器会自动产生合成版本

47、派生类继承基类构造函数的方式是提供一条注明了(直接)基类名的using声明语句;通常情况下,using只是令某个名字在当前作用域可见,但是当作用于构造函数时,using声明语句将会使编译器产生代码,派生类继承基类的构造函数,其派生的部分成员将会默认初始化;不管using出现在哪,基类的私有构造函数在派生类中还是一个私有类型的,其访问级别不会被using改变;

48、当一个基类的构造函数有默认实参时,这些实参不会被继承,派生类会获得多个继承的构造函数,每个构造函数都将省略掉一个含有默认实参的形参

猜你喜欢

转载自blog.csdn.net/qq_38224589/article/details/81913480