静态联编和动态联编
编译器对非虚函数使用静态联编,对虚函数使用动态联编
静态联编是指在编译时根据指针类型已知需要调用哪个函数,这在定义指针时就已经确定,而动态联编是指根据父类指针指向的不同的子类来调用不同的子类函数,这是两者最大的区别。
虚函数与虚函数表
代码:
#include <bits/stdc++.h>
using namespace std;
class A1 {
public:
virtual void T1() {
cout << "A1:T1 is called\n";
}
virtual void T2() {
cout << "A1:T2 is called\n";
}
};
class A2 {
public:
virtual void T1() {
cout << "A2:T1 is called\n";
}
virtual void T2() {
cout << "A2:T2 is called\n";
}
};
class B: public A1, public A2 {
public:
virtual void T1() {
cout << "B:T1 is called\n";
}
virtual void T3() {
cout << "B:T3 is called\n";
}
};
int main() {
B *t1 = new B();
A1 *t2 = t1;
A2 *t3 = t1;
t1->T1();
t1->A1::T2();
t1->A2::T2();
t1->T3();
t2->T1();
t2->T2();
t3->T1();
t3->T2();
return 0;
}
这里的虚函数表为
A1
A1:T1() A1:T2()
A2:
A2:T1() A2:T2()
B:
A1: B:T1() A1:T2() B:T3()
A2: B:T1() A2:T2()
结果如下
B:T1 is called
A1:T2 is called
A2:T2 is called
B:T3 is called
B:T1 is called
A1:T2 is called
B:T1 is called
A2:T2 is called
要点:
1.每个父类都有自己的虚表,子类会继承父类的虚表
2.子类的成员函数被放到了第一个父类的表中
3.覆盖的新函数被放到了虚表中原来父类虚函数的位置
4.有虚函数的类有一个8位大小的虚指针来指向虚表,使用虚函数会让对象增大,且每次调用函数都要在表中查找地址,效率较低
5.构造函数不能定义为虚函数
虚基类
代码
#include <bits/stdc++.h>
using namespace std;
class A {
public:
A(int aa) {
a = aa;
cout << "A is constructed\n";
cout << a << '\n';
}
int a;
};
class A1: virtual public A {
public:
A1(int aa): A(aa) {
a += 5;
cout << "A1 is constructed\n";
cout << a << '\n';
}
};
class A2: virtual public A {
public:
A2(int aa): A(aa) {
a += 10;
cout << "A2 is constructed\n";
cout << a << '\n';
}
};
class B: public A {
public:
B(int aa): A(aa) {
a += 100;
cout << "B is constructed\n";
cout << a << '\n';
}
};
class C: public A1, public A2, public B {
public:
C(int aa): A(aa), A1(aa), A2(aa), B(aa) {
cout << "C is constructed\n";
cout << "A::a=" << A1::a << '\n';
cout << "B::a=" << B::a << '\n';
}
};
int main() {
C *t1 = new C(1);
return 0;
}
A1,A2是继承A的虚基类,这导致C继承A1,A2时只会继承一次A类,而B不是虚基类,所以B类会再一次继承A类(也就是现在C有两个变量:A的a,和B从A继承的a)
运行结果
A is constructed
1
A1 is constructed
6
A2 is constructed
16
A is constructed
1
B is constructed
101
C is constructed
A::a=16
B::a=101
要点:
1.对同一个虚基类的构造函数只调用一次,且是在第一次出现时调用
2.整个继承结构中,所有直接或间接的派生类都必须在构造函数的成员初始化表中列出对虚基类构造函数的调用,以初始化在虚基类中定义的数据成员(不能像普通继承一样层层传递)
3.同一层次中同时包含虚基类和非虚基类,应先调用虚基类的构造函数,再调用非虚基类的构造函数,最后调用派生类构造函数
抽象类和纯虚函数
在成员函数的形参后面写上=0,则成员函数为纯虚函数
拥有纯虚函数的类一般叫做抽象类,其函数的实现方法由其子类实现,目的是为一个类族提供公共接口(可以理解为大家都拥有的个性)
#include <bits/stdc++.h>
using namespace std;
class shape {
public:
virtual void showname() = 0;
virtual void area() = 0;
};
class circle : public shape {
public:
void showname() {
cout << "i am circle\n";
}
virtual void area() {
cout << "area=" << 3.1415926 * r *r << '\n';
};
circle(int x) {
r = x;
}
private:
int r;
};
class square: public shape {
public:
void showname() {
cout << "i am square\n";
}
void area() {
cout << "area=" << r *r << '\n';
};
square(int x) {
r = x;
}
private:
int r;
};
int main() {
shape *t1 = new circle(2);
shape *t2 = new square(2);
t1->showname();
t1->area();
t2->showname();
t2->area();
return 0;
}
结果如下
i am circle
area=12.5664
i am square
area=4
注意:
1.纯虚函数没有函数体
2.当类声明中拥有纯虚函数时不能创建该类对象,只能把它用作基类