C++ 是如何实现面向对象编程的
面向对象的三个特性
- 封装:将对象的状态(数据成员)和行为(成员函数)捆绑在一起,并控制对它们的访问。封装的主要目的是隐藏类的内部实现细节,并仅暴露必要的接口供外部使用。这样可以保护数据,防止外部代码意外或恶意地改变对象的状态。
- 数据成员可以被声明为 private、protected 或 public。
- 成员函数通常也是 public 的,以便用户可以调用这些函数来操作对象。
- 使用 private 和 protected 修饰符可以限制对数据成员的直接访问。
- 继承:允许创建新的类(称为派生类或子类)从现有的类(称为基类或父类)继承属性和方法。这样可以复用代码,同时也可以扩展或修改基类的行为。
- 派生类可以继承基类的所有非私有成员。
- 派生类可以添加新的成员变量和方法,或者重写基类的方法。
- 继承支持层次化的分类结构,便于建模现实世界的关系
- 多态: 允许使用一个接口来表示多种类型的特性。在 C++ 中,多态通常是通过 虚函数 (virtual function)来实现的。当一个函数被声明为虚函数时,可以在派生类中重写该函数,并且可以通过基类的指针或引用调用派生类中的重写版本。
- 支持运行时多态性(runtime polymorphism),即可以在运行时确定调用哪个版本的函数。
- 通常与虚函数和接口有关
虚函数 和 纯虚函数
虚函数的实现机制
C++ 中多态的实现原理
多态是通过虚函数实现的,虚函数的地址保存在虚函数表中,虚函数表的地址保存在含有虚函数的类的实例对象的内存空间中。
重载 | 重写 | 隐藏
- 函数重载: 同一可访问区 内被声明几个具有 不同 参数列(参数的类型、个数、顺序)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。
- 函数重写(覆盖): 派生类中存在 重新定义 的函数。函数名、参数列表、返回值类型都必须同基类中被重写的函数一致,只有函数体不同。派生类调用时会调用派生类的重写函数,不会调用被重写函数。重写的基类中被重写的函数 必须 有
virtual修饰。 - 函数隐藏: 在面向对象编程中,特别是在 C++ 中,当一个派生类(derived class)中定义了一个与基类(base class)中相同名称的成员函数时,如果没有特别标记为
virtual,那么派生类中的成员函数就会“隐藏”掉基类中的同名成员函数。这意味着当你通过派生类的对象调用这个函数时,调用的是派生类中的版本,而不是基类中的版本。
class Base
{
public:
void fun(int tmp, bool tag ) { cout << "Base::fun(int tmp, bool tmp1)" << endl; }
virtual void vir_fun(int* array) { cout << "Base::vir_fun(int* array)" << endl; }
}
class Derived: public Base
{
public:
void fun(int tmp); // 隐藏基类中的同名函数
void fun(float tmp); // 重载 参数类型不同(相对于上一个函数)
void fun(int tmp, float tmp1); // 重载 参数个数不同(相对于上一个函数)
void fun(float tmp, int tmp1); // 重载 参数顺序不同(相对于上一个函数)
int fun(int tmp); // error: 'int A::fun(int)' cannot be overloaded 错误:注意重载不关心函数返回类型
virtual void vir_fun(int* array) { cout << "Derived::vir_fun(int* array)"<< endl; }
};
int main()
{
Derived ex;
ex.fun(1); // Derive::fun(int tmp)
ex.fun(1, false); // error: candidate expects 1 argument, 2 provided
ex.fun.Base.fun(1, true); // Base::fun(int bool)
Base *base = new Derived();
base->vir_fun({1,3,4}); // Derived::vir_fun(int* array)
return 0;
}
重载和重写的区别
- 范围区别:对于作用函数的 空间 而言,重载发生在同一个类的内部,重写发生在不同的类之间(子类和父类之间)。
- 参数区别:重载的函数需要与原函数有相同的函数名、不同的参数列表,不关注函数的返回值类型;重写的函数的函数名、参数列表和返回值类型都必须和原函数相同,父类中被重写的函数需要有 virtual 修饰。
- virtual:重写的函数基类中必须有 virtual 关键字的修饰,重载的函数可以有 virtual 关键字的修饰也可以没有(取决于重载的函数是否被派生)。
隐藏和重写, 重载的区别
- 范围区别:隐藏与重载 范围不同,隐藏发生在不同类中。
- 参数区别:隐藏函数和被隐藏函数参数列表可以相同,也可以不同,但函数名一定相同;当参数不同时,无论基类中的函数是否被 virtual 修饰,只要派生类中有一个同名函数, 基类函数都是被隐藏,而不是重写。
- 利用重写可以实现多态,而隐藏不可以: 如果使用基类指针 p 指向派生类对象,利用这个指针调用函数时,对于隐藏的函数,会根据指针的类型去调用函数;对于重写的函数,会根据指针所指对象的类型去调用函数。重写必须使用 virtual 关键字,此时会更改派生类虚函数表的表项。
- 隐藏是发生在编译时,即在编译时由编译器实现隐藏,而重写一般发生运行时,即运行时会查找类的虚函数表,决定调用函数接口