引入《C++ Primer》P534的概念
静态类型:编译时已知的类型,变量声明或表达式生成时的类型
动态类型: 变量或者表达式在内存中的对象类型
#include <iostream>
using namespace std;
class A {
public:
void f() {cout << "A\n";}
};
class B :public A {
public:
B() {}
void f() {cout << "B\n";}
};
int main()
{
A a = B();
a.f();//输出A
}
C++中:
B继承A,B对象向上转型变成A类型对象时,如果调用同名函数(和Java的方法重写有区别),则只能调用A类的函数,因为a被当成了A类型的对象(静态类型是A)
如果被定义为了A类类型还想使用B类的方法,一种办法是声明f()为虚函数,采用指针或者引用调用方法的形式,晚期联编确定调用函数
下面是Java中重写基类函数的调用情况,和java有区别!!
package XXX;
class ABC{
void f() {
System.out.println("A");
}
}
class BD extends ABC{
void f() {
System.out.println("B");
}
}
public class Test
{
public static void main(String[] args)
{
ABC a=new BD();
a.f();//输出B
}
}
说明:
Java中:如果是上转型对象,则调用B类的重写的方法,有点类似于接口回调
C++中的方法重写的另外一种情况:
#include <iostream>
using namespace std;
class A {
public:
void f() {
cout << "A\n";
}
};
class B :public A {
public:
B() {
}
void f() {
cout << "B\n";
}
};
int main()
{
B b = B();
b.f();//输出B
b.A::f();//输出A
std::cout << "Hello World!\n";
}
如果代码写成:
A b = B();/*即使你b的动态类型是B,但是它被“切片”了,A的复制构造函数把B的基类部分复制了一下,所以b中只有A类的函数和成员*/
b.B::f();//编译报错,因为A类中没有B类方法f,(静态类型是A且A不是B的派生类)
但是写成
B b=B();/*#3*/
b.A::f();/*静态类型是B但是B类继承了A类的f()所以调用A::f()可以*/
下面动态联编也不起作用,因为A中没有f
class A{};
class B:public A{
public:
void f(){cout << "B\n";}
};
int main(){
A* a = &B();
a->f();//编译报错
}
a的类型已经是A,所以A中没有f,即使你动态联编,在a中也查找不到f,此时编译报错
动态联编的前提是
类中有这个方法才能进行动态联编
稍微修改一下:
就🆗了
#include <iostream>
using namespace std;
class A {
public:
void f() {
cout << "A\n";
}
};
class B :public A {
public:
};
int main() {
B* a = &B();
a->A::f();//编译通过
}
名称查找规则
在派生类的函数中访问变量或者用对象变量访问函数/成员变量,先在派生类中查找,如果找不到再依次上溯所有父类查找
最后还是找不到再编译报错
所以说
如果你定义的是基类指针,但是调用是调用派生类独有的函数,那么动态联编就起作用前
就已经编译报错了,因为根据名称查找规则,这仅仅在对派生类有效(基类不能向派生类中查找,反过来可行)
总结
根据指针/引用的静态类型
①指针是基类,调用基类函数直接调用,若调用子类方法不能调用,除非声明该函数是虚函数
②指针是派生类,调用基类函数需要用“基类::方法名”调用,若调用派生类,则直接调用即可