C++作为一门OOP语言,必定有面向对象语言的三大特性:封装,继承,多态.
继承作为面向对象语言代码复用的重要手段,继承允许我们在原有类的基础上进行扩展,增加功能。
继承是类层次上的复用,呈现面向对象语言的层次结构设计。
继承的权限问题:
- public继承:直接继承基类中的public,protected成员为派生类的public,protected成员
- protecedt继承:继承基类中的public,protected成员为派生类的protected成员
- private继承:继承基类中的public,protected成员为派生类的private成员
注意:
基类中的private成员无论何种继承在派生类中都不可见。
可见protected关键字因继承而产生
#include<string.h>
#include<iostream>
using namespace std;
class Person{
public:
Person()
:_name("Bob")
,_age(18)
{}
string _name;
protected:
int _age;
};
class Student:protected Person{
public:
Student()
:_id(1)
{
cout<<_name<<endl;
cout<<_age<<endl;
cout<<_id<<endl;
};
private:
int _id;
};
int main()
{
Student s;
return 0;
}
基类和派生类之间的赋值转换
- 派生类对象可以赋值给基类对象/指针/引用,称为切片操作。
- 基类对象不能直接赋值给派生类对象。
- 基类对象向派生类对象转换可以使用dynamic_cast<>进行安全转换。
继承中的作用域
- 继承中基类和派生类有独立的作用域
- 基类与派生类有同名成员,构成隐藏。派生类屏蔽对基类同名成员的直接访问,这种情况称为重定义。
- 使用中,如非设计,尽量不要构成隐藏,易造成概念的混乱
#include<iostream>
#include<string.h>
using namespace std;
class Person{
public:
Person(string& name, int age)
:_name(name)
,_age(age)
{}
void show()
{
cout<<"Person::show():"<<_name<<": "<<_age<<endl;
}
string _name;
int _age;
};
class Student: public Person{
public:
Student(string name = "space", int age = 18, int id = 1)
:Person(name,age)
,_id(id)
{}
void show()
{
cout<<"Student::show():"<<_name<<": "<<_age<<" "<<_id<<endl;
}
int _id;
};
int main()
{
Student s;
s.Person::show();
s.show();
return 0;
}
派生类中的默认成员函数
- 派生类的构造函数必须调用基类的构造函数初始化基类部分的成员。
- 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
- 派生类的operator=必须调用基类的operator=完成基类的复制
- 派生类的析构函数调用完成后,会自动调用基类的析构函数清理基类成员。保证基类成员的清除。
- 派生类对象初始化时,先调用基类的构造函数再调用派生类的构造函数
- 派生类对象析构时,清理先调用派生类析构再调用基类析构
#include<string.h>
#include<iostream>
using namespace std;
class Person{
public:
Person(const string& name)
:_name(name)
{
cout<<"Person(){}~~~"<<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 string& name, int id)
:Person(name)
,_id(id)
{
cout<<"Student(){}~~~"<<endl;
}
Student(const Student& s)
:Person(s)
,_id(s._id)
{
cout<<"Student(const Student& s){}~~~"<<endl;
}
Student& operator==(const Student& s)
{
cout<<"Student& operator==(const Student& s){}~~~"<<endl;
if(this != &s)
{
Person::operator=(s);
_id = s._id;
}
return *this;
}
~Student()
{
cout<<"~Student(){}~~~"<<endl;
}
protected:
int _id;
};
int main()
{
Student s("Bob",18);
Student s2(s);
cout<<endl;
Student s3("Mary",17);
s3 = s;
return 0;
}
继承与友元
友元关系不能继承
static成员与继承
若基类定义了static成员,整个继承体系中只有一个这样的static成员。
菱形继承与菱形虚拟继承
C++支持多继承关系,而菱形继承是多继承的一种特殊情况。
多继承:一个子类有两个以上的直接父类称为多继承
继承关系如图:
菱形继承会导致基类成员的数据二义性,及数据冗余问题。
解决如上问题使用虚拟继承。
虚拟继承使用虚基表解决,每个直接父类对象(student,teacher)的第一个成员为虚基表指针,虚基表指针指向虚基表,虚基表中存储着从当前位置到公共父类成员(person)的偏移量,通过偏移量可以找到person的成员(只有一份)。解决数据二义性及数据冗余问题。
继承和组合:
C++中语法复杂,体现在支持多继承,多继承中会出现很多问题如菱形继承等,实际使用中最好不要使用多继承。
继承组合:
public继承是一种is-a 关系,每个派生类对象都是一个基类对象。
组合关系是一种 has-a 关系,假设B组合了A,那么每个B对象中都有一个A对象。
优先使用对象组合,而不是类继承
继承允许派生类使用基类的构造来初始化,一定程度上破坏了封装,派生类和基类之间耦合度高,依赖关系太强(白箱复用)。
对象组合是类继承以外的复用选择,新的复杂功能可以通过组装或组合对象来实现。要求有良好的接口(黑箱复用)
实际使用多使用对象组合,耦合度低,代码维护性好 。