这次面试的一家公司是做计算机视觉方向的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. 创建型设计模式
- 单例模式(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() {}
};
- 工厂方法模式(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();
}
};
- 抽象工厂模式(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. 结构型设计模式
- 适配器模式(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();
}
};
- 装饰器模式(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();
// 添加新功能
}
};
- 桥接模式(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. 行为型设计模式
- 观察者模式(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(); }
};
- 命令模式(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++ 特有的设计模式和技术
- RAII(资源获取即初始化)
- 目的:利用对象的生命周期管理资源,确保资源在对象销毁时被释放,防止资源泄漏。
- 实现要点:
- 在构造函数中获取资源。
- 在析构函数中释放资源。
- 示例:
class Resource
{
public:
Resource() { /* 获取资源,如分配内存、打开文件等 */ }
~Resource() { /* 释放资源 */ }
};
- 智能指针(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>();
- 模板模式(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,它们各自有不同的特点和使用场景。
- 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 获得所有权
- 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 共享所有权
- 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. 智能指针的优点
- 自动内存管理:智能指针会自动管理内存,当智能指针对象超出作用域时,资源会被自动释放,无需显式调用delete,有效防止内存泄漏。
- 异常安全性:使用智能指针后,即使在异常抛出后离开作用域,资源也会被正确释放,避免了因为异常而导致的资源泄漏问题。
- 可控的资源生命周期:智能指针提供了一种明确且安全的资源所有权管理方式,程序员可以通过智能指针控制资源的生命周期,避免了悬挂指针等问题。
- 简化代码:使用智能指针后,不再需要手动管理内存的分配和释放,代码更加简洁易读,减少了程序员的心智负担。
5.3. 智能指针的注意事项
- 避免循环引用:使用std::shared_ptr时要注意循环引用问题,这可能导致内存无法释放。解决方法是引入std::weak_ptr来打破循环引用。
- 性能考虑:std::shared_ptr的引用计数机制会带来一定的性能开销,特别是在频繁创建和销毁对象的场景中,需要注意这种开销。
- 使用场景的适配:不同的智能指针有各自的适用场景,应该根据具体的需求选择合适的智能指针类型。例如,在不需要共享所有权的情况下,优先选择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等多平台运行。
- 信号与槽机制:独特的事件驱动机制,支持松耦合的对象通信。
- 多线程:提供了易于使用的线程类和同步机制。
- 应用领域:
- 桌面应用程序开发、嵌入式系统界面开发、跨平台工具开发等。