【C++】多态:虚函数
1. 如此继承
#include <iostream>
using namespace std;
class Animal {
public :
Animal(string name) : name(name) {}
void run() {
cout << "I don't know how to run" << endl;
}
private :
string name;
};
class Cat : public Animal {
public :
Cat(string name) : Animal(name) {}
void run() {
cout << "I can run with four legs" << endl;
}
};
int main() {
Cat a("Tom");
Animal &b = a;
Animal *c = &a;
a.run();
b.run();
c->run();
return 0;
}
运行结果:
I can run with four legs
I don't know how to run
I don't know how to run
代码中,a、b 和 c 都是指向同一个对象,但是调用 run 方法后结果却不同,违反了我们的认知规律。这是为什么呢?因为普通成员方法的特性是跟着类走,也就是说*调用普通成员方法时,要看调用该方法的符号是什么类型的*。
在 main
函数内:
int main() {
Cat a("Tom");
Animal &b = a;
Animal *c = &a;
a.run(); //a的类型是Cat,所以调用的是Cat的run方法
b.run(); //b是Animal类型的引用,所以调用的是Animal的run方法
c->run();//c是Animal类型的指针,所以调用的是Aniaml的run方法
return 0;
}
普通成员方法的调用是在编译期就确定了的行为。
在编译期的时候,编译器会将上面三行的 run 方法的调用替换成相应的函数地址:
- 第5行会被替换成
Cat
类的run
方法的地址; - 第6行会被替换成为
Animal
类中的run
方法的地址; - 第7行会被替换成为
Animal
类中的run
方法的地址。
2. 虚函数
2.1 虚函数的特性
#include <iostream>
using namespace std;
class Animal {
public :
Animal(string name) : name(name) {}
virtual void run() {
cout << "I don't know how to run" << endl;
}
private :
string name;
};
class Cat : public Animal {
public :
Cat(string name) : Animal(name) {}
//父类中的方法变成virtual,则子类中同名的方法也就变成了virtual
void run() {
cout << "I can run with four legs" << endl;
}
};
int main() {
Cat a("Tom");
Animal &b = a;
Animal *c = &a;
a.run();
b.run();
c->run();
return 0;
}
运行结果:
I can run with four legs
I can run with four legs
I can run with four legs
普通成员方法是跟着类走的,而虚函数是跟着对象走的。
理解代码段:
int main() {
Cat a("Tom");
Animal &b = a;
Animal *c = &a;
a.run(); //通过对象a调用的,而run是虚函数,编译器就会分析对象a具体指向的是什么类型的对象,是Cat类型,于是调用Cat类的run方法
b.run(); //b是Animal类型的引用,但是指向Cat类对象,所以调用的还是Cat类的run方法
c->run(); //同上
return 0;
}
2.2 虚函数的作用
对外展现统一的接口
#include <iostream>
using namespace std;
class Animal {
public :
Animal(string name) : name(name) {}
virtual void run() {
cout << "I don't know how to run" << endl;
}
private :
string name;
};
class Cat : public Animal {
public :
Cat(string name) : Animal(name) {}
//父类中的方法变成virtual,则子类中同名的方法也就变成了virtual
void run() {
cout << "I can run with four legs" << endl;
}
};
class People : public Animal {
public :
People(string name) : Animal(name) {}
void run() {
cout << "I can run with two leges" << endl;
}
};
class Bat : public Animal {
public :
Bat(string name) : Animal(name) {}
void run() {
cout << "I can fly" << endl;
}
};
int main() {
#define MAX_N 10
srand(time(0));
//有10个空间
Animal **zoo = new Animal*[MAX_N];
for (int i = 0; i < 10; i++) {
switch (rand() % 3) {
case 0: zoo[i] = new Cat("cat"); break;
case 1: zoo[i] = new People("People"); break;
case 2: zoo[i] = new Bat("bat"); break;
}
}
for (int i = 0; i < MAX_N; i++) zoo[i]->run();
return 0;
}
运行结果:
zoo[i]->run
到底调用的是哪个方法,在编译期是不能确定的,本质是因为虚函数是跟着对象走的,而对象是在程序运行期才产生的,只有产生了对象,虚函数到底调用的是哪个方法才能知道,即虚函数是程序运行期才能确定的。
小结:
- 普通成员方法 跟着 类 走,在 编译期 就能确定
- 虚函数 跟着 对象 走,在 运行期 才能确定
2.3 override 关键字
override
关键字起到说明作用,规范化代码的语义。override
关键字只能用来mark虚函数,可以减少程序出现bug。
class Animal {
public :
Animal(string name) : name(name) {}
virtual void run() {
cout << "I don't know how to run" << endl;
}
private :
string name;
};
class Cat : public Animal {
public :
Cat(string name) : Animal(name) {}
void run() override {
cout << "I can run with four legs" << endl;
}
};
2.4 final 关键字
2.4.1 final关键字的作用
final
关键字的作用:禁止子类重写父类中的同名函数。
/*************************************************************************
> File Name: final.cpp
> Author: Maureen
> Mail: Maureen@qq.com
> Created Time: 日 1/ 9 20:23:10 2022
************************************************************************/
#include <iostream>
using namespace std;
class Animal {
public :
Animal(string name) : name(name) {}
virtual void run() {
cout << "I don't know how to run" << endl;
}
virtual void fl1() {}
private :
string name;
};
class Cat : public Animal {
public :
Cat(string name) : Animal(name) {}
//父类中的方法变成virtual,则子类中同名的方法也就变成了virtual
void run() override final {
cout << "I can run with four legs" << endl;
}
void fl1() override {}
};
class Tiger : public Cat {
public :
void run() {}
};
编译器会报错:
final.cpp:33:10: error: declaration of 'run' overrides a 'final' function
void run() {}
^
final.cpp:25:10: note: overridden virtual function is here
void run() override final {
^
1 error generated.
错误原因:Tiger
类中的 run
方法没有办法覆盖父类 Cat
中的 run
方法,因为 Cat
中的 run
方法已经标记了 final
关键字,所以子类不能重写该方法。
2.4.2 final关键字的应用场景
当希望所有的派生类中包括外部访问该方法的时候表现得一模一样时,就需要将该方法标记为 final
。
class Animal {
public :
Animal(string name) : name(name) {}
virtual void run() {
cout << "I don't know how to run" << endl;
}
virtual void getName() final {}
virtual void fl1() {}
private :
string name;
};
那么就能保住 Animal
类的所有子类调用 getName
方法都是调用的 Animal
类的这个方法。
2.5 虚函数为什么能跟着对象走
存在虚函数后,编译器对对象的存储区做了一定的调整:
对象的头 8 个字节存储的是虚函数表的首地址。
代码示例:
#include <iostream>
using namespace std;
class A {
public :
virtual void say() {
cout << "Class A say" << endl;
}
virtual void run() {
cout << "Class A run" << endl;
}
};
class B : public A {
public :
void say() override {
cout << "Class B say" << endl;
}
};
class C : public A {
public :
void run() override {
cout << "Class C run" << endl;
}
};
#define TEST(a) test(a, #a)
void test(A &a, string class_name) {
cout << "object " << class_name << endl;
a.say();
a.run();
cout << "======" << endl << endl;
return ;
}
int main() {
A a;
B b;
C c;
TEST(a);
TEST(b);
TEST(c);
return 0;
}
运行结果:
object a
Class A say
Class A run
======
object b
Class B say
Class A run
======
object c
Class A say
Class C run
======
三个类的虚函数表:
三个对象指向不同的虚函数表:
即是说,相同类型下的不同对象存储的虚函数表是同一张:
a1
和 a2
都是 A 类型对象,它们指向同一张虚函数表。
这就是有了虚函数后的对象存储模型,这就解释了”为什么虚函数可以跟着对象走“。
子类中与父类虚函数同名的函数之所以叫做覆盖,是因为虚函数表中存储的相关函数的地址被修改为子类中相关函数的地址,所谓的覆盖是在虚函数表中的覆盖。
2.5.1 验证 “虚函数表的存在”
验证虚函数表的存在即是验证对象的头8个字节指向的是虚函数表的地址:让 b
对象的地址指向 c
的虚函数表。
#include <iostream>
using namespace std;
class A {
public :
virtual void say() {
cout << "Class A say" << endl;
}
virtual void run() {
cout << "Class A run" << endl;
}
};
class B : public A {
public :
void say() override {
cout << "Class B say" << endl;
}
};
class C : public A {
public :
void run() override {
cout << "Class C run" << endl;
}
};
#define TEST(a) test(a, #a)
void test(A &a, string class_name) {
cout << "object " << class_name << endl;
a.say();
a.run();
cout << "======" << endl << endl;
return ;
}
int main() {
A a;
B b;
C c;
TEST(a);
TEST(b);
TEST(c);
//认为b的存储区中存储着一个个void *的地址
//将b对象存储的地址修改为c对象中存储的地址(即头8个字节)
((void **)(&b))[0] = ((void **)(&c))[0];
TEST(b);
return 0;
}
运行结果:
object a
Class A say
Class A run
======
object b
Class B say
Class A run
======
object c
Class A say
Class C run
======
object b #b对象就运行的是C类的虚函数表中的方法
Class A say
Class C run
======
2.5.2 验证 “虚函数表中存储着虚函数”
#include <iostream>
using namespace std;
class A {
public :
virtual void say() {
cout << "Class A say" << endl;
}
virtual void run() {
cout << "Class A run" << endl;
}
};
class B : public A {
public :
void say() override {
cout << "Class B say" << endl;
}
};
class C : public A {
public :
void run() override {
cout << "Class C run" << endl;
}
};
#define TEST(a) test(a, #a)
void test(A &a, string class_name) {
cout << "object " << class_name << endl;
a.say();
a.run();
cout << "======" << endl << endl;
return ;
}
//声明函数指针
typedef void (*func)();
int main() {
A a;
B b;
C c;
TEST(a);
TEST(b);
TEST(c);
//认为b的存储区中存储着一个个void *的地址
//将b对象存储的地址修改为c对象中存储的地址(即头8个字节)
((void **)(&b))[0] = ((void **)(&c))[0];
TEST(b);
//调用了b对象指向的虚函数表中的say方法
//找到b对象指向的虚函数表中的第一个虚函数
((func **)(&b))[0][0](); //Class A say
return 0;
}
运行结果:
object a
Class A say
Class A run
======
object b
Class B say
Class A run
======
object c
Class A say
Class C run
======
object b
Class A say
Class C run
======
Class A say
修改 say
方法,传入一个参数:
#include <iostream>
using namespace std;
class A {
public :
virtual void say(int x) {
cout << "Class A say : " << x << endl;
}
virtual void run() {
cout << "Class A run" << endl;
}
};
class B : public A {
public :
void say(int x) override {
cout << "Class B say" << endl;
}
};
class C : public A {
public :
void run() override {
cout << "Class C run" << endl;
}
};
#define TEST(a) test(a, #a)
void test(A &a, string class_name) {
cout << "object " << class_name << endl;
a.say(123);
a.run();
cout << "======" << endl << endl;
return ;
}
typedef void (*func)(int);
int main() {
A a;
B b;
C c;
TEST(a); //正常调用say方法
TEST(b);
TEST(c);
//认为b的存储区中存储着一个个void *的地址
//将b对象存储的地址修改为c对象中存储的地址(即头8个字节)
((void **)(&b))[0] = ((void **)(&c))[0];
TEST(b);
//使用函数指针调用say方法
((func **)(&b))[0][0](100);
return 0;
}
运行结果:
object a
Class A say : 123
Class A run
======
object b
Class B say
Class A run
======
object c
Class A say : 123
Class C run
======
object b
Class A say : 123
Class C run
======
Class A say : 73896
可以发现,正常调用say
方法的时候,参数x
可以被准确地赋值;但是通过函数指针调用的时候,却不能进行正确的赋值。这就涉及到this
指针的问题。
如virtual void say(int x)
方法,我们看到的是传入一个参数,正常调用的时候,编译器会将say
方法处理为传入两个参数的形式:say(this, int)
。这就是底层实现this
指针的原理,this
指针其实就是成员函数的一个隐藏参数。
这就能解释为什么通过函数指针调用的形式不能正确进行赋值,因为少传了一个参数。进行如下修改:
#include <iostream>
using namespace std;
class A {
public :
virtual void say(int x) {
cout << "Class A say : " << x << endl;
}
virtual void run() {
cout << "Class A run" << endl;
}
};
class B : public A {
public :
void say(int x) override {
cout << "Class B say" << endl;
}
};
class C : public A {
public :
void run() override {
cout << "Class C run" << endl;
}
};
#define TEST(a) test(a, #a)
void test(A &a, string class_name) {
cout << "object " << class_name << endl;
a.say(123);
a.run();
cout << "======" << endl << endl;
return ;
}
//添加指针参数
typedef void (*func)(void *, int);
int main() {
A a;
B b;
C c;
TEST(a); //正常调用say方法
TEST(b);
TEST(c);
//认为b的存储区中存储着一个个void *的地址
//将b对象存储的地址修改为c对象中存储的地址(即头8个字节)
((void **)(&b))[0] = ((void **)(&c))[0];
TEST(b);
//使用函数指针调用say方法,传入当前对象即b的地址
((func **)(&b))[0][0](&b, 100);
return 0;
}
运行结果:
object a
Class A say : 123
Class A run
======
object b
Class B say
Class A run
======
object c
Class A say : 123
Class C run
======
object b
Class A say : 123
Class C run
======
Class A say : 100 #成功给x赋值
2.5.3 正确理解 “处理流程” 和 “数据”
#include <iostream>
using namespace std;
class A {
public :
A() { x = 200; }
virtual void say(int x) {
cout << "this->x = " << this->x << endl;
cout << "Class A say : " << x << endl;
}
virtual void run() {
cout << "Class A run" << endl;
}
int x;
};
class B : public A {
public :
B() { x = 300; }
void say(int x) override {
cout << "Class B say" << endl;
}
};
class C : public A {
public :
C() { x = 400; }
void run() override {
cout << "Class C run" << endl;
}
};
#define TEST(a) test(a, #a)
void test(A &a, string class_name) {
cout << "object " << class_name << endl;
a.say(123);
a.run();
cout << "======" << endl << endl;
return ;
}
typedef void (*func)(void *, int);
int main() {
A a;
B b;
C c;
TEST(a); //正常调用say方法
TEST(b);
TEST(c);
//认为b的存储区中存储着一个个void *的地址
//将b对象存储的地址修改为c对象中存储的地址(即头8个字节)
((void **)(&b))[0] = ((void **)(&c))[0];
TEST(b);
//使用的是A类中的say方法
((func **)(&b))[0][0](&b, 100); //输出300。this指向的是b对象,所以访问的是B类中的x
return 0;
}
((func **)(&b))[0][0](&b, 100);
代码输出300,虽然调用的是A类的 say
方法,但是此时的 this
指向的是 b
对象,所以输出300。
同理,((func **)(&b))[0][0](&c, 100);
结果为400;((func **)(&b))[0][0](&a, 100);
结果为200。
用这个实验说明:本质上“数据”和“处理流程”是可以分开的。
3. 成员函数指针
问:普通函数指针为什么不能存储成员方法地址?
答:因为成员方法被编译器修饰过,实际上有个默认的隐藏参数 this
指针,而普通的函数指针类型对不上。
普通的函数指针没有 this
的概念,所以就出现了成员函数指针。
#include <iostream>
using namespace std;
class A {
public :
A() { x = 200; }
virtual void say(int x) {
cout << "this->x = " << this->x << endl;
cout << "Class A say : " << x << endl;
}
virtual void run() {
cout << "Class A run" << endl;
}
int x;
};
class B : public A {
public :
B() { x = 300; }
void say(int x) override {
cout << "Class B say" << endl;
}
};
class C : public A {
public :
C() { x = 400; }
void run() override {
cout << "Class C run" << endl;
}
};
#define TEST(a) test(a, #a)
void test(A &a, string class_name) {
cout << "object " << class_name << endl;
a.say(123);
a.run();
cout << "======" << endl << endl;
return ;
}
typedef void (*func)(void *, int);
int main() {
A a;
B b;
C c;
TEST(a); //正常调用say方法
TEST(b);
TEST(c);
//认为b的存储区中存储着一个个void *的地址
//将b对象存储的地址修改为c对象中存储的地址(即头8个字节)
((void **)(&b))[0] = ((void **)(&c))[0];
TEST(b);
//使用的是A类中的say方法
((func **)(&b))[0][0](&b, 100);
//成员方法指针
void (A::*p)(int); //p(A*, )
p = &A::say;
(a.*p)(12345); //调用了A类的成员方法say
return 0;
}
运行结果:
object a
this->x = 200
Class A say : 123
Class A run
======
object b
Class B say
Class A run
======
object c
this->x = 400
Class A say : 123
Class C run
======
object b
this->x = 300
Class A say : 123
Class C run
======
this->x = 300
Class A say : 100
this->x = 200
Class A say : 12345
成员函数指针的应用:随机调用 A 类中的 regx 成员方法
#include <iostream>
using namespace std;
class A {
public :
A() { x = 200; }
virtual void say(int x) {
cout << "this->x = " << this->x << endl;
cout << "Class A say : " << x << endl;
}
virtual void run() {
cout << "Class A run" << endl;
}
void reg1() {
cout << "reg1 function " << endl;
}
void reg2() {
cout << "reg2 function " << endl;
}
void reg3() {
cout << "reg3 function " << endl;
}
int x;
};
int main() {
A a;
//随机调用A类中的regX函数
//成员函数指针数组
void (A::*p[3])();
p[0] = &A::reg1;
p[1] = &A::reg2;
p[2] = &A::reg3;
for (int i = 0; i < 10; i++) {
(a.*p[rand() % 3])();
}
return 0;
}
运行结果:
reg2 function
reg2 function
reg3 function
reg3 function
reg2 function
reg3 function
reg1 function
reg3 function
reg3 function
reg2 function
4. 成员属性指针
定义一个指向 A 类中整型变量的指针:
#include <iostream>
using namespace std;
class A {
public :
A() { this->x = 200; this->y = 400; }
virtual void say(int x) {
cout << "this->x = " << this->x << endl;
cout << "Class A say : " << x << endl;
}
virtual void run() {
cout << "Class A run" << endl;
}
void reg1() {
cout << "reg1 function " << endl;
}
void reg2() {
cout << "reg2 function " << endl;
}
void reg3() {
cout << "reg3 function " << endl;
}
int x, y;
};
int main() {
A a;
int A::*q; //成员属性指针
q = &A::x; //q指向A类的x字段
cout << (a.*q) << endl; //等价于a.x ,结果为200
q = &A::y;
cout << (a.*q) << endl; //400
void (A::*p[3])();//成员函数指针数组,用于随机调用A类中的regX函数
p[0] = &A::reg1;
p[1] = &A::reg2;
p[2] = &A::reg3;
for (int i = 0; i < 10; i++) {
(a.*p[rand() % 3])();
}
return 0;
}
运行结果:
200
400
reg2 function
reg2 function
reg3 function
reg3 function
reg2 function
reg3 function
reg1 function
reg3 function
reg3 function
reg2 function
无论是成员函数指针还是成员属性指针,都可以看成是字段等价替换功能。
5. 析构函数
#include <iostream>
using namespace std;
class A {
public :
A() {
cout << "A constructor" << endl;
}
//virtual void say() {
// cout << "Class A" << endl;
//}
~A() {
cout << "A destructor " << endl;
}
};
class B : public A {
public :
B() {
cout << "B constructor" << endl;
}
//virtual void say() {
// cout << "Class B" << endl;
//}
~B() {
cout << "B destructor " << endl;
}
};
class C : public A {
public :
C() {
cout << "C constructor" << endl;
}
//virtual void say() {
// cout << "Class C" << endl;
//}
~C() {
cout << "C destructor " << endl;
}
};
int main() {
srand(time(0));
A *p;
switch (rand() % 2) {
case 0: p = new B(); break;
case 1: p = new C(); break;
}
delete p;
//cout << dynamic_cast<B *>(p) << endl;
//cout << dynamic_cast<C *>(p) << endl;
return 0;
}
运行结果:
A constructor
B constructor
A destructor
可以发现,析构的时候,B类对象并没有被析构,说明在这种情况下,没有对B类对象进行正确的析构。
本质原因还是普通的成员方法是跟着类 走的,delete p;
会调用 p 指向对象的析构函数,但是现在无论哪个类中的析构函数都是普通成员方法,而目前 p 是 A 类的指针,所以只调用了 A 类的析构函数。
通常情况下,在这种具有继承关系的类的设计,析构函数一定要设计为虚函数。
修改析构函数为虚函数:
#include <iostream>
using namespace std;
class A {
public :
A() {
cout << "A constructor" << endl;
}
//virtual void say() {
// cout << "Class A" << endl;
//}
virtual ~A() {
cout << "A destructor " << endl;
}
};
class B : public A {
public :
B() {
cout << "B constructor" << endl;
}
//virtual void say() {
// cout << "Class B" << endl;
//}
virtual ~B() {
cout << "B destructor " << endl;
}
};
class C : public A {
public :
C() {
cout << "C constructor" << endl;
}
//virtual void say() {
// cout << "Class C" << endl;
//}
virtual ~C() {
cout << "C destructor " << endl;
}
};
int main() {
srand(time(0));
A *p;
switch (rand() % 2) {
case 0: p = new B(); break;
case 1: p = new C(); break;
}
delete p;
//cout << dynamic_cast<B *>(p) << endl;
//cout << dynamic_cast<C *>(p) << endl;
return 0;
}
运行结果:
A constructor
B constructor
B destructor
A destructor
假设现在有如下需求:1、使用 dynamic_cast 的功能;2、设计的类中没有任何虚函数。
基于以上关于析构函数的分析,也解决了类中没有虚函数的时候,还想调用 dynamic_cast
的需求。
根据C++设计原则,只要存在继承关系,析构函数就一定是虚函数,所以只要存在继承关系,大概率就可以使用 dynamic_cast
,因为一定存在虚函数表,最起码析构函数是虚函数。
6. 纯虚函数
纯虚函数:当前类不关心该函数的实现到底是什么样的,但是派生类中会实现该函数。
纯虚函数的性质:不实现纯虚函数的子类无法实例化(即创建对象)
#include <iostream>
using namespace std;
class Animal {
public :
Animal(string name) : name(name) {}
virtual void run() = 0; //纯虚函数,没有任何实现
virtual void getName() final {}
virtual void fl1() {}
private :
string name;
};
class Cat : public Animal {
public :
Cat(string name) : Animal(name) {}
void run() override final {
cout << "I can run with four legs" << endl;
}
void fl1() override {}
};
class People : public Animal {
public :
People(string name) : Animal(name) {}
void run() {
cout << "I can run with two leges" << endl;
}
};
class Bat : public Animal {
public :
Bat(string name) : Animal(name) {}
void run() {
cout << "I can fly" << endl;
}
};
class Horse : public Animal {
public :
Horse() : Animal("Horse") {}
};
int main() {
return 0;
}
程序中的 Horse
类虽然没有实现父类 Animal
中的纯虚函数 run
,但是可以通过编译,原因在于 Horse
类没有进行实例化。
如果在 main
函数中创建 Horse
类的对象:
int main() {
Horse h;
return 0;
}
程序就会报错,因为 Horse
类中有一个未实现的纯虚函数。这就是在强制我们实现父类的纯虚函数:
class Animal {
public :
Animal(string name) : name(name) {}
virtual void run() = 0; //纯虚函数,没有任何实现
virtual void getName() final {}
virtual void fl1() {}
private :
string name;
};
class Horse : public Animal {
public :
Horse() : Animal("Horse") {}
void run() override {
cout << "Horse, I can run with four leges" << endl;
}
};
int main() {
Horse h;
return 0;
}
纯虚函数的作用:用来规定子类中必须实现的方法。
延伸出一个概念 接口,接口只规定了对外的表现形式,没有规定具体的功能。纯虚函数一般是用来定义接口的。
接口应用:分别用数组和堆实现优先队列
#include <iostream>
#include <vector>
using namespace std;
//实现优先队列
class IQueue {
public :
virtual void push(int) = 0;
virtual void pop() = 0;
virtual bool empty() = 0;
virtual int top() = 0;
virtual int size() = 0;
};
//用数组实现优先队列
class vector_queue : public IQueue, public vector<int> {
public :
void push(int x) override {
//加不加前缀修饰都可以
//也可以直接写成:push_back(x)
this->vector<int>::push_back(x);
return ;
}
void pop() override {
if (empty()) return ;
vector<int>::iterator p = this->begin();
//找到最大值的位置
for (auto iter = begin(); iter != end(); iter++) {
if (*iter > *p) p = iter;
}
//删除最大值
erase(p);
return ;
}
bool empty() override {
return size() == 0;
}
int top() override {
if (empty()) return 0;
int ans = at(0);
for (int i = 1; i < size(); i++) {
ans = max(at(i), ans);
}
return ans;
}
int size() override {
return this->vector<int>::size();
}
};
//用堆实现优先队列
class heap_queue : public IQueue, public vector<int> {
public :
void push(int x) override {
push_back(x);
up_maintain(size());
return ;
}
void pop() override {
std::swap(at(0), at(size() - 1));
pop_back();
down_maintain(1);
return ;
}
bool empty() override {
return size() == 0;
}
int top() override {
if (empty()) return 0;
return at(0);
}
int size() override {
return this->vector<int>::size();
}
private:
void up_maintain(int ind) {
while (ind > 1 && at(ind - 1) > at((ind / 2) - 1)) {
std::swap(at(ind - 1), at((ind / 2) - 1));
ind /= 2;
}
return ;
}
void down_maintain(int ind) {
while (ind * 2 <= size()) {
int temp = ind;
if (at(ind * 2 - 1) > at(temp - 1)) temp = ind * 2;
if (ind * 2 + 1 <= size() && at(ind * 2) > at(temp - 1)) temp = ind * 2 + 1;
if (temp == ind) break;
std::swap(at(temp - 1), at(ind - 1));
ind = temp;
}
return ;
}
};
int main() {
srand(time(0));
vector_queue q1;
heap_queue q2;
for (int i = 0; i < 10; i++) {
int val = rand() % 100;
q1.push(val);
cout << "push q1 : " << val << endl;
}
while (!q1.empty()) {
cout << q1.top() << " ";
q1.pop();
}
cout << endl;
for (int i = 0; i < 10; i++) {
int val = rand() % 100;
q2.push(val);
cout << "push q2 : " << val << endl;
}
while (!q2.empty()) {
cout << q2.top() << " ";
q2.pop();
}
cout << endl;
return 0;
}
运行结果:
可见,接口屏蔽了内部实现细节。
7. 抽象类
拥有纯虚函数的类无法生成对象,将这些类叫做抽象类,通常用来实现接口。
class Animal { //抽象类,无法生成对象
public :
Animal(const string &name) : __name(name) {}
virtual void run() = 0;
protected :
string __name;
};
class Cat : public Animal {
public :
Cat() : Animal("cat") {}
void run() override {
cout << "I can run with 4 legs" << endll
}
};
8. 小结
virtual 关键字
语义:子类的这个方法可能会跟父类的有所不同
成员方法调用时:
virtual
关键字的方法跟着【对象】- 非
virtual
关键字的方法跟着 【类】
限制:不能用来修饰【类方法 - static
】
纯虚函数
语义:子类肯定会有这个方法,而父类只能说「抱歉」
应用场景:定义接口
相关文章
- C++学习5-面向对象编程基础(构造函数、转换构造、静态数据成员、静态成员函数、友元)
- C++ 类
- C++和C中的函数如何相互调用
- C++函数模板
- (笔试题)关于C++的虚函数和多态性
- (C++)浅谈多态基类析构函数声明为虚函数
- C语言/C++常见习题问答集锦(十六)
- 成功解决基于VS2015(Visual Studio2015)编写C++程序调试时弹出窗口一闪而过的问题
- c++冒泡排序
- 解答私信@被c++折磨头秃的花季美少女 //C++ 利用指针数组输入10个单词,编写函数对10个单词进行排序并输出,要求判断是否有相同的单词,如果有相同的单词在输出时该单词只输出一次。
- C++20新特性全在这一张图里了
- C++类中创建线程
- c++多态和虚函数表实现原理
- C/C++,SIGNAL和SIGSET函数
- C++“窗体”程序设计启蒙(之二)
- C++11 std::function函数包装器
- C++中虚函数多态实现的原理刨析
- 【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表