继承
1、概念:
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。
一个新类从已有的类中获得其已有的特性称为继承,被继承的称为父类(Base class)或基类,新产生的类称为派生类或子类。
2、访问限定符与继承关系
类的成员有三种访问限定符:public(公有)、protected(保护)、
private(私有)
继承的方式:public(公有继承)、protected(保护继承)、
Private(私有继承)
3、继承的方式:
继承权限规则表
继承方式 | 基类的public成员 | 基类的protected成员 | 基类的private成员 | 继承引起的访问控制变化 |
Public | Public | Protected | 不可见 | 基类成员在派生类中访问权限不变 |
Protected | Protected | Protected | 不可见 | 基类的非私有成员成为子类的保护成员 |
Private | Private | Private | 不可见 | 基类的所有成员成为子类的私有成员 |
基类:
class Base
{
public:
Base()
:pub(1)
,pro(2)
,pri(3)
{}
public:
int pub;
protected:
int pro;
private:
int pri;
};
(1)公有继承:采用公有继承时,基类成员的访问权限在派生类中不变
class Derived1:public Base
{
public:
Derived1()
:d_pub(4)
,d_pro(5)
,d_pri(6)
{}
void SetData()
{
pub = 0;
pro = 0;
pri = 0; //子类无法访问基类的私有成员
}
public:
int d_pub;
protected:
int d_pro;
private:
int d_pri;
};
void FunTest()
{
Base b;
b.pub = 1;
b.pro = 2;
b.pri = 3; //类外无法访问类的保护、私有成员
Derived1 d1;
d1.pub = 1;
d1.pro = 2;
d1.pri = 3; //子类对象无法访问基类的保护、私有成员
d1.d_pub = 4;
d1.d_pro = 4;
d1.d_pri = 4; //类外无法访问类的保护、私有成员
}
(2)保护继承:基类的公有、保护成员成为派生类的保护成员。
class Derived2:protected Base
{
public:
Derived2()
:d_pub(4)
,d_pro(5)
,d_pri(6)
{}
void SetData()
{
pub = 0; //成为子类的保护成员
pro = 0;
pri = 0; //子类无法访问基类的私有成员
}
public:
int d_pub;
protected:
int d_pro;
private:
int d_pri;
};
void FunTest()
{
Base b;
b.pub = 1;
b.pro = 2;
b.pri = 3; //类外无法访问类的保护、私有成员
Derived2 d2;
d2.pub = 1; //成为子类的保护成员,无法访问
d2.pro = 2;
d2.pri = 3; //子类对象无法访问基类的保护、私有成员
d2.d_pub = 4;
d2.d_pro = 4;
d2.d_pri = 4; //类外无法访问类的保护、私有成员
}
(3)私有继承
class Derived3:private Base
{
public:
Derived3()
:d_pub(4)
,d_pro(5)
,d_pri(6)
{}
void SetData()
{
pub = 0; //成为子类的私有成员
pro = 0;
pri = 0; //子类无法访问基类的私有成员
}
public:
int d_pub;
protected:
int d_pro;
private:
int d_pri;
};
void FunTest()
{
Base b;
b.pub = 1;
b.pro = 2;
b.pri = 3; //类外无法访问类的保护、私有成员
Derived3 d3;
d3.pub = 1; //成为子类的私有成员,无法访问
d3.pro = 2;
d3.pri = 3; //子类对象无法访问基类的保护、私有成员
d3.d_pub = 4;
d3.d_pro = 4;
d3.d_pri = 4; //类外无法访问类的保护、私有成员
}
2、 总结:
(1) 类的保护成员和私有成员在类外部都不可访问,基类的私有成员在派生类都不可访问。
(2) public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。
例如:有一个Horse类可以保存关于马的所有信息,身高体重等等,那么我们就可以从Horse类中派生出白马类,白马类包含所有Horse类的成员,在白马类中可以新增关于白马的成员,这个成员通常不用于Horse类。
class Horse
{
public:
int Tall;
int Weight;
};
classWhiteHorse:public Horse
{
public:
int Color;
};
WhiteHorse是Horse的一个子类,包括了Horse类的所有成员,is_a是一种关系
(3) protetced/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是 has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。
实现继承的主要目标是代码重用,我们发现类B和类C存在同样的代码,因此我们设计了一个类 A,用于存放通用的代码,基于这种思路的继承称为实现继承。
基类的成员在派生类中是私有的,基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们,基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。
class Banana
{..};
class Lauch
{
private:
classBanana; ......
};
(4) 基类的 private 成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为 protected 。可以看出保护成员限定符是因继承才出现的。
(5) 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
3、 派生类的默认成员函数
在继承关系里面,在派生类中如果没有显示定义这六个成员函数,编译系统则会默认合成这六个默认
的成员函数。
(1) 构造函数调用顺序:
进入子类的构造函数——>调用基类构造函数——>执行子类构造函数
说明:
1)、基类没有缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表。
2)、基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数。
3)、基类定义了带有形参表构造函数,派生类就一定定义构造函数
(2) 析构函数调用顺序:
派生类析构函数——>派生类包含成员对象的析构函数——>基类析构函数
class Base
{
public:
Base()
{
cout<<"Base()"<<endl;
}
Base(int b, int r, int i)
:pub(1)
,pro(2)
,pri(3)
{
cout<<"Base(int , int , int)"<<endl;
}
~Base()
{
cout<<"~Base()"<<endl;
}
public:
int pub;
protected:
int pro;
private:
int pri;
};
class Derived3:public Base
{
public:
Derived3()
{
cout<<"Derived3()"<<endl;
}
Derived3(int d1, int d2, int d3)
:Base(d1, d2, d3)
{
d_pub = d1;
cout<<"Derived3() ----"<<"d_pub="<<d_pub<<endl;
}
~ Derived3()
{
cout<<"~Derived3()"<<endl;
}
public:
int d_pub;
protected:
int d_pro;
private:
int d_pri;
};
void FunTest()
{
Derived3 d0;
Derived3 d3(1, 2, 3);
}
4、即成体系中的作用域:
(1)在继承体系中基类和派生类是两个不同作用域。
(2) 子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中,可以使用 基类::基类成员 访问)--隐藏 --重定义
(3) 注意在实际中在继承体系里面最好不要定义同名的成员
5、继承与转换--赋值兼容规则:(public继承)
(1)子类对象可以赋值给父类对象(切割/切片)
(2)父类对象不能赋值给子类对象
(3)父类的指针/引用可以指向子类对象
(4)子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)
class Base
{
public:
Base()
{
cout<<"Base()"<<endl;
}
Base(int b, int r, int i)
:pub(1)
,pro(2)
,pri(3)
{ cout<<"Base(int , int , int)"<<endl; }
~Base()
{cout<<"~Base()"<<endl;}
public:
int pub;
protected:
int pro;
private:
int pri;
};
class Derived:public Base
{
public:
Derived()
{ cout<<"Derived3()"<<endl; }
Derived(int d1, int d2, int d3)
:Base(d1, d2, d3)
{
d_pub = d1;
cout<<"Derived()"<<endl;
}
~ Derived()
{ cout<<"~Derived()"<<endl; }
public:
int d_pub;
protected:
int d_pro;
private:
int d_pri;
};
void FunTest()
{
Base b0;
Base b1(1, 2, 3);
Base* pBase;
Derived d0;
Derived d1(4, 5, 6);
Derived* pDerived;
b0 = d0; //子类对象可以赋值给父类对象
b1 = d1;
d0 = bo; // 父类对象不能赋值给子类对象
d1 = b1;
pBase = &d0; //父类的指针/引用可以指向子类对象
pBase = &d1;
Base& _b0 = d0;
Base& _b1 = d1; //父类的指针/引用可以指向子类对象
pDerived = &b0;
Derived& = b1;//子类的指针/引用不能指向父类对象
pDerived = (Derived *)&b1; //可以使用的强制类型转换使子类的指针/引用指向父类对象
}
6、友元与继承
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
class Base
{
public:
Base()
{
pub = 10;
pro = 10;
pri = 10;
cout<<"Base()"<<endl;
}
~Base()
{
cout<<"~Base()"<<endl;
}
public:
int pub;
protected:
int pro;
private:
int pri;
friend void show();
};
class Derived:public Base
{
public:
Derived()
{
d_pri= 10 ;
cout<<"Derived3()"<<endl;
}
private:
int d_pri;
};
void show()
{
Base b;
cout<<b.pri<<endl; //友元函数可以访问基类的私有成员
Derived d;
cout<<d.d_pri<<endl; //友元函数不能继承,基类的友元函数不可以访问子类的私有成员
}
7、继承的类型
(1)单继承:
class A
{
public:
int data1;
} ;
class B :public A
{
public:
int data2;
} ;
(2)多继承:
class A
{
public:
int data1;
} ;
class B
{
public:
int data2;
} ;
class C :public A ,public B
{
public:
int data3;
} ;
(3) 菱形继承:
class A
{
public:
int data1;
} ;
class B: public A
{
public:
int data2;
} ;
class C:public A
{
public:
int data3;
} ;
class D:public B,public C
{
public:
int data4;
} ;
D类的对象中存在两份A对象成员,菱形继承存在二义性和数据冗余问题
8、虚继承--解决菱形继承的二义性和数据冗余的问题
class A
{
public:
int data1;
} ;
class B: virtual public A
{
public:
int data2;
} ;
class C: virtual public A
{
public:
int data3;
} ;
class D:public B,public C
{
public:
int data4;
} ;
void FunTest()
{
D d;
d.data1 = 1;
d.data2 = 2;
d.data3 = 3;
d.data4 = 4;
cout<<sizeof(B)<<endl;//12
cout<<sizeof(D)<<endl;//24
//0x0098F73C 000ecc88 00000002 000ecc94 00000003 00000004 00000001
}
(1)虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余&浪费空间的问题。
(2)虚继承体系看起来好复杂,在实际应用我们通常不会定义如此复杂的继承体系。一般不到万不得已都不要定义菱形结构的虚继承体系结构,
因为使用虚继承解决数据冗余问题也带来了性能上的损耗。
就写到这了,有什么不对的大家指正哈!!!