文章目录
1.继承的三种方式以及继承的防止
派生类继承基类一共有三种方式,分别是public、private、protected。
采用哪种方式继承则相应的派生类将会赋予从基类继承的成员的相应的权限。
首先,每个派生类只能继承基类的共有成员(public区域的成员),但是每个继承方式有以上三种。
class Derived:public Base;//公有继承
class Derived:private Base;//私有继承
class Derived:protected Base;//保护继承
公有继承将会把从基类继承来的成员放入public区域,此时类的所有对象和有缘均可以访问,私有继承将会把继承来的成员放入private区域,此时仅有类的成员函数和友元可以访问,受保护继承将会把继承来的额成员放入到protected区域,此时仅有基类和派生类的成员函数可以访问。
继承防止关键字final
如果我们的某个类不希望被继承则我们在类声明时加上关键字final,这时任何尝试继承该类的行为都将报错。
calss NoDerived final{};
calss Derive:pubilc NoDerived;//错误
2. 基类指针或者引用指向派生类;
当基类的指针或者引用和派生类绑定时,我们可以用基类的指针或者引用访问派生类继承基类的那部分成员。
class Base
{
public:
void test_b()
{
}
};
class Derived :public Base
{
void test_d()
{
}
};
Base* b;
Derived son;
Base& b1 = son;//基类的引用绑定派生类的对象
b = &son;//基类指针指向派生类对象
b->test_b();//正确,使用Base::test_b();
b->test_d();//错误,虽然指向的是派生类的对向,但是指针类型是基类的类型
b1.test_d()//错误
如果想通过基类的引用或者指针来访问派生类特有的成员,则需要将该引用或者指针进行强制类型转换会Derived的类型,前提是基类的指针或者引用所绑定的对象是派生类的。
static_cast<Derived&>(b1).test_b();//强制类型转换后可以使用派生类特有的成员
派生类的指针或者引用不能绑定基类 !!!
派生类向基类赋值
当我们用派生类的对象给基类的对象赋值或者传参时,派生类特有的部分将会被忽略掉。
Derived d;
Base B(d);//d中特有的部分将会被忽略,仅仅拷贝继承的部分
Base B=d;//d中特有的部分将会被忽略。
3.派生类的构造与析构
构造函数用于初始化
派生类进行初始化操作时会首先初始化基类,然后按照初始化列表来初始化自己的成员。
Derived(int v1=0,int v2=0):Base(v1),num(v2){}//将参数v1传递给Base的构造函数
Derived(int v1) :num(v1) {}//将会调用Base的默认构造
虽然派生类可以直接访问基类的成员,但是我们通过派生类来初始化继承自基类的成员时我们依然应该通过公共接口来或者构造函数进行初始化,而不应该直接修改从基类继承而来的成员函数。
虚析构函数
对于继承中的拷贝控制成员,我们应该小心虚构函数对动态内存的释放问题,如果我们按照我往常的方式定义析构函数,则当我们delete一个指向派生类的对象的时候将会放生未定义行为。
Base *B=new Base;
delete B;//正确,调用Base的析构函数
B=new Derived;//基类的指针指向派生类
delete B;//错误,将会调用Base的析构,产生未定义行为。
为了避免上述的情况,我们将基类的析构函数定义为虚函数,这样当用基类的指针指向派生类并delete时也不会产生未定义行为。
class Base
{
public:
virtual ~Base();
}
Base *B=new Base;
delete B;//正确,调用Base的析构函数
B=new Derived;//基类的指针指向派生类
delete B;//正确,将会调用Derived的析构。
=delete和继承
当基类中的构造或者析构使用=delete时,对其基类将会产生较大影响。
- 基类中的默认构造、拷贝构造、拷贝赋值、析构函数如果是被删除的,则其基类中的同样也是被删除的,因为基类将无法操作基类成员。
- 如果基类中的析构函数是delete的,那么派生类的合成的默认和拷贝构造函数将会是删除的,因为基类部分成员无法被销毁。
- 基类的析构函数是删除的或者不可访问的,则派生类的移动构造函数将会是被删除的。
如果派生类想要实现相应的移动和拷贝构造操作,则应该自定义相应的函数,并且要自行完成对基类成员的操作。
继承和拷贝控制
派生的拷贝控制成员的实现和常规的有所不同,我们在拷贝和释放时既要考虑自己的成员同时还要考虑从基类继承的成员。
派生类的拷贝和移动
class D:Base{
public:
//拷贝构造,先用基类的构造函数完成基类的拷贝,
//再拷贝自己的成员
D(const D& d):Base(d)//Base(d)拷贝基类成员
{
/*处理自己的成员*/
}
//拷贝构造,先用基类的构造函数完成基类的拷贝,
//再拷贝自己的成员
D( D&& d):Base(std::move(d))//Base(std::move(d))拷贝基类成员
{
/*处理自己的成员*/
}
D & operator=(const D &rhs)
{
Base::operator=(rhs);//调用基类的赋值运算符
//为派生类赋值
//处理自赋值和释放资源
return *this;
}
}
对于派生类的拷贝和移动一定不能忽略基类的成员
4.继承中的虚函数
参考:虚函数
5.抽象基类
所谓抽象基类就是类中含有纯虚函数,抽象基类不能创建对象,只能由其派生类创建对象,且派生类必须覆盖抽象基类的纯虚函数。
纯虚函数只需要声明即可,不需要具体的实现,纯虚函数的声明方式。
test()=0;//test为纯虚函数
纯虚函数只要在函数的参数列表后面加上=0即可。
抽象基类及其例子
class test_b {//抽象基类
public:
virtual void test() = 0;//纯虚函数
};
class test_d:public test_b
{
void test()//覆盖纯虚函数
{
cout << "hello";
}
};
6.友元与继承以using的使用
不管是友元函数还是友元类都不具有继承性,哪个类声明的友元仅针对该类有效
当我们采用私有的方式继承某个类时,则继承的成员将会是私有的,即将会属于private作用域,如果我们希望某个类是public或者是protected的,我们可以使用using来进行声明
class test_b {
public:
int a;
int b;
};
class test_d:private test_b
{
public:
using test_b::a;//a将会属于public
protected:
using test_b::b;//b将会属于protected
};
7.基类和派生类中的同名函数
派生类中的成员名字和基类中的名字相同时,基类中的函数将会被隐藏,即使参数列表不同。
struct Base{
int fun();
}
struct Derived:Base
{
int fun(int);
}
Derived d;
d.fun();//错误,派生类中的fun函数由参数,基类的无参的fun函数将会被隐藏;
d.fun(1);//正确
d.Derived::fun();//正确,可以采用::来显式来访问基类的函数。
8.继承和容器
我们可以用基类的对象指向派生类的对象,但是当我们用基类的对象指向派生类的时候,我们只能访问其基类的部分,派生类特有的部分将会被切掉,因此当我们用装父类对象的容器装派生类时将会导致派生类被“切掉”,所以当我们想用容器时,我们应该用装派生类对象的指针或者引用的容器,这样我们可以用static_cast的方式将其强制转换称为派生类的对象的指针或者引用。