目录
前言
在面向对象编程中,重载、重写和隐藏是三种不同的概念,通常用于方法或函数的定义。它们的主要区别在于应用的场景、规则,以及发生的时间点。
- 重载(Overloading):在同一个作用域中,多个同名函数,参数列表不同,返回类型可以相同或不同。常用于提供不同的参数组合的多种实现。
- 重写(Overriding):子类重新实现父类的虚函数,函数签名必须相同,实现动态多态性。
- 隐藏(Hiding):子类定义了一个与父类同名但非虚函数的函数,函数可能参数不同。子类的函数隐藏了父类的函数,静态绑定在编译时决定调用哪个版本。
1. 重载(Overloading)
重载是指在同一个作用域内,定义多个同名的函数或方法,但它们的参数列表不同(参数的个数或类型不同),以实现不同的功能。重载的主要特征是函数名相同,参数列表不同,与返回类型无关。
特点:
- 同名函数:函数名相同。
- 参数列表不同:参数的类型、数量或顺序不同。
- 与返回值无关:重载不依赖于返回类型。
示例:
class Math {
public:
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
};
在这个例子中,函数 add
被重载了三次。尽管函数名相同,但参数的类型和数量不同,因此编译器能够根据传入参数的类型和数量来选择正确的函数。
使用场景:
重载通常用于当同一个操作可以对不同类型或不同数量的参数执行时,让代码更加简洁和易于理解。
2. 重写(Overriding)
重写是指在子类中重新定义从父类继承的函数,以改变其行为。重写的前提是父类的函数必须是虚函数(在 C++ 中使用 virtual
关键字),子类通过相同的函数签名(包括函数名、参数和返回类型)来重新实现该函数。
特点:
- 继承关系:必须有继承关系,子类重写父类的虚函数。
- 函数签名相同:函数名、参数列表、返回类型必须与父类中的函数相同。
- 动态多态性:通过指向父类的指针或引用来调用重写的函数时,执行的是子类的实现(基于动态绑定)。
示例:
class Animal {
public:
virtual void sound() {
std::cout << "Animal sound" << std::endl;
}
};
class Dog : public Animal {
public:
void sound() override {
std::cout << "Bark" << std::endl;
}
};
在这个例子中,Dog
类重写了 Animal
类中的 sound
函数。尽管父类和子类都有 sound
函数,但调用 Dog
的 sound
函数时,会输出 Bark
,这是子类对父类方法的重写。
使用场景:
重写通常用于实现动态多态性,即允许子类提供不同的实现以替代父类的方法,特别是在设计抽象类或接口时非常常见。
3. 隐藏(Hiding)
隐藏是指在子类中定义了一个与父类同名的函数,但该函数不是重写父类的虚函数,通常是由于没有使用 virtual
关键字。此时,子类的函数会隐藏父类的同名函数,不管参数是否相同。调用隐藏的函数时,函数的选择依赖于指针或对象的静态类型(即编译时的类型),而不是动态类型。
特点:
- 继承关系:同样发生在继承关系中,但与重写不同的是,父类的函数不是虚函数,或者子类定义的函数与父类函数签名不同。
- 同名但不覆盖:子类的函数与父类的同名函数不会相互覆盖,而是隐藏父类的函数。
- 静态绑定:根据对象的静态类型调用函数,而不是动态类型。
示例:
class Base {
public:
void display() {
std::cout << "Base class display" << std::endl;
}
};
class Derived : public Base {
public:
void display() { // 非虚函数,隐藏了基类的 display
std::cout << "Derived class display" << std::endl;
}
};
在这个例子中,Derived
类中的 display
函数隐藏了 Base
类的同名函数。即使通过 Derived
类的对象调用 display
,父类的 display
函数也不会被调用,除非通过显式调用 Base::display()
。
使用场景:
函数隐藏一般是无意发生的,可能会导致意想不到的行为,因此通常不推荐使用。可以通过显式指定作用域来调用被隐藏的函数,例如 Base::display()
。