把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
public
: 所有实体都可以访问protected
: 只允许本类(和子类)的成员函数访问private
: 只允许本类的成员函数+友元类或友元函数访问(友元类暂时还没学习)派生类(子类) : 基类(父类)。
举例:cat类继承了Felid(猫科动物类)
class Felid {
public:
};
class Cat : public Felid {
public:
};
本模块主要参考了 The Four Polymorphisms in C++ (翻译+一点改动,英文描述可能更清晰,建议最好看原文,我这里主要是方便复习)
总结:多态就是一个函数 / 运算符… 的多种不同实现形态(同名,但是用法 / 效果不同)。
使用哪一个具体实现呢?(要根据参数,子类类型…来确定)
运行时多态:运行时确定使用哪一个函数;
编译时多态:编译时确定使用哪一个具体函数。
四种类型,分别是(后面分别有例子介绍):
子类型多态(Subtype polymorphism,运行时多态)():虚函数;
参数多态(Parametric polymorphism,编译时):类模板、函数模板;
重载(Ad-hoc polymorphism / overloading,编译时):函数重载、运算符重载;
强制多态(Coercion Polymorphism,编译 / 运行时)((implicit or explicit) casting):基本类型转换、自定义类型转换;
猫科动物类:Felid
。假定所有猫科动物都会叫:meow()
但是每种猫咪叫法不同,家猫cat
, 老虎tiger
, 豹猫Ocelot
各有不同的叫声,因此Felid
用虚函数virtual void meow() = 0;
,而各自真实的叫声,在子类家猫cat
, 老虎tiger
, 豹猫Ocelot
中具体实现
// file cats.h
class Felid {
public:
virtual void meow() = 0;
};
class Cat : public Felid {
public:
void meow() { std::cout << "Meowing like a regular cat! meow!\n"; }
};
class Tiger : public Felid {
public:
void meow() { std::cout << "Meowing like a tiger! MREOWWW!\n"; }
};
class Ocelot : public Felid {
public:
void meow() { std::cout << "Meowing like an ocelot! mews!\n"; }
};
猫科动物叫声调用如下
#include
#include "cats.h"
void do_meowing(Felid *cat) {
cat->meow();
}
int main() {
Cat cat;
Tiger tiger;
Ocelot ocelot;
do_meowing(&cat);
do_meowing(&tiger);
do_meowing(&ocelot);
}
因为cat
, tiger
,ocelot
都是派生于Felid
,因此都可以调用meow()
成功,输出如下
Meowing like a regular cat! meow!
Meowing like a tiger! MREOWWW!
Meowing like an ocelot! mews!
为什么被称为运行时多态?
函数的多态发生于运行时(运行时通过在虚函数表中查找地址来确定函数的resolution)
The resolution of polymorphic function calls happens at runtime through an indirection via the virtual table.
——即,编译的时候,编译器并没有确定需要调用的地址,而是在运行时才会确定。(运行时通过在虚函数表中,找到对应的函数指针)
参数多态主要是,让不同的类型,可以运行相同的代码。
举例:C++中关于max()
函数的定义
#include
#include
template <class T>
T max(T a, T b) {
return a > b ? a : b;
}
int main() {
std::cout << ::max(9, 5) << std::endl; // 9
std::string foo("foo"), bar("bar");
std::cout << ::max(foo, bar) << std::endl; // "foo"
}
这里的max
函数就是类型T
上的一个多态函数。
编译时多态? —— 因为是编译时确定的(编译时获取到类型,就确定了准确的max函数)
同名函数 / 运算符,对不同类型有不同的效果。这个比较常见,比如函数重载和运算符重载
这里对add
函数进行重载,让他分别可以对int
类型和std::string
类型起作用:
#include
#include
int add(int a, int b) {
return a + b;
}
std::string add(const char *a, const char *b) {
std::string result(a);
result += b;
return result;
}
int main() {
std::cout << add(5, 9) << std::endl; // 14
std::cout << add("hello ", "world") << std::endl; // hello world
}
经常需要发生比如(float b
得到的是一个int
类型的输入, int a
得到的是一个float
类型的输入 —— 需要类型转换)
float b = 6; // int gets promoted (cast) to float implicitly
int a = 9.99 // float gets demoted to int implicitly
使用C++/C的强制类转换的时候也同样会用到,比如(unsigned int *)
or (int)
or C++'s static_cast
, const_cast
, reinterpret_cast
, or dynamic_cast
.
发生在隐式( isn’t explicit
)调用类的构造函数时
#include
class A {
int foo;
public:
A(int ffoo) : foo(ffoo) {}
void giggidy() { std::cout << foo << std::endl; }
};
void moo(A a) {
a.giggidy();
}
int main() {
moo(55); // prints 55
}
上面的代码能够正确输出内容,是因为moo(55)
的时候,进行了类型转换int -> A
定义int
操作符的例子(定义int操作要return this -> v
)
class CrazyInt {
int v;
public:
CrazyInt(int i) : v(i) {}
operator int() const { return v; } // conversion from CrazyInt to int
};
调用
#include
void print_int(int a) {
std::cout << a << std::endl;
}
int main() {
CrazyInt b = 55;
print_int(999); // prints 999
print_int(b); // prints 55
}
这里传入了b
(class CrazyInt
类型),但是仍然转换成了int
类型。