目录
目录
继承的概念和定义
继承的概念:
继承是一种使对象复用的有效手段,被继承的类叫做父类(基类),继承类的类叫做子类(派生类)
继承的定义:
继承的格式:
继承的关系:
1、继承方式和访问限定符都有三种权限
2、假如访问权限和继承方式结合,取权限小的访问方式
3、private中的不可见不是说不会被继承,是被继承下来我们不能够去使用它
4、假如我们想让外界访问不了基类成员,又想让派生类访问,那么就可以使用protect访问限定符修饰成员
5、假如我们不写继承方式,class创建出来的类默然是private,struct创建出来的类默然是public,但还是推荐手动加上继承方式
6、一般而言使用public继承方式,因为其他继承方式的可维护性低
基类和派生类的赋值转换(切片)
派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用,但是基类不可以赋值给派生类,这种方式就叫做切片
person p = s;
person* ptr = &s;
person& ref = s;
//都叫切片
继承中的作用域
1、基类和子类都拥有自己独立的作用域
2、当基类和派生类的成员变量同名时候,就会构成隐藏(也叫重定义),这个时候我们需要使用父类::成员名来进行访问,成员函数同名,也会构成隐藏
3、继承中尽量不要定义同名的成员
继承中的默认成员函数
构造函数:
1、假如不写自己成员,和类对象一样(内置类型值拷贝,自定义类型调用它的拷贝构造)
2、继承父类的成员,必须使用父类的构造函数初始化
3、父类成员不能够使用初始化列表初始化,需要用父类对象隐式传参初始化
class A
{
public:
A(int x = 0)
:_a(x)
{}
int _a;
};
class B : public A
{
public:
B()
:A(2) //显示调用父类对象初始化
,_b(0)
{}
int _b;
};
拷贝构造:
1、假如不写自己成员,和类对象一样(内置类型值拷贝,自定义类型调用它的拷贝构造)
2、继承父类的成员,必须使用父类的构造函数初始化
class A
{
public:
//构造函数
A(int x = 0)
:_a(x)
{}
//拷贝构造
A(A& a)
:_a(a._a)
{}
int _a;
};
class B : public A
{
public:
//构造函数
B(int x = 0)
:A(x) //显示调用父类对象初始化
,_b(0)
{}
//拷贝构造
B(B& b)
:A(b) //显示调用父类对象初始化,进行切片
, _b(b._b)
{}
int _b;
};
赋值运算符重载:
1、假如不写自己成员,和类对象一样(内置类型值拷贝,自定义类型调用它的拷贝构造)
2、显示调用父类的 父类::operator=,需要指定::类域
class A
{
public:
//构造函数
A(int x = 0)
:_a(x)
{}
//拷贝构造
A(const A& a)
:_a(a._a)
{}
//赋值运算符重载
A& operator=(const A& a)
{
_a = a._a;
return *this;
}
int _a;
};
class B : public A
{
public:
//构造函数
B(int x = 0)
:A(x) //显示调用父类对象初始化
,_b(0)
{}
//拷贝构造
B(const B& b)
:A(b) //显示调用父类对象初始化,进行切片
, _b(b._b)
{}
//赋值运算符重载
B& operator=(const B& b)
{
A::operator=(b); //显示调用父类的operator=,需要用::指定类域
_b = b._b;
return *this;
}
int _b;
};
析构函数:
1、子类的析构函数和父类的析构构成隐藏,由于后面多态的需要,析构函数会统一被处理成destructor(),需要指定调用
2、不需要显示调用父类析构函数,会自动调用,保证顺序
class A
{
public:
//构造函数
A(int x = 0)
:_a(x)
{}
//拷贝构造
A(const A& a)
:_a(a._a)
{}
//赋值运算符重载
A& operator=(const A& a)
{
_a = a._a;
return *this;
}
~A()
{
cout << "~A" << endl;
}
int _a;
};
class B : public A
{
public:
//构造函数
B(int x = 0)
:A(x) //显示调用父类对象初始化
, _b(0)
{}
//拷贝构造
B(const B& b)
:A(b) //显示调用父类对象初始化,进行切片
, _b(b._b)
{}
//赋值运算符重载
B& operator=(const B& b)
{
A::operator=(b); //显示调用父类的operator=,需要用::指定类域
_b = b._b;
return *this;
}
~B()
{
cout << "~B" << endl;
//析构函数不能构成重载,因为编译器会自动变成destroctor,为了保持子类先析构的顺序,所有不需要我们自己调用父类的析构函数
}
int _b;
};
不想让类继承
方式一:将类的构造函数设置为私有
子类不能调用父类构造函数初始化来实例化对象,所以不能继承
缺点:我们自己也不能够实例化出对象
class A
{
private:
//构造函数
A(int x = 0)
:_a(x)
{}
int _a;
};
方式二:c++11
类名后面添加final,就不能够被继承了
class A final
{
public:
//构造函数
A(int x = 0)
:_a(x)
{}
int _a;
};
继承和友元的关系
派生类不会继承基类的友元关系
具体意思是基类中的友元函数不能使用派生类中的成员
继承和静态成员的关系
对于静态成员,如果被继承下来,那么整个继承类中都只会有这一份静态成员
继承中的菱形继承问题
class A
{};
class B:public A
{};
class C:public A
{};
class D:public B, public C
{};
假如D继承了B和C,B和C又同时继承了A,这个时候就会出现菱形继承情况
多继承会产生两个情况
1、二义性,不是到使用的是B继承下来的A的成员还是C继承下来的A的成员(这个时候需要类名::成员来指定使用的是哪个继承下来的变量)
2、数据冗余,存有两份A
继承里的虚继承
为了解决多继承中菱形继承的问题,c++创建了一种虚继承的方式来解决这种问题
class A
{};
class B:virtual public A
{};
class C:virtual public A
{};
class D:public B, public C
{};
在:后面腰部位置后面加上virtual关键字,来进行多继承,这样就只会创建一份A的成员了,在同时继承的两个成员旁边进行虚继承
继承和组合
在写两个类之间的关系的时候,尽量多使用组合,而不是继承
继承:
class Car
{};
class Baoma:public Car //继承
{};
组合:组合就是在一个类中声明另一个类的对象
class Baoma:public Car
{};
class Person
{
public:
Baoma b; //组合就是在一个类中声明另一个类的对象
};
继承会破坏类的封装性,因为public继承可以访问protect成员,组合不会,还是只能访问public的成员
假如两个类是is-a的关系,还是可以使用继承
class Car
{};
class Baoma:public Car //is-a的关系,所以可以用继承,Baoma是Car
{};
假如两个类是has-a的关系,更推荐使用组合
class Baoma
{};
class Person //has-a的关系,可以用组合,Person有一辆宝马
{
public:
Baoma b;
};