继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
class A
{
public:
A(int a)
:_a(a)
{
}
void print()
{
cout<<"i am son"<<endl;
}
private:
int _a;
}
class B:public A //B类公有继承A类
{
private:
int _b;
}
void test()
{
B b;
b.print()//继承A类,所以使用A类中的公有函数
}
继承定义格式
派生类 :继承方式 父类
class son:public father
继承方式有三种public protected private
派生类成员访问限定
1.基类的public成员:
派生类不论哪一种继承,其成员都对应该种访问权限。比如,派生类protected继承,那么该派生类的中继承的基类成员变为protected类型
2.基类的protected成员:
派生类的public继承和protected继承都会使得其成员访问权限变为protected,而如果private继承,则其成员访问权限都是private
3.基类的private成员:
不论派生类哪一种继承方式,其基类成员对于派生类中都不可见,不可访问。
总结
class person
{
public:
int _num;
string _name;
}
class student:public person
{
public:
int _age;
}
void test()
{
student st;
person ps;
ps = st;//发生切片操作,即这样操作后ps访问不了派生类中的_age成员,相当于将这个成员切掉
person* p = &st;//子类对象地址可以赋给基类指针
person& pp = st;//子类对象可以赋给基类引用对象
//基类对象不能赋值给子类对象
person* pr = &st;
student* stu = (student*)pr//基类指针可以通过强制类型转换赋给子类指针
}
继承中的作用域
class person
{
public:
int _num;
string _name;
int _age;
}
class student:public person
{
publci:
void print()
{
cout<<_age<<endl;//基类和子类中都有成员_age,这时会屏蔽父类中的_age
}
public:
int _age;
}
函数隐藏
class A
{
public:
void fun()
{
cout<<"A::fun()"<<endl;
}
public:
int _age;
};
class B:public A
{
public:
void fun(int a)
{
cout<<"B::fun(int a)"<<a<<endl;
}
public:
int age;
};
void test()
{
B b;//定义一个B对象
b.fun(100);//调用B类中的fun函数
cout<<endl;
b.A::fun();//若想要访问A类中的fun函数,则需要显示定义
}
6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个 成员函数是如何生成的呢?
class A
{
public:
A(int num)//构造函数
:_num(num)
{
cout<<"A(int num)"<<endl;
}
A(const A& p)//拷贝构造函数
:_num(p._num)
{
cout<<"A(const A& p)"<<endl;
}
void fun()
{
cout<<"A::fun()"<<endl;
}
A& operator=(const A& p)//=运算符重载
{
cout<<"A& operator=()"<<endl;
if(this != &p)
_num = p._num;
return *this;
}
~A()//析构函数
{
cout<<"~A()"<<endl;
}
public:
int _num;//A类中公有成员
};
class B:public A//B类公有继承A类
{
public:
B(int num,int age)
:A(num)//A类中没用默认构造函数所以需要显示调用A类的构造函数
,_age(age)
{
cout<<"B(int num,int age)"<<endl;
}
B(const B& p)
:A(p)//显示调用A类中的拷贝构造函数
,_age(p._age)
{
cout<<"B(const B& p)"<<endl;
}
B& operator=(const B& p)
{
cout<<"B& operator=()"<<endl;
if(this != &p)
_age = p._age;
return *this;
}
void fun(int a)
{
A::fun();//显示调用A类中的fun函数
cout<<a<<endl;
}
~B()//先调用B类中的析构函数然后再调用A类中的析构函数
{
cout<<"~B()"<<endl;
}
public:
int _age;
};
void test()
{
B b(15,16);
B b1(b);
B b2(1,1);
b1 = b2;
}
上述test中B b(15,16);的运行结果
上述test中B b1(b);的运行结果
在一个继承类体系中,如果定义了一个静态成员,那么在这个整个体系中都只有这一个成员
class A
{
public:
A(){
++_num}
public:
static int _num;//A类中定义静态成员_num
};
int A::_num = 0;//静态成员在类外定义
class B:public A
{
public:
int _age;
};
void test()
{
B b;
B b1;
B b2;
cout<<A::_num<<endl;
B::_num = 0;
cout<<A::_num<<endl;
}
单继承:一个子类只有一个直接父类时称这个继承关系为单继承
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
菱形继承:菱形继承是多继承的一种特殊情况即:两个派生类继承同一个基类,另外一个新的派生类,继承这两个派生类,形成一个菱形继承结构。会有数据的冗余性。
class A
{
public:
int _num;
}
class B:public A//B类公有继承A类
{
public:
int _age;
}
class C:public A//C类公有继承A类
{
public:
string _name;
}
class D:public B,public C//D继承B和C,即菱形继承
{
public:
int _grand;
}
void test()
{
D d;
d._num = 10;//无法确定访问的是哪一个_num
//可以显示指定访问哪一个_num
d.B::_num = 1;//访问B类中的_num
d.C::_num = 2;//访问C类中的_num
}
正因为这里存在数据冗余,固引出了虚拟继承
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在B和C的继承 A时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。
class A
{
public:
int _a;
}
class B:virtual public A//B类虚拟继承A
{
public:
int _b;
}
class C:virtual public A//C类虚拟继承A
{
public:
int _c;
}
class D:public B,public C//D继承B和C,即菱形虚拟继承
{
public:
int _d;
}
void test()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
}
虚拟继承,会让A对象在内存中只有一份,而B和C则会通过指针找到公共的A,所以这里B和C的两个指针,指向的一张表,B和C的两个指针称为虚基表指针,这两个表称为虚基表。其中,虚基表中存有偏移量,通过这个偏移量,可以找到内存中的公共的A 。