继承,详指类的继承,被继承的类称为基类,另一个类称为派生类,派生类继承了基类的特性,继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。
类继承的形式如下所示,
class A
{
public:
void FunTest1()
{ }
private:
int _a;
};
class B:public A
{
public:
void FunTest2()
{ }
private:
int _b;
};
在此段代码中,类B 公有继承了 类A的成员函数及成员变量;
那么,类继承的继承权限与基类各成员的访问权限有什么联系呢?经过一系列的验证,可得以下结论:
继承方式 \ 基类成员的访问权限 | public成员 | protected 成员 | private 成员 |
public 继承 | public | protected | 可以继承,但访问不了 |
protected 继承 | protected | protected | 可以继承,但访问不了 |
private继承 | private | private | 可以继承,但访问不了 |
class A
{
public:
A()
{
cout<<"A()"<<endl;
}
~A()
{
cout<<"~A()"<<endl;
}
void Show1()
{
cout<<"_a1="<<_a1<<endl;
cout<<"_a2="<<_a2<<endl;
cout<<"_a3="<<_a3<<endl;
}
public:
int _a1;
protected:
int _a2;
private:
int _a3;
};
class B:public A
{
public:
B()
{
cout<<"B()"<<endl;
}
~B()
{
cout<<"~B()"<<endl;
}
void Show2()
{
cout<<"_b1="<<_b1<<endl;
cout<<"_b2="<<_b2<<endl;
cout<<"_b3="<<_b3<<endl;
}
public:
int _b1;
protected:
int _b2;
private:
int _b3;
};
void FunTest()
{
B b;
b._a1=10;
}
int main()
{
FunTest();
system("pause");
return 0;
}
在继承方式中,
(1)基类的private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。
(2)public继承是一个接口继承,保持is-a原则,每个基类可用的成员对派生类也可用,因为每个派生类对象也都是一个基类对象。
对象模型,即指对象中各个成员的布局形式;以下为例:
class A
{
public:
void FunTest()
{}
int _a1;
protected:
int _a2;
private:
int _a3;
};
class B:public A
{
public:
void FunTest(int)
{}
int _b;
};
(3)protected/private是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是has-a的关系原则,protected/private的实现效果与下例相似。
class Time;
class Date
{
public:
int _year;
int _month;
int _date;
Time t;
}
(4)使用关键字class时默认的继承方式为private,使用关键字struct时默认的继承方式为public,在写继承关系时,最好显式给出继承方式,除此之外,实际运用中,一般都使用public继承;
派生类的默认成员函数
在继承关系中,若没有显式定义派生类的六个默认成员函数,编译系统则会默认合成这六个成员函数。
继承关系中构造函数与析构函数的调用顺序
构造函数:调用派生类构造函数——>在派生类构造函数的初始化列表中调用基类构造函数——>派生类构造函数体
说明:
(1)若基类没有缺省构造函数,派生类必须在初始化列表中显式给出基类名和参数列表;
(2)若基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数;
(3)若基类定义了带有形参表的构造函数,派生类就一定定义构造函数,且在派生类的初始化列表中必须显式调用基类构造函数;
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
class B:public A
{
public:
B()
:A(12)
,_b(0)
{}
private:
int _b;
};
void FunTest()
{
B b;
}
析构函数:派生类析构函数——>派生类包含成员对象析构函数(调用顺序和成员对象在类中声明的顺序相反)——>基类析构函数(调用顺序和基类在派生列表中声明顺序相反)
说明:对象先创建后清理(析构)
继承体系中的作用域
(1)在继承体系中,基类和派生类是两个不同的作用域;
(2)基类和派生类有同名成员(同名隐藏),派生类成员将屏蔽基类对成员的直接访问,且对于基类与派生类中的同名函数,满足“函数仅同名即隐藏,与函数的参数列表无关”;
class A
{
public:
void FunTest()
{}
int _a;
};
class B:public A
{
public:
void FunTest(int)
{}
int _a;
int _b;
};
void FunTest()
{
B b;
b.A::_a=1;
b._a=10;
b.A::FunTest();
b.FunTest(2);
}
(3)在实际的继承体系中最好不要定义同名的成员;
继承与转换(public继承)
(1)派生类对象可以给基类对象赋值,基类对象不能给派生类对象赋值;
(2)基类的指针或引用可以指向派生类对象,派生类的指针或引用不可以指向基类对象(可通过强制类型转换完成);
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
class B:public A
{
public:
B()
:A(12)
,_b(0)
{}
private:
int _b;
};
int main()
{
A a;
B b;
a=b;
A&a1 = b;
A*a2 = &b;
B&b1 =(B&)a;
B*b2 =(B*)&a;
system("pause");
return 0;
}
那么,有哪些函数可以继承,有哪些函数不可以继承呢?
友元函数:友元函数不是基类的成员函数,所以友元函数不可继承,即基类友元不能访问派生类的私有或保护成员;
静态成员:基类定义了static成员,则整个继承体系中只有一个这样的成员,无论派生类有多少个,都只有一个static成员实例;
class Person
{
public:
Person()
{++_c;}
protected:
string _name;
public:
static int _c; //基类与派生类公用,只有一个
};
int Person::_c=0;
class Student:public Person
{
protected:
int _num;
};
class Graduate:public Person
{
protected:
string _Course;
};
void FunTest()
{
Student s1;
Student s2;
Student s3;
Graduate s4;
cout<<""<<Person::_c<<endl; //4
Student::_c=0;
cout<<""<<Person::_c<<endl; //0
}
单继承&多继承&菱形继承
单继承示意图:
多继承示意图:
class Base1
{
public:
int _b1;
};
class Base2
{
public:
int _b2;
};
class Deriverd:public Base1,public Base2
{
public:
int _d;
}
以上面代码为例,多继承的派生类对象模型为:
访问时,则有:
Deriverd d;
Base1& b1=d; // b1=&d;
Base2& b2=d; // b2=(B2*)[(int)&d +sizeof(Base1)];
菱形继承示意图:
class B
{
public:
int _b;
};
class C1:public B
{
public:
int _c1;
};
class C2:public B
{
public:
int _c2;
};
class Deriverd:public C1,public C2
{
public:
int _d;
};
以上面代码为例,菱形继承的派生类对象模型为:
菱形继承的问题?
对于派生类Deriverd 所创建的对象d,若d 直接访问基类B的成员_b,则出现指代不明确的问题,为了解决这个问题,出现了虚继承——解决了菱形继承的二义性和数据冗余的问题;
菱形虚拟继承示意图
class B
{
public:
int _b;
};
class C1:virtual public B
{
public:
int _c1;
};
class C2:virtual public B
{
public:
int _c2;
};
class Deriverd:public C1,public C2
{
public:
int _d;
};
int main()
{
Deriverd d;
d._b=1;
d._c1=2;
d._c2=3;
d._d=4;
return 0;
}
以上面代码为例,菱形虚拟继承的派生类的对象模型为:
菱形虚拟继承的原理是:通过加入指向偏移量表格的指针,将基类B原保存2份的数据成员保存1份,解决了菱形继承的二义性和数据冗余的问题。