目录
[1.1 作业功能要求](#1.1 作业功能要求)
[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)一个 vector 存 key
2)一个 vector 存 value
(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>
用于输入输出,比如 cin 和 cout
(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个存进去的 keykey:当前用户输入的 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之后,才真正往里面放入内容 - 后面执行
get或delete时,才会遍历已有的数据进行比较
所以不是"没存进去先比较",而是"先定义空容器,后续有数据了再比较"。
六、本节重难点与常见疑问
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 keys 和 key 的区别
这是变量命名上很容易混的地方。
(1)key
表示一个单独的关键字
(2)keys
表示很多个 key 组成的容器
这里同理:
key:一个键keys:很多键
七、小结
本节通过一个"简易缓存数据库"作业,把前面学过的多个 C++ 基础知识点串联了起来。
我们不仅练习了:
stringvectorgetlineistringstreamforiferase
还真正完成了一个可运行的小项目。
更重要的是,这个作业背后体现的是一种很典型的程序设计思想:
把用户输入解析为命令,再根据命令操作程序内部的数据结构。
虽然这里实现得还比较简单,但它已经非常接近很多真实程序的基本工作流程了,比如:
- 命令行工具
- 配置解析器
- 简易管理系统
- 键值缓存结构