Inheritance & Polymorphism | 继承与多态👀
约 2243 个字 296 行代码 预计阅读时间 11 分钟
Inheritance | 继承👀
放在 Reuse (重用) 中来讲
- Reusing the interface | 重用接口
- Inheritance is to take the existing class, clone it, and then make additions and modifications to the clone
classDiagram
Shape <-- Circle
Circle : draw()
Circle : erase()
Shape <-- Line
Line : draw()
Line : erase()
Shape <-- Square
Square : draw()
Square : erase()
Shape : draw()
Shape : erase()
What is Inheritance
- Language implementions technique
- Also an important component of the OOP design methodology
- Allows sharing of design for
- Member variables
- Member functions
- Interfaces
- Key technology in C++
- The ablitity to define the behavior or implementation of one class as a superset of another class ()
- Inheritance allows us to define one class as an extension of another
- Advantages of inheritance
- Avoids code duplication
- Code reuse
- Easier maintenance
- Extentibility
code
// Define an Employee class
class Employee {
public:
Employee(const std::string& name, const std::string& ssn);
const std::string& get_name() const;
void print() const;
void print(const std::string& msg) const;
protected:
std::string m_name;
std::string m_ssn;
};
Employee::Employee(const std::string& name, const std::string& ssn)
: m_name(name), m_ssn(ssn)
{
// Initializer list sets up the values
}
// Employee member functions
inline const std::string& Employee::get_name() const
{
return m_name;
}
inline void Employee::print() const
{
std::cout << m_name << endl;
std::cout << m_ssn << endl;
}
inline void Employee::print(const std::string& msg) const
{
std::cout << msg << endl;
print();
}
class Manager : public Employee {
public:
Manager(const std::string& name, const std::string& ssn, const std::string& title);
const std::string& title_name() const;
void std::string& get_title() const;
void print() const;
protected:
std::string m_title;
};
Manager::Manager(const std::string& name, const std::string& ssn, const std::string& title)
: Employee(name, ssn), m_title(title)
{
// Initializer list sets up the values
}
// Manager member functions
inline const std::string& Manager::get_title() const
{
return m_title;
}
inline void Manager::print() const
{
Employee::print();
std::cout << m_title << endl;
}
- 继承的变量,最好使用继承的函数来操作
- 父类的东西在子类中都存在,但子类无法直接使用父类中的
private variable
,但是可以通过public function
来操作 name hide
(C++ 中特有的),父类中重载的函数,如果在子类中重写了其中的一个那么父类的重载函数在子类中都不存在了 (如 main.cpp 中的m.print("Manager")
就是错误的)- 父类的
protected variable
在子类中相当于子类的private variable
- 子类的地址中,第一部分存的是父类的变量,第二部分才是自己的,故一般先初始化父类对象
- 当用子类来看作一个父类的对象,只对子类地址前面的部分操作就是对父类的操作
- Scpoes and access in C++
class B: public A
只有public A
才是 OOP 语义上的继承class B: A
默认class B: private A
->A
中的public
和protected
都变成private
class B: protected A
->A
中的public
和protected
都变成protected
Polymorphism | 多态性👀
从继承引申出来的概念
- Public Inheritance should imply substitution
- If B isa A (表达一种继承关系), you can use a B anywhere an A can be used
- If B isa A, then everything that is true for A is also true for B
- Be careful if the substition is not valid
- If B isa A (表达一种继承关系), you can use a B anywhere an A can be used
Up-casting | 向上造型👀
- Is to regard an object of the derived class as an object of the base class
- It is to say: Students are human beings. You are students. So you are human beings.
graph LR
A[HumanBeing] --> B[Students]
- Upcasting is the act of converting from a Derived reference or pointer to a Base class reference or pointer
graph LR
A[Base] --> B[Derived]
Upcasting examples
Manager peter("Peter", "123-45-6789", "VP");
Employee* e = &peter; // Upcasting
Employee& e = peter; // Upcasting
- Lose type information about the object:
ep->print(cout); //prints base class version
code
#include <iostream>
using namespace std;
class A
{
public:
int i;
A(){ i = 10; cout << "A()" << i << endl; }
void f() { cout << "A::f()" << endl; }
};
class B : public A
{
public:
int i;
B(){ i = 20; cout << "B()" << i << endl; }
void f() { cout << "B::f()" << endl; }
};
int main()
{
B b;
A *p = &b; // Upcasting
b.f();
p->f();
cout << sizeof(*p) << endl;
int *pi = (int*)p; // Downcasting
cout << pi[0] << "," << pi[1] << endl;
cout << p->i << endl;
cout << b.i << endl;
}
- 针对
b.f()
和p->f()
- A 和 B 中有相同函数,但是调用的是哪个函数,取决于指针的类型
- 当使用
p->f()
时,p
是A
类型的,故调用的是A
中的f()
,而不是B
中的f()
- 当使用
b.f()
时,b
是B
类型的,故调用的是B
中的f()
- 在 A 中的
f()
前加上virtual
关键字,这样就可以实现多态性 (即virtual void f() { cout << "A::f()" << endl; }
) - 加了
virtual
关键字后, A 和 B 中的相同函数是override
的关系,即 B 中的f()
覆盖了 A 中的f()
-
Polymorphism is based on
Upcast
andDynamic Binding
- Upcast: take an object of the derived class as an object of the base one
- Dynamic Binding (Binding -> which function to be called):
- Static Binding: call the function as the code
- Dynamic Binding: call the function of the object
Example
void render(Shape* p) { p->render(); // calls correct render function } // for given Shape! void func() { Ellipse ell(10, 20); ell.render(); // static -- Ellipse::render() Circle cir(10); cir.render(); // static -- Circle::render() render(&ell); // dynamic -- Ellipse::render() render(&cir); // dynamic -- Circle::render() }
- 在 C++ 中,当使用基类的引用或指针调用一个
virtual function
时,发生动态绑定 - 上述代码中
Shape* p
被称为polymorphic variable
, 多态变量有两种类型- Static type: 声明类型 -> 即
Shape *
- Dynamic type: 实际类型 -> 运行时刻才知道,即
Ellipse
或Circle
- Static type: 声明类型 -> 即
Quote
Virtual functions👀
- Non-virtual functions
- Compiler generates
static
, or direct call to stated type - Faster to execute
- Compiler generates
- Virtual functions
- Can be transparently overriden in a derived class
- Objects carry a pack of their virtual functions
- Compiler checks pack and dynamically calls the right function
- If compiler knows the functions at compile time, it will generate a static call
-
Calls up the chain (如何在子类中调用父类函数)—— use
Base::f()
- No need to copy the old stuff!
How virtuals work in C++
- 任何一个有
virtual
类的对象,在对象里面第一个成员是一个指针(VPTR
,8 个字节(对 64 位 OS) ),这个指针指向一个表 (vtable
) vtable
中存放这个类中所有virtual
函数的地址vtable
是静态的,
#include <iostream>
using namespace std;
class A
{
public:
int i;
A(){ i = 10; cout << "A()" << i << endl; }
virtual void f() { cout << "A::f()" << endl; }
};
class B : public A
{
public:
int i;
B(){ i = 20; cout << "B()" << i << endl; }
void f() { cout << "B::f()" << endl; }
};
int main()
{
B b;
A *p = &b; // Upcasting
long long **vp = (long long**)p;
void (*pf)() = (void (*)())(*(*vp));
cout << "------" << endl;
pf();
}
引申
- 此时
A()
中调用的f()
是 A 中的f()
g()
中调用的f()
是 B 中的f()
- 在构造函数中 “看起来 virtual 不起作用了”
- Actually
VPTR
是由编译器在构造函数刚开始的时候被写入,vtable
是在链接时分配好地址 - 父类的构造函数在执行时会把父类 的
VPTR
填入 (即 A 的构造函数中f()
还是动态绑定) - 子类的构造函数发生时再把子类的
VPTR
写入
- Actually
-
What happens if
- Area of
circ
is sliced off (Only the part ofcirc
that fits inelly
gets copied) - 但
VPTR
并未改变 ->VPTR
构造出后就不会再改变 (除非暴力修改)
- Area of
-
Virtuals and reference arguments
- References act like pointers
-
Virtual destructors
- Make destructors virtual if they might be inherited
delete p;
做的两件事情p->destructor();
-> 存在绑定问题, 若 p 的静态类型(Shape)的析构函数不是virtual
则只在此处执行静态绑定,只执行Shape
类的析构(但实际 p 指向的对象是Ellipse
, 此时Ellipse
的析构未做);但若是virtual
编译器会在此处动态绑定,才会做有效的析构free
- [Note] : 除非保证自己的类不会被继承,就将类的析构函数设置为
virtual
-> 这个类就会有VPTR
和vtable
- 那么其实 c++ 的每个类都应该有
vtable
-> 通过VPTR
和vtable
的关系,我们建立了一种对象和类之间的关联 —— 可以实现 RTTI (Run Time Type Identification) | 但 c++ 暂不支持 RTTI
- Want
Ellipse::~Ellipse()
to be called- Must declare
Shape::~Shape()
virtual - It will call
Shape::~Shape()
automatically
- Must declare
- If
Shape::~Shape
is not virtual, onlyShape::~Shape()
will be invoked
Overriding👀
-
overriding redefines the body of a virtual function
- 指子类和父类有同名函数(函数名称相同、参数表也相同,且父类中声明
virtual
)
class Base { public: virtual void f(); } class Derived : public Base { public: virtual void f(); // override Base::f() }
- 父类中有
virtual
, 子类可以不加virtual
. 但习惯上还是加上virtual
,以免子类的继承者忘记加上virtual
导致无法动态绑定 (貌似并不需要,测试了一下,不加virtual
也可以动态绑定)
- 指子类和父类有同名函数(函数名称相同、参数表也相同,且父类中声明
Overloading vs. Overriding
- Overloading
- 在一个层级上,均在子类 or 父类 or free 函数中有同名函数(函数名称相同,参数表不同)
- Overriding
- 指子类和父类有同名函数(函数名称相同、参数表也相同,且父类中声明
virtual
) - 返回类型也要求相同,但可以是父类返回类型的子类型
- 指子类和父类有同名函数(函数名称相同、参数表也相同,且父类中声明
Name
父类中发生重载,比如父类中有多个 virtual
的函数 func()
, 而子类只 override 了其中的一个。那么其余的重载函数便不在子类存在了 | Name Hiding
code
class Expr
{
public:
virtual Expr* new_expr();
virtual Expr& clone();
virtual Expr self();
};
class Binary : public Expr
{
public:
virtual Binary* new_expr(); // OK
virtual Binary& clone(); // OK
virtual Binary self(); // Error
};
- 父类返回类型是 pointer or reference 的时候,子类可以是父类返回类型的子类型
- 但父类返回类型是对象本身的时候,子类返回类型不能是子类的(会被 sliced off)
Tips
-
Never redefine an inherited non-virtual function
- Non-virtual functions are statically bound
- No dynamic dispatch
-
Never redefine an inherited default parameter value
- 默认参数只是出现在头文件中,不是静态绑定(若在子类中修改,结果不一定符合预期)
Abstract base classes👀
- An abstract base class has pure virtual functions | 纯虚函数
- Only interface defined
- No function body given
- Abstract base classes cannot be instantiated | 不能被实例化
- Must derive a new class/classes
- Must supply definitions for all pure virtuals before class can be instantiated
class XYPos { ··· }; // x, y point
class Shape
{
public:
Shape();
virtual void draw() = 0; // pure virtual fucntion
void move(const XYPos&);
virtual void resize();
protected:
XYPos center;
};
class Circle : public Shape
{
public:
virtual void draw(); // override
···
};
class Rectangle : public Shape
{
public:
virtual void draw(); // override
···
};
virtual function() = 0;
即可将此函数标记为 纯虚函数 | pure virtual function- 一旦一个类中有一个纯虚函数,这个类就称为抽象类(不能用于制造对象)
Shape
的存在是为了让Circle
和Rectangle
继承,提供一个共同的接口
- Why use
- Modeling
- Force correct behavior
- Define interface without defining implementation
- When use
- Not enough information is available
- When designing for interf inheritance
- Protocol/Interface classes are abstract base classes with
- All non-static member functions are pure virtual except for the destructor
- Virtual destructor with empty body
- No non-static member variables, inherited or otherwise
- May contain static members
- 即继承子类实现接口的规范的公开函数,但接口并不提供任何函数的实现
Multiple Inheritance👀
C++ 是唯一支持多继承的语言
Consultant
picks up the attributes of bothMTS
andTemporary
name, id, employer, degree_info
- 注意继承父类用逗号隔开,且每一个父类前面都要加
public
表示继承多个父类的public
- 多态继承的出现是为了解决 C++ 没有单根结构的问题
多继承的问题
- 对于父类 A, 其有两个子类 B 和 C, D 有同时继承 B 和 C
- 此时若 A 中有一变量 i,D 调用 i 时,会出现二义性,即不知道是调用 B 中的 i 还是 C 中的 i
- 为了解决此问题,C++ 提出虚继承(virtual public),比如 B 和 C 虚继承 A,那么将不会把 A 中的变量链接到 B 和 C 中而是 B 指向一个 A,C 指向一个 A,这样 D 中的 B 和 C 会指向同一个 A,就不会出现二义性了
- 后来提出的
模板
是更好的解决方案 —— 不要使用多继承\
Quote
本文总阅读量:
次