八股文整理C++

1. 如何理解CPP的多态?

多态有如下优势:

1. 增加代码复用: 让派生类不需要重复基类的代码逻辑。

2. 动态绑定: 用同一个函数,基于派生类的不同实现,去调用同一名称的方法,

class Shape {
public:
    virtual void draw() const = 0; // 纯虚函数
};

class Circle : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing Circle" << std::endl;
    }
};

class Rectangle : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing Rectangle" << std::endl;
    }
};

void render(const Shape& shape) {
    shape.draw(); // 多态调用
}

2. 如何理解动态绑定(dynamic binding)

动态绑定(Dynamic Binding),又称为"运行时绑定"或 "后期绑定",是C++中多态性实现的核心机制之一。动态绑定指的是在程序运行时,依据对象的实际类型来决定调用哪个成员函数,而不是在编译时决定。这与静态绑定(Static Binding)不同,静态绑定是在编译阶段就确定了函数调用的具体实现。

动态绑定的工作原理

动态绑定主要通过虚函数表(vtable)和虚函数指针(vptr)来实现。以下是其工作原理的详细介绍:

1. 虚函数表(vtable)
   - 每个包含虚函数的类在编译时都会生成一张虚函数表,表中存储着该类所有虚函数的地址。
   - 派生类会继承基类的虚函数表,并且如果派生类重写了基类中的虚函数,虚函数表中的对应位置就会存放派生类的虚函数地址。

2. 虚函数指针(vptr)
   - 每个包含虚函数的对象实例都会有一个指向虚函数表的指针(即vptr),这个指针在对象创建时自动初始化,指向该对象所属类的虚函数表。
   - 当通过基类指针或引用调用虚函数时,程序会使用这个虚函数指针找到相应的虚函数表,然后根据表中的函数地址进行函数调用。

3. 运行时决策
   - 当通过基类指针或引用调用虚函数时,具体调用哪个函数在运行时决定。这是因为程序根据对象的类型通过vptr找到正确的vtable,并从中获取实际应调用的函数地址。
   - 这种机制使得C++能够实现运行时多态,即使你在编写代码时使用的是基类类型,但在运行时调用的是派生类的函数。

示例代码
以下是一个简单的示例,展示了动态绑定的实际工作方式:


#include <iostream>

class Base {
public:
    virtual void show() const {
        std::cout << "Base class show function" << std::endl;
    }
    virtual ~Base() = default; // 虚析构函数,确保正确释放派生类对象
};

class Derived : public Base {
public:
    void show() const override {
        std::cout << "Derived class show function" << std::endl;
    }
};

int main() {
    Base* basePtr = new Derived(); // 基类指针指向派生类对象
    basePtr->show(); // 调用派生类的 show() 函数,动态绑定在这里发生

    delete basePtr; // 正确调用派生类的析构函数
    return 0;
}

运行时行为
- 在这个例子中,`basePtr` 是一个 `Base` 类型的指针,但它指向的是 `Derived` 类的对象。调用 `basePtr->show()` 时,虽然 `basePtr` 类型为 `Base*`,但由于动态绑定,程序在运行时会调用 `Derived` 类的 `show()` 方法,而不是 `Base` 类的 `show()` 方法。
  
动态绑定的优点
1. 实现运行时多态:通过动态绑定,程序可以处理不同类型的对象,而不需要在编译时知道对象的实际类型。这使得程序更加灵活。
  
2. 简化代码:动态绑定允许通过基类接口处理不同类型的对象,避免了复杂的条件语句和手动类型检查。

3. 支持框架和库的可扩展性:开发者可以通过继承和重写虚函数,扩展库或框架的功能,而不需要修改库或框架的核心代码。

需要注意的地方
- 动态绑定带来的灵活性同时也可能会带来一定的性能开销,特别是在大量频繁调用虚函数的情况下,因为每次函数调用都需要通过vptr和vtable进行查找。
- 虚函数的使用需要合理设计,过度使用可能导致代码复杂性增加。

总结
动态绑定是C++中实现多态的关键机制,通过在运行时决定函数调用,动态绑定使得面向对象编程中的多态性成为可能。它提高了程序的灵活性和可扩展性,但在设计时需要权衡性能和可维护性。

3. volatile关键字有什么用,常用于什么地方

volatile 是 C 和 C++ 编程语言中的一个关键字,用于告诉编译器被声明的变量可能会在程序的其他部分被异步地修改。这意味着编译器不能对这个变量进行某些优化操作,因为它无法假设变量的值在某个时间点是确定的。以下是对 `volatile` 关键字用途和常见应用场景的详细介绍:

1. 用途
   - 防止编译器优化:编译器通常会对代码进行优化,例如将某些变量的值缓存到寄存器中,而不每次都从内存读取。如果某个变量被声明为 `volatile`,编译器就会被告知这个变量可能随时在外部被修改,因此每次使用时都需要从内存读取最新的值,而不是使用缓存值。

 2. 常见应用场景

   - 硬件寄存器访问:
     当你在编写驱动程序或嵌入式系统代码时,常常需要直接与硬件打交道。这些硬件设备通过内存映射寄存器与软件交互。因为这些寄存器的值可能随时变化(由硬件触发),所以需要将它们声明为 `volatile`,以确保程序每次都从内存中读取寄存器的最新值。
     

```cpp
     volatile int *status_register = (int *)0x4000;
     while (!(*status_register & READY_BIT)) {
         // 等待硬件准备就绪
     }
     ```

   - 多线程编程:
     在多线程环境中,多个线程可能共享某些变量。当一个线程修改这些变量时,其他线程需要能够及时获取最新的值。将这些共享变量声明为 `volatile` 可以防止编译器对其进行不当的优化,从而确保每个线程读取到的都是最新值。

     volatile bool stop = false;
     void thread_function() {
         while (!stop) {
             // 执行一些任务
         }
     }

   - 中断服务程序(ISR):
     在嵌入式系统中,中断服务程序通常会异步修改某些变量。主程序代码可能需要持续检查这些变量的状态变化。在这种情况下,将这些变量声明为 `volatile` 可以确保主程序读取的是中断服务程序更新后的值。


     volatile int flag = 0;
     void ISR() {
         flag = 1;  // 中断服务程序修改 flag
     }

3. 注意事项
   - `volatile` 不保证原子性:虽然 `volatile` 确保变量每次都从内存读取,但它并不保证对变量的访问是原子的。换句话说,如果多个线程同时访问和修改一个 `volatile` 变量,可能仍然会出现竞态条件。为了解决这个问题,通常需要结合锁(如 `mutex`)或原子操作来使用。
   - 适用范围:`volatile` 主要用于防止编译器的优化,对于防止硬件或多线程环境下的数据失效非常有效。但如果你不清楚变量是否会在外部被修改,就不应该滥用 `volatile`,以免影响程序性能。

4. 示例代码

#include <atomic>
#include <thread>
#include <iostream>

volatile bool done = false;

void worker() {
    while (!done) {
        // 在这里执行一些工作
    }
    std::cout << "Worker thread done!" << std::endl;
}

int main() {
    std::thread t(worker);
    std::this_thread::sleep_for(std::chrono::seconds(1));
    done = true;
    t.join();
    return 0;
}


在这个例子中,`done` 变量被声明为 `volatile`,以确保 worker 线程能够及时察觉到 `done` 的变化。

5. 总结
`volatile` 关键字在防止编译器优化、确保变量值实时更新方面发挥了重要作用,特别是在嵌入式编程和多线程编程中。理解 `volatile` 的正确使用场景,对于编写高效和正确的代码至关重要。

C++中内存分配的方式

静态内存分配是在编译时确定内存空间的大小,并且该内存分配在整个程序运行期间都是固定的。以下是静态分配的几种常见方式:

  • 全局变量和静态变量:这些变量的内存分配在程序开始时进行,并且在程序结束时释放。

  • 局部变量:局部变量在函数或代码块中定义,内存分配在执行到该变量的定义时进行,当函数或代码块结束时自动释放。

动态内存分配是在程序运行时根据需要分配内存空间的方式,通常使用 C++ 提供的 newdelete 操作符来完成。

虚函数和纯虚函数的区别

在C++中,**虚函数**和**纯虚函数**是实现**多态性**的重要机制。它们使得通过基类指针或引用调用派生类的函数成为可能。下面是对虚函数和纯虚函数的详细解释:

1. 虚函数(Virtual Function)

虚函数是一个在基类中声明的函数,并且可以在派生类中被重新定义(重写)。当基类指针或引用指向一个派生类对象时,通过这个指针或引用调用虚函数时,将执行派生类的版本,而不是基类的版本。这种行为被称为**动态绑定**或**运行时多态性**。

#### 1.1 **虚函数的定义**
- 在基类中,使用关键字 `virtual` 声明一个函数为虚函数。派生类可以选择重写这个函数。

  class Base {
  public:
      virtual void show() {
          std::cout << "Base show" << std::endl;
      }
  };

  class Derived : public Base {
  public:
      void show() override {  // 重写虚函数
          std::cout << "Derived show" << std::endl;
      }
  };

  int main() {
      Base* b = new Derived();
      b->show();  // 调用 Derived::show() 而不是 Base::show()
      delete b;
      return 0;
  }

  在这个例子中,尽管 `b` 是一个指向 `Base` 类型的指针,但由于 `show()` 是虚函数,所以在运行时,它调用了 `Derived` 类的 `show()` 函数。

1.2 虚函数的特点
- 动态绑定:虚函数通过虚函数表(vtable)和虚函数指针(vptr)机制实现动态绑定。每个包含虚函数的类都有一个虚函数表,存储了指向该类所有虚函数的指针。
  
- 多态性:虚函数是实现多态性的重要手段,可以使得基类指针或引用在运行时调用派生类的重写函数。

- 性能影响:由于虚函数需要在运行时通过虚函数表查找实际的函数地址,因此比非虚函数略慢。

 2. 纯虚函数(Pure Virtual Function)

纯虚函数是一个在基类中声明但不提供实现的函数,要求所有派生类都必须提供该函数的实现。包含纯虚函数的类被称为抽象类,这种类无法实例化,只能作为基类来使用。

2.1 纯虚函数的定义
- 纯虚函数通过在函数声明后添加 `= 0` 来定义。

  class Base {
  public:
      virtual void show() = 0;  // 纯虚函数
  };

  class Derived : public Base {
  public:
      void show() override {
          std::cout << "Derived show" << std::endl;
      }
  };

  int main() {
      // Base b;  // 错误:无法实例化抽象类
      Base* b = new Derived();
      b->show();  // 调用 Derived::show()
      delete b;
      return 0;
  }

  在这个例子中,`Base` 类是一个抽象类,因为它包含一个纯虚函数 `show()`。派生类 `Derived` 必须实现这个纯虚函数,否则 `Derived` 也将是抽象类。2.2 纯虚函数的特点
- 抽象类:包含一个或多个纯虚函数的类是抽象类,无法实例化。
  
- 接口设计:纯虚函数常用于接口设计,确保派生类提供特定的功能实现。例如,定义一个动物类,要求所有动物都能发出声音,但每种动物的声音不同,那么可以使用纯虚函数:

  class Animal {
  public:
      virtual void sound() = 0;  // 纯虚函数
  };

  class Dog : public Animal {
  public:
      void sound() override {
          std::cout << "Woof" << std::endl;
      }
  };

  class Cat : public Animal {
  public:
      void sound() override {
          std::cout << "Meow" << std::endl;
      }
  };

- 强制重写:派生类必须实现纯虚函数,否则派生类本身也会变成抽象类,无法实例化。

 3. 虚函数与纯虚函数的比较

- 虚函数:基类可以提供虚函数的默认实现,派生类可以选择重写它们。虚函数允许对象通过基类指针或引用调用派生类的函数实现。
  
- 纯虚函数:基类不提供纯虚函数的实现,派生类必须实现这些函数。纯虚函数用于定义接口或抽象基类,强制派生类实现特定的功能。

总结

- 虚函数:用于实现多态性,使基类指针或引用能够调用派生类的重写函数。基类可以提供虚函数的默认实现。
- 纯虚函数:用于定义抽象类,要求派生类必须实现特定的函数。这是强制实现多态的一种方式。

虚函数和纯虚函数是C++中实现多态性的重要工具,在设计可扩展、可维护的面向对象系统时非常有用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值