适合小白的嵌入式软件项目(C++)详解-----卡码缓存系统(二)实现最简单缓存
2026/6/23 22:46:46 网站建设 项目流程

本期我们进入实际操作环节,我采用Windows系统用VScode ssh远程连接Linux服务器进行操作。

因为本人一直跑的是python的项目,配置的anaconda环境,这里conda deactivate退出。

新建cache文件夹 并cd cache ,下一步搭建基本的代码框架。

基本的项目框架

首先我们需要三个文件夹,include放头文件,src放源代码文件,build放编译生成的中间文件以及可执行程序;

命令:mkdir include src build

结构:

cache
├── include(类的声明,函数声明,结构体定义)
├── src(真正执行的 C++ 代码,函数实现,main 函数)
└── build(编译垃圾桶/编译产物目录,不把它和源代码混在一起)

接下来需要创建一个空文件,CMakeLists.txt(C++ 项目的“编译说明书”)

这个文件是给 CMake 用的。(CMake 是 C++ 项目常用的构建工具)

它负责告诉编译器:

项目叫什么?

用哪个 C++ 标准?

头文件在哪里?

源代码在哪里?

最后生成什么可执行文件?

命令:touch CMakeLists.txt

最后就是项目核心的LRUCache.h以及main.cpp;(先从最基础的LRU淘汰策略做起)

命令:touch include/LRUCache.h touch src/main.cpp

总结一下:

CMakeLists.txt 编译说明书
include/LRUCache.h LRU缓存类的头文件
src/main.cpp 程序入口和测试代码
build 编译生成文件存放处

框架搭好之后开始编辑内容,先写C++项目的编译配置文件,因为我们后面不会直接手动写很长的g++编译命令,而是用 CMake 来管理项目。没有CMakeLists.txt,CMake 就不知道怎么编译你的项目。

cat > CMakeLists.txt << 'EOF'
cmake_minimum_required(VERSION 3.10)##版本号

project(kama_cache)##项目名称

set(CMAKE_CXX_STANDARD 17)##C++17标准
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include_directories(include)##头文件放进include

add_executable(kama_cache
src/main.cpp
)##把src/main.cpp编译成一个可执行程序,程序名字叫kama_cache
EOF

接下来在main里编译一个简单的测试程序

#include <iostream>

int main()

{

std::cout << "Kama Cache project is running!" << std::endl;

return 0;

}
##std::cout输出工具,<<将右边的内容放到左边输出流里,<< std::endl换行

下一步执行程序

cd build
cmake ..

让 CMake 去上一级目录..CMakeLists.txt,然后根据它生成 Makefile

其中CMakeLists.txt 是我们写的“项目编译说明书”
cmake .. 根据说明书生成 Makefile
make 根据 Makefile 真正编译代码

这里已经生成了可执行文件,下一步直接在目录下./kama_cache。

[ 50%] Building CXX object CMakeFiles/kama_cache.dir/src/main.cpp.o

先把 C++ 代码翻译成机器更接近能执行的中间文件
[100%] Linking CXX executable kama_cache

把编译好的中间文件和需要的库组合起来,变成真正能运行的程序
[100%] Built target kama_cache

这里涉及编译器原理,放在下一节;

成功!!!再复习一下流程。

写源代码

告诉 CMake 项目怎么编译

在 build 文件夹里生成编译文件

真正编译生成可执行程序

运行程序

最简单的缓存项目

LRUcache基础架构

int key + int value + 哈希表 + 双向链表;

#ifndef LRU_CACHE_H #define LRU_CACHE_H #include <unordered_map> //包含哈希表 class LRU_Cache//定义LRU_Cache类 { private: struct Node//定义双向链表节点类 { int key; int value; Node *prev; Node *next; Node (int k, int v) :key(k), value(v), prev(nullptr), next(nullptr) {} }; //; private : int capacity_; std::unordered_map<int, Node *> cache_;//哈希表,根据key快速找到节点 Node *dummyHead_;//虚拟节点,指向头尾节点 Node *dummyTail_; public: LRU_Cache(int capacity)//建立一个空的双向链表函数 : capacity_(capacity) { dummyHead_ = new Node(0, 0); dummyTail_ = new Node(0, 0); dummyHead_->next = dummyTail_; dummyTail_->prev = dummyHead_; } ~LRU_Cache()//释放内存 { Node *current = dummyHead_; while (current != nullptr) { Node *nextNode = current->next; delete current; current = nextNode; } } }; #endif

LRUCache 是一个缓存类。

它里面有:

1. Node 节点
每个节点保存 key、value、prev、next。

2. capacity_
缓存最大容量。

3. cache_
哈希表,用 key 快速找到 Node 节点。

4. dummyHead_ 和 dummyTail_
双向链表的虚拟头节点和虚拟尾节点。

写代码需注意:

class / struct 的大括号后面要加分号。函数的大括号后面一般不加分号。

Node(int k, int v) 是构造函数,用来初始化节点。

std::unordered_map<int, Node*> cache_ 是哈希表:int key -> Node* 节点地址。

LRUCache(int capacity) : capacity_(capacity)表示创建缓存时,把容量保存到成员变量 capacity_。

读取与写入函数

在public里加入get(int key)和put(int key , int value)

int get(int key) //缓存的查询函数

{

if(cache_.find(key)==cache_.end())

{

return -1;

}

Node* node = cache_[key];

moveToHead(node);

return node->value;

}

void put(int key, int value) //缓存的插入函数

{

if(cache_.find(key)!=cache_.end())//key已经存在

{

Node *node = cache_[key];//cache_[]是哈希表的访问方式,返回的是Node*类型

node->value = value;

moveToHead(node);

}

if(cache_.size()>=capacity_)//key不存在,容量已满

{

Node *tailNode = removeTail();

cache_.erase(tailNode->key);//从哈希表中删除

delete tailNode;

}

Node *newNode = new Node(key, value);

cache_[key] = newNode;//插入哈希表

addToHead(newNode);//插入双向链表头部

}

简单的测试

在main.cpp中写一段测试代码,测试这几个功能

put 插入数据
get 查询数据
访问后移动到最近使用
容量满后淘汰最久未使用数据

另外重点解释一下:哈希表里的链表与我们自己写的LRU双向链表要区分开,哈希表正常存放数据,他解决哈希冲突与我们无关是标准库的事,但是内部每个节点能够快速指向我们写的LRU双向链表上的节点地址,所以我写了put(1, 100); put(2, 200);不管哈希表怎么存,我们LRU双向链表上都是前后两个点。

#include <iostream> #include "LRUCache.h" int main() { LRU_Cache cache(2); // 创建一个容量为2的LRU缓存 cache.put(1, 100); // 缓存中添加键值对 (1, 100) cache.put(2, 200); // 缓存中添加键值对 (2, 200) std::cout <<"get(1): " <<cache.get(1) << std::endl; // 输出: 100,键1存在于缓存中 cache.put(3, 300); // 缓存容量已满,移除最久未使用的键2 std::cout <<"get(2): " <<cache.get(2) << std::endl; std::cout <<"get(3): " <<cache.get(3) << std::endl; return 0; }

std::cout << "get(1): " << cache.get(1) << std::endl;

//std::cout是标准输出对象,C++里允许连续输出,这句话拆成三段:

std::cout << "get(1): ";
std::cout << cache.get(1);
std::cout << std::endl; //换行

依旧是cd build make(CMakeLists.txt 是编译说明; cmake .. 生成 Makefile; make 根据 Makefile 真正编译代码) ./kama_cache(运行当前目录下的可执行文件)

成功!!!

编译器

既然这里提到编译器,就顺便解释编译器。等待补充中。。。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询