继承:可以使面向对象的代码进行复用,允许在保持原有的类特性之上进行扩展,增加新的功能
基类:最早被继承的类称之为基类,也叫父类。
派生类:利用继承机制,新的类可以从已有的类中派生,而这些新派生的类成为派生类。
继承格式:
class B:public A B类公有继承于A类
在继承机制中:当一个类在被继承时,不加继承权限,则系统会默认继承权限是private,当一个struct 结构体在被继承时,不加继承权限,则会被默认成public继承,
继承关系&访问限定符:
测试代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class A
{
public:
A()
:_a1(1)
,_a2(2)
,_a3(3)
{}
int _a1;
protected:
int _a2;
private:
int _a3;
};
class B:public A
{
public:
B()
:_b1(4)
,_b2(5)
,_b3(6)
{}
void Display()
{
cout<<_a1<<_a2<<_a3<<endl;
cout<<_b1<<_b2<<_b3<<endl;
}
int _b1;
protected:
int _b2;
private:
int _b3;
};
int main()
{
B b;
b.Display();
return 0;
}
程序运行结果:崩溃了,因为Display函数访问_a3时,访问出错,因为_a3是基类A中的private成员,可以被继承,但是不能被访问。
对Display函数进行修改:
void Display()
{
cout<<_a1<<_a2<<endl;
cout<<_b1<<_b2<<_b3<<endl;
}
这样就不会访问出错,导致程序崩溃,运行结果:
总结:
1.基类的private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
2.public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。
3.protected/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是 has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。私有继承以为这is-implemented-in-terms-of(是根据……实现的)。通常比组合(composition)更低级,但当一个派生类需要访问基类保护成员或需要重定义基类的虚函数时它就是合理的。
4.不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见(不能访问)。
5.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
6.在实际运用中一般使用都是public继承,极少场景下才会使用protetced/private继承.
继承体系中构造函数的调用次序:
构建一个新的派生类对象时,函数会首先调用派生类的构造函数,但是在还没有构造派生类对象的成员前,会首先构造基类的成员对象,也就是说在派生类的构造函数中调用基类的构造函数,当基类的对象构造完毕后,才会构造派生类的成员,完成一个派生类对象的构造。
继承体系中析构函数的调用顺序:
析构掉一个派生类对象时,函数首先会调用派生类的析构函数,在派生类的析构函数体内,首先析构派生类中的成员,完成后,派生类的析构函数并没有退出,而是在退出前,调用基类的析构函数,析构掉派生类对象中的基类对象,当派生类中的所有基类对象被析构完成后,派生类的析构函数退出。
测试代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class A
{
public:
A()
:_a1(1)
,_a2(2)
,_a3(3)
{
cout<<"A()"<<endl;
}
~A()
{
cout<<"`A()"<<endl;
}
int _a1;
protected:
int _a2;
private:
int _a3;
};
class C
{
public:
C()
:_c1(1)
,_c2(2)
,_c3(3)
{
cout<<"C()"<<endl;
}
~C()
{
cout<<"`C()"<<endl;
}
int _c1;
protected:
int _c2;
private:
int _c3;
};
class B:public A,public C
{
public:
B()
:_b1(4)
,_b2(5)
,_b3(6)
{
cout<<"B()"<<endl;
}
~B()
{
cout<<"`B()"<<endl;
}
int _b1;
protected:
int _b2;
private:
int _b3;
};
int main()
{
B b;
return 0;
}
测试结果:
继承与转换–赋值兼容规则–public继承:
- 子类对象可以赋值给父类对象(切割/切片)
- 父类对象不能赋值给子类对象
- 父类的指针/引用可以指向子类对象
- 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)
继承关系中:友元函数是不能被继承的,但是基类中的静态成员可以被继承,而且不管被继承多少次,派生类中也是只有这一个静态成员
单继承:一个派生类只继承一个基类。
例:class Derived : public Base
对象模型:
测试代码:
#include <iostream>
using namespace std;
class Base
{
public:
Base()
:_b(0)
{}
int _b;
};
class Derived : public Base
{
public:
Derived()
:_d(0)
{}
int _d;
};
int main()
{
Derived d;
d._b = 1;
d._d = 2;
return 0;
}
内存显示:
多继承:一个派生类继承于多个基类。
例:class Derived : public Base1, public Base2
对象模型:
测试代码:
#include <iostream>
using namespace std;
class Base1
{
public:
Base1()
:_b1(0)
{}
int _b1;
};
class Base2
{
public:
Base2()
:_b2(0)
{}
int _b2;
};
class Derived : public Base1,public Base2
{
public:
Derived()
:_d(0)
{}
int _d;
};
int main()
{
Derived d;
d._b1 = 1;
d._b2 = 2;
d._d = 3;
return 0;
}
内存显示:
菱形继承:也称“砖石继承”。
例:
class c1:public Base
class c2:public Base
class d:public c1,public c2
对象模型:
测试代码:
#include <iostream>
using namespace std;
class A
{
public:
A()
:_a(0)
{
}
int _a;
};
class C1:public A
{
public:
C1()
:_c1(0)
{}
int _c1;
};
class C2:public A
{
public:
C2()
:_c2(0)
{}
int _c2;
};
class Derived : public C1,public C2
{
public:
Derived()
:_d(0)
{}
int _d;
};
int main()
{
Derived d;
d._c1 = 1;
d._c2 = 2;
d._d = 3;
d.C1::_a = 4;
d.C2::_a = 5;
return 0;
}
内存显示:
从代码中可以看出,对基类对象成员的使用中,必须加上作用域解析符,因为在内存中基类的对象成员出现了两次,分别在两个不同的类的对象成员中,在不加作用域解析符的情况下,编译器不知道该调用哪个_a,导致程序崩溃。
从内存显示的结果可以看到,菱形继承存在二义性,就是基类对象的成员,在最后的派生类对象中出现了两次,而且还可以赋不同的值,在对派生类对象的使用中会出现错误,后面的虚拟菱形继承会解决掉这个问题。
虚拟继承:
例:class Derived : virtual public Base
对象模型:
测试代码:
#include <iostream>
using namespace std;
class A
{
public:
A()
:_a(0)
{}
int _a;
};
class D:virtual public A
{
public:
D()
:_d(0)
{}
int _d;
};
int main ()
{
D d;
d._a = 1;
d._d = 2;
return 0;
}
内存显示:
菱形虚拟继承:
例:
class A
class C1 : virtual public A
class C2 : virtual public A
class D : public C1,public C2
对象模型:
测试代码:
#include <iostream>
using namespace std;
class A
{
public:
A()
:_a(0)
{}
int _a;
};
class C1:virtual public A
{
public:
C1()
:_c1(0)
{}
int _c1;
};
class C2:virtual public A
{
public:
C2()
:_c2(0)
{}
int _c2;
};
class D:public C1,public C2
{
public:
D()
:_d(0)
{}
int _d;
};
int main()
{
D d;
d._a = 1;
d._c1 = 2;
d._c2 = 3;
d._d = 4;
return 0;
}
内存显示:
从内存显示中可以看出,菱形虚拟继承,对于基类对象成员,只有一份,这样就不会出现菱形继承时所导致的二义性问题。
同名隐藏:
如果子类和父类中出现的了同名的成员函数,那么子类的成员函数会将父类中的成员函数给屏蔽掉,当调用该同名成员函数时,只会调用子类中的成员函数。