C++ 智能指针学习笔记

智能指针简介智能指针是一种 RAII(Resource Acquisition Is Initialization)技术,用于管理动态分配的内存。智能指针的优点是可以自动释放内存,避免内存泄漏。 C++11 标准引入了三种智能指针:std::unique_ptr、std::shared_ptr 和 std::weak_ptr。 它们都定义在头文件 <memory> 中。 unique_ptrunique_ptr 是一种独占所有权的智能指针,它保证同一时间只有一个指针可以指向对象。 unique_ptr 的创建#include <iostream> #include <memory> int main() { // 使用 new 创建 unique_ptr std::unique_ptr<int> up1(new int(10)); std::cout << *up1 << std::endl; // 使用裸指针创建 unique_ptr int count = 20; std::unique_ptr<int> up2(&count); std::cout << *up2 << std::endl; // 使用 make_unique 创建 unique_ptr auto up2 = std::make_unique<int>(20); std::cout << *up2 << std::endl; return 0; } unique_ptr 的拷贝和赋值unique_ptr 不能拷贝,但可以移动。 #include <iostream> int main() { std::unique_ptr<int> up1(new int(10)); std::unique_ptr<int> up2 = std::move(up1); std::cout << *up2 << std::endl; return 0; } unique_ptr 的释放unique_ptr 会在离开作用域时自动释放内存。 #include <iostream> int main() { std::unique_ptr<int> up1(new int(10)); std::cout << *up1 << std::endl; return 0; } unique_ptr 的自定义删除器unique_ptr 支持自定义删除器,可以用于释放动态分配的内存。 ...

2024年06月26日 · 3 min · Cassius0924

Redis 为什么采用单线程,为什么性能好

Redis 采用单线程的原因是因为在 内存中 进行读写操作,CPU不是Redis的性能瓶颈,而是内存和带宽,所以采用单线程可以避免 线程切换和锁的开销。 Redis 是什么?Redis 是一个开源的内存数据库,它可以存储键值对,支持多种数据结构,如字符串(string)、哈希(hash)、列表(list)、集合(set)、有序集合(zset)等。 Redis 是不是单线程?实际上,Redis 是多线程的,其内部有以下几个线程: redis-server:主线程,负责接收客户端的连接,读取请求,发送响应。 bio-close-file:负责异步关闭大文件。 bio-aof-fsync:负责将 AOF 文件异步刷到磁盘。 bio-lasy-free:负责异步释放大内存。 jemalloc-bg-thread:负责内存碎片整理。 io-thread:IO 线程,负责 read/write,decode/encode。 笔记 AOF(Append Only File) 是 Redis 的一种持久化方式,将所有写操作追加到文件末尾,重启时重新执行 AOF 文件中的命令即可恢复数据。实时硬盘操作,不会丢失数据,但是会影响性能。 ...

2024年06月26日 · 1 min · Cassius0924

堆排序学习笔记

在学习堆排序之前,我们先来了解一下堆这种数据结构。 堆的概念堆是一种特殊的树形数据结构,它满足以下性质: 堆必须是一个 完全二叉树 。 堆序性:堆中任意节点的值总是不大于或不小于其子节点的值。 根据堆序性,我们可以将堆分为两种类型: 大顶堆:每个节点的值都大于或等于其子节点的值 小顶堆:每个节点的值都小于或等于其子节点的值 所以,如果一个完全二叉树的一个节点即大于其父节点,又大于其子节点,那么这个树就不是一个堆。小于同理。 笔记 完全二叉树的性质 ...

2024年06月26日 · 2 min · Cassius0924

C++ 类的运算符为什么要使用引用返回

代码class AClass { private: int _count; public: AClass() : _count(0) { std::cout << "Default constructor called\n"; } // 赋值运算符,返回引用 AClass &operator=(int cnt) { _count = cnt;k return *this; } // 后置自增运算符,返回引用 int &operator++(int) { ++_count; return *this; } }; 如果不使用引用返回,其实也是可以运行的,只不过会在返回时调用拷贝构造函数,生成临时对象,然后再调用析构函数释放临时对象,这样会多出一次拷贝构造和析构的开销。而使用引用返回,可以直接返回对象的引用,避免了这个开销。 需要注意的是,如果我们返回值类型,我们是不能直接修改返回值的: class AClass { private: int _count; public: // 省略构造函数 // 赋值运算符,返回引用 AClass operator=(int cnt) { _count = cnt; return *this; } // 后置自增运算符,返回引用 int operator++(int) { ++_count; return *this; } void print() const { std::cout << "AClass: count = " << _count << '\n'; } }; int main() { AClass a; a.print(); // 输出 AClass: count = 0 (a++) = 10; a.print(); // 输出 AClass: count = 1 (a++)++; a.print(); // 输出 AClass: count = 2 } 可以看到,如果我们返回值类型,我们是不能直接修改返回值的。虽然 a++ 已经修改了 a 的值,但是 a++ 返回的是一个修改后的 a 对象的拷贝,所以 (a++) = 10; 或者 (a++)++; 修改的是这个拷贝对象,而不是原对象 a。 ...

2024年06月25日 · 1 min · Cassius0924

C++ 类的流插入和流提取运算符为什么要声明为友元函数

友元函数版代码class AClass { private: int _count; std::string _str; std::vector<int> _vec; public: friend std::ostream &operator<<(std::ostream &os, const AClass &a) { os << "AClass: count = " << a._count << ", str = " << a._str << ", vec size = " << a._vec.size(); return os; } }; 为什么要声明为友元函数先理解一下友元函数,它实际上是一个普通函数,不属于类成员,但它又是一个特殊的普通函数,因为它可以访问类的私有成员。因此 operator<< 和 operator>> 声明为友元函数的目的很明显,就是为了能够访问类的私有成员。 实际上,如果它们不声明为友元函数,也是可以实现的,例如下面代码: class AClass { private: int _count; std::string _str; std::vector<int> _vec; public: std::ostream &operator<<(std::ostream &os) { os << "AClass: count = " << _count << ", str = " << _str << ", vec size = " << _vec.size(); return os; } }; 但是这样就需要特殊的方法来调用这个 operator<< 函数,因为它不再是一个普通函数,而是一个类成员函数: int main() { AClass a; a << std::cout; // 错误,不能这样调用 a.operator<<(std::cout); // 正确 return 0; } 这样显然不够直观,不是一个正常人类写的代码:) 所以,为了代码的可读性和可维护性,我们将 operator<< 和 operator>> 声明为友元函数,这样就可以直接使用 << 和 >> 运算符来操作类的对象了。 ...

2024年06月25日 · 1 min · Cassius0924

Socket 编程之 IO 多路复用学习笔记

什么是 IO 多路复用?阻塞 IO 与 非阻塞 IO我们先了解一下阻塞 IO,阻塞 IO 是指应用程序在读写数据时,如果没有数据可读或者写,应用程序会一直 阻塞在那里 ,直到有数据可读或者写。 与它相反的是非阻塞 IO,是指应用程序在读写数据时,无论是否有数据可读写,都 立即返回 ,若没有数据可读写将会返回一个错误码,通过不断轮询来检查是否有数据可读或者写。 IO 多路复用由于 Scoket 默认是阻塞 IO,所以很多初学者在处理多个连接时,会为每个连接创建一个线程来处理,但这样做会引起 CPU 的上下文切换,降低系统的性能。 有一种更“优雅”的方式,那就是 IO 多路复用,也称为事件驱动模型(Event-driven IO)。IO 多路复用是指内核一旦发现进程指定的一个或者多个 IO 事件已经就绪,就通知该进程。IO 多路复用模型中,只需要一个线程就可以同时处理多个连接。 通俗易懂的说,IO 多路复用就是将多个 IO 事件交给内核,内核帮我们监听这些 IO 事件,当有 IO 事件就绪时,内核会通知我们,我们只需要处理就绪的 IO 事件即可。 IO 多路复用的优点 一个线程可以同时处理多个连接,减少线程的创建和销毁 降低了系统开销,提高了系统的并发性能 IO 多路复用的实现方式 select poll epoll (Linux) kqueue (FreeBSD) IOCP(Windows) 其中 epoll 是 Linux 下的 IO 多路复用机制,kqueue 是 FreeBSD(macOS 就属于 FreeBSD)下的 IO 多路复用机制。 下面的伪代码是 IO 多路复用的最基本实现方式: ...

2024年06月24日 · 7 min · Cassius0924

搭建自动化部署的 GitHub.io 个人静态博客指南

本文章主要介绍如何使用 GitHub Actions 实现基于 MkDocs 的 GitHub.io 个人静态博客的自动化部署。 本指南主要分为以下几个部分: 创建 GitHub 仓库 创建 MkDocs 项目 配置 GitHub Actions 部署到 GitHub Pages 创建 GitHub 仓库由于我们的博客是托管在 GitHub io 上的,所以我们需要一个 GitHub 仓库来存放我们的 MkDocs 博客。 创建仓库首先,我们需要在 GitHub 上创建一个新的仓库,仓库名可以是 <username>.github.io,其中 <username> 是你的 GitHub 用户名。 克隆仓库然后,我们需要将这个仓库克隆到本地: git clone <repository-url> cd <repository-name> 创建 MkDocs 项目安装 MkDocsMkDocs 是一个 Python 项目使用 pip 安装: pip install mkdocs 我们需要创建一个 MkDocs 项目,可以使用 MkDocs 官方提供的模板来创建。直接在仓库根目录执行以下命令即可: mkdocs new . 然后,我们需要安装 MkDocs Material 主题。当然我们也可以选择其他主题,可以在 MkDocs 的 WiKi 查看更多主题。 ...

2024年06月17日 · 3 min · Cassius0924

Windows Socket API 和 Linux Socket API

本文章主要介绍 Windows 下和 Linux 下的 Socket 编程区别,即 Windows Socket API 和 Linux Socket API 的区别。 头文件Windows 环境下的 Socket 编程需要以下头文件: <WinSock2.h> <WS2tcpip.h> 笔记 如果使用 MSVC 编译器,那么还需要使用预处理指令 #pragma comment(lib, "Ws2_32.lib") 来链接 Ws2_32.lib 库。 ...

2024年06月16日 · 2 min · Cassius0924

Windows VSCode 使用 JetBrians Mono 字体

本文主要介绍如何在 Windows 系统下的 Visual Studio Code 中使用 JetBrians Mono 字体。 下载字体可能你跟着网上很多教程一样,一上来就在 VS Code 中设置字体,结果发现设置不生效,这是因为你没有安装 JetBrians Mono 字体。 JetBrians Mono 字体下载地址:JetBrainsMono 安装字体下载字体后,解压缩,进入 ttf 文件夹,找到 JetBrainsMono-Regular.ttf 文件,双击打开,点击安装即可。当然你也可以全部字体都安装 :) 设置 VS Code打开 VS Code 的设置,搜索 editor.fontFamily,将其值设置为 JetBrains Mono,如下图所示: 重启 VS Code最后一步,重启 VS Code 即可。

2024年06月16日 · 1 min · Cassius0924

解决 “warning C4819- 该文件包含不能在当前代码页(936)中表示的字符” 问题指南

起因是因为我在对我的 C++ 项目进行跨平台适配,从 macOS 平台移植到 Windows 平台时,在使用 Cmake + MSVC 编译后,出现了这个问题。 问题原因这是由于 Windows 平台默认使用的是 GBK 编码,而 macOS 平台上使用的是 UTF-8 编码。 解决方法方法一在 CMakeLists.txt 文件中添加如下代码: add_compile_options("$<$<C_COMPILER_ID:MSVC>:/source-charset:utf-8>") 方法二在 CMakeLists.txt 文件中添加如下代码: if(MSVC) target_compile_options(<你的项目名> PRIVATE "/utf-8") endif()

2024年06月16日 · 1 min · Cassius0924