◆继承:
★继承概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。
继承定义格式
★继承关系&访问限定符
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原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。
- protetced/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是 has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。
- 不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见(不能访问)。
- 使用关键字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;
};
- 继承体系中的作用域
- 在继承体系中基类和派生类是两个不同作用域。
- 子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中,可以使用 基类::基类成员 访问)--隐藏 --重定义
- 注意在实际中在继承体系里面最好不要定义同名的成员。
class Person
{
public:
Person( const char * name = "" , int id = 0)
: _name(name ), _num( id)
{}
protected:
string _name; // 姓名
int _num; // 身份证号
};
class Student: public Person
{
public :
Student(const char * name, int id, int stuNum)
: Person(name , id ), _num(stuNum )
{}
void DisplayNum()
{
cout<<" 身份证号: "<<Person :: _num<< endl ;
cout<<" 学号"<< _num << endl ;
}
protected :
int _num ; // 学号
};
★继承与转换--赋值兼容规则--public继承
- 子类对象可以赋值给父类对象(切割/切片)
- 父类对象不能赋值给子类对象
- 父类的指针/引用可以指向子类对象
- 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)
★友元与继承
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
class Person
{
friend void Display(Person &p , Student&s);
protected :
string _name ; // 姓名
};
class Student: public Person
{
protected :
int _stuNum ; // 学号
};
void Display(Person &p , Student &s)
{
cout<<p._name<<endl;
cout<<s._name<<endl;
cout<<s._stuNum<<endl;
}
void TestPerson1()
{
Person p;
Student s;
Display (p, s);
}
★继承与静态成员
基类定义了static成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。
class Person
{
public :
Person(){++ _count;}
protected :
string _name ; // 姓名
public :
static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person
{
protected :
int _stuNum ; // 学号
};
class Graduate :public Student
{
protected:
string _seminarCourse; // 研究科目
};
void TestPerson1()
{
Student s1;
Student s2;
Student s3;
Graduate s4;
cout<<"人数:"<<Person::_count<<endl;
Student ::_count = 0;
cout<<"人数:"<<Person::_count<<endl;
}
★单继承&多继承&菱形继承
【单继承】
一个子类只有一个直接父类时称这个继承关系为单继承。
【多继承】
一个子类有两个或以上直接父类时称这个继承关系为多继承
【菱形继承】
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 = "xxx";
a.Teacher ::_name = "yyy";
}
- 虚继承--解决菱形继承的二义性和数据冗余的问题
- 虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余&浪费空间的问题。
- 虚继承体系看起来好复杂,在实际应用我们通常不会定义如此复杂的继承体系。一般不到万不得已都不要定义菱形结构的虚继承体系结构,因为使用虚继承解决数据冗余问题也带来了性能上的损耗。
(继承及其访问限定符)&&(派生类及其默认成员函数)&&(赋值兼容规则)