继承
1.概念:
继承是面向对象的程序设计使代码复用的重要手段,在保持原有类特性的基础上进行拓展。
2.继承的定义格式:
class 派生类(或子类) : 继承方式(public,protected,private) 基类(或父类)
class A
{
};
//继承
class B: public A
{
};
3.继承中父类成员的访问方式
- 父类private成员在子类中都是不可见,即父类的私有成员虽然继承到了子类中,但是在子类中不能访问;
- 父类的其他成员在子类的访问方式 等于 成员在父类的访问限定符和继承方式取最小的结果,public > protected > private
- class的默认继承方式是private,struct的默认继承方式是public;
- 一般使用public继承方式,把基类中的私有成员定为protected访问。
4.切片
- 子类可以给父类赋值(对象,指针,引用)
- 父类对象不能赋给子类对象,父类指针或引用可以赋给子类指针或者引用,但是存在安全问题。
5.继承中的作用域
- 在继承体系中,子类和父类有独立的作用域
- 隐藏/重定义:子类和父类中有同名成员时,子类成员将屏蔽对父类同名成员的直接访问
- 成员函数的隐藏,只需要函数名相同就构成隐藏
6.派生类的默认成员函数
1.构造函数:
子类的构造函数必须调用父类的构造函数初始化父类中的成员。
2.拷贝构造:
派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
(1)若子类中未显式定义拷贝构造,自动调用父类的拷贝构造。
(2)若子类中显式定义拷贝构造,则编译器会自动调用父类的构造函数。
(3)子类中显示调用父类的拷贝构造会发生切片。
3.赋值运算符
派生类的operator=必须要调用基类的operator=完成基类的赋值。
(1)子类中未显式定义赋值运算符,则调用父类的赋值运算符。
(2)子类中显式定义赋值运算符,则调用子类的赋值运算符。
(3)子类中显式调用父类的赋值运算符,则会先调用子类,在调用父类的赋值运算符。
4.析构函数
(1)派生类的析构函数会先调用子类的析构,再调用父类析构。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
(2)父类的析构函数不用在子类中显式调用。
class Person
{
public:
//构造函数
Person(const char* name="Mark")
:_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 char* name="Peter",const int id=8)
:Person(name)
, _id(id)
{
cout << "Student()" << endl;
}
//拷贝构造函数
Student(const Student& st)
:Person(st)
{
_id = st._id;
cout << "Student(const Student& st)" << endl;
}
//赋值运算符重载
Student& operator=(const Student& s)
{
if (this != &s)
{
Person::operator=(s);
_id = s._id;
}
cout << "Student& operator=(const Student& s)" << endl;
return *this;
}
//析构函数
~Student()
{
cout << "~Student()" << endl;
}
private:
int _id;
};
int main()
{
Student s;
Student s2(s);
Student s1;
s1 = s;
return 0;
}
7.不能被继承的类
(1)将构造函数私有
//不能被继承的类
class FinClass
{
private:
FinClass(){}
};
(2)定义类时在类名后面加final
//不能被继承的类
class Fin final
{
};
8.菱形继承
- 至少含有四个类,具有数据冗余和二义性的缺点
- 菱形虚拟继承可以解决菱形继承的二义性和数据冗余的问题。 在菱形继承的直接父类前加virtual
9.继承和组合
- 继承是is的关系,继承一定程度破坏了基类的封装,基类的改变,对派生类类有很大的影响。派生类和基类间的依 赖关系很强,耦合度高。
- 组合是has的关系,组合类之间没有很强的依赖关系,耦合度低,代码维护性好。
- 尽量使用组合