
C++编程专栏
介绍现代C++语法,数据结构,算法,STL用法
黑不溜秋的
GPU全栈博主
展开
专栏收录文章
- 默认排序
- 最新发布
- 最早发布
- 最多阅读
- 最少阅读
-
一文带你掌握C++编程:从入门到精通
本系列文章持续更新中...原创 2024-08-10 13:57:19 · 2167 阅读 · 0 评论 -
C++ STL 随机数用法介绍
目录一:C语言中的随机数二:C++中的随机数 1. 生成随机数的例子 2. 随机数引擎 3. 随机数引擎适配器 4. C++中预定义的随机数引擎,引擎适配器 5. 随机数分布二:C++中的随机数 1. 生成随机数的例子 2. 随机数引擎 引擎 说明 random_device 真随机数(非基于软件生成器),通常用来产生随机种子 linear_congruential_en原创 2024-07-07 14:32:24 · 432 阅读 · 0 评论 -
C++ STL 多线程库用法介绍
【代码】C++ STL 多线程库用法介绍。原创 2024-07-07 11:35:45 · 690 阅读 · 0 评论 -
C++ STL 时间日期用法介绍
C++ 标准库提供了Time Library,它由时间点,时间段,时钟三部分组成,此外,该库还提供了日期功能,日历支持,时区支持,以及输入和输出。原创 2024-07-06 19:12:49 · 338 阅读 · 0 评论 -
C++ STL 正则表达式用法介绍
【代码】C++ 正则表达式(std::regex_search使用)原创 2024-06-18 00:10:01 · 948 阅读 · 0 评论 -
C++ dijkstra 最短路径算法
Dijkstra算法是荷兰科学家Edsger W. Dijkstra于1956年提出的单源最短路径算法。该算法采用贪心策略,通过优先队列选择当前距离起点最近的节点,更新其邻居的最短路径。文章详细介绍了算法步骤:初始化距离、使用最小堆、松弛操作等,并提供了C++实现代码,包括邻接表构建、Dijkstra核心算法和测试用例。代码展示了如何计算两点间最短路径,处理不可达情况,并通过三个测试案例验证了算法的正确性(可达路径计算、更优路径选择及不可达情况处理)。原创 2025-07-08 21:59:39 · 349 阅读 · 0 评论 -
C++ 基于广度优先搜索(BFS)的拓扑排序算法
Kahn拓扑排序算法通过广度优先搜索处理有向无环图,时间复杂度O(V+E)。该算法不断选择入度为0的节点加入结果序列,若最终节点数不足则检测到环。摘要代码实现了一个Graph类,包含添加边和拓扑排序方法,并通过多个测试用例验证功能:包括多路径合并、复杂DAG、独立链合并及环检测。测试结果显示算法正确实现了拓扑排序,并能有效识别环结构。原创 2025-07-06 21:49:24 · 210 阅读 · 0 评论 -
C++ 语言特性31 - 协程介绍(2)
摘要:协程是一种可挂起与恢复执行的"高级函数",用于处理异步操作和数据生成等复杂场景。C++20引入协程支持,通过co_await和co_yield关键字实现非阻塞等待和生成器功能。协程概念早在1963年提出,现已被Python、JavaScript等现代语言广泛采用。C++函数的演进从基础函数到模板、lambda,最终在C++20迎来协程,实现了惰性求值和更灵活的流程控制能力。协程为C++带来了资源友好的异步编程方式,特别适合系统级开发需求。原创 2025-07-05 21:00:08 · 900 阅读 · 0 评论 -
C++ 基于深度优先搜索(DFS)的拓扑排序算法
本文介绍了基于深度优先搜索(DFS)的拓扑排序算法实现。算法通过递归遍历有向图的节点,在回溯时将节点压入栈中,最终获得拓扑排序的逆序。同时使用onstack数组检测图中是否存在环(回边)。文章提供了C++实现代码,包括Graph类、DFS函数和拓扑排序函数,并通过三个测试案例验证了算法的正确性:前两个测试展示了正常有向无环图的拓扑排序结果,第三个测试验证了环检测功能。当检测到环时,算法会抛出"cycle detected in graph"异常。原创 2025-07-05 11:25:57 · 171 阅读 · 0 评论 -
C++26 下一代C++标准
摘要:C++26作为下一代C++标准,将延续C++20的革新步伐,带来三大核心特性:反射(Reflection)增强元编程能力,契约(Contracts)规范接口设计,以及std::execution框架统一异步编程。从C++98的模板基础到C++11的现代特性,再到C++20的四大革新(范围库/协程/概念/模块),C++26有望成为继C++11和C++20之后又一里程碑式版本。该标准计划2025年完成,将显著提升C++在编译时编程、系统设计和并发处理方面的表现。原创 2025-07-04 23:38:58 · 1015 阅读 · 0 评论 -
C++编程指南40 - 避免不经意间写出不通用的代码
在编写代码时,应尽量使用更通用、更抽象的方式,而不是绑定到具体实现细节,以提高代码的可重用性和适用性。以下规则有助于提升代码的通用性、可维护性与可扩展性:迭代器比较:用!替代,因为!更广泛适用于非有序容器。类型使用:如果函数只使用基类接口,就应接受基类引用,而不是派生类,避免对具体类型的无必要依赖。空判断方式:使用empty()而不是,因为某些容器没有size(),但支持判断是否为空。强制执行建议:编译器或代码分析工具应能发现并提示这些不通用的用法,避免影响泛型和复用能力。原创 2025-04-29 21:35:04 · 223 阅读 · 0 评论 -
C++编程指南39 - 不要特化函数模板
应避免特化函数模板,而应通过重载来实现不同类型或参数的处理。对函数模板进行特化会引入一些不容易察觉的问题,因为特化是针对特定类型提供一个具体实现。对于函数模板,如果你对某个类型进行了特化,那么编译器仅仅使用那个特化版本,其他版本不再参与选择。而重载是指定义多个函数模板,它们的参数类型,个数或顺序有所不同,编译器会根据传入的参数选择合适的版本。特化模板的坏处:它不参与重载解析,一旦你进行了模板特化,编译器会优先选择与特化版本完全匹配的函数,不会参与其他重载模板的选择。推荐使用重载。原创 2025-04-28 21:13:18 · 280 阅读 · 0 评论 -
C++编程指南38 - 使用 static_assert 检查类是否符合某个 concept
如果我们打算让一个类符合某个 concept,那么尽早验证可以避免给使用者带来麻烦。C++20 引入了 Concepts(概念),用来对类型(类、结构体等)进行语义约束,例如是否可复制、是否可迭代、是否可调用等等。是一种编译时断言,它可以在编译期间验证某个条件是否为真,如果为假,编译器就会报错。原创 2025-04-21 21:24:21 · 345 阅读 · 0 评论 -
C++编程指南37 - 使用 concept 限制模板参数类型
concept是C++20 引入的语言特性,concept 是一种用于约束模板参数的编译期布尔表达式(谓词),它描述了一个类型应该具有的某些性质(比如是否可比较、是否可拷贝、是否支持某种操作等)。// 如果 T 没有 size(),编译器报个几十行的模板错误报错晦涩难懂,用户根本不知道为什么错,引入 Concept 之后,我们可以提前限制参数类型,并提供更清晰的错误信息:假设定义一个 concept 要求类型T有一个size()成员函数,且能返回可以转换为的值。原创 2025-04-21 21:17:37 · 339 阅读 · 0 评论 -
C++ 编程指南36 - 使用Pimpl模式实现稳定的ABI接口
C++ 的类布局(尤其是私有成员变量)直接影响它的 ABI(应用二进制接口)。如果你在类中添加或修改了私有成员,即使接口不变,编译器生成的二进制布局也会变,从而导致 ABI 不兼容。这意味着使用这个类的代码需要重新编译,严重破坏了库的二进制兼容性。为避免这种情况,Pimpl(Pointer to Implementation) 模式应运而生。Pimpl将类的实际实现细节封装在另一个类中(impl),对外只暴露一个指向实现的智能指针(如。原创 2025-04-13 22:42:13 · 366 阅读 · 0 评论 -
C++ 编程指南35 - 为保持ABI稳定,应避免模板接口
模板在 C++ 中是编译期展开的,不同模板参数会生成不同的代码,这使得模板类/函数天然不具备 ABI 稳定性。为了保持ABI稳定,接口不要直接用模板,先用普通类打个底,模板只是“外壳”,这样 ABI 才稳定。这样做有两个好处:所有模板实例共享一个实现代码(避免为每个List<T>生成一份几乎相同的add_frontunlink等函数)。核心逻辑只定义一次,因此更容易保持 ABI 稳定性,并且编译速度更快。原创 2025-04-13 22:31:15 · 436 阅读 · 0 评论 -
C++ 编程指南34 - C++ 中 ABI 不兼容的典型情形
ABI(Application Binary Interface)是二进制层面的接口规范。如果一个库的 ABI 发生了变化,那么基于旧 ABI 编译的代码可能在运行时与新库不兼容(即使接口名字都一样也不行)。那么在C++中编程中,哪些情形会导致ABI不兼容呢?下面逐一列举一下。原创 2025-04-13 22:17:23 · 1047 阅读 · 0 评论 -
C++ 编程指南33 - 使用模板来表达适用于多种参数类型的算法
在 C++ 中,模板(Templates)提供了一种强大的泛型编程方式,使代码可以适用于不同的数据类型,而无需重复编写类似的逻辑。模板的主要目标是:泛化能力(Generality):能够适用于不同的数据类型,提高代码的通用性。减少源代码量(Minimizing the amount of source code):避免为不同数据类型重复编写代码,提高代码复用性。互操作性(Interoperability):使代码能够在不同类型的容器或数据结构之间共享。原创 2025-04-02 22:44:24 · 322 阅读 · 0 评论 -
C++编程指南32 - 模板编程时要避免过度约束以提高通用性
由于使用模板会提高代码的抽象级别,所以在编写模板代码时,要尽量让代码更灵活、通用,而不是只针对某些特定的操作或类型。目的是为了提高代码的重用性和效率。不要过度限制模板的类型要求,让代码更通用。尽量避免只要求单一操作,要考虑更通用的操作集合。用概念来约束类型,而不是直接依赖操作符,这样代码能处理更多类型,增加灵活性和复用性。原创 2025-04-01 21:15:41 · 525 阅读 · 0 评论 -
C++编程指南31 - 除非绝对必要,否则不要使用无锁编程
无锁编程容易出错,并且需要专家级的知识,包括:语言特性(如 C++ 的计算机架构(如 CPU 缓存一致性协议)数据结构(如无锁队列、无锁栈)高层次的并发机制(如线程和互斥锁)本质上是基于无锁编程实现的,但它们隐藏了底层的复杂性,使开发者更容易正确使用。如果必须使用无锁数据结构,优先使用现成的库(Facebook 开源库)实现了无锁链表libcds提供成熟的无锁数据结构(队列、哈希表等)原创 2025-03-31 20:57:30 · 319 阅读 · 0 评论 -
C++编程指南30 - 不要为初始化编写自己的双重检查锁定
自 C++11 起,静态局部变量的初始化已经是线程安全的。结合 RAII(资源获取即初始化)模式,静态局部变量可以完全取代双重检查锁定的需求。此外,也可以实现相同的目的。因此,应该使用 C++11 的静态局部变量或,而不是自己编写双重检查锁定。不要 手动实现双重检查锁定(DCLP, Double-Checked Locking Pattern)。进行一次性初始化,适用于非局部的初始化情况。C++11 的线程安全静态局部变量,适用于局部作用域的初始化。原创 2025-03-25 23:21:07 · 513 阅读 · 0 评论 -
C++编程指南29 - 仅在与非 C++ 代码交互时使用 volatile
volatile关键字用于声明与**“非 C++”代码或不遵循 C++ 内存模型的硬件交互的对象。例如,它常用于表示硬件寄存器**,因为这些寄存器的值可能在程序未直接操作的情况下发生变化。原创 2025-03-25 22:54:13 · 461 阅读 · 0 评论 -
C++编程指南28 - 使用 std::async() 启动并发任务
与(之前介绍的)避免使用裸指针管理资源类似,我们应该避免直接使用std::thread和std::promise,而是使用std::async 这样的工厂函数来启动并发任务。std::async能够自动决定是创建新线程,还是重用已有的线程,从而避免直接管理std::thread带来的复杂性和潜在错误。原创 2025-03-13 22:35:17 · 236 阅读 · 0 评论 -
C++ 编程指南27 - 始终将 mutex 与它所保护的数据一起定义,并尽可能使用 synchronized_value<T>
在多线程编程中,互斥锁(std::mutex)的作用是保护共享数据的访问。但如果mutex锁的使用不明显:程序员可能会忘记获取mutex就访问数据,导致数据竞争(race condition)。锁管理混乱:代码阅读者难以明确哪个mutex保护哪个数据,可能会误用错误的mutex,导致死锁或数据不一致。封装性不足:数据和mutex分离,使得访问数据变得不安全。原创 2025-03-11 23:57:23 · 369 阅读 · 0 评论 -
C++ 编程指南26 - 尽量缩短在临界区(critical section)内的执行时间
在临界区中持有互斥锁(mutex)的时间越短,线程之间的等待时间就越少,从而减少线程被挂起和恢复的开销,提高程序的并发性能。一般来说,无法自动检测是否持有mutex过长。但可以标记裸露的lock()和unlock()调用,鼓励使用 RAII 进行资源管理。或者进行代码审查(Code Review) 确保临界区最小化。原创 2025-03-10 23:58:38 · 338 阅读 · 0 评论 -
C++编程指南25 - 不要在没有条件的情况下等待
如果没有条件就进行等待(wait()),可能会导致以下问题:一是某个线程可能会错过其他线程的通知,从而一直处于等待状态。二是线程可能被唤醒后发现根本没有可执行的任务,导致不必要的上下文切换和资源消耗。永远不要 在没有条件的情况下调用wait(),否则可能会导致线程永远等待或不必要的唤醒。始终 使用条件谓词(如!q.empty())来控制wait(),确保线程只有在满足特定条件时才会被唤醒。检查代码,确保所有wait()调用都使用了谓词表达式,否则应该标记为潜在错误。原创 2025-03-10 23:53:45 · 441 阅读 · 0 评论 -
C++编程指南24 - 避免线程频繁的创建和销毁
线程的创建和销毁是昂贵的操作,尤其在多线程程序中频繁创建和销毁线程时,可能会导致性能问题。特别是当消息处理需要创建多个线程时,系统资源会被过度消耗。在高并发处理的场景中,线程池是非常常见的解决方案。原创 2025-03-08 21:49:58 · 360 阅读 · 0 评论 -
C++编程指南23 - 在无关线程之间共享资源时应使用shared_ptr
当多个线程需要共享一个堆内存(即动态分配的内存)时,如果这些线程之间没有直接关系,比如它们的生命周期不重叠,那么使用shared_ptr(智能指针)是最安全的做法。因为如果有多个线程要共享一块内存,而且这块内存需要在某个时候被删除,直接用普通指针可能会导致一些问题,比如内存泄漏或者在某个线程结束时,其他线程仍然在访问这块已经被删除的内存。shared_ptr是一种智能指针,它会自动管理内存的释放。只有当所有的线程都不再使用这块内存时,shared_ptr才会销毁它,避免了内存泄漏和重复释放的问题。原创 2025-03-06 23:02:13 · 412 阅读 · 0 评论 -
C++编程指南22 - 在线程之间传递少量数据时,使用值传递,而不是引用或指针传递
传递少量数据时,复制比通过某些锁机制共享数据更便宜。复制数据自然会导致唯一所有权(简化代码),并消除了数据竞争的可能性。注意:“少量数据”的定义是无法精确界定的。这个规则的核心思想是在线程间传递少量数据时,尽量使用值传递,而非引用或指针传递。避免数据竞争通过值传递,数据拥有独立的副本,不需要担心多线程间对同一内存位置的访问冲突。这避免了数据竞争(data race),简化了代码。相比之下,如果使用引用或指针传递,多个线程可能会同时访问和修改同一数据,导致难以调试的并发问题。复制的开销较低。原创 2025-03-05 23:09:54 · 485 阅读 · 0 评论 -
C++编程指南21 - 线程detach后其注意变量的生命周期
如果一个线程被detach()了,那么它的生命周期将独立于创建它的作用域。全局变量(global/static objects)堆上分配的对象(free-store allocated objects,即new创建的对象) 其他作用域的对象可能会在线程访问它们之前被销毁,导致 悬空指针(dangling pointer) 和 未定义行为。原创 2025-03-02 22:07:25 · 344 阅读 · 0 评论 -
C++编程指南20 - 使用 joining_thread以确保线程不会在变量生命周期之外运行
在多线程编程中,如果一个线程需要访问外部作用域的变量,就必须保证这个变量在 线程结束之前依然有效,否则可能会出现 悬空指针(dangling pointer) 的问题,导致 未定义行为。原创 2025-03-02 21:50:36 · 287 阅读 · 0 评论 -
C++编程指南19 - 在持有锁的情况下,绝不要调用未知代码(例如回调函数)
在持有互斥锁的情况下调用 未知代码(例如回调函数或虚函数) 可能会导致 死锁(deadlock) 或 长时间阻塞(blocking),从而影响程序的性能和正确性。原创 2025-03-02 20:40:24 · 334 阅读 · 0 评论 -
C++编程指南18 - 使用 std::lock() 或 std::scoped_lock 来同时获取多个互斥锁
当一个线程需要同时获取多个std::mutex互斥锁时,如果不注意锁的获取顺序,可能会导致 死锁(deadlock)。为了解决这个问题,C++ 提供了和,它们可以确保多个互斥锁的获取是 无死锁 的。原创 2025-03-02 20:25:13 · 290 阅读 · 0 评论 -
C++编程指南17 - 使用 RAII(资源获取即初始化),避免直接调用 lock()/unlock()
本规则核心思想是避免手动管理锁的获取和释放,而是利用 C++ 的 RAII(Resource Acquisition Is Initialization) 机制,通过或来自动管理锁的生命周期。如果手动调用lock()和unlock()代码可能在unlock()之前 提前返回,导致锁未释放。代码可能在unlock()之前 抛出异常,导致锁未释放。维护代码的人可能会忘记调用unlock(),导致锁未释放。原创 2025-02-25 21:29:57 · 347 阅读 · 0 评论 -
C++ 设计模式 - 并发模式概述
在并发领域,有许多成熟的设计模式。它们不仅用于处理共享和修改时的同步挑战,还涉及并发架构。本文将从总体上介绍这些模式。在并发领域,一个至关重要的术语是数据竞争,什么是数据竞争?数据竞争指的是至少有两个线程同时访问一个共享变量,并且至少有一个线程试图修改该变量。如果程序存在数据竞争,它将导致未定义行为。这意味着任何结果都有可能发生,因此程序的行为将变得无法预测和推理。数据竞争的一个必要条件是可变的共享状态。如果你能够妥善处理共享或修改,就不会发生数据竞争。这正是同步模式所关注的重点。原创 2025-02-24 21:14:22 · 1110 阅读 · 0 评论 -
C++编程指南16 - 尽可能使用工具验证并发代码
经验表明,C++并发代码极难编写正确;相比于顺序代码,编译时检查、运行时检查在发现并发错误方面的效果往往较差。而微妙的并发错误可能会引发严重问题,如内存损坏、死锁或安全漏洞。线程安全是一个复杂的问题,即使是经验丰富的程序员也容易犯错。因此,利用工具是降低风险的重要策略。市面上有许多商用或开源的工具,既有学术研究用途的,也有工业生产环境中的。然而,不同项目的需求和约束千差万别,因此我们不推荐具体工具,而是介绍一些通用方法。静态分析工具(Static enforcement tools)原创 2025-02-24 13:08:34 · 250 阅读 · 0 评论 -
C++编程指南15 - 不要使用 volatile 进行同步
在 C++ 中,volatilevolatile变量的读写不是原子操作,可能被中断。volatile不能保证多线程访问时的正确性。防止指令重排序(Memory Ordering):编译器和 CPU 仍然可能对volatile变量的操作进行优化或重排序。C++ 的volatile仅用于防止编译器优化访问,但不会提供线程安全性。原创 2025-02-21 13:00:12 · 426 阅读 · 0 评论 -
C++ 设计模式 - 策略模式
策略模式是一种行为设计模式,来源于《设计模式:可复用面向对象软件的基础》一书。它定义了一组算法,并将它们封装成独立的对象。策略模式在标准模板库(STL)中被广泛使用。原创 2025-02-21 12:48:09 · 704 阅读 · 0 评论 -
C++设计模式 - 模板模式
模板方法(Template Method)是一种行为型设计模式。它定义了一个算法的基本框架,并且可能是《设计模式:可复用面向对象软件的基础》一书中最常用的设计模式之一。模板方法的核心思想很容易理解。我们需要定义一个包含多个固定步骤的算法框架。具体的实现类只能重写这些步骤,但不能改变整体框架。这些步骤通常被称为钩子方法(hook methods)。原创 2025-02-10 23:57:27 · 426 阅读 · 0 评论 -
C++ 设计模式 - 访问者模式
访问者模式将作用于对象层次结构的操作封装为一个对象,并使其能够在不修改对象层次结构的情况下定义新的操作。《设计模式:可复用面向对象软件的基础》一书中的访问者模式因两个原因而具有传奇色彩:一是因为它的复杂性,二是因为它使用了一种名为“双重分派”的技术。双重分派指的是根据对象和函数参数选择成员函数的过程。当然,访问者模式的复杂性主要在于 C++ 本身不支持双重分派。在讨论单分派和双重分派之前,让我先谈谈访问者模式。什么是“双重分派”?原创 2025-02-09 15:55:18 · 977 阅读 · 0 评论