C++零基础到工程实战(4.3.8):基于 vector 实现一个简易缓存数据库

目录

一、本节作业要求

[1.1 作业功能要求](#1.1 作业功能要求)

(1)插入数据

(2)查找数据

(3)删除数据

[1.2 作业实现要求](#1.2 作业实现要求)

[1.3 本节作业的意义](#1.3 本节作业的意义)

二、作业需求分析

[2.1 这个"缓存数据库"本质上是什么](#2.1 这个“缓存数据库”本质上是什么)

[2.2 为什么使用两个 vector](#2.2 为什么使用两个 vector)

三、实现思路

[3.1 用户输入命令](#3.1 用户输入命令)

[3.2 命令拆分](#3.2 命令拆分)

[3.3 数据存储](#3.3 数据存储)

[3.4 数据查找](#3.4 数据查找)

[3.5 数据删除](#3.5 数据删除)

四、完整代码实现与运行

[4.1 代码实现](#4.1 代码实现)

[4.2 代码运行效果演示](#4.2 代码运行效果演示)

[4.2.1 输入示例](#4.2.1 输入示例)

[4.2.2 输出示例](#4.2.2 输出示例)

[4.2.3 结果分析](#4.2.3 结果分析)

五、代码详细解析

[5.1 头文件与命名空间](#5.1 头文件与命名空间)

[5.2 定义两个 vector](#5.2 定义两个 vector)

[5.3 使用 getline 获取整行命令](#5.3 使用 getline 获取整行命令)

[5.4 istringstream iss(input); 到底是什么意思](#5.4 istringstream iss(input); 到底是什么意思)

[5.4.1 它的本质是什么](#5.4.1 它的本质是什么)

[5.4.2 iss >> cmd; 读取了什么](#5.4.2 iss >> cmd; 读取了什么)

[5.5 insert 插入命令的处理过程](#5.5 insert 插入命令的处理过程)

[5.6 查找 key 的逻辑](#5.6 查找 key 的逻辑)

[5.6.1 index = -1 表示什么](#5.6.1 index = -1 表示什么)

[5.6.2 if (keys[i] == key) 是什么意思](#5.6.2 if (keys[i] == key) 是什么意思)

[5.6.3 为什么 keys[i] 不是一个字母](#5.6.3 为什么 keys[i] 不是一个字母)

[5.7 为什么"前面没存进去"却还能比较](#5.7 为什么“前面没存进去”却还能比较)

六、本节重难点与常见疑问

[6.1 getline(cin, input) 和 cin >> input 有什么区别](#6.1 getline(cin, input) 和 cin >> input 有什么区别)

[6.2 istringstream 为什么有用](#6.2 istringstream 为什么有用)

[6.3 keys 和 key 的区别](#6.3 keys 和 key 的区别)

七、小结


一、本节作业要求

本节作业的核心目标是:使用 C++ 基础语法,实现一个简易缓存数据库程序

这个"小型缓存数据库"并不是真正意义上的数据库系统,而是一个入门级的键值存储(Key-Value)模拟程序。

用户通过输入命令,可以完成数据的插入、查询和删除。

1.1 作业功能要求

根据本次作业要求,程序需要支持以下功能:

(1)插入数据

用户输入:

cpp 复制代码
insert xcj datastring

表示将:

  • key:xcj
  • value:datastring

存入缓存数据库中,并提示"存储成功"。

(2)查找数据

用户输入:

cpp 复制代码
get xcj

程序需要根据关键字 xcj 找到对应的值,并输出:

cpp 复制代码
datastring

(3)删除数据

用户输入:

cpp 复制代码
delete xcj

程序需要删除关键字 xcj 对应的数据,并提示删除成功。

1.2 作业实现要求

本次作业还要求实现时满足以下几点:

(1)使用 getline 获取用户输入的一整行命令
(2)使用两个 vector 存储数据
1)一个 vectorkey
2)一个 vectorvalue
(3)程序主体使用 main 函数完成,不额外拆分函数也可以
(4)完成命令解析、查找逻辑和删除逻辑


1.3 本节作业的意义

把前面学过的知识点串起来,完成一个真正可以运行的小项目。

虽然这个程序规模不大,但它几乎涵盖了 C++ 初学阶段很多关键知识点,比如:

  • getline 读取整行输入
  • string 字符串处理
  • istringstream 命令拆分
  • vector<string> 存储多个字符串
  • for 循环遍历查找
  • if 条件判断
  • erase 删除元素

可以说,这一节就是前面基础内容的一次综合练习。


二、作业需求分析

2.1 这个"缓存数据库"本质上是什么

从本质上来说,这个作业是在实现一个最简单的键值对存储系统

比如输入:

cpp 复制代码
insert xcj datastring
insert key2 data2
insert key3 data3123

那么程序内部要保存的数据关系就是:

cpp 复制代码
xcj -> datastring
key2 -> data2
key3 -> data3123

这和真实数据库中的"键值映射"思想很像,只不过这里我们没有用真正的数据库,而是用 vector 来模拟。

2.2 为什么使用两个 vector

题目要求:

  • 一个 vector 存放 key
  • 一个 vector 存放 value

因此可以这样设计:

cpp 复制代码
vector<string> keys;
vector<string> values;

它们通过相同下标建立对应关系:

bash 复制代码
keys[0] <-> values[0]
keys[1] <-> values[1]
keys[2] <-> values[2]

例如:

cpp 复制代码
keys[0] = "xcj";
values[0] = "datastring";

表示:

cpp 复制代码
xcj -> datastring

三、实现思路

3.1 用户输入命令

因为用户输入的命令可能是这样:

cpp 复制代码
insert xcj datastring

这里一整行里包含多个单词,中间还有空格

如果使用普通的:

cpp 复制代码
cin >> input;

那么只能读取到第一个单词,不能完整获取整行命令

因此这里要使用:

cpp 复制代码
getline(cin, input);

这样就可以把整条命令完整读进来。

3.2 命令拆分

读取到整行字符串之后,还需要把它拆成:

  • 命令名 insert
  • key xcj
  • value datastring

这里使用:

cpp 复制代码
istringstream iss(input);

字符串转成"字符串输入流",然后再像 cin 一样读取

cpp 复制代码
string cmd;
iss >> cmd;

这样就能先读出命令。

3.3 数据存储

程序运行过程中,用户可能会不断插入数据,因此我们使用动态容器**vector<string>****:**

cpp 复制代码
vector<string> keys;
vector<string> values;

当执行 insert 时,把 key 放入 keys,把 value 放入 values

3.4 数据查找

查找的核心就是遍历 keys

cpp 复制代码
for (int i = 0; i < (int)keys.size(); ++i)
    {
    if (keys[i] == key)
        {
            index = i;
            break;
        }
    }

找到后,再通过相同下标去 values 中取值。

3.5 数据删除

删除时,既要删 keys 中对应位置的 key,也要删 values 中对应位置的 value:

cpp 复制代码
keys.erase(keys.begin() + index);
values.erase(values.begin() + index);

这样才能保证两个 vector 的下标继续一一对应。


四、完整代码实现与运行

4.1 代码实现

cpp 复制代码
#include <iostream>
#include <string>
#include <vector>
#include <sstream>

using namespace std;

int main(int argc, char* argv[])
{
    vector<string> keys;     // 存放 key
    vector<string> values;   // 存放 value

    cout << "==============================" << endl;
    cout << "  简易缓存数据库系统启动成功" << endl;
    cout << "  支持命令:insert / get / delete / show / exit" << endl;
    cout << "==============================" << endl;

    string input;

    while (true)
    {
        cout << "\n请输入命令 > ";
        getline(cin, input);

        if (input.empty())
        {
            continue;
        }

        istringstream iss(input);
        string cmd;
        iss >> cmd;

        // 1. 插入数据
        if (cmd == "insert")
        {
            string key;
            string value;

            iss >> key;
            getline(iss >> ws, value); // 读取剩余内容作为 value,支持空格

            if (key.empty() || value.empty())
            {
                cout << "命令格式错误,正确格式:insert key value" << endl;
                continue;
            }

            int index = -1;
            for (int i = 0; i < (int)keys.size(); ++i)
            {
                if (keys[i] == key)
                {
                    index = i;
                    break;
                }
            }

            if (index != -1)
            {
                values[index] = value;
                cout << "更新成功:[" << key << "] = " << value << endl;
            }
            else
            {
                keys.push_back(key);
                values.push_back(value);
                cout << "存储成功:[" << key << "] = " << value << endl;
            }
        }

        // 2. 查找数据
        else if (cmd == "get")
        {
            string key;
            iss >> key;

            if (key.empty())
            {
                cout << "命令格式错误,正确格式:get key" << endl;
                continue;
            }

            int index = -1;
            for (int i = 0; i < (int)keys.size(); ++i)
            {
                if (keys[i] == key)
                {
                    index = i;
                    break;
                }
            }

            if (index != -1)
            {
                cout << "输出值:" << values[index] << endl;
            }
            else
            {
                cout << "未找到 key: " << key << endl;
            }
        }

        // 3. 删除数据
        else if (cmd == "delete")
        {
            string key;
            iss >> key;

            if (key.empty())
            {
                cout << "命令格式错误,正确格式:delete key" << endl;
                continue;
            }

            int index = -1;
            for (int i = 0; i < (int)keys.size(); ++i)
            {
                if (keys[i] == key)
                {
                    index = i;
                    break;
                }
            }

            if (index != -1)
            {
                keys.erase(keys.begin() + index);
                values.erase(values.begin() + index);
                cout << "显示删除成功" << endl;
            }
            else
            {
                cout << "删除失败,未找到 key: " << key << endl;
            }
        }

        // 4. 显示所有数据
        else if (cmd == "show")
        {
            if (keys.empty())
            {
                cout << "当前缓存为空" << endl;
            }
            else
            {
                cout << "当前缓存内容如下:" << endl;
                for (int i = 0; i < (int)keys.size(); ++i)
                {
                    cout << keys[i] << " -> " << values[i] << endl;
                }
            }
        }

        // 5. 退出程序
        else if (cmd == "exit")
        {
            cout << "程序退出成功" << endl;
            break;
        }

        // 6. 未知命令
        else
        {
            cout << "未知命令,请重新输入" << endl;
        }
    }

    return 0;
}

4.2 代码运行效果演示

4.2.1 输入示例

cpp 复制代码
insert xcj datastring
insert key2 data2
insert key3 data3123
get xcj
delete key2
show
exit

4.2.2 输出示例

bash 复制代码
存储成功:[xcj] = datastring
存储成功:[key2] = data2
存储成功:[key3] = data3123
输出值:datastring
显示删除成功
当前缓存内容如下:
xcj -> datastring
key3 -> data3123
程序退出成功

4.2.3 结果分析

从运行结果可以看出:

(1)insert 可以成功插入数据

(2)get 可以根据 key 取出对应的 value

(3)delete 可以删除指定 key 对应的数据

(4)show 可以查看当前缓存中的全部内容

(5)整个程序已经具备了一个最基础键值缓存系统的雏形


五、代码详细解析

5.1 头文件与命名空间

cpp 复制代码
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
using namespace std;

(1)<iostream>

用于输入输出,比如 cincout

(2)<string>

用于字符串类型 string

(3)<vector>

用于动态数组容器 vector

(4)<sstream>

用于字符串流 istringstream,把一整行字符串拆分成多个部分


5.2 定义两个 vector

cpp 复制代码
vector<string> keys;
vector<string> values;

这里定义了两个字符串类型的动态数组

(1)keys 用来保存关键字

(2)values 用来保存对应的值

例如执行:

cpp 复制代码
insert xcj datastring

后,程序内部可能变成:

cpp 复制代码
keys[0] = "xcj";
values[0] = "datastring";

5.3 使用 getline 获取整行命令

cpp 复制代码
string input;
getline(cin, input);

这一句的作用是:
从控制台读取用户输入的一整行内容。

比如用户输入:

cpp 复制代码
insert xcj datastring

那么:

cpp 复制代码
input = "insert xcj datastring"

如果这里不用 getline,而用 cin >> input,那么只能读到:

cpp 复制代码
insert

后面的内容就拿不到了。


5.4 istringstream iss(input); 到底是什么意思

这是本节一个非常容易疑惑的点。

cpp 复制代码
istringstream iss(input);
string cmd;
iss >> cmd;

5.4.1 它的本质是什么

可以把它理解成:

  • cin:从键盘读取
  • iss:从字符串里读取

也就是说,前面用**getline 读到的是一整个字符串:**

cpp 复制代码
"insert xcj datastring"

istringstream 的作用,就是把这个字符串变成"可按空格一个个读取的流"。

5.4.2 iss >> cmd; 读取了什么

这句会读取第一个单词,也就是命令名:

cpp 复制代码
cmd = "insert"

之后再继续读取,就可以得到 key 和 value。

所以这三句代码的整体作用就是:

把用户输入的一整行命令拆开,先取出第一个单词作为命令。


5.5 insert 插入命令的处理过程

cpp 复制代码
if (cmd == "insert")
{
string key;
string value;

iss >> key;
getline(iss >> ws, value);

这里的逻辑是:

(1)先读命令后的第一个单词作为 key
(2)再把剩余内容作为 value

例如输入:

cpp 复制代码
insert xcj datastring

那么:

cpp 复制代码
key = "xcj"
value = "datastring"

如果用户输入的是:

cpp 复制代码
insert name hello world

那么这里的:

cpp 复制代码
getline(iss >> ws, value);

能把带空格的内容一起读出来:

cpp 复制代码
value = "hello world"

5.6 查找 key 的逻辑

cpp 复制代码
int index = -1;
for (int i = 0; i < (int)keys.size(); ++i)
{
if (keys[i] == key)
{
index = i;
break;
}
}

这一段是整个程序查找逻辑的核心。

5.6.1 index = -1 表示什么

这里用 index 表示"目标 key 所在的位置"。

  • 如果找到了,就让 index = i
  • 如果没找到,就保持 -1

因此 -1 可以理解为一个"未找到标记"。

5.6.2 if (keys[i] == key) 是什么意思

这句是在比较:

  • keys[i]:第 i 个存进去的 key
  • key:当前用户输入的 key

如果两者相等,说明找到了对应位置。

例如:

cpp 复制代码
keys[0] = "xcj"
keys[1] = "key2"

用户输入:

cpp 复制代码
get key2

那么:

cpp 复制代码
key = "key2"

循环时会依次比较:

cpp 复制代码
keys[0] == "key2" // false
keys[1] == "key2" // true

找到后:

cpp 复制代码
index = 1;
break;

5.6.3 为什么 keys[i] 不是一个字母

因为:

cpp 复制代码
vector<string> keys;

这里**keys** 里面存的是很多个 string 字符串,不是字符。

所以:

cpp 复制代码
keys[i]

取出来的是第 i 个字符串,比如 "xcj""key2",而不是单个字符。

这一点要和下面这种情况区分开:

cpp 复制代码
string s = "xcj";
s[0]

这里的 s[0] 才是单个字符 'x'

也就是说:

(1)keys[i]:取 vector 里的第 i 个字符串
(2)str[i]:取字符串里的第 i 个字符

这两个写法表面看起来很像,但含义完全不同。


5.7 为什么"前面没存进去"却还能比较

很多同学会觉得:

前面只是定义了 vector<string> keys;,并没有马上 push_back 数据,那后面怎么比较?

答案是:

空的时候其实不会比较。

因为查找代码写的是:

cpp 复制代码
for (int i = 0; i < (int)keys.size(); ++i)

如果现在**keys 是空的**,那么:

cpp 复制代码
keys.size() == 0

循环就变成:

cpp 复制代码
for (int i = 0; i < 0; ++i)

一次都不会执行。

也就是说:

  • 程序刚启动时,keys 只是一个空容器
  • 只有用户执行 insert 之后,才真正往里面放入内容
  • 后面执行 getdelete 时,才会遍历已有的数据进行比较

所以不是"没存进去先比较",而是"先定义空容器,后续有数据了再比较"。


六、本节重难点与常见疑问

6.1 getline(cin, input)cin >> input 有什么区别

(1)cin >> input

读取到空格就停止

(2)getline(cin, input)

读取整行内容,直到按下回车

对于本节这种命令行程序来说,命令一般都是一整行,因此更适合用 getline


6.2 istringstream 为什么有用

因为它可以把整行字符串再次拆开读取。

例如:

cpp 复制代码
string input = "get xcj";
istringstream iss(input);

string cmd, key;
iss >> cmd >> key;

结果就是:

cpp 复制代码
cmd = "get"
key = "xcj"

它特别适合做命令解析、配置行解析、简单脚本输入处理。


6.3 keyskey 的区别

这是变量命名上很容易混的地方。

(1)key

表示一个单独的关键字

(2)keys

表示很多个 key 组成的容器

这里同理:

  • key:一个键
  • keys:很多键

七、小结

本节通过一个"简易缓存数据库"作业,把前面学过的多个 C++ 基础知识点串联了起来。

我们不仅练习了:

  • string
  • vector
  • getline
  • istringstream
  • for
  • if
  • erase

还真正完成了一个可运行的小项目。

更重要的是,这个作业背后体现的是一种很典型的程序设计思想:

把用户输入解析为命令,再根据命令操作程序内部的数据结构。

虽然这里实现得还比较简单,但它已经非常接近很多真实程序的基本工作流程了,比如:

  • 命令行工具
  • 配置解析器
  • 简易管理系统
  • 键值缓存结构
相关推荐
苏宸啊2 小时前
C++异常
c++
HABuo2 小时前
【linux网络基础(二)】理解端口号&UDP、TCP协议&网络字节序
linux·服务器·c语言·网络·c++·ubuntu·centos
牢姐与蒯3 小时前
c++数据结构之二叉搜索树
数据结构·c++·搜索
Morwit3 小时前
【力扣hot100】 416. 分割等和子集
数据结构·c++·算法·leetcode·职场和发展
qeen873 小时前
【算法笔记】二分查找与二分答案
c语言·c++·笔记·学习·算法·二分
Sylvia-girl3 小时前
类与对象(下)
c++·友元函数·类与对象
Hello eveybody3 小时前
介绍最大公因数和最小公约数(C++)
java·开发语言·c++
宵时待雨3 小时前
优选算法专题3:二分查找
数据结构·c++·算法·leetcode·职场和发展
Byte不洛3 小时前
理解C++异常机制:栈展开、异常传播与异常安全
c++·异常处理·后端开发·编程基础·try catch