继承与虚继承
1:继承
(1).一个简单继承实现
#include<iostream>
using
namespace
std;
class
Person
{
public:
String _name;
};
class
Teacher
::
public
Person
{
public:
int
_id;
};
int
main()
{
return
0;
}
其中,Person 称为父类(或基类);Teacher 称为子类(或派生类)
“class
Teacher
::
public
Person” 中public 为继承关系
(2).三种继承关系
简而言之就是:不论是继承方式还是基类的类成员访问限定符 两者取其范围小的那一种限定符修饰子类。
注意:
1.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,但是最好显示的写出继承方式。
2.
不管是哪种继承方式,在子类中都可以访问父类的公有成员和保护成员,但是父类的私有成员存在但是在子类中不可见(不能
访问)。
3.public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。
(3)public继承--赋值兼容规则
#include<iostream>
using
namespace
std;
class
Person
{
public:
void
Display()
{
cout<<_name
<<endl;
}
protected
:
string _name
;
//
姓名
};
class
Student
:
public
Person
{
public:
int
_num
;
//
学号
};
i.子类对象可以赋值给父类对象(切片/切割),父类对象不能赋值给子类对象
void
Test
()
{
Person p
;
Students;
p
=
s;//子类对象赋值给父类对象
s = p;//父类对象赋值给子类对象
错的!!!
}
ii.子类对象的指针或引用可以赋值给父类对象的指针或引用
void
Test
()
{
Person p
;
Students;
Person*
p1
=
&s;
Person&
r1
=
s;
}
iii.父类对象的指针或引用 不可以 赋值给子类对象的指针或引用(除非进行类型强转)
void
Test
()
{
Person p
;
Students;
Student*
p2
=
(Student*)&p;
Student&r2
=
(Student&)
p;
}
(4)隐藏(重定义)
定义:子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中,可以使用基类 :: 基类成员访问)
注意在实际中在继承体系里面最好不要定义同名的成员
2.派生类默认成员函数
在继承关系里面,在派生类中如果没有显示定义这六个成员函数,编译系统则会默认合成这六个默认的成员函数。
#include<iostream>
using
namespace
std;
#include<string>
class
Person
{
public:
Person(const
char*name
=
" ")
:_name(name)
{
cout<<"Person(const
char*name = "
")"<<endl;
}
Person(const
Person&
p)
:_name(p._name)
{
cout<<"Person(const
Person& p)"<<endl;
}
Person&
operator=(const
Person&
p)
{
cout<<"Person&
operator=(const Person& p))"<<endl;
if(this
!=
&p)
{
_name
=
p._name;
}
return
*this;
}
~Person()
{
cout<<"~Person()"<<endl;
}
protected
:
string _name
;
//
姓名
};
class
Student
:
public
Person
{
public:
Student(const
char*name
=
" ",int
num
=
0)
:Person(name)
,_num(num)
{
cout<<"Student(const
char*name = "
",int num = 0)"<<endl;
}
Student(const
Student&
s)
:Person(s)
,_num(s._num)
{
cout<<"Student(const
Student& s))"<<endl;
}
Student&
operator=(const
Student&
s)
{
cout<<"Student&
operator=(const Student& s)"<<endl;
if(this
!=
&s)
{
Person::operator
=(s);
_num
=
s._num;
}
return
*this;
}
~Student()
{
cout<<"~Student()"<<endl;
}
void
Display()
{
cout<<"姓名:"<<_name<<endl;
cout<<"学号:"<<_num<<endl;
}
protected:
int
_num
;
//
学号
};
void
Test()
{
Student s1("noora",1);
Student s2(s1);
Student s3("Joe",2);
s2
=
s3;
s1.Display();
s2.Display();
s3.Display();
}
3.单继承&多重继承
(1). 单继承--一个子类只有一个直接父类时称这个继承关系为单继承
(2).多继承--一个子类有两个或以上直接父类时称这个继承关系为多继承
(3)菱形继承
菱形继承存在的问题:二义性 和 数据冗余
#include<iostream>
using
namespace
std;
#include<string>
class
Person
{
public:
string _name;
//姓名
};
class
Student
:
public
Person
{
protected:
int
_num;//学号
};
class
Teacher
:
public
Person
{
protected:
int
_id;//职工编号
};
class
Assistant
:
public
Student,
public
Teacher
{
protected:
string _majorCourse;//主修课程
};
void
Test()
{
Assistant a;
//显示指定访问哪个父类的成员
a.Student::_name
=
"Tom";
a.Teacher::_name
=
"Jerry";
}
int
main()
{
Test();
return
0;
}
对于子类Assistant而言, name存在两种,一个是Student类中的name ,一个是Teacher类中的name.
但是实际上只需要一个name 就可以,两个name既导致了二义性,也导致了数据冗余。
如何解决二义性和数据冗余?————虚继承
在所要继承的共同基类前加 virtual 将共同基类设置为虚基类,这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,
同一个函数名也只有一个映射。
Student,Teacher中都分别包含了一个指向Person的虚基类指针列表vbptr(virtual base table pointer)(虚基表指针),其中记录的是从Student,Teacher的vbtable的首地址(vbptr)到Person的元素之间的偏移量