《C++ Primer》读书笔记第十五章-3-构造函数和拷贝控制、容器与继承

笔记会持续更新,有错误的地方欢迎指正,谢谢!

构造函数和拷贝控制

虚析构函数

继承体系中的析构函数应该定义为虚函数。
以前说过,定义了析构函数就要定义拷贝和赋值,但这里基类的析构函数是例外。

合成拷贝控制与继承

派生类中定义为删除的拷贝控制与基类的关系

class B
{
public:
    B(); //默认构造函数声明
    B(const B&) = delete; //定义为删除的拷贝构造函数
    //既然定义了拷贝构造函数,就不会合成移动操作了
};
class D : public B
{
    //没有声明任何东西,只是单纯继承了B
};
D d; //正确:D的合成默认构造函数调用B的默认构造函数,自己反正也没成员
D d2(d); //错误:因为B的拷贝构造函数是delete的,所以D的也是delete,无法被调用

移动操作与继承

移动操作:基类多有虚析构函数,所以不会合成移动操作,如果确实需要,应首先在基类中显式定义,此时要同时显式定义拷贝操作:

class Quote
{
public:
    Quote() = default; //强行合成默认构造函数

    Quote(const Quote&) = default; //拷贝构造函数
    Quote(Quote&&) = default; //移动构造函数
    Quote& operator=(const Quote&) = default; //拷贝赋值
    Quote& operator=(Quote&&) = default; //移动赋值

    virtual ~Quote() = default; //虚析构函数
};

如此,Quote的派生类也会自动获得合成的移动操作。

派生类的拷贝控制成员

派生类的拷贝和移动构造函数在拷贝和移动自有成员的同时,也要拷贝和移动基类部分,赋值运算符也类似。

定义派生类的拷贝或移动构造函数
class Base
{

};
class D : public Base
{
//我们要拷贝或移动基类部分,就必须在派生类的构造函数初始值列表中显式调用
public:
    D(const D& d) : Base(d) {} //调用基类的拷贝构造函数来拷贝基类部分
    //这儿比较特殊的是Base(d)会去匹配Base的拷贝构造函数,虽然人家其实接受B类型

    D(D&& d) : Base(std::move(d)){}
};
派生类赋值运算符

也就是拷贝赋值和移动赋值。

D &D::operator=(const D &rhs)
{
    Base::operator=(rhs); //显式地为基类部分赋值:(合成的或自定义的)基类的拷贝赋值运算符将释
    //放掉左侧对象的基类部分的旧值,然后利用rhs为其赋一个新值
    //接下来为派生类自己的部分赋值(省略)
    return *this;
}
派生类析构函数

特简单,不用管,跟普通的以前的一样。

注意:
对象销毁的顺序与创建顺序相反:派生类析构函数先执行,然后执行基类的析构函数。

也就是,对直接基类部分拷贝后,再拷贝类本身的成员;销毁本身后再销毁基类,基类部分会自动销毁。

禁止在构造或析构函数中调用虚函数

假设我们在基类的构造函数中调用派生类的某个函数去访问其成员,这会派生类对象还没构建完成,怎么能访问呢?于是,C++禁止了这种行为。

继承的构造函数

  1. 派生类类不能继承基类的默认构造函数、拷贝构造函数、移动构造函数;
  2. 但派生类能够重用其基类自定义的构造函数;
  3. 派生类中的基类成员调用基类的构造函数初始化,派生类中的其他成员将被默认初始化。
class Bulk_quote : public Disc_quote
{
public:
    using Disc_quote::Disc_quote; //使用using继承Disc_quote的构造函数
};

把using放到构造函数上时,using声明语句将令编译器产生代码,而且,编译器会生成一个与基类对应的派生类构造函数,针对上面的例子生成的派生类构造函数如下:

Bulk_quote(const string& book, double price, size_t qty, double disc) : Disc_quote(book, price, qty, disc) {}
继承的构造函数的特点
  1. 构造函数的using声明不会改变该构造函数的访问级别
  2. 如果基类的构造函数是explicit或constexpr的,则继承的构造函数也是
  3. 当一个基类构造函数含有默认实参,派生类将获得多个继承的构造函数,其中每个构造函数分别忽略掉一个含有默认实参的形参。。

另外,如果派生类自己又定义了一个和基类构造函数具有相同参数列表的构造函数,那么基类的这个构造函数不会被继承,就用派生类自己定义的就好了,覆盖了呗~

容器与继承

容器与继承

我们不能把具有继承关系的多种类型的对象直接存放在容器中:
错误的例子:

vector<Quote> basket;
basket.push_back(Bulk_quote("a", 50, 10, 0.25));

basket的元素是Quote对象,因此当我们向其中添加Bulk_quote对象时,属于派生类的部分会被忽略,那我们应该怎么做呢?

答:容器中应放置指针(最好是智能指针)而非对象。

例子:

vector<shared_ptr<Quote>> basket;
basket.push_back(make_shared<Quote>("a", 50));
basket.push_back(make_shared<Quote>("b", 50, 10, 0.25));
cout << basket.back()->net_price(15) << endl; //打印折扣后的价格,这回派生类对象是完整的

继承与组合

派生类应反映与基类的Is A关系,公有派生类的对象应该可以用在任何需要基类对象的地方;类之间Has A则是包含成员的意思。

编写Basket类

对于C++面向对象的编程来说,并不是直接用对象,用的是指针和引用。

由于博主时间原因,具体实现我就不写了,请见书~

文本查询程序再探

由于博主时间原因,具体实现我就不写了,请见书~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值