【Redis#11】Redis 在 C++ 客户端下的安装使用流程(一条龙服务)
一、安装使用 --Ubuntu 下启用
1. 前置依赖 - hiredis
- hiredis 是一个用 C 语言实现的 Redis 客户端库,
redis-plus-plus
库基于hiredis
实现。 - 在开始之前,请确保已安装
libhiredis-dev
,可以通过以下命令安装:
sudo apt install libhiredis-dev
2. 安装 redis-plus-plus
C++操作redis的库有很多,咱们此处使用 redis-plus-plus。这个库的功能强大,使用简单。Github 地址:【https://github.com/sewenew/redis-plus-plus】
redis-plus-plus
是一个功能强大且易于使用的 C++ Redis 客户端库。它具有统一的接口风格,使得使用起来非常方便。- 以下是安装步骤:克隆
redis-plus-plus
的 GitHub 仓库到本地
git clone https://github.com/sewenew/redis-plus-plus.git
- 其实在 Ubuntu 下 由于网速限制,可能会出现超时 clone 失败情况,当然大家可以去网上搜教程改变 ip 来提高下载速度。但是这里我的思路是 下载压缩包到本地,然后把压缩包拖入到 Ubuntu 中,然后解包即可
进入克隆下来的项目目录,并创建构建目录:
cd redis-plus-plus
mkdir build && cd build
- 创建一个 build 目录是习惯做法,并非必要,目的是为了让编译生成得到临时文件放到 build 下,避免污染源代码目录
由于 redis-plus-plus 是使用 CMake 作为构建工具的
- CMake:相当于是 Makefile 的升级版,因为 Makefile 本身功能比较简陋,比较原始,写起来也更加麻烦,所以实际开发中很少手写 Makefile,所以需要通过程序来生成 Makefile,cmake(C 语言)就是一个生成 Makefile 的工具
使用 CMake 配置并编译安装:
sudo apt install cmakelighthouse@VM-8-10-ubuntu:build$ cmake .. # 生成 makefile, 此次 .. 指向的是 CMakeLists.txt 文件所在目录lighthouse@VM-8-10-ubuntu:build$ ll
total 104
drwxrwxr-x 6 lighthouse lighthouse 4096 Jul 4 09:34 ./
drwxrwxr-x 7 lighthouse lighthouse 4096 Jul 4 09:29 ../
drwxrwxr-x 2 lighthouse lighthouse 4096 Jul 4 09:34 cmake/
-rw-rw-r-- 1 lighthouse lighthouse 19575 Jul 4 09:34 CMakeCache.txt
drwxrwxr-x 7 lighthouse lighthouse 4096 Jul 4 09:34 CMakeFiles/
-rw-rw-r-- 1 lighthouse lighthouse 8885 Jul 4 09:34 cmake_install.cmake
-rw-r--r-- 1 lighthouse lighthouse 3616 Jul 4 09:34 CPackConfig.cmake
-rw-r--r-- 1 lighthouse lighthouse 4100 Jul 4 09:34 CPackSourceConfig.cmake
-rw-rw-r-- 1 lighthouse lighthouse 33406 Jul 4 09:34 Makefile
drwxrwxr-x 3 lighthouse lighthouse 4096 Jul 4 09:34 src/
drwxrwxr-x 3 lighthouse lighthouse 4096 Jul 4 09:34 test/lighthouse@VM-8-10-ubuntu:build$ make # 编译
lighthouse@VM-8-10-ubuntu:build$ sudo make install # 把编译生成.a .so库拷贝到系统目录
说明:
redis-plus-plus
库支持多种方式传递参数,如 初始化列表或迭代器对。- 当函数需要返回多个数据时,通常会使用插入迭代器将结果添加到容器中。
- 对于可能返回无效值的情况,
redis-plus-plus
通常会使用std::optional
来表示。 - 上面编译安装的库默认在
/usr/local
路径下的
3. 示例
Quick Start 创建一个简单的 C++ 程序来连接 Redis 并发送一个 ping
命令,以检查连接是否成功。
① 包含 redis-plus-plus 的头文件路径如下:
/usr/local/include/sw/redis++ # sw 目录是作者名字缩写lighthouse@VM-8-10-ubuntu:redis++$ ll
total 668
drwxr-xr-x 3 root root 4096 Jul 4 09:37 ./
drwxr-xr-x 3 root root 4096 Jul 4 09:37 ../
-rw-r--r-- 1 root root 29717 Jun 12 23:42 cmd_formatter.h
-rw-r--r-- 1 root root 5026 Jun 12 23:42 command_args.h
-rw-r--r-- 1 root root 78953 Jun 12 23:42 command.h
-rw-r--r-- 1 root root 4211 Jun 12 23:42 command_options.h
-rw-r--r-- 1 root root 5803 Jun 12 23:42 connection.h
-rw-r--r-- 1 root root 5195 Jun 12 23:42 connection_pool.h
-rw-r--r-- 1 root root 1361 Jun 12 23:42 cxx_utils.h
-rw-r--r-- 1 root root 4353 Jun 12 23:42 errors.h
-rw-r--r-- 1 root root 66 Jul 4 09:34 hiredis_features.h
drwxr-xr-x 2 root root 4096 Jul 4 09:37 patterns/
-rw-r--r-- 1 root root 1466 Jun 12 23:42 pipeline.h
-rw-r--r-- 1 root root 68779 Jun 12 23:42 queued_redis.h
-rw-r--r-- 1 root root 7118 Jun 12 23:42 queued_redis.hpp
-rw-r--r-- 1 root root 58456 Jun 12 23:42 redis_cluster.h
-rw-r--r-- 1 root root 56322 Jun 12 23:42 redis_cluster.hpp
-rw-r--r-- 1 root root 1019 Jun 12 23:42 redis++.h # 包含 redis-plus-plus 的头文件
-rw-r--r-- 1 root root 175414 Jun 12 23:42 redis.h
-rw-r--r-- 1 root root 50024 Jun 12 23:42 redis.hpp
-rw-r--r-- 1 root root 2344 Jun 12 23:42 redis_uri.h
-rw-r--r-- 1 root root 21785 Jun 12 23:42 reply.h
-rw-r--r-- 1 root root 3883 Jun 12 23:42 sentinel.h
-rw-r--r-- 1 root root 3458 Jun 12 23:42 shards.h
-rw-r--r-- 1 root root 3664 Jun 12 23:42 shards_pool.h
-rw-r--r-- 1 root root 8242 Jun 12 23:42 subscriber.h
-rw-r--r-- 1 root root 1200 Jun 12 23:42 tls.h
-rw-r--r-- 1 root root 2101 Jun 12 23:42 transaction.h
-rw-r--r-- 1 root root 6295 Jun 12 23:42 utils.h
-rw-r--r-- 1 root root 992 Jun 12 23:42 version.h# 如果找不到的,也可以用 find 进行查找
lighthouse@VM-8-10-ubuntu:~$ find /usr/ -name "redis++*"/usr/local/lib/pkgconfig/redis++.pc
/usr/local/include/sw/redis++
/usr/local/include/sw/redis++/redis++.h
② 编写 main.cc 代码
#include <iostream>
#include <string>
#include <sw/redis++/redis++.h>using namespace std;int main(){// 构建Redis对象的时候,在构造函数中,指定Redis服务器的地址和端口sw::redis::Redis redis("tcp://127.0.0.1:6379");string ret = redis.ping();cout << ret << endl;return 0;
}
③ Makefile 文件编写(需要引入 库文件, redis++ 的静态库/ hiredis的静态库/ 线程库)
main: main.ccg++ -o $@ $^ -std=c++17 /usr/local/lib/libredis++.a /usr/lib/x86_64-linux-gnu/libhiredis.a -lpthread
.PHONY: clean
clean:rm main
④ 结果如下:
lighthouse@VM-8-10-ubuntu:redis-code$ ./main
PONG
注意:如果我们启用了密码验证的话,就会输出错误信息如下:
- Redis 默认是没有设置密码的。如果你之前设置了密码(比如通过修改
redis.conf
文件中的requirepass
配置项),那么每次连接都需要使用.auth("your_password")
进行认证。
terminate called after throwing an instance of 'sw::redis::ReplyError'what(): NOAUTH Authentication required.
Aborted (core dumped)
此时就需要在创建 Redis 对象后,添加下面一段代码:
// 如果 Redis 设置了密码,请在这里添加认证
redis.auth("your_redis_password"); // 替换为你的 Redis 密码
二、常用类型接口学习
-
接口的相似性:sw/redis++ 的接口设计与 Redis 原生命令非常相似,这使得熟悉 Redis 命令的开发者可以快速上手。
-
参数传递方式:
- 当需要传入多个参数时,有两种方法:
- 使用容器(如 std::vector 或 std::set)保存参数,并传递容器的迭代器。
- 直接使用初始化列表 {} 传递参数。
-
键值对传递:如果需要传递键值对,可以使用 std::pair 来表示,例如 std::pair<std::string, std::string>。
-
常用 C++ 类型:
- OptionalString:用于接收可能为 nil 的返回值,可以判断是否为空。
- StringView:用于传递只读字符串,但通常直接使用 std::string 即可。
- long long:用于接收整数类型的返回值。
- double:用于 ZSET 中的分数(score)类型。
-
输出多个变量:如果函数需要输出多个变量,应该 传递一个插入迭代器(如 std::inserter 或 std::back_inserter(返回值为
std::back_insert_iterator
)),这样可以将结果直接插入到容器中。
前置准备
下面执行模板代码如下:
#include <iostream>
#include <string>
#include <vector>
#include <chrono>
#include <sw/redis++/redis++.h>
#include "../util.hpp"
using namespace std::chrono_literals; // 字面量
using sw::redis::Redis;int main(){sw::redis::Redis redis("tcp://127.0.0.1:6379");// 如果 Redis 设置了密码,请在这里添加认证redis.auth("123456"); // 替换 Redis 密码// 测试函数return 0;
}
Makefile 文件如下:
test:test.ccg++ -o $@ $^ -std=c++17 /usr/local/lib/libredis++.a /usr/lib/x86_64-linux-gnu/libhiredis.a -lpthread
.PHONY: clean
clean:rm -f test test.o
Util.hpp 代码如下:
#pragma#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <unordered_map>// 1. 通用的容器打印函数, 可以打印任何类型的容器
template<typename T>
inline void printContainer(const T& container) {for (const auto& elem : container) std::cout << elem << std::endl;
}
// 2. 专为存储 std::pair 类型元素的容器设计
template<typename T>
inline void printContainerPair(const T& container) {for (auto& elem : container) {// 此处预期 elem 是一个 std::pairstd::cout << elem.first << ": " << elem.second << std::endl;}
}
// 3. 专为存储 std::optional 类型元素的容器设计
template<typename T>
inline void printContainerOptional(const T& container) {for (const auto& elem : container) {// 此处预期 elem 是一个 optional 类型的元素, 打印之前, 先判定一下, 看是否有效if (elem) {std::cout << elem.value() << std::endl;} else {std::cout << "元素无效" << std::endl;}}
}
1. 通用命令
① get/set
这里的 set 函数如下:
bool set(const sw::redis::StringView &key, const sw::redis::String View &val, bool keepttl, sw::redis::UpdateType type = sw::redis::UpdateType::ALWAYS)
- 这里的参数和之前的 set 命令选项有很大关联
- 这里用到了 StringView 的类型(在 sw::redis 命名空间 中,StringView 只读不可修改)
编写代码如下:
void test1(sw::redis::Redis &redis){std::cout << "Get 和 Set 使用" << std::endl;redis.flushall(); // 使用前先清空数据库, 避免之前残留数据产生干扰redis.set("key1", "value1"); // 使用 set 命令设置键值对// 使用 get 命令获取键的值auto value = redis.get("key1"); // 这里返回的OptionalString可以表示一个非法值if (value) { // optional 可以隐式转换成 bool 类型std::cout << "key1: " << *value << std::endl; // 输出: key1: value2} else {std::cout << "key1 not found" << std::endl;}auto value2 = redis.get("key2"); // key2 不存在std::cout << "key2: " << value2.value() << std::endl; // 输出: key2: <nil>, 这里就不能用指针来输出了, 最好用 value() 方法
}
输出如下:
lighthouse@VM-8-10-ubuntu:common$ ./test
Get 和 Set 使用
key1: value1
terminate called after throwing an instance of 'std::bad_optional_access'what(): bad optional access
Aborted (core dumped)
- 这里 key2 不存在,因此 value2 就是 optional 的非法状态,就会报异常
② exists/del
编写代码如下:
void test2(sw::redis::Redis &redis){std::cout << "Exists 和 Del 使用" << std::endl;redis.flushall();redis.set("key", "value1");auto ret = redis.exists("key");std::cout << "key exists: " << ret << std::endl;// 多级判断 redis.exists({"key1", "key2", "key3"}) 返回一个 值 表示几个 key 存在auto ret = redis.exists({"key", "key2", "key3"});std::cout << "key, key2, key3 exists: " << ret << std::endl; redis.del("key");ret = redis.exists("key");std::cout << "key exists after delete: " << ret << std::endl;
}
输出如下:
lighthouse@VM-8-10-ubuntu:common$ ./test
Exists 和 Del 使用
key exists: 1
key exists after delete: 0
③ keys
编写代码如下:
void test3(sw::redis::Redis &redis){std::cout << "Keys 使用" << std::endl;redis.flushall();redis.set("key1", "value1");redis.set("key2", "value2");redis.set("key3", "value3");// 获取所有的键 // keys 的第二个参数, 是一个插入迭代器, 需准备好一个保存结果的容器// 接下来再创建一个插入迭代器指向容器的位置, 把 keys 的结果插入到容器中std::vector<std::string> ret;auto it = std::back_insert_iterator(ret);redis.keys("*", it); // 匹配所有键for (const auto& key : ret) {std::cout << "Key: " << key << std::endl;}
}
输出如下:
lighthouse@VM-8-10-ubuntu:common$ ./test
Keys 使用
Key: key1
Key: key3
Key: key2
④ expire/ttl
编写代码如下:
void test4(sw::redis::Redis &redis){using namespace std::chrono_literals; // 使用 chrono 的字面量std::cout << "Expire 和 TTL" << std::endl;redis.flushall();redis.set("key1", "value1");redis.expire("key1", std::chrono::seconds(5)); // 设置 key1 的过期时间为 5 秒std::this_thread::sleep_for(std::chrono::seconds(3s)); // 等待 3 秒long long time = redis.ttl("key1"); // 获取 key1 的剩余过期时间 if (time) {std::cout << "TTL for key1: " << time << " seconds" << std::endl;} else {std::cout << "key1 does not have a TTL" << std::endl;}
}
输出如下:
lighthouse@VM-8-10-ubuntu:common$ ./test
Expire 和 TTL
TTL for key1: 2 seconds
⑤ type
编写代码如下:
void test5(sw::redis::Redis &redis){std::cout << "Type" << std::endl;redis.flushall();redis.set("key1", "1111");std::string res = redis.type("key1"); std::cout << "Type of key1: " << res << std::endl; redis.hset("hash1", "field1", "value1");res = redis.type("hash1");std::cout << "Type of hash1: " << res << std::endl; // 输出: Type of hash1: hash
}
输出如下:
lighthouse@VM-8-10-ubuntu:common$ ./test
Type
Type of key1: string
Type of hash1: hash
2. String
① set 使用
void test1(sw::redis::Redis &redis){std::cout << "Set 带有超时时间" << std::endl;redis.flushall();redis.set("key", "111", 10s);std::this_thread::sleep_for(3s);long long time = redis.ttl("key");std::cout << "time: " << time << std::endl;
}void test2(sw::redis::Redis &redis){std::cout << "set NX 和 XX" << std::endl;redis.flushall();redis.set("key", "111");// set 的重载版本中, 没有单独提供 NX 和 XX 的版本, 必须搭配过期时间的版本来使用. redis.set("key", "222", 0s, sw::redis::UpdateType::EXIST);auto value = redis.get("key");if (value) {std::cout << "value: " << value.value() << std::endl;} else {std::cout << "key 不存在!" << std::endl;}
}// 输出如下
Set 带有超时时间
time: 7
set NX 和 XX
value: 222
② mget & mset
- 可以把多个键值对提前组织到容器 vector<pair<string, string>> 中. 以迭代器的形式告诉 mset
void test3(sw::redis::Redis &redis){std::cout << "mset & mget" << std::endl;redis.flushall();// // 写法一: 初始化列表描述多个键值对// redis.mset({// std::make_pair("key1", "value1"),// std::make_pair("key2", "value2"),// std::make_pair("key3", "value3")// });// 写法二: 使用 std::vector<std::pair<std::string, std::string>> 描述多个键值对, 然后以迭代器的形式告诉 msetstd::vector<std::pair<std::string, std::string>> keys = {{"key1", "111"},{"key2", "222"},{"key3", "333"}};redis.mset(keys.begin(), keys.end());std::vector<sw::redis::OptionalString> res;auto it = std::back_inserter(res);redis.mget({"key1", "key2", "key3"}, it);printContainerOptional(res);
}// 输出
mset & mget
111
222
333
③ getrange & setrange
void test4(Redis& redis) {std::cout << "getrange 和 setrange" << std::endl;redis.flushall();redis.set("key", "abcdefghijk"); std::string result = redis.getrange("key", 2, 5);std::cout << "result: " << result << std::endl;redis.setrange("key", 2, "xyz");auto value = redis.get("key");std::cout << "value: " << value.value() << std::endl;
}// 输出
getrange 和 setrange
result: cdef
value: abxyzfghijk
④ incr & decr
void test5(Redis& redis) {redis.flushall();redis.set("key", "100");//对比关注 如下两种返回结果//返回结果1:long longlong long result = redis.incr("key");std::cout << "result: " << result << std::endl;//返回结果2:对象auto value = redis.get("key");std::cout << "value: " << value.value() << std::endl;result = redis.decr("key");std::cout << "result: " << result << std::endl;value = redis.get("key");std::cout << "value: " << value.value() << std::endl;
}// 输出
result: 101
value: 101
result: 100
value: 100
3. List
① lpush & rpush & lrange
void test1(Redis& redis){redis.flushall(); redis.lpush("mylist", "111"); // 插入单个元素, 基于初始化列表// 插入一组元素, 基于迭代器std::vector<std::string> values = {"222", "333", "444"};redis.rpush("mylist", values.begin(), values.end());// 获取列表元素std::vector<std::string> res;// auto it = std::back_inserter(res);redis.lrange("mylist", 0, -1, std::back_insert_iterator(res));// 也可以用下面的写法// redis.lrange("mylist", 0, -1, std::back_inserter(res));printContainer(res);
}// 输出
111
222
333
444
关于 std::back_inserter(res)
和 std::back_insert_iterator(res)
std::back_inserter(res)
是工厂函数,返回std::back_insert_iterator<std::vector<std::string>>
。std::back_insert_iterator(res)
是直接构造迭代器,模板实参由 CTAD(C++17 起)或你手动写出。
编译器展开后两种写法生成的临时对象类型一模一样,行为也一样:
operator=
里调用res.push_back(value)
,因此最终res
的内容、性能、异常安全性均无任何差异。
结论:用 std::back_inserter(res)
更短、更惯用;用 std::back_insert_iterator(res)
只是显式写出类型,二者可互换。
② lpop & rpop
void test2(Redis& redis){redis.flushall();redis.rpush("key", {"1", "2", "3", "4"}); // 构造一个 listauto result = redis.lpop("key");if (result) std::cout << "lpop: " << result.value() << std::endl;result = redis.rpop("key");if (result) std::cout << "rpop: " << result.value() << std::endl;
}// 输出
lpop: 1
rpop: 4
③ blpop
void test3(Redis& redis){redis.flushall(); auto res = redis.blpop({"key1", "key2"}, 5s);if (res) {std::cout << "blpop: " << res->first << ", " << res->second << std::endl;} else {std::cout << "blpop timeout" << std::endl;}
}// 输出
blpop timeout
- TIPS:对于
std::optional
类型来说,可以直接使用->
访问optional
内部包含的元素的成员
④ llen
void test4(Redis& redis){std::cout << "llen" << std::endl;redis.flushall(); redis.rpush("mylist", {"1", "2", "3", "4"});auto len = redis.llen("mylist");std::cout << "Length of mylist: " << len << std::endl;
}// 输出
Length of mylist: 4
4. Hash
① Hset 和 Hget
向哈希中添加字段-值对,并从中获取特定字段的值。
void test1(Redis& redis){// 清空数据库redis.flushall();// 设置单个字段-值对redis.hset("key", "f1", "111");// 使用 std::pair 设置另一个字段-值对redis.hset("key", std::make_pair("f2", "222"));// 批量设置多个字段-值对redis.hset("key", {std::make_pair("f3", "333"), std::make_pair("f4", "444")});// 使用容器批量设置std::vector<std::pair<std::string, std::string>> fields = {std::make_pair("f5", "555"),std::make_pair("f6", "666")};redis.hset("key", fields.begin(), fields.end());// 获取并打印字段 f3 的值auto result = redis.hget("key", "f3");if (result) {std::cout << "result: " << result.value() << std::endl;} else {std::cout << "result 无效!" << std::endl;}
}// 输出
result: 333
② Hexists & Hdel & Hlen
- 检查指定字段是否存在于给定的哈希中
- 删除哈希中的一个或多个字段,并输出受影响的字段数量
- 查看哈希长度
void test2(Redis& redis){// 清空数据库redis.flushall();redis.hset("key", {std::make_pair("f1", "111"), std::make_pair("f2", "222"), std::make_pair("f3", "333")});std::cout << "key 的字段数量: " << redis.hlen("key") << std::endl;std::cout << "f1 是否存在: " << redis.hexists("key", "f1") << std::endl;redis.hdel("key", "f2");std::cout << "删除f2后, f2 是否存在: " << redis.hexists("key", "f2") << std::endl;
}// 输出
key 的字段数量: 3
f1 是否存在: 1
删除f2后, f2 是否存在: 0
③ Hkeys 和 Hvals
获取哈希中的所有 field 和对应的 value,并分别打印出来
void test3(Redis& redis){// 清空数据库redis.flushall();redis.hset("key", {std::make_pair("f1", "111"), std::make_pair("f2", "222"), std::make_pair("f3", "333")});std::vector<std::string> fields;redis.hkeys("key", std::back_inserter(fields));printContainer(fields);// 获取所有字段的值std::vector<std::string> values;redis.hvals("key", std::back_inserter(values));printContainer(values);
}// 输出
f1 f2 f3
111 222 333
④ Hmget 和 Hmset
一次性设置多个字段-值对 ( vector<pair>
),并从哈希中获取多个字段的值。
void test4(Redis& redis){// 清空数据库redis.flushall();redis.hset("key", {std::make_pair("f1", "111"), std::make_pair("f2", "222"), std::make_pair("f3", "333")});// 再次批量设置更多字段-值对std::vector<std::pair<std::string, std::string>> pairs = {std::make_pair("f4", "444"),std::make_pair("f5", "555"),std::make_pair("f6", "666")};redis.hmset("key", pairs.begin(), pairs.end());// 获取并打印指定字段的值std::vector<std::string> values;auto it = std::back_inserter(values);redis.hmget("key", {"f1", "f2", "f3"}, it);printContainer(values);
}// 输出
111 222 333
5. Set
① Sadd & Smembers & Spop
此示例展示了如何向集合中添加元素以及如何获取集合中的所有成员,并且如何随机移除集合中的一个元素,并打印被移除的元素或相应的错误信息
void test1(Redis& redis){std::cout << "sadd & spop & smembers" << std::endl;redis.flushall();redis.sadd("myset", "111"); // 插入单个元素, 基于初始化列表// 插入一组元素, 基于迭代器std::vector<std::string> values = {"222", "333", "444"};redis.sadd("myset", values.begin(), values.end());auto num = redis.spop("myset"); // 随机弹出1个元素std::cout << "spop: " << num.value_or("null") << std::endl;// 获取集合元素std::set<std::string> res;redis.smembers("myset", std::back_insert_iterator(res));printContainer(res);
}// 输出
spop: 222
111
333
444
- 注意,保存
smembers
结果时,使用std::set
可能更合适,因为集合中的元素是无序且唯一的。
② Sismember 和 Scard
此示例展示了如何向集合中添加元素以及如何获取集合中的所有成员。
void test2(Redis& redis){redis.flushall();redis.sadd("key", {"111", "222", "333"});std::cout << "集合元素个数: " << redis.scard("key") << std::endl; std::cout << "是否包含元素 111: " << redis.sismember("key", "111") << std::endl;std::cout << "是否包含元素 444: " << redis.sismember("key", "444") << std::endl;
}// 输出
集合元素个数: 3
是否包含元素 111: 1
是否包含元素 444: 0
③ Sinter 和 SinterStore
此示例说明了如何找到两个集合的交集,并将结果存储在一个新的集合中,同时打印新集合的大小和内容
void test3(Redis& redis){std::cout << "sinter & sinterstore" << std::endl;redis.flushall();redis.sadd("key1", {"111", "222", "333"});redis.sadd("key2", {"111", "222", "444"});// 计算两个集合的交集std::set<std::string> result;auto it = std::inserter(result, result.end());redis.sinter({"key1", "key2"}, it);// 打印交集结果printContainer(result);long long len = redis.sinterstore("key3", {"key1", "key2"});std::cout << "len: " << len << std::endl;
}// 输出
111 222
len: 2
④ srem 和 smove
void test4(Redis& redis){redis.flushall();redis.sadd("set1", {"a", "b", "c"});redis.sadd("set2", {"d", "e"});// 移除元素redis.srem("set1", "b");std::cout << "set1 after srem: ";std::vector<std::string> res;redis.smembers("set1", std::back_insert_iterator(res));printContainer(res);// 移动元素redis.smove("set1", "set2", "c");std::cout << "set1 after smove: ";std::vector<std::string> res1;redis.smembers("set1", std::back_insert_iterator(res1));printContainer(res1);std::cout << "set2 after smove: ";std::vector<std::string> res2;redis.smembers("set2", std::back_insert_iterator(res2));printContainer(res2);
}// 输出
set1 after srem: c a
set1 after smove: a
set2 after smove: c e d
6. Zset
① Zadd 和 Zrange & Zscore
- 向有序集合中添加成员,并通过
zrange
命令以两种不同的方式获取成员:只查询成员或同时查询成员和分数。 - 获取指定成员的分数,并输出结果
void test1(Redis& redis){redis.flushall();redis.zadd("key", {std::make_pair("a", 98), std::make_pair("b", 97)});// 使用容器批量添加成员及其分数std::vector<std::pair<std::string, double>> members = {std::make_pair("c", 95),std::make_pair("d", 93)};redis.zadd("key", members.begin(), members.end());// 获取所有成员std::vector<std::string> membersResults;redis.zrange("key", 0, -1, std::back_inserter(membersResults));printContainer(membersResults);// 获取并打印成员 c 的分数auto score = redis.zscore("key", "c");if (score) {std::cout << "score: " << score.value() << std::endl;} else {std::cout << "score 无效" << std::endl;}// 获取并打印所有成员及其分数std::vector<std::pair<std::string, double>> membersWithScore;redis.zrange("key", 0, -1, std::back_inserter(membersWithScore));printContainerPair(membersWithScore);
}// 输出
d c b a
score: 95
d: 93
c: 95
b: 97
a: 98
zrange支持两种主要的风格:
- 只查询member, 不带score
- 查询member同时带score
关键就是看插入迭代器指向的容器的类型:
- 指向的容器只是包含一个string, 就是只查询member
- 指向的容器包含的是一个pair, 里面有string和 double, 就是查询member同时带有score
② Zcard & Zrem
- 计算有序集合中的成员数量,并输出结果
- 从有序集合中删除一个成员,并输出删除后的集合大小
void test2(Redis& redis){redis.flushall();redis.zadd("key", {std::make_pair("a", 98), std::make_pair("b", 97)});std::cout << "key 的成员数量: " << redis.zcard("key") << std::endl;std::cout << "a 是否存在: " << redis.zscore("key", "a").has_value() << std::endl;redis.zrem("key", "b");std::cout << "删除 b 后, b 是否存在: " << redis.zscore("key", "b").has_value() << std::endl;
}// 输出
key 的成员数量: 2
a 是否存在: 1
删除 b 后, b 是否存在: 0