1. 重写
针对虚函数,子类可以重写父类中声明的虚函数。
如果用父类的指针或引用来调用函数,那么会检查这个函数的静态类型是否被声明为virtual:
- 如果是,那么将调用这个函数的动态类型的版本。所谓动态类型的版本,就是如果子类进行了重写,那么就是用重写后的版本, 如果没有重写,那么还是父类的版本。例如:
#include <iostream>
class Animal {
public:
virtual void eat() {
std::cout << "animal eat" << std::endl;
}
};
class Dog : public Animal {
public:
void eat(std::string food) {
std::cout << "dog eat " << food << std::endl;
}
};
int main() {
Animal* ani = new Dog();
/*
ani是父类指针,eat()是virtual的,那么将调用动态版本:
但是子类并没有重写eat(),所以子类的eat()依然是父类版本。
因此打印的是animal eat。
这里并不会产生名字隐藏,因为我们是通过父类指针来调用的:
首先进行名字查找的作用域是在父类,而不是子类,所以不存在名字隐藏的问题。
*/
ani->eat();
}
- 否则,将调用这个函数的静态类型的版本。例如:
#include <iostream>
class Animal {
public:
void run() {
std::cout << "animal run" << std::endl;
}
};
class Dog : public Animal {
public:
void run() {
std::cout << "dog run" << std::endl;
}
};
int main() {
Animal* ani = new Dog();
// 从静态类型作用域中进行名字查找,找到run,但是并没有virtual修饰:
// 于是调用静态类型的版本
ani->run(); // 打印aniaml run ,而不是 dog run
}
2. 重载
针对同一个作用域下,函数名字相同但是函数签名不同的函数,编译器会根据调用时的实参的个数、类型来自动匹配合适的函数。
3. 隐藏
名字隐藏的现象(定义):内层作用域的名字会导致外层作用域的同名对象不可见。
名字隐藏的本质原因是:名字查找先于类型检查。
注意,子类是父类的内层作用域。
典型的例子是:
- 父类中定义了一个函数void eat();
- 子类中也定义了一个函数void eat(std::string food);
- 那么子类在调用eat的时候,必须传入参数food,否则将编译不过。这是因为,编译先进行名字查找,查到了内层作用域中有eat,于是就无视了父类中的eat。然后进行类型检查,此时会发现类型不匹配。
#include <iostream>
class Animal {
public:
void eat() {
std::cout << "animal eat" << std::endl;
}
};
class Dog : public Animal {
public:
void eat(std::string food) {
std::cout << "dog eat " << food << std::endl;
}
void print() {
// this->eat(); // 编译不过,eat()被隐藏了
Animal::eat(); // 可以编译过。
}
};
int main() {
Dog d;
// d.eat(); // 错误,编译不过。父类eat被编译器无视了。
d.eat("shi");
}