继承中的类作用域
一个对象、引用或指针的静态类型决定了该对象的哪些成员是可见的。即使动态类型与静态类型不一致(基类的引用和指针绑定到派生类对象上),但我们使用哪些成员依然由静态类型决定。
举个例子:
给Bulk_quote增加一个成员test,Quote中并没有test成员。
class Bulk_quote : public Disc_quote
{
public:
Bulk_quote() = default;
Bulk_quote(const string& s, double prc, size_t min, double disCount):
Disc_quote(s,prc,disCount,min){}
double net_price(size_t n) const override;
void debug() const override;
void test()
{
cout << "lalala";
}
};
int main()
{
Bulk_quote b("cc", 100, 10, 0.1);
Quote& test = b;
test.test();
b.test();
}
你会发现名为test的Quote对象是无法调用test成员的。因为test是静态类型,b是动态类型。静态类型决定了能访问哪些成员。
派生类也能重用定义在其直接基类或间接基类中的名字。此时,定义在派生类的名字将隐藏定义在基类的名字。
。
一个小插曲:
C++中,名字查找发生在类型查找之前。一旦在作用域中找到了所需的名字,编译器就会忽略掉外层作用域中的同名实体。 说白了就是懒。
如果我们在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体。
在不同作用域中,无法重载函数名。
同样,定义派生类中的函数也不会重载其基类中的成员。也就是如果派生类成员和基类成员同名,则派生类将在其作用域内隐藏该基类成员。可以通过类作用域运算符来使用被覆盖的对象。
虚函数与作用域
上图也是为什么基类和派生类中,虚函数形参列表必须相同。如果不相同,则派生类就不是覆盖基类的虚函数,而是将其隐藏了。
函数调用解析过程:
以p->men()为例:
重要
- 首先确定p的静态类型
- 到p的静态类型对应的类中查找men,如果找不到,则去基类查找,如果还找不到则报错。
- 找到后进行类型检查(看参数是否匹配)
- 如果合法,则编译器根据调用是否为虚函数产生不同的代码。
构造函数与拷贝控制
。
虚析构函数
。
基类通常应该定义虚析构函数,这样我们就能动态分配继承体系中的对象了。
为什么基类要定义虚析构函数?
想象一种情况,当我们delete一个动态分配的对象的指针时,会调用析构函数。
如果我们delete的指针指向继承体系中的某个类型,那就有可能出现静态类型和动态类型不一致的情况。
例如我们delete一个Quote*的指针,而这个指针实际指向的是一个Bulk_quote的对象,那我们应该确保编译器执行Bulk_quote的析构函数。因此,通过在基类中将析构函数定义成虚函数以确保执行正确的析构函数版本。
基类的析构函数为虚函数,则派生类的析构函数都是虚函数,从而可以进行动态绑定。
一个基类总是需要析构函数,而且要将其定义为虚析构函数,但它不一定需要拷贝和赋值操作。
如果一个类定义了析构函数,即使是使用合成版本,编译器也不会为这个类合成移动操作。
。
合成拷贝控制与继承
。
在继承体系中,一个类合成的拷贝控制不仅要完成自身的初始化、赋值或销毁,还要负责其直接基类的初始化、赋值或销毁。
例如构造函数:
合成的Bulk_quote默认构造函数运行Disc_quote的默认构造函数,后者又运行Quote的默认构造函数。
顺序是:Quote构造函数->Disc_quote构造函数->Bulk_quote构造函数
合成的拷贝构造函数也是这个道理。但是值得注意,是合成版本还是自定义版本没有太大影响。唯一的要求是相应成员何以访问。
析构函数也是如此:对派生类的析构函数来说,它除了销毁派生类自己的成员,还要负责销毁基类的成员。
。
移动操作与继承
。
大多数基类都会定义一个虚析构函数。因此,在默认情况下,基类通常不含有合成的移动操作,而且在它的派生类中,也没有合成的移动操作。 当我们确实需要移动操作时,应首先在基类中定义。即使使用合成版本也要显式的定义出来。这样派生类就会自动获得移动操作。
。
派生类的拷贝控制成员
。
根据上面所写的,一个类不仅要完成自身的初始化,赋值和销毁,还要负责其基类的初始化,赋值和销毁。
因此拷贝和移动构造函数,在拷贝移动自有成员的同时,也要拷贝移动基类成员。赋值运算符也是一样。但析构函数只负责销毁派生类自己分配的资源。
。
定义派生类的拷贝或移动构造函数
。
当为派生类定义拷贝或移动构造函数时,我们通常使用对应的基类构造函数初始化对象的基类部分。
class Base {\*... *\}
class D : public Base
{
public:
//默认情况下,基类的默认构造函数初始化基类的部分
//要想使用拷贝或移动构造函数,必须在初始化列表中显式调用
D(const D& d):Base(d)
{
\*...*\
}
D(D&& d):Base(std::move(d))
{
\*...*\
}
}
Base(d)一般会匹配Base的拷贝构造函数。
。
定义派生类的拷贝赋值运算符
。
与拷贝和移动构造函数一样,派生类的赋值运算符也必须显式的为其基类部分赋值。
D& D::operator=(const D& rhs)
{
Base::operator=(rhs);//为基类部分赋值
//按照过去的方式为派生类成员赋值
return *this;
}
值得注意的是,无论基类的构造函数或赋值运算符是自定义版本还是合成版本,派生类的对应操作都能使用它们。
。
派生类析构函数
。
在析构函数体执行完成后,对象的成员会被隐式销毁。类似的,对象的基类部分也是隐式销毁的。
派生类析构函数只负责销毁由派生类自己分配的资源。
class D:public Base
{
public:
//Base::~Base()被自动调用执行
~D{/*清理派生类成员的操作*/}
}
练习题的一个例子:构造Bulk_quote的拷贝控制操作。
Bulk_quote.h
#pragma once
#include "Quote.h"
#include"Disc_quote.h"
using namespace std;
class Bulk_quote : public Disc_quote
{
public:
Bulk_quote() = default;
Bulk_quote(const string& s, double prc, size_t min, double disCount):
Disc_quote(s,prc,disCount,min){}
double net_price(size_t n) const override;
void debug() const override;
Bulk_quote(const Bulk_quote& bq) :Disc_quote(bq)
{
cout << "hello i am 拷贝构造函数Bulk_quote" << endl;
}
Bulk_quote& operator=(const Bulk_quote& bq)
{
cout << "hello i am 拷贝赋值运算符Bulk_quote" << endl;
Disc_quote::operator=(bq);
return *this;
}
Bulk_quote(Bulk_quote&& bq) :Disc_quote(std::move(bq))
{
cout << "hello i am 移动构造函数Bulk_quote" << endl;
}
Bulk_quote& operator=(Bulk_quote&& bq)
{
cout << "hello i am 移动赋值运算符Bulk_quote" << endl;
Disc_quote::operator=(std::move(bq));
return *this;
}
};
Bulk_quote.cpp
#include "Bulk_quote.h"
double Bulk_quote::net_price(size_t n) const
{
if (n >= quantity)
{
return n * (1 - discount) * price;
}
else
{
return n * price;
}
}
void Bulk_quote::debug() const
{
cout << "i am bulk_quote:" << quantity << " " << discount << endl;
}
Disc_quote.h
#pragma once
#include "Quote.h"
using namespace std;
class Disc_quote : public Quote
{
public:
Disc_quote() = default;
Disc_quote(const string& s, double sales_price, double dis, size_t qua) :
Quote(s, sales_price), discount(dis), quantity(qua){}
virtual double net_price(size_t n) const = 0;
virtual void debug() const = 0;
Disc_quote(const Disc_quote&);
Disc_quote& operator=(const Disc_quote&);
Disc_quote(Disc_quote&&);
Disc_quote& operator=(Disc_quote&&);
protected:
double discount = 0.1;
size_t quantity = 100;
};
Disc_quote.cpp
#include "Disc_quote.h"
Disc_quote::Disc_quote(const Disc_quote& dq) :Quote(dq)
{
cout << "hello i am 拷贝构造函数Disc_quote" << endl;
discount = dq.discount;
quantity = dq.quantity;
}
Disc_quote& Disc_quote::operator=(const Disc_quote& dq)
{
cout << "hello i am 拷贝赋值运算符Disc_quote" << endl;
if (this != &dq)
{
Quote::operator=(dq);
discount = dq.discount;
quantity = dq.quantity;
}
return *this;
}
Disc_quote::Disc_quote(Disc_quote&& dq):Quote(std::move(dq))
{
cout << "hello i am 移动构造函数Disc_quote" << endl;
discount = std::move(dq.discount);
quantity = std::move(dq.quantity);
}
Disc_quote& Disc_quote::operator=(Disc_quote&& dq)
{
cout << "hello i am 移动赋值运算符Disc_quote" << endl;
if (this != &dq)
{
Quote::operator=(std::move(dq));
discount = dq.discount;
quantity = dq.quantity;
}
return *this;
}
Quote.h
#pragma once
#include<iostream>
#include<string>
using namespace std;
class Quote
{
public:
Quote() = default;
Quote(const string& s,double sales_price):bookNo(s),price(sales_price){}
Quote(const Quote&);//拷贝构造函数
Quote& operator=(const Quote&);//拷贝赋值运算符
Quote(Quote&&);//移动构造函数
Quote& operator=(Quote&&);//移动赋值运算
string isbn() const;
virtual double net_price(size_t n) const;//虚函数
virtual void debug() const;
virtual ~Quote() {}//对析构函数进行动态绑定
private:
string bookNo;
protected:
double price = 0.0;
};
ostream& print_total(ostream& os, const Quote& item, size_t n);
Quote.cpp
#include "Quote.h"
ostream& print_total(ostream& os, const Quote& item, size_t n)
{
double ret = item.net_price(n);
os << "ISBN:" << item.isbn()
<< " #sold: " << n << " total due: " << ret << endl;
item.debug();
return os;
}
string Quote::isbn() const
{
return bookNo;
}
double Quote::net_price(size_t n) const
{
return n * price;
}
void Quote::debug() const
{
cout << "i am quote:" << bookNo << " " << price << endl;
}
Quote::Quote(const Quote& q)
{
cout << "hello i am 拷贝构造函数Quote" << endl;
bookNo = q.bookNo;
price = q.price;
}
Quote& Quote::operator=(const Quote& q)
{
cout << "hello i am 拷贝赋值运算符Quote" << endl;
if (this != &q)
{
bookNo = q.bookNo;
price = q.price;
}
return *this;
}
Quote::Quote(Quote&& q)
{
cout << "hello i am 移动构造函数Quote" << endl;
bookNo = std::move(q.bookNo);
price = std::move(q.price);
q.bookNo = "";
q.price = 0;
}
Quote& Quote::operator=(Quote&& q)
{
cout << "hello i am 移动赋值运算符Quote" << endl;
if (this != &q)
{
bookNo = std::move(q.bookNo);
price = std::move(q.price);
q.bookNo = "";
q.price = 0;
}
return *this;
}
test.cpp
#include<iostream>
#include"Quote.h"
#include"Bulk_quote.h"
#include"More_quote.h"
using namespace std;
int main()
{
//Quote q("dangdang", 100);
//Bulk_quote b("cc", 100, 10, 0.1);
//More_quote e("aa", 100, 5, 0.1);
//print_total(cout, q, 10);
//print_total(cout, b, 10);
//print_total(cout, e, 10);
Bulk_quote test("cuinan", 100, 10, 0.1);
Bulk_quote test1(test);
Bulk_quote test2;
test2 = test;
Bulk_quote test3(std::move(test));
}
这样拷贝控制就完了。
。
继承的构造函数
。
在C++11新标准中,派生类能够重用其直接基类定义的构造函数,也可以说是继承。一个类只初始化它的直接基类,所以也只能继承其直接基类的构造函数。类不能继承默认、拷贝、移动构造函数。
继承方式:提供一条注明了基类名的using声明语句。 对应基类的每个构造函数(除了上面提到的那仨),都会被继承。在派生类中生成形参列表完全相同的构造函数。
注意using不改变构造函数的访问级别。
例如在Bulk_quote中使用using来继承构造函数
class Bulk_quote : public Disc_quote
{
public:
Bulk_quote() = default;
//Bulk_quote(const string& s, double prc, size_t min, double disCount):
// Disc_quote(s,prc,disCount,min){}
using Disc_quote::Disc_quote;
}
这条继承的构造函数与注释掉的这条是等价的。
好,今天就到这里了。