首页 > 代码库 > C++ primer读书笔记10-继承

C++ primer读书笔记10-继承

封装,继承,多态是C++的三大基本概念,这里着重总结一下继承相关的东西


1 类派生列表
类派生列表指定派生类要继承的基类,派生列表中有一个或者多个基类如:
class B : public A1,protected A2,private A3
但是单继承时最常见的,多继承不多见


2 派生类的定义
派生类继承时,会包含父类的所有成员,即便私有成员不能被访问。父类中的虚函数,在派生类中一般也要定义,如


果不定义的话,派生类将继承基类的虚函数


3 基类必须是已经定义的
一个仅仅声明的类,是不能出现在派生类的类派生列表中。因此派生类很可能要访问基类的列表,但是如果基类还没


被定义,所以这样做是错误的。


4 派生类的声明
派生类的列表不能出现在派生类的列表中,如:
class B : public A; //error
正确的声明应是:
Class B;
Class A;


5 继承修饰符
C++中的继承方式有三种,public,protected,private继承(简称3p)。不管是何种继承方式,基类中的private成员


都不能被派生的用户代码和成员定义代码访问。接下来具体说说三种继承方式:
<1>private继承,基类的所有成员在派生类中都是private成员,但是基类的private成员依旧不能被访问
<2>protected继承,基类的public,protected成员变成子类的protected成员,基类的private成员依旧不能被访问
<3>public继承,基类的public变成派生类的public,protected变成派生类的protected,基类的private不能被访问


6 protected 修饰符
当protected修饰类成员的时候,类成员可以被类自身成员定义所使用,但是不能被用户代码使用,这点类似private
其次,protected修饰的成员可以被子类访问到,这点类似public,但是不用于private。


7 不能被继承的成员
构造函数,复制控制成员(复制构造函数,赋值操作符,析构函数)这是不能被继承的函数


8 派生类的构造函数
派生类构造函数一般格式:
DeriveClass(xxx):baseClass(xxx),x1(x),x2(x)....
<1> 派生类的构造函数除了要初始化自己新定义的成员外,还要初始化基类的成员,顺序是先调用基类的构造函数初始化基类的成员,然后再初始化自己的新成员(顺序是声明的顺序)
<2> 如果没有自己定义构造函数,编译器会合成一个默认的构造函数,先调用基类的默认构造函数,然后就再去初始化其他新的成员。
<3> 这里需要注意的是:如果基类没有默认构造函数,那么编译器就不能合成默认构造函数,如果这个时候再不去定义派生类的默认构造函数,那样就会出错。诸如--“error C2512: “Derive”: 没有合适的默认构造函数可用。”
<4> 如果不在派生类构造函数的初始化列表中指定基类的构造函数,则会调用基类的默认构造函数
<5> 派生类构造函数的默认实参派生类可以将构造函数的所有参数都设置为默认参数,可以使用0-n个默认参数

9 派生类中的复制控制
复制控制包括复制构造函数,赋值操作符,析构函数
<1>没有定义复制构造函数,编译器会自己合成一个复制构造函数,它会调用基类的复制构造函数
<2>自定义复制构造函数,一般要调用基类的复制构造函数,否则会出意外
<3>没有定义赋值操作符,编译会自动合成一个赋值操作符,它会调用基类的复制操作符
<4>自定义赋值操作符要提防自身赋值,一般都是这么做
Derive& Derived::operator =(Derived& rh)
{
if(this != &rh)
{
Base::operator=(rh);
//do whatever needed to clean up the old value
//assign the members from the derived
}
}

这样做的原因就是为了防止自身赋值,在自身赋值的时候,我们通常要先把动态开辟的内存clear掉,如果不加上if(this != &rh)的话,那么在进行自身赋值的时候,就会把自己给废了(clear掉自己)。
<5> 派生类析构函数
这个和前两个不同,派生类的析构函数只负责自己新定义对象的析构,不负责基类对象的析构。在派生类对象析构的时候,编译器会先调用派生类自己的析构函数,然后调用基类的析构函数。每个类不管基类还是派生类,都只负责清除自己的成员。
以上的复制控制示例代码如下所示:
#pragma once
#include <iostream>
using namespace std;
class Base
{
public:
Base(){ m = 10;cout<<"Base default constructor"<<endl;}
Base(int x):m(x){}
Base(Base& rh){cout<<"the base copy constructor"<<endl;};
~Base(void){cout<<"Base destructor"<<endl;}
Base& operator = (Base& base){ this->m = base.m;cout<<"the base operator ="<<endl;return 


*this;} 
private:
int m;
};


class Derive :
public Base
{
public:
~Derive(void){cout<<"Derived destructor"<<endl;}
private:
int mx;
};


void main()
{
Derive X;
cout<<"this is the separtor-----------------"<<endl;
Derive Y = X;
Y = X;
}

执行结果:
Base default constructor
this is the separtor-----------------
the base copy constructor
the base operator =
Derived destructor
Base destructor
Derived destructor
Base destructor


10 派生类函数的调用
在继承的情况下,派生类的作用域嵌套在基类的作用域中,如果不能再派生类中确定名字,就会在外围的基类中朝赵名字的定义。
<1>派生类调用函数是这样的原则:先在派生类中查找名字,查到就停止;查不到的就在基类中查找。
<2>静态类型,动态类型
静态类型:是指不需要考虑表达式的执行期语义,仅分析程序文本而决定的表达式类型。静态类型仅依赖于包含表达式的程序文本的形式,而在程序运行时不会改变
动态类型:由一个左值表达式表示的左值所引用的最终派生对象的类型。一个右值表达式的动态类型,就是它的静态类型。
<3>对象,引用或者指针的静态类型决定了对象能够执行的行为
因此引用或者指针不能执行派生类中新定义的成员名字。
<4>基类与派生类的名字发生冲突
基类与派生类中有相同的名字的时候,派生类会屏蔽基类中同名的成员。如果非要调用基类的成员,那么就必须显式的调用,如:Base::func();
<5>基类成员函数的屏蔽
基类和派生类有相同名字的函数,但是原型不一样(参数),那么派生类将不能直接调用基类的那个同名的函数。根据之前的原则,找到名字相同的就不再往基类找了,所以下面的程序是错误的。
class A
{
public:
void fun(){};
}
class B : public A
{
public:
void fun(int){};
private int x;
}
B b;
b.fun(11); //OK
b.func();  //error

11 using
可以在派生类中使用using来改变继承自基类中的成员的级别,前提是派生类对基类的成员具有访问权限。
//Base.h
#pragma once
#include <iostream>
using namespace std;
class Base
{
public:
Base(void){ n = 100; };
~Base(void){};
size_t size()const{return n;}
protected:
//private:
size_t n;
int fn(int x){return x;};
int fn(){return 11;}
};

//Derived.h
#pragma once
#include "base.h"
class Derived :
private Base
{
public:
Derived(void){};
~Derived(void){};
using Base::size;
using Base::fn;
};

//main.cpp
#include "Base.h"
#include "Derived.h"
#include <iostream>
using namespace std;

void main()
{
Derived XX;
Base YY;
cout<<XX.size()<<endl;
cout<<XX.fn()<<endl;
cout<<XX.fn(2)<<endl;
system("pause");
}

在这里我们也可以看到如果基类的函数有重载的话,针对同一个using Base::XXX,可以使所用的名字为XXX的基类函数在派生类中得到声明。真可可谓一次声明,多个使用。


12 friend 跟继承的关系
友元跟继承没有关系。基类的派生类不能被基类的派生类访问。友元类的派生类对基类没有访问权限。


13 引用转换,转换对象
引用转换:派生类对象转换为基类类型的引用
转换对象:用派生类对象转换为基类的对象,这个时候形参是固定的,编译和运行时候的对象都是基类类型对象。派生类的基类部分被复制到形参中。
引用转换:派生类对象转换为基类的引用。但是如果赋值的话也是把派生类的基类部分赋值给基类对象,这和指针的切片效应一个道理。


14 基类和派生类的转换
派生类转对象赋给基类也是有条件才能行的,这个条件就是派生类是通过public来继承的,这样的话不论是在成员代码还是在用户代码中,都能实现 
Derived d;
Base b = d;


如果是通过protected继承的,那么只能在成员定义中使用这样的代码。