概念
通过继承机制,可以利用已有的数据类型来定义新的数据类型。所定义的新的数据类型不仅拥有新定义的成员,而且还同时拥有旧的成员。我们称已存在的用来派生新类的类为基类,又称为父类。由已存在的类派生出的新类称为派生类,又称为子类。
定义格式
继承关系
public(公有继承)、privata(私有继承)、protected(保护继承)
代码
class Base {
public:
Base()
{ cout<<"B()" <<endl; }
~Base ()
{ cout<<"~B()" <<endl; }
void ShowBase()
{
cout<<"_pri = " <<_pri<< endl;
cout<<"_pro = " <<_pro<< endl;
cout<<"_pub = " <<_pub<< endl;
}
private:
int _pri;
protected:
int _pro;
public:
int _pub;
};
class Derived:public Base
{
public:
Derived()
{
cout<<"D()"<<endl;
}
~Derived ()
{ cout<<"~D()"<<endl; }
void ShowDerived()
{ cout<<"_d_pri = "<<_d_pri<< endl;
cout<<"_d_pro = "<<_d_pro<< endl;
cout<<"_d_pub = "<<_d_pub<< endl; }
private:
int _d_pri;
protected:
int _d_pro;
public:
int _d_pub; };
总结
- 基类的private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
-
public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,把每个子类对象当作一个父类对象。
-
protected/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分, 是 has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的 都是公有继承。私有继承以为这is-implemented-in-terms-of(是根据……实现的)。通常比 组合(composition)更低级,但当一个派生类需要访问基类保护成员或需要重定义基类的虚函数时它就是合理的。
-
不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见(不能访问)。
-
使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
-
在实际运用中一般使用都是public继承,极少场景下才会使用protetced/private继承.
继承关系中构造函数的调用顺序
1、基类没有缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表。 2、基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数。
3、基类定义了带有形参表构造函数,派生类就一定定义构造函数。
调用顺序:一个派生类对象创建时先调用派生类的构造函数,在访问初始化列表时调用基类的构造函数,执行完后再回到派生类的构造函数中执行函数体内容。而析构函数正好相反,先访问派生类的析构函数,执行完函数体后,隐式访问基类的析构函数,执行完后再回到派生类的函数中结束。
class Test1
{
public:
Test1( int data)
{
cout <<"Test1()"<<endl;
}
~Test1 ()
{
cout<< "~Test1()"<<endl ;
}
};
class Test2
{
public:
Test2( int data)
{
cout <<"Test2()"<<endl;
}
~Test2 ()
{
cout<< "~Test2()"<<endl ;
}
};
class Base1
{ public:
Base1( int data): _data(data)
{
cout <<"Base1()"<<endl;
}
~Base1 ()
{
cout<< "~Base1()"<<endl ;
}
protected:
int _data;
};
class Base2
{
public:
Base2( int data): _data2(data )
{
cout <<"Base2()"<<endl;
}
~Base2 ()
{
cout<< "~Base2()"<<endl ;
}
protected:
int _data2;
};
class Derive: public Base1, public Base2
{
public:
//Derive(): Base1(0), Base2(1),t1(3), t2(4)
//Derive(): Base2(0), Base1(1),t2(3), t1(4)
//Derive(): t1(3), t2(4), Base1(0), Base2(1)
Derive(): t2 (3), t1 (4), Base2 (0), Base1(1)
{
cout <<"Derive()"<<endl;
}
~Derive ()
{
cout<< "~Derive()"<<endl ;
}
protected:
Test1 t1;
Test2 t2;
};
int main()
{
Derive v1;
return 0;
}
结果
作用域
- 在继承体系中基类和派生类是两个不同作用域。
- 子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中,可以 使用 基类::基类成员 访问)--隐藏 --重定义
- 注意在实际中在继承体系里面最好不要定义同名的成员。
继承和转换
- 子类对象可以赋值给父类对象(切割/切片)
- 父类对象不能赋值给子类对象
- 父类的指针/引用可以指向子类对象
- 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)
友元与继承
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
继承和静态成员
基类定义了static成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有 一个static成员实例。
继承方式
- 单继承 一个子类只有一个直接父类时称这个继承关系为单继承。 如: class Son :public Father 2.多继承 一个子类有两个或以上直接父类时称这个继承关系为多继承 如:class Son : public Father1,public Father2 3.菱形继承
菱形继承存在二义性和冗余数据的问题,为了解决这个问题,出现了虚继承
虚继承
对比
如果对这四种情况分别求sizeof(a), sizeof(b)。结果是什么样的呢?下面是输出结果:(在vc6.0中运行) 第一种:4,12 第二种:4,4 第三种:8,16 第四种:8,8
这是为什么呢?
因为每个存在虚函数的类都要有一个4字节的指针指向自己的虚函数表,所以每种情况的类a所占的字节数应该是没有什么问题的,那么类b的字节数怎么算呢?看“第一种”和“第三种”情况采用的是虚继承,那么这时候就要有这样的一个指针vptr_b_a,这个指针叫虚类指针,也是四个字节;还要包括类a的字节数,所以类b的字节数就求出来了。而“第二种”和“第四种”情况则不包括vptr_b_a这个指针,这回应该木有问题了吧。
c++重载、覆盖、隐藏的区别和执行方式
既然说到了继承的问题,那么不妨讨论一下经常提到的重载,覆盖和隐藏
成员函数被重载的特征
(1相同的范围(在同一个类中); (2函数名字相同; (3参数不同; (4virtual 关键字可有可无。
“覆盖”是指派生类函数覆盖基类函数,特征是:
(1不同的范围(分别位于派生类与基类); (2函数名字相同; (3)参数相同; (4)基类函数必须有virtual 关键字。
“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,特征是:
(1)如果派生类的函数与基类的函数同名,但是参数不同,此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。 (2)如果派生类的函数与基类的函数同名,但是参数相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
小结:说白了就是如果派生类和基类的函数名和参数都相同,属于覆盖,这是可以理解的吧,完全一样当然要覆盖了;如果只是函数名相同,参数并不相同,则属于隐藏。
三种情况怎么执行: 重载:看参数。 隐藏:用什么就调用什么。 覆盖:调用派生类。