继承
虚函数
-
声明
- 派生类必须在其内部对所有重新定义的虚函数进行声明,派生类可以在这样的函数之前加上关键字virtual关键字,但不是必须。
- 关键词virtual只能出现在类内的声明函数语句之前,而不能用于类外部的函数定义,在派生类中相应的函数将隐式的是虚函数(不加virtual的情况)
- 派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参类型必须要与它覆盖的基类函数完全一致,返回值也一样,但是如果是引用或指针的化,也就是说如果D由B派生得到,则基类的虚函数可以返回B*而派生类的对应函数可以返回D*.
-
定义
- 必须为每个虚函数提供定义,不管是否有被使用到,因为我们直到运行时才能知道到底调用了哪个模板
- override 关键字
- 重写基类中的虚函数
- 在形参、const关键字、引用限定符后面
- 只有虚函数才能被重新
- final 关键字
- 不允许派生类覆盖
- override、final 关键字
出现在形参列表包括(const和引用修饰符)以及尾置返回累心之后。
-
范围
- 任何构造函数指外的非静态函数都可以是虚函数,也可以是析构函数
- 一旦某个函数被声明程虚函数,则派生类中它都是虚函数
默认实参
虚函数可以有默认实参,若函数调用了默认实参,则实参值由静态类型决定,所以基类和派生类中定义的默认实参最好一致
class Quote
{
public:
...
virtual void d(int a = 10,double b = 20.0 ) {
std::cout << "Quote: "<<"a: " << a << " b: " << b << std::endl;
};
...
};
class Bulk_quote :
public Quote
{
public:
...
virtual void d(int a = 20, double b = 30) {
std::cout <<"Bulk_quote: "<< "a: " << a << " b: " << b << std::endl;
};
...
};
Bulk_quote bulk_quote("1-1-1",10,10,0.5); Quote quote("1-1-2", 10); quote.d(); // Quote: a: 10 b: 20 Quote& q = bulk_quote; q.d();//Bulk_quote: a: 10 b: 20
如果派生类的默认参数和基类的默认参数不一致,传入派生类函数将是基类函数定义的默认实参
回避虚函数机制
- 使用作用域
Bulk_quote *bulk_quote;
bulk_quote->Quote::net_price(42)
该代码强行调用Quote的net_price函数,而不管bulk_quote实际指向的对象类型是什么,该调用将在编译时完成解析
如果一个派生类虚函数需要调用它的基类版本,但是没有使用作用域符,则在运行时将被解析为对派生类版本自身调用,从而导致无限递归
基类通常应该定义一个虚析构函数,即使该函数不执行任何操作也是如此!
继承与静态成员
- 如果基类定义了一个静态成员,则在整个继承体系中只存在该成员的唯一定义。
- 如果静态成员是public或protected的那么基类和派生类都能使用该静态成员
阻止继承(final 关键字)
- 类(不能继承)
class Quote final{} // 不能作为基类
- 函数
类型转换与继承
Bulk_quto bulk;
Quote* itemP = &bulk;
Bulk_quote *bulkP = itemP;// 错误,不能将基类转换成派生类
Bulk_quote *bulkP = (Bulk_quote *)itemP; //
Bulk_quote *bulkP = dynamic_cast<Bulk_quote *>(itemP);
dynamic_cast
class A{...};
class B:public A{...};
class C:public B{...};
void Fun1(B* pB)
{
A* pA = (A*)pB;
C* pC = (C*)pB;
...
}
如果这样调用Fun1:
Fun1(((B*)new C));
的确不会有问题,但如果是这样呢:
Fun1(new B);
pC不会为NULL,能够想到使用pC指针时就程序就悲剧了. 更严重情况下,如果是这样:
Fun1((B*)0X00005678);//0X00005678是一个随机值
pA,pC就不会是NULL,强制类型转换总是能够成功的,但使用这两个指针时程序肯定崩溃.当然你可以使用异常处理机制来处理这样的错误,不过这有点大才小用的感觉,最好能够找到一种能够检查出类型转换能否成功的办法.这时dynamic_cast就能大显身手了.
A* pA = dynamic_cast<A*>pB;// upcast.
if (NULL == pA){...}
C* pC = dynamic_cast<C*>pB;// downcast.
if (NULL == pC){...}
静态类型和动态类
-
静态类型 静态类型是在编译时已知的,是变量声明时的类型或者表达式生成的类型
-
动态类型 其动态类型是变量或者表达式表示内存的对象的类型,直到运行时才可知
如果表达式既不是指针也不是引用,则其动态类型和静态类型会一直一样
访问控制和继承
- protected
- 对类用户不可以访问,但是派生类成员和友元来说是可以访问的
- 派生类的成员和友元只能通过派生类对象来访问基类的受保护成员
class Quote
{
public:
Quote() { std::cout << "Quote default"; };
Quote(const std::string& book, double sales_price) :bookNo(book), price(sales_price) {}
virtual ~Quote() = default;
private:
std::string bookNo;
protected:
double price = 0.0;
};
class Bulk_quote :public Quote
{
public:
double net_price(std::size_t n) const override;
double GetPrice(Bulk_quote& bulk) { return bulk.price; }
// double GetPrice(Quote& quote) { return quote.price; } // 错误,不能通过Quote的对象访问
};
class Quote
{
friend double GetPrice(Quote& quote);
private:
std::string bookNo;
protected:
double price = 0.0;
};
double GetPrice(Quote& quote) { return quote.price; } // 正确,友元函数可以访问对象中的所有成员
- 派生类访问符的影响 派生类访问符对于派生类的成员(友元)能否访问其直接基类的成员没有什么影响, 派生类访问符的目的是控制派生类用户(包括派生类的派生类)对于基成员的访问权限
#pragma once
class Base
{
public:
int public_value = 0;
private:
int private_vale = 0;
protected:
int protected_value = 0;
};
class Pub_Derv :public Base {
void debug() {
// private_vale 不能访问
std::cout << public_value << protected_value;
}
};
class Prv_Derv:private Base
{
void debug() {
// private_vale 不能访问
std::cout << public_value << protected_value;
}
};
class Pro_Derv :protected Base
{
void debug() {
// private_vale 不能访问
std::cout << public_value << protected_value;
}
};
int main()
{
pub.public_value;
//pub.private_vale; // 错误,不可访问
//pub.protected_value; 错误,不可访问
Prv_Derv prv;
//prv.public_value;// 错误,不可访问
//prv.private_vale; // 错误,不可访问
//prv.protected_value; 错误,可访问
Pro_Derv pro;
//prv.public_value;// 错误,不可访问
//pub.private_vale; // 错误,私有不能访问
//pub.protected_value; //错误不可访问
}
- 派生访问说明符还可以控制继承派生类新类的访问权限
#pragma once
class Base
{
public:
int public_value = 0;
private:
int private_vale = 0;
protected:
int protected_value = 0;
};
class Pub_Derv :public Base {
void debug() {
// private_vale 不能访问
std::cout << public_value << protected_value;
}
};
class Prv_Derv:private Base
{
void debug() {
// private_vale 不能访问
std::cout << public_value << protected_value;
}
};
class Pro_Derv :protected Base
{
void debug() {
// private_vale 不能访问
std::cout << public_value << protected_value;
}
};
class Derivaed_from_public :public Pub_Derv {
int use() { return protected_value; }
int use2() { return public_value; }
};
class Derivaed_from_private :public Prv_Derv {
// 错误, protected_value 变成私有的
// int use() { return protected_value; }
// // 错误, public_value 变成私有的
//int use() { return public_value; }
};
总结
派生类访问说明符会将派生类中继承基类部分的访问控制符改变,导致派生类的派生类访问会受派生类影响,也就是说孙子类要访问爷爷类中的public属性还要看父类中的派生类访问符。
派生类向基类转换的可访问性
- 只有当D公有的继承B时,用户代码才能使用派生类向基类转换
- 不论D以什么方式继承B,则D的成员函数和友元,都能使用派生类向基类转换
- 如果D继承B的方式时public或protected,则D的派生类成员可以使用D向B的类型转换
友元和继承
- 不能继承友元关系;每个类负责控制各自成员的访问权限