大家好,我是阿Q。
最近几天给大家整理了一些面经,将一些在面试过程中遇到的问题给大家进行了总结,并给出了我自己的一些建议,希望大家在看的过程中有点收获。
今天跟大家一起学习C++的虚函数。
信息隐藏:封装可以将类的内部细节隐藏起来,不暴露给外部,提高了安全性和防止误用。 简化接口:封装通过提供清晰的接口简化了类的使用,使用者只需关注如何使用接口而不需要了解内部实现。 提高可维护性:内部实现的修改不会影响外部使用者,从而提高了代码的可维护性。
#include <iostream>#include <string>class Student {private:std::string name;int age;public:// 构造函数Student(const std::string& n, int a) : name(n), age(a) {}// 获取姓名std::string getName() const {return name;}// 设置年龄void setAge(int a) {if (a >= 0) {age = a;}}// 显示学生信息void displayInfo() const {std::cout << "Name: " << name << ", Age: " << age << std::endl;}};int main() {Student student("Alice", 20);// 使用公有接口获取和设置信息student.setAge(21);std::cout << "Student Name: " << student.getName() << std::endl;student.displayInfo();return 0;}
代码重用:继承允许在不重复编写代码的情况下扩展和修改现有类,提高了代码的重用性。 层次结构:继承可以创建类的层次结构,使得代码更有组织性和可扩展性。 多态性支持:继承是多态性的基础,通过基类指针或引用调用派生类的方法实现多态行为。
#include <iostream>#include <string>// 基类class Animal {protected:std::string name;public:Animal(const std::string& n) : name(n) {}void eat() {std::cout << name << " is eating." << std::endl;}};// 派生类class Dog : public Animal {public:Dog(const std::string& n) : Animal(n) {}void bark() {std::cout << name << " is barking." << std::endl;}};int main() {Dog myDog("Buddy");myDog.eat(); // 继承自基类myDog.bark(); // 派生类自己的方法return 0;}
编译时多态性(静态多态性):通过函数重载实现,编译器在编译时根据函数参数的类型和数量来选择调用合适的函数。这种多态性是在编译时解析的。 运行时多态性(动态多态性):通过虚函数和继承实现,允许在运行时根据对象的实际类型来调用适当的函数。这种多态性是在运行时解析的。
灵活性:多态性允许在不同的情境下以通用的方式处理不同类型的对象,提高了代码的灵活性。 可扩展性:可以轻松地添加新的派生类而不影响现有的代码,增加了系统的可扩展性。 简化接口:多态性简化了代码的接口,允许使用者按统一的方式与不同类型的对象交互。
#include <iostream>#include <vector>class Shape {public:virtual void draw() {std::cout << "Drawing a shape." << std::endl;}};class Circle : public Shape {public:void draw() override {std::cout << "Drawing a circle." << std::endl;}};class Square : public Shape {public:void draw() override {std::cout << "Drawing a square." << std::endl;}};int main() {std::vector<Shape*> shapes;shapes.push_back(new Circle());shapes.push_back(new Square());for (Shape* shape : shapes) {shape->draw(); // 多态性:根据对象的实际类型调用适当的方法}// 释放内存for (Shape* shape : shapes) {delete shape;}return 0;}
class A {public:virtual void v_a(){}virtual ~A(){}int64_t _m_a;};int main(){A* a = new A();return 0;}

首先在主函数的栈帧上有一个 A 类型的指针指向堆里面分配好的对象 A 实例。 对象 A 实例的头部是一个 vtable 指针,紧接着是 A 对象按照声明顺序排列的成员变量。(当我们创建一个对象时,便可以通过实例对象的地址,得到该实例的虚函数表,从而获取其函数指针。) vtable 指针指向的是代码段中的 A 类型的虚函数表中的第一个虚函数起始地址。 虚函数表的结构其实是有一个头部的,叫做 vtable_prefix ,紧接着是按照声明顺序排列的虚函数。 注意到这里有两个虚析构函数,因为对象有两种构造方式,栈构造和堆构造,所以对应的,对象会有两种析构方式,其中堆上对象的析构和栈上对象的析构不同之处在于,栈内存的析构不需要执行 delete 函数,会自动被回收。 typeinfo 存储着 A 的类基础信息,包括父类与类名称,C++关键字 typeid 返回的就是这个对象。 typeinfo 也是一个类,对于没有父类的 A 来说,当前 tinfo 是 class_type_info 类型的,从虚函数指针指向的vtable 起始位置可以看出。
class Panent{public:virtual void A(){cout<<"Panent::A"<<endl;}virtual void B(){cout<<"Panent::B"<<endl;}virtual void C(){cout<<"Panent::C"<<endl;}};

class Children: public Panent{public:virtual void A(){cout<<"Children::A"<<endl;}virtual void B1(){cout<<"Children::B1"<<endl;}virtual void C1(){cout<<"Children::C1"<<endl;}}




进入到构造函数体之前。在这个阶段如果存在虚函数的话,虚表指针被初始化。如果存在构造函数的初始化列表的话,初始化列表也会被执行。 进入到构造函数体内。这一阶段是我们通常意义上说的构造函数。

文章转载自阿Q正砖,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




