C++面试模拟01

第一部分:基础知识

  1. :解释 const 关键字的作用,以及在什么场景下你会使用 const

  2. :在 C++ 中,newmalloc 的区别是什么?

  3. :请解释什么是“深拷贝”和“浅拷贝”?在什么情况下我们需要进行深拷贝?

  4. :C++ 中的引用和指针有什么区别?各自的使用场景是什么?


第二部分:面向对象编程

  1. :请解释 C++ 中的“继承”和“多态”。可以举例说明如何实现多态吗?

  2. :假设你有一个基类 Shape 和派生类 CircleRectangle。如何设计一个类层次结构来支持多态调用?请写出代码示例。

  3. :C++ 支持多重继承(multiple inheritance),但是它可能会带来问题。请解释这些问题并说明如何使用虚继承来解决其中一个问题。


第三部分:STL(标准模板库)

  1. :C++ 标准库中的 std::vectorstd::list 有什么区别?分别适用于什么场景?

  2. std::mapstd::unordered_map 的区别是什么?它们的时间复杂度分别是多少?

  3. :在什么情况下你会使用 std::deque 而不是 std::vectorstd::list


第四部分:并发与多线程

  1. :请解释 C++ 中的多线程机制。如何使用 std::thread 创建线程?请写出代码示例。

  2. :什么是互斥锁(Mutex)?如何在 C++ 中使用它来保护共享资源?

  3. :什么是条件变量(Condition Variable)?请简要描述它的使用场景。


第五部分:C++ 高级特性

  1. :C++11 引入了移动语义(Move Semantics)。请解释移动语义的作用以及它与复制语义的区别。

  2. :什么是 RAII(资源获取即初始化)?为什么在 C++ 中非常重要?

  3. :C++20 引入了协程(Coroutines)。你能描述一下协程的基本概念和使用场景吗?


第六部分:现场编程

  1. :写一个函数,接收一个整数数组,移除所有重复的元素,并返回不重复的元素个数。请尽可能优化时间复杂度和空间复杂度。
int removeDuplicates(std::vector<int>& nums);

 我的想法:

        1.如果给了nums里int的范围,可以使用哈希法去重

        2.没有范围或者范围太大,使用力扣里的“移除元素”的方法,操作双指针。先排序。

  1. :请写一个单例模式(Singleton Pattern)的实现。保证线程安全。

结束问题

  1. :在你的开发经历中,是否遇到过多线程的竞争条件?你是如何解决的?

  2. :你平时如何优化代码性能?有哪些常用的工具和方法?

C++知识点/面试问题指南-CSDN博客

newmalloc 的区别

  • 类型检查

    • new:会调用构造函数,分配内存时会执行类型检查。
    • malloc:不执行类型检查,返回 void* 类型的指针,需要手动转换类型。
  • 返回类型

    • new:返回指定类型的指针,不需要类型转换。
    • malloc:返回 void*,需要显式进行类型转换。
  • 异常处理

    • new:如果分配失败,会抛出 std::bad_alloc 异常。
    • malloc:如果分配失败,返回 NULL,需要手动检查。
  • 构造/析构函数

    • new:调用构造函数,负责初始化对象。
    • malloc:只分配内存,不会调用构造函数。
  • 释放内存

    • new:使用 delete 释放内存,并调用析构函数。
    • malloc:使用 free 释放内存,不会调用析构函数。

引用和指针的区别

  • 引用(Reference)

    • 引用是某个变量的别名,必须在声明时进行初始化,之后不能更改引用的对象。
    • 语法简单、直观,不需要解引用操作符(*)。
    • 一旦绑定到某个对象,就不能重新绑定。
  • 指针(Pointer)

    • 指针是存储内存地址的变量,可以指向任意对象或 NULL,可以在运行时改变所指向的对象。
    • 需要使用解引用操作符(*)来访问指向的对象。
    • 指针可以被重新分配、动态分配内存,使用更加灵活。
  • 区别

    • 引用不能为空,指针可以为空。
    • 引用一旦绑定,不能更改;指针可以在运行时改变指向的对象。
    • 引用更适合用于参数传递,指针更适合需要动态管理内存的场景。
  • 使用场景

    • 引用适用于函数参数传递和返回值,当你想避免拷贝数据时,通常使用引用。
void foo(int& ref);  // 引用传递

指针适用于需要动态分配内存的场景,或者需要改变指向的对象。

int* ptr = new int(5);  // 动态分配内存

C++ 中的“继承”和“多态”

  • 继承

    • 继承是一种面向对象编程的机制,允许从一个现有的类(基类/父类)创建一个新类(派生类/子类),以复用基类的成员变量和成员函数。

    • 继承可以让派生类拥有基类的属性和方法,并且可以扩展新的方法或重写基类的方法。

多态

  • 多态是指相同的函数或运算符在不同的对象上表现出不同的行为。C++ 支持编译时多态(通过函数重载和运算符重载)和运行时多态(通过虚函数实现)。

  • 多态使得程序能够通过基类的指针或引用来调用派生类的实现,从而实现灵活性。

  • 示例(虚函数实现多态):

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

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

void func(Base& obj) {
    obj.show();  // 会根据传入的对象类型调用相应的 show() 方法
}

设计 ShapeCircleRectangle 类层次结构

#include <iostream>
#include <cmath>

class Shape {
public:
    virtual double area() const = 0;   // 纯虚函数,表示该类是抽象类
    virtual ~Shape() {}                // 虚析构函数
};

class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) {}
    double area() const override {     // 实现多态
        return M_PI * radius * radius;
    }
};

class Rectangle : public Shape {
private:
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    double area() const override {
        return width * height;
    }
};

int main() {
    Shape* shapes[2];
    shapes[0] = new Circle(5.0);
    shapes[1] = new Rectangle(4.0, 6.0);

    for (int i = 0; i < 2; i++) {
        std::cout << "Area: " << shapes[i]->area() << std::endl;
        delete shapes[i];  // 注意内存管理
    }

    return 0;
}

C++ 中的多重继承问题及虚继承

  • 多重继承的问题
    • 二义性问题:当一个类同时继承自两个具有同名成员的基类时,派生类会出现二义性。

    • 菱形继承问题:在多重继承中,如果一个类从两个基类派生,而这两个基类又继承自同一个基类,派生类会继承两份相同的基类成员,这就造成了冗余数据和二义性问题。

    • 菱形继承示例

class A {
public:
    void show() { std::cout << "A's show" << std::endl; }
};

class B : public A {};
class C : public A {};

class D : public B, public C {};  // D 继承了两份 A 类的成员
  • 虚继承的解决方案:使用虚继承解决菱形继承问题,确保派生类只继承一份基类的成员。

  • 虚继承示例

class A {
public:
    void show() { std::cout << "A's show" << std::endl; }
};

class B : virtual public A {};    // 虚继承
class C : virtual public A {};    // 虚继承

class D : public B, public C {};  // D 只继承一份 A 的成员

 解释:通过 virtual 关键字,B 和 C 虚继承自 A,D 类中只有一份 A 类的成员,避免了二义性和数据冗余问题。

std::vectorstd::list 的区别

  • 底层实现

    • std::vector:基于动态数组实现,元素在内存中是连续存储的。
    • std::list:基于双向链表实现,元素在内存中是非连续存储的。
  • 时间复杂度

    • 访问vector 支持常数时间的随机访问(O(1)),因为其元素是连续存储的;而 list 访问任意位置的元素都需要线性时间(O(n))。
    • 插入/删除list 在任何位置插入或删除元素的时间复杂度为常数时间(O(1)),而 vector 在尾部插入/删除元素的时间复杂度为 O(1),但在中间插入或删除元素时需要移动数据,时间复杂度为 O(n)
  • 适用场景

    • std::vector:适用于需要频繁随机访问元素,或在末尾插入、删除元素的场景。
    • std::list:适用于需要频繁在中间插入或删除元素的场景,但不需要随机访问。

std::mapstd::unordered_map 的区别

  • 底层实现

    • std::map:基于红黑树(自平衡二叉搜索树)实现,键值对按键的顺序存储。
    • std::unordered_map:基于哈希表实现,键值对无序存储。
  • 时间复杂度

    • std::map:插入、删除、查找的平均时间复杂度为 O(log n)
    • std::unordered_map:插入、删除、查找的平均时间复杂度为 O(1),最坏情况下为 O(n)
  • 适用场景

    • std::map:适用于需要按键排序的场景。
    • std::unordered_map:适用于对性能要求较高、且不需要排序的场景。

何时使用 std::deque 而不是 std::vectorstd::list

  • std::deque:双端队列(double-ended queue),在两端插入和删除元素的时间复杂度为 O(1),并支持常数时间的随机访问。

  • 使用场景

    • 当需要在容器的两端频繁进行插入和删除操作,同时还需要随机访问时,使用 std::deque 是最好的选择。

    • std::vector 不同,deque 支持在头部进行高效的插入和删除操作。

    • std::list 不同,deque 支持随机访问。

RAII(Resource Acquisition Is Initialization,资源获取即初始化)是一种重要的 C++ 编程习惯,确保资源的正确管理。RAII 的核心思想是:将资源的生命周期绑定到对象的生命周期。当对象被创建时获取资源,当对象被销毁时释放资源。

  • 优点

    1. 自动管理资源:资源的分配和释放与对象的生命周期绑定,避免资源泄漏(如内存泄漏、文件句柄泄漏)。
    2. 异常安全:如果在资源使用过程中发生异常,RAII 确保在栈展开时资源会被正确释放。
    3. 简化代码:RAII 自动处理资源的释放,减少手动管理资源的复杂性。
  • 常见场景

    1. 内存管理:智能指针(如 std::unique_ptrstd::shared_ptr)通过 RAII 管理动态分配的内存。
    2. 文件管理std::fstream 等标准库类通过 RAII 确保文件在对象销毁时自动关闭。
  • 示例

class FileHandler {
private:
    FILE* file;
public:
    FileHandler(const char* filename) {
        file = fopen(filename, "r");
        if (!file) {
            throw std::runtime_error("File open failed");
        }
    }
    
    ~FileHandler() {
        if (file) {
            fclose(file);  // 确保文件关闭
        }
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值