【实习】C++方向面试经验3

这次面试的一家公司是做计算机视觉方向的C++软件开发,主要考察了C++的一些基本知识点。

1. 做项目过程中有什么收获?

2. 简单说一下C和C++的区别。

笔者主要从C语言是面向过程的,C++是面向对象的这个角度回答。下面从语言范式、面向对象编程(OOP)、标准库、内存管理、函数重载和运算符重载、命名空间、异常处理、类型安全、适用场景和编译时长做一些扩展。

2.1. 语言范式

  • C:C 是一种面向过程的编程语言。程序是通过函数调用和全局变量的方式来组织的。
  • C++:C++ 是一种面向对象的编程语言,同时也支持面向过程和泛型编程。C++ 提供了类和对象的概念,支持继承、封装和多态性。

2.2. 面向对象编程(OOP)

  • C:C 不支持面向对象编程。没有类、继承、多态、封装等面向对象的特性。
  • C++:C++ 提供了面向对象编程的全部特性,允许程序员定义类、创建对象、使用继承和多态来实现更复杂和模块化的代码。

2.3. 标准库

  • C:C 的标准库(C 标准库)主要包含一些基本的函数,如字符串处理、输入输出、内存管理等。
  • C++:C++ 标准库除了包含 C 标准库的内容外,还引入了大量面向对象的库,如标准模板库(STL),它提供了诸如容器(如 vector, list, map),算法(如 sort, find),迭代器等强大的工具。

2.4. 内存管理

  • C:在 C 中,内存管理主要通过 malloc 和 free 来完成,需要手动管理内存。
  • C++:C++ 提供了 new 和 delete 运算符来分配和释放内存。此外,C++ 引入了智能指针(如 std::unique_ptr, std::shared_ptr)来自动管理内存,减少内存泄漏的风险。

2.5. 函数重载和运算符重载

  • C:C 不支持函数重载和运算符重载。
  • C++:C++ 支持函数重载和运算符重载,这允许程序员定义相同名称的函数或运算符,但具有不同的参数列表或实现。

2.6. 命名空间

  • C:C 中没有命名空间,所有全局变量和函数共享同一个全局命名空间,容易发生命名冲突。
  • C++:C++ 引入了命名空间 (namespace) 概念,允许将代码组织在不同的命名空间中,减少命名冲突。

2.7. 异常处理

  • C:C 没有内置的异常处理机制,通常使用返回值来处理错误。
  • C++:C++ 提供了异常处理机制 (try, catch, throw),可以用来优雅地处理错误和异常情况。

2.8. 类型安全

  • C:C 的类型检查相对宽松,容易发生类型转换错误。
  • C++:C++ 提供了更严格的类型检查和更安全的类型转换方式(如 static_cast, dynamic_cast, const_cast 和 reinterpret_cast)。

2.9. 面向系统级编程 vs. 面向高层应用

  • C:C 更接近底层硬件,适合编写系统级程序,如操作系统、驱动程序。
  • C++:C++ 提供了更高层次的抽象,适合编写应用程序、游戏引擎、大型软件系统等。

2.10. 编译时长

  • C:由于没有面向对象和模板的复杂性,C 代码通常编译速度更快。
  • C++:C++ 的编译器需要处理复杂的模板和类层次结构,因此编译时间可能更长。

总结来说,C 更加简洁和底层,适合系统编程,而 C++ 更加灵活和高层,适合复杂的应用开发。

3. C++常用的设计模式了解哪一些?

本人只回答了了解单例模式。下面给出扩展回答。
以下是C++中常用的一些设计模式,按照创建型(Creational)、结构型(Structural)和行为型(Behavioral)进行分类介绍,并给出C++中特有的一些设计模式和技术。

3.1. 创建型设计模式

  1. 单例模式(Singleton)
  • 目的:确保一个类只有一个实例,并提供全局访问点。
  • 实现要点:
    • 构造函数私有化。
    • 提供一个静态方法用于获取唯一实例。
  • 线程安全的实现(如使用 std::mutex)。
  • 示例
class Singleton 
{
public:
    static Singleton& getInstance() 
    {
        static Singleton instance;
        return instance;
    }
    Singleton(const Singleton&) = delete;
    void operator=(const Singleton&) = delete;
private:
    Singleton() {}
};
  1. 工厂方法模式(Factory Method)
  • 目的:定义一个创建对象的接口,让子类决定实例化哪一个类。
  • 实现要点:
    • 创建一个产品基类。
    • 定义工厂基类,声明创建产品的接口。
    • 具体工厂类实现创建具体产品的逻辑。
  • 示例
class Product 
{
public:
    virtual void use() = 0;
};

class ConcreteProductA : public Product 
{
public:
    void use() override { /* 实现 */ }
};

class Factory 
{
public:
    virtual Product* createProduct() = 0;
};

class ConcreteFactoryA : public Factory 
{
public:
    Product* createProduct() override 
    { 
    	return new ConcreteProductA(); 
    }
};
  1. 抽象工厂模式(Abstract Factory)
  • 目的:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
  • 实现要点:
    • 定义多个产品族的基类。
    • 定义抽象工厂接口,声明创建每个产品的方法。
    • 具体工厂实现创建每个具体产品。
  • 示例
class AbstractProductA { /* ... */ };
class AbstractProductB { /* ... */ };

class AbstractFactory 
{
public:
    virtual AbstractProductA* createProductA() = 0;
    virtual AbstractProductB* createProductB() = 0;
};

class ConcreteFactory1 : public AbstractFactory 
{
public:
    AbstractProductA* createProductA() override 
    { 
    	return new ConcreteProductA1(); 
    }
    AbstractProductB* createProductB() override 
    { 
    	return new ConcreteProductB1(); 
    }
};

3.2. 结构型设计模式

  1. 适配器模式(Adapter)
  • 目的:将一个类的接口转换成客户期望的另一个接口,使原本不兼容的类可以一起工作。
  • 实现要点:
    • 目标接口定义客户期望的接口。
    • 适配器类实现目标接口,并持有被适配类的实例。
  • 示例
class Target 
{
public:
    virtual void request() = 0;
};

class Adaptee 
{
public:
    void specificRequest() { /* ... */ }
};

class Adapter : public Target 
{
private:
    Adaptee adaptee;
public:
    void request() override 
    { 
    	adaptee.specificRequest(); 
    }
};
  1. 装饰器模式(Decorator)
  • 目的:动态地给对象添加职责,而无需改变其接口。
  • 实现要点:
    • 定义一个组件接口。
    • 装饰器类实现组件接口,并持有一个组件对象的引用。
    • 具体装饰器类添加新的功能。
  • 示例
class Component 
{
public:
    virtual void operation() = 0;
};

class ConcreteComponent : public Component 
{
public:
    void operation() override { /* 基本操作 */ }
};

class Decorator : public Component 
{
protected:
    Component* component;
public:
    Decorator(Component* c) : component(c) {}
    void operation() override { component->operation(); }
};

class ConcreteDecorator : public Decorator 
{
public:
    ConcreteDecorator(Component* c) : Decorator(c) {}
    void operation() override 
    {
        Decorator::operation();
        // 添加新功能
    }
};
  1. 桥接模式(Bridge)
  • 目的:将抽象部分与其实现部分分离,使它们可以独立变化。
  • 实现要点:
    • 定义抽象类,持有实现类的引用。
    • 抽象类通过实现类完成具体操作。
    • 实现类定义具体的实现细节。
  • 示例
class Implementor 
{
public:
    virtual void operationImpl() = 0;
};

class ConcreteImplementor : public Implementor 
{
public:
    void operationImpl() override { /* 实现细节 */ }
};

class Abstraction 
{
protected:
    Implementor* implementor;
public:
    Abstraction(Implementor* impl) : implementor(impl) {}
    virtual void operation() 
    {
        implementor->operationImpl();
    }
};

3.3. 行为型设计模式

  1. 观察者模式(Observer)
  • 目的:定义对象之间的一对多依赖关系,当一个对象改变状态时,所有依赖它的对象都会收到通知并自动更新。
  • 实现要点:
    • 定义主题(Subject)接口,管理观察者的注册和通知。
    • 定义观察者(Observer)接口,声明接收更新的方法。
    • 具体主题和具体观察者实现各自的接口。
  • 示例
class Observer 
{
public:
    virtual void update() = 0;
};

class Subject 
{
private:
    std::vector<Observer*> observers;
public:
    void attach(Observer* obs) { observers.push_back(obs); }
    void notify() 
    {
        for(auto& obs : observers) obs->update();
    }
};

class ConcreteObserver : public Observer 
{
public:
    void update() override { /* 处理更新 */ }
};

2.策略模式(Strategy)

  • 目的:定义一系列算法,将每个算法封装起来,并使它们可以互换,策略模式让算法独立于使用它的客户端变化。
  • 实现要点:
    • 定义策略接口,声明算法的方法。
    • 具体策略类实现策略接口。
    • 上下文类持有一个策略对象的引用,并通过策略对象执行算法。
  • 示例
class Strategy 
{
public:
    virtual void execute() = 0;
};

class ConcreteStrategyA : public Strategy 
{
public:
    void execute() override { /* 算法A */ }
};

class Context 
{
private:
    Strategy* strategy;
public:
    void setStrategy(Strategy* s) { strategy = s; }
    void executeStrategy() { strategy->execute(); }
};
  1. 命令模式(Command)
  • 目的:将请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,队列请求或记录日志,以及支持可撤销的操作。
  • 实现要点:
    • 定义命令接口,声明执行操作的方法。
    • 具体命令类实现命令接口,并持有接收者对象。
    • 调用者类持有命令对象并在适当时刻调用其执行方法。
  • 示例
class Command 
{
public:
    virtual void execute() = 0;
};

class Receiver 
{
public:
    void action() { /* 执行动作 */ }
};

class ConcreteCommand : public Command 
{
private:
    Receiver* receiver;
public:
    ConcreteCommand(Receiver* r) : receiver(r) {}
    void execute() override { receiver->action(); }
};

class Invoker 
{
private:
    Command* command;
public:
    void setCommand(Command* cmd) { command = cmd; }
    void executeCommand() { command->execute(); }
};

3.4. C++ 特有的设计模式和技术

  1. RAII(资源获取即初始化)
  • 目的:利用对象的生命周期管理资源,确保资源在对象销毁时被释放,防止资源泄漏。
  • 实现要点:
    • 在构造函数中获取资源。
    • 在析构函数中释放资源。
  • 示例:
class Resource 
{
public:
    Resource() { /* 获取资源,如分配内存、打开文件等 */ }
    ~Resource() { /* 释放资源 */ }
};
  1. 智能指针(Smart Pointer)
  • 目的:自动管理动态分配的内存,避免内存泄漏和悬挂指针。
  • 类型:
    • std::unique_ptr:独占所有权,不能复制,只能移动。
    • std::shared_ptr:共享所有权,引用计数。
    • std::weak_ptr:辅助 std::shared_ptr,避免循环引用。
  • 示例:
std::unique_ptr<MyClass> ptr1(new MyClass());
std::shared_ptr<MyClass> ptr2 = std::make_shared<MyClass>();
  1. 模板模式(Template Method)
  • 目的:定义一个操作的算法骨架,将某些步骤延迟到子类,使得子类可以在不改变算法结构的情况下重新定义算法的某些步骤。
  • 实现要点:
    • 在基类中定义算法的框架。
    • 在基类中声明需要子类实现的步骤(通常是虚函数)。
  • 示例:
class AbstractClass 
{
public:
    void templateMethod() 
    {
        step1();
        step2();
        step3();
    }
    virtual void step1() = 0;
    virtual void step2() { /* 默认实现 */ }
    virtual void step3() = 0;
};

class ConcreteClass : public AbstractClass 
{
public:
    void step1() override { /* 实现步骤1 */ }
    void step3() override { /* 实现步骤3 */ }
};

4. 说一下单例模式懒汉式和饿汉式

笔者主要回答了在多线程环境下的两者的区别。下面给出一些扩展回答。
懒汉式单例模式和饿汉式单例模式是两种常见的单例模式实现方式,它们的主要区别在于实例的创建时机。

4.1. 饿汉式单例模式

概念

饿汉式单例模式在类加载时就创建实例。也就是说,无论是否需要这个实例,在程序启动时实例就已经被创建。这种方式线程安全,不需要额外的同步锁定机制。

优点

实现简单,线程安全,因为实例在类加载时就创建好,没有多线程访问时的同步问题。

缺点

如果实例一直没有使用,可能会导致资源浪费,因为它在程序启动时就被创建。

C++ 实现:

class Singleton 
{
private:
    static Singleton* instance;
    Singleton() {}  // 构造函数私有化

public:
    static Singleton* getInstance() 
    {
        return instance;
    }
};

// 在类外定义并初始化静态成员
Singleton* Singleton::instance = new Singleton();

在这个例子中,Singleton::instance在程序一开始就会被创建,并且在程序生命周期内一直存在。

4.2. 懒汉式单例模式

概念

懒汉式单例模式在第一次使用时才创建实例。这种方式避免了程序启动时不必要的资源消耗。懒汉式通常需要处理多线程同步问题,以避免多个线程同时创建多个实例。

优点

只有在真正需要时才创建实例,节省资源。

缺点

需要处理线程安全问题,通常会使用锁来保证线程安全,可能会影响性能。

C++ 实现

#include <mutex>

class Singleton 
{
private:
    static Singleton* instance;
    static std::mutex mtx;
    Singleton() {}  // 构造函数私有化

public:
    static Singleton* getInstance() 
    {
        if (instance == nullptr) 
        {
            std::lock_guard<std::mutex> lock(mtx);  // 使用锁保护
            if (instance == nullptr) 
            {
                instance = new Singleton();
            }
        }
        return instance;
    }
};

// 在类外定义静态成员
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

在这个实现中,getInstance方法在第一次调用时会创建单例实例。为了保证线程安全,使用了std::mutex来锁定创建过程,避免多个线程同时创建多个实例。

4.3. 懒汉式与饿汉式的比较

  • 创建时机:饿汉式在类加载时就创建实例,而懒汉式在第一次使用时才创建实例。
  • 资源利用:饿汉式可能会浪费资源,如果实例一直未被使用。懒汉式更加节省资源,因为它只有在需要时才创建实例。
  • 线程安全:饿汉式天生线程安全,因为实例在类加载时已经创建好。懒汉式需要额外的线程同步机制来保证线程安全。
  • 复杂性:饿汉式实现简单,无需考虑多线程问题。懒汉式实现稍复杂,需要考虑线程安全。

在实际应用中,选择哪种方式要根据具体的需求来决定。如果在程序启动时就需要使用单例实例,或者不关心实例创建时的性能影响,可以选择饿汉式。反之,如果实例的创建是耗资源的操作,并且有可能在整个生命周期中都不需要实例,可以选择懒汉式。

5. 谈一下对智能指针的了解。

笔者介绍了三种智能指针的区别。下面是扩展回答。
智能指针(Smart Pointers)是C++11引入的一种自动化管理动态内存的工具,旨在减少内存泄漏和悬挂指针等问题。在传统的C++编程中,开发者需要手动管理动态分配的内存,通过调用new分配内存并使用delete释放内存。如果开发者忘记释放内存或者在异常处理、函数返回过程中意外地跳过了释放步骤,可能会导致内存泄漏。智能指针通过RAII(Resource Acquisition Is Initialization)机制,确保动态分配的内存能够被正确管理和释放。

5.1. 常见的智能指针类型

C++标准库中主要提供了三种智能指针:std::unique_ptr、std::shared_ptr和std::weak_ptr,它们各自有不同的特点和使用场景。

  1. std::unique_ptr
  • 特性:
    • std::unique_ptr是独占所有权的智能指针,意味着一个unique_ptr对象拥有它所指向的对象,并且不能复制,只能移动。
    • 当std::unique_ptr对象超出其作用域时,所管理的资源(内存)会自动释放。
  • 使用场景:
    • 适用于对象的独占所有权场景,当不再需要该对象时会自动释放资源。
    • 适合用于工厂函数的返回值或类成员中管理唯一拥有权的资源。
  • 示例:
std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
std::unique_ptr<int> ptr2 = std::move(ptr1); // ptr1 失去所有权,ptr2 获得所有权
  1. std::shared_ptr
  • 特性:
    • std::shared_ptr是共享所有权的智能指针,多个shared_ptr对象可以指向同一个资源,资源的生命周期由引用计数来管理。
    • 当最后一个引用该资源的shared_ptr对象销毁时,资源会被释放。
  • 使用场景:
    • 适用于需要共享资源的场景,比如多线程环境中多个对象共同使用同一个资源。
    • 适合用于资源的生命周期复杂且难以明确管理的情况下。
  • 示例:
std::shared_ptr<int> ptr1 = std::make_shared<int>(20);
std::shared_ptr<int> ptr2 = ptr1; // ptr1 和 ptr2 共享所有权
  1. std::weak_ptr
  • 特性:
    • std::weak_ptr是一种辅助的智能指针,它不拥有资源的所有权,而是对资源的一个弱引用。它不会影响资源的引用计数。
    • 主要用于打破std::shared_ptr之间的循环引用(circular reference),防止内存泄漏。
  • 使用场景:
    • 适用于std::shared_ptr之间可能出现循环引用的场景,例如双向引用的场景。
    • 适合用于希望观察对象的状态但不影响其生命周期的场景。
  • 示例:
std::shared_ptr<int> sptr = std::make_shared<int>(30);
std::weak_ptr<int> wptr = sptr; // wptr 不影响 sptr 的引用计数
if (auto spt = wptr.lock()) {   // 通过 lock 获取 shared_ptr
    // 使用 spt 进行操作
}

5.2. 智能指针的优点

  1. 自动内存管理:智能指针会自动管理内存,当智能指针对象超出作用域时,资源会被自动释放,无需显式调用delete,有效防止内存泄漏。
  2. 异常安全性:使用智能指针后,即使在异常抛出后离开作用域,资源也会被正确释放,避免了因为异常而导致的资源泄漏问题。
  3. 可控的资源生命周期:智能指针提供了一种明确且安全的资源所有权管理方式,程序员可以通过智能指针控制资源的生命周期,避免了悬挂指针等问题。
  4. 简化代码:使用智能指针后,不再需要手动管理内存的分配和释放,代码更加简洁易读,减少了程序员的心智负担。

5.3. 智能指针的注意事项

  1. 避免循环引用:使用std::shared_ptr时要注意循环引用问题,这可能导致内存无法释放。解决方法是引入std::weak_ptr来打破循环引用。
  2. 性能考虑:std::shared_ptr的引用计数机制会带来一定的性能开销,特别是在频繁创建和销毁对象的场景中,需要注意这种开销。
  3. 使用场景的适配:不同的智能指针有各自的适用场景,应该根据具体的需求选择合适的智能指针类型。例如,在不需要共享所有权的情况下,优先选择std::unique_ptr而不是std::shared_ptr。

6. STL容器平常使用得多吗?了解哪些容器。

笔者回答了STL常见的容器,包括顺序容器、关联容器(有序和无序)等。
以下是一些常用的STL容器及其特点:

6.1. std::vector

  • 概述:动态数组,支持快速的随机访问和在末尾的插入、删除操作。
  • 特点:
    • 支持随机访问(O(1)时间复杂度)。
    • 在末尾添加或删除元素的效率高(O(1)摊销时间复杂度)。
    • 插入或删除其他位置的元素需要移动元素,效率较低(O(n)时间复杂度)。
    • 动态扩容,内部自动管理内存。
  • 适用场景:需要频繁进行随机访问、追加操作的场景。

6.2. std::list

  • 概述:双向链表,支持高效的插入和删除操作。
  • 特点:
    • 双向链表,支持常数时间的插入和删除操作(O(1)时间复杂度)。
    • 不支持随机访问,遍历速度较慢(O(n)时间复杂度)。
    • 更适合在中间频繁插入或删除元素的场景。
  • 适用场景:需要频繁在中间位置进行插入或删除操作的场景。

6.3. std::deque

  • 概述:双端队列,支持在两端进行高效的插入和删除操作。
  • 特点:
    • 支持随机访问,类似std::vector(O(1)时间复杂度)。
    • 支持在两端高效插入和删除元素(O(1)时间复杂度)。
    • 相比std::vector,不适合频繁在中间位置插入或删除元素。
  • 适用场景:需要在两端频繁插入和删除元素的场景。

6.4. std::set 和 std::unordered_set

std::set:

  • 概述:基于红黑树实现的有序集合,存储唯一的元素。
  • 特点:元素自动排序,支持O(log n)时间复杂度的插入、删除和查找操作。
  • 适用场景:需要有序集合、且不允许重复元素的场景。

std::unordered_set:

  • 概述:基于哈希表实现的无序集合,存储唯一的元素。
  • 特点:不保证元素顺序,支持O(1)时间复杂度的插入、删除和查找操作。
  • 适用场景:不需要元素顺序,只关心高效查找和插入的场景。

6.5. std::map 和 std::unordered_map

std::map:

  • 概述:基于红黑树实现的有序键值对容器。
  • 特点:键值对按键自动排序,支持O(log n)时间复杂度的插入、删除和查找操作。
  • 适用场景:需要有序键值对、且键唯一的场景。

std::unordered_map:

  • 概述:基于哈希表实现的无序键值对容器。
  • 特点:不保证键值对的顺序,支持O(1)时间复杂度的插入、删除和查找操作。
  • 适用场景:需要高效查找、不关心顺序的键值对存储场景。

6.6. std::stack 和 std::queue

std::stack:

  • 概述:基于std::deque或std::vector实现的栈,遵循后进先出(LIFO)原则。
  • 特点:只允许在顶部插入和删除元素。
  • 适用场景:需要LIFO结构的场景,如递归计算。

std::queue:

  • 概述:基于std::deque实现的队列,遵循先进先出(FIFO)原则。
  • 特点:只允许在一端插入,在另一端删除元素。
  • 适用场景:需要FIFO结构的场景,如任务调度。

6.7. std::priority_queue

  • 概述:基于堆(通常是最大堆)实现的优先队列。
  • 特点:
    • 元素按照优先级排列,访问最大或最小元素的时间复杂度为O(1)。
    • 插入和删除元素的时间复杂度为O(log n)。
  • 适用场景:需要按照优先级处理任务的场景,如调度算法。

7. 自己写代码有没有碰到多线程线程不安全的情况?如何解决?

本人回答了使用锁和条件变量的方式。下面是一些补充。
在多线程编程中,线程不安全的情况时有发生,尤其是在多个线程同时访问和修改共享数据时。常见的线程不安全问题包括数据竞争(Data Race)、死锁(Deadlock)、竞态条件(Race Condition)等。

7.1. 线程不安全的常见场景

共享数据的并发访问:如果多个线程同时读取和写入一个共享变量,而没有任何同步措施,可能导致数据不一致或不可预测的结果。例如:

int counter = 0;

void incrementCounter() 
{
    for (int i = 0; i < 1000; ++i) 
    {
        ++counter;
    }
}

int main() 
{
    std::thread t1(incrementCounter);
    std::thread t2(incrementCounter);
    t1.join();
    t2.join();
    std::cout << "Final counter value: " << counter << std::endl;
    return 0;
}

由于counter变量在多个线程中同时被修改,最终的值可能不会是预期的2000。

7.2. 解决线程不安全问题的方法

  • 使用互斥锁(Mutex):互斥锁(std::mutex)是最常见的解决方法之一,它可以确保一次只有一个线程访问被保护的代码块,从而避免数据竞争。
#include <iostream>
#include <thread>
#include <mutex>

int counter = 0;
std::mutex mtx;

void incrementCounter() 
{
    for (int i = 0; i < 1000; ++i) 
    {
        std::lock_guard<std::mutex> lock(mtx); // 使用 lock_guard 自动加锁解锁
        ++counter;
    }
}

int main() 
{
    std::thread t1(incrementCounter);
    std::thread t2(incrementCounter);
    t1.join();
    t2.join();
    std::cout << "Final counter value: " << counter << std::endl;
    return 0;
}

通过使用std::lock_guard自动管理std::mutex的加锁和解锁,可以确保在多个线程中安全地更新counter变量。

  • 使用原子操作(Atomic Operations):C++11引入了原子操作(std::atomic),它是一种轻量级的解决方案,适用于简单的变量操作,可以避免使用互斥锁带来的性能开销。
#include <iostream>
#include <thread>
#include <atomic>

std::atomic<int> counter(0);

void incrementCounter() 
{
    for (int i = 0; i < 1000; ++i) 
    {
        ++counter;
    }
}

int main() 
{
    std::thread t1(incrementCounter);
    std::thread t2(incrementCounter);
    t1.join();
    t2.join();
    std::cout << "Final counter value: " << counter << std::endl;
    return 0;
}

在这个例子中,std::atomic确保了对counter变量的所有操作都是原子的,因此不会出现数据竞争问题。

  • 使用条件变量(Condition Variable):条件变量(std::condition_variable)通常用于线程间的同步,特别是在一个线程需要等待另一个线程完成某项操作时。例如,生产者-消费者问题通常使用条件变量来协调生产者和消费者之间的工作。
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>

std::queue<int> dataQueue;
std::mutex mtx;
std::condition_variable cv;
bool finished = false;

void producer() 
{
    for (int i = 0; i < 10; ++i) 
    {
        std::lock_guard<std::mutex> lock(mtx);
        dataQueue.push(i);
        cv.notify_one(); // 通知等待中的消费者
    }
    finished = true;
    cv.notify_all(); // 通知所有消费者生产结束
}

void consumer() 
{
    while (true) 
    {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, []{ return !dataQueue.empty() || finished; });

        while (!dataQueue.empty()) {
            std::cout << "Consumed: " << dataQueue.front() << std::endl;
            dataQueue.pop();
        }

        if (finished) break;
    }
}

int main() 
{
    std::thread t1(producer);
    std::thread t2(consumer);

    t1.join();
    t2.join();

    return 0;
}

在这个例子中,std::condition_variable用于同步生产者和消费者的操作,确保数据在正确的时机被消费。

8. 有没有了解过OpenCV和QT?

OpenCV和Qt是两个广泛应用于C++开发中的库,各自具有不同的功能和特点,但它们也可以结合使用来开发强大的图像处理和图形用户界面(GUI)应用程序。

8.1. OpenCV

OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习库。它主要用于实时计算机视觉应用开发,支持丰富的图像处理功能。

  • 主要功能:
    • 图像处理:包括滤波、边缘检测、图像转换、图像金字塔等。
    • 视频处理:支持读取、写入和处理视频流。
    • 计算机视觉:包括人脸检测、对象识别、运动分析、特征检测等。
    • 机器学习:支持基本的机器学习算法,如分类、回归、聚类等。
  • 应用领域:
    • 自动驾驶、安防监控、医疗影像处理、增强现实(AR)、机器人视觉等。

8.2. Qt

Qt是一个跨平台的C++应用程序开发框架,广泛用于图形用户界面(GUI)开发。除了GUI,它还提供了丰富的库用于网络编程、数据库访问、多媒体处理等。

  • 主要功能:
    • GUI开发:支持窗口、按钮、文本框等控件,以及布局管理、事件处理等。
    • 跨平台支持:一次开发,编译后可在Windows、macOS、Linux等多平台运行。
    • 信号与槽机制:独特的事件驱动机制,支持松耦合的对象通信。
    • 多线程:提供了易于使用的线程类和同步机制。
  • 应用领域:
    • 桌面应用程序开发、嵌入式系统界面开发、跨平台工具开发等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值