Redis 简单编写C++客户端

news2024/10/23 13:23:18

  我们之前的学习都是通过Redis自带的命令行式的客户端来使用Redis的,我们在执行命令的时候,都是手动执行的。然而这种操作方式并不是日常开发的主要形式。

更多的时候,是使用Redis的api来实现定制化的Redis客户端程序,进一步来操作服务器。

虽然还有些带有图形化界面的客户端,它们的本质跟命令行式的客户端一样,都是通用性质的,在工作中还是会更希望用到专用的,定制化的客户端程序。

认识RESP

说到自定义客户端,我们为什么能够自定义客户端呢?对于qq,微信这样的app,我们又能不能自定义客户端呢?肯定是不能的,因为这些程序没有公开自己使用的自定义协议。 

我们能够对Redis服务自定义客户端,是因为Redis自定义的应用层协议是公开的。

名字就叫做RESP。

在官网文档这里描述了这个协议的优点:

1.简单好实现。

2.快速进行解析。

3.肉眼可以阅读的。

Redis服务器,传输层是基于TCP协议的,但是和TCP又没有强耦合的。

 

这里的RESP我们只需要简单了解就可以了,因为目前已经有很多大佬实现了对这套协议的解析/构造了,我们只需要拿着大佬们提供的库就可以较为方便的完成与Redis服务器的通信操作了。 

安装 redis-plus-plus 

在GitHub上

另外,在使用这个库之前,还需要再安装一个C语言的Redis客户端的库,因为redis-plus-plus依赖这个库。

就是这个hiredis,安装方式有两种:

1.源码安装(比较麻烦)

 需要我们把源码下过来,然后进行编译安装,这种方式容易出幺蛾子,比如环境兼容问题之类的

安装这个我们可以直接使用包管理器来安装

Ubuntu下

apt install libhiredis-dev

CentOS下

yum install hiredis-devel.x86_64

 注意需要管理员权限。

安装完hiredis后,就正式安装redis-plus-plus了。

redis-plus-plus只能用源码安装的方式,另外在Ubuntu下安装要比CentOS下安装要容易一些,因为CentOS下许多软件的版本太老了,环境可能不大兼容,所以这里重点说明在Ubuntu下的安装。

官网给出的源码安装方法:

git clone https://github.com/sewenew/redis-plus-plus.git

cd redis-plus-plus

mkdir build

cd build

cmake ..

make

make install

cd ..

另外,学到这里我们需要学会一个新的构建工具,以前我们都是使用的makefile,但是makefile太简陋,原始了,makefile需要程序员手写,这样效率太低了。

Cmake是一个自动构建makefile的工具,通过程序来生成makefile,现在实际开发很少使用makefile了。

使用Cmake,可以先创建一个目录,然后进入到这个目录中,这个步骤并不是必须的,而是因为Cmake在生成的时候会有很多中间文件,有可能会污染源文件,所以特地把生成的文件放到一个专门的目录下。

如果Ubuntu下没有安装Cmake,可以安装一下:

apt  install cmake

 make安装好后就是这个样子

另外关于这个make install的作用,在我们编译完成后

 

像.a .so这样库都是在当前编译目录下的,后续写代码时不一定找得到这里的库,因此推荐把这些库拷贝到系统目录中,手动拷贝比较麻烦,而Cmake早就帮我们做好了,直接执行

make install

就可以自动的帮我们把这些库拷贝到系统目录下了。

最后,在编写代码的时候,要使用这个库,并且刚刚的安装的日志找不到了,可以使用find命令来查找

第一个是头文件,第二个是库。 

find查找redis++库的示例:

find /usr/ -name "redis++*"

 一般安装的库头文件都会放在/usr/ 下。

简单命令的使用 

ping 

hello.cc

#include <iostream>
#include <sw/redis++/redis++.h>

using std::cout;
using std::endl;

int main()
{
    sw::redis::Redis redis("tcp://127.0.0.1:6379");
    std::string ret = redis.ping();
    cout << ret << endl;

    return 0;
}

在初始化redis对象时,用了一个url,也就是唯一资源定位符。url确实是Http协议用的最多,但并不是Http专属的。 

 makefile

 如果在CentOS上,这些库的位置可能不一样,但是查找库的方式是一样的(用find)

hello:hello.cc
	g++ -std=c++17 -o $@ $^ /usr/local/lib/libredis++.a /usr/lib/x86_64-linux-gnu/libhiredis.a -pthread

.PHONY:clean
clean:
	rm -f hello

这里注意要把库的位置写上。

get/set

先看代码:

generic.cc

#include <iostream>
#include <sw/redis++/redis++.h>

using std::cout;
using std::endl;

void test(sw::redis::Redis& redis)
{
    redis.flushall(); // 先清空数据库,避免干扰

    redis.set("key1","11");
    redis.set("key2","22");
    redis.set("key3","33");

    auto val1 = redis.get("key1");
    auto val2 = redis.get("key2");
    auto val3 = redis.get("key3");
    auto val4 = redis.get("key4");

    if(val1)
    {
        cout << "val1 = " << val1.value() << endl;
    }

    if(val4)
    {
        cout << "val4 = " << val4.value() << endl;
    }
}

int main()
{
    sw::redis::Redis redis("tcp://127.0.0.1:6379");
    test(redis);

    return 0;
}

 makefile

.PHONY:all
all:hello generic
hello:hello.cc
	g++ -std=c++17 -o $@ $^ /usr/local/lib/libredis++.a /usr/lib/x86_64-linux-gnu/libhiredis.a -pthread
generic:generic.cc
	g++ -std=c++17 -o $@ $^ /usr/local/lib/libredis++.a /usr/lib/x86_64-linux-gnu/libhiredis.a -pthread

.PHONY:clean
clean:
	rm -f hello
	rm -f generic

补充:

 在set的参数,关于key的类型是一个StringView,它跟string类型不同的就是,string既可以读还可以修改,StringView只能读,不能修改。

用StringView的好处就是效率比std::string更高。

另外关于get函数的返回值

这是一个OptionalString类型。

这个类型可以表示无效值,因为std::string和string*都不方便表示无效值,因此redis-plus-plus作者自己封装了一个这样的类型。并在在C++14版本中也引入了optional类型。

 这个类型在这里并不支持 移位运算符的重载,我们可以通过如下的方式来获取它的string内容,再进行打印

如果这个 optional是一个无效值,那么当我们试图获取它的string对象时就会抛出一个异常。

然而C++的异常其实并不好用。

 我们可以用if判定的方式来代替异常。

另外,像我们这样进行单元测试的时候,像flushall这样清楚数据的操作放在函数开头比较好。

 exists / del

这两个函数就很简单了。

exists

void test2(sw::redis::Redis& redis)
{
    cout << "exists " << endl;
    redis.flushall();

    redis.set("key1","11");
    redis.set("key2","22");
    redis.set("key3","33");

    auto ret = redis.exists("key1");
    cout << ret << endl;

    ret = redis.exists("key5"); // 不存在
    cout << ret << endl;

    ret = redis.exists({"key1","key2","key3","key5"});
    cout << ret << endl;
}

exists存在重载,可以传一个初始化列表,这样可以检测多个key。 

执行结果:

del

void test3(sw::redis::Redis& redis)
{
    cout << "del " << endl;
    redis.flushall();

    redis.set("key1","11");
    redis.set("key2","22");
    redis.set("key3","33");

    auto ret = redis.del("key1");
    cout << ret << endl;

    ret = redis.del("key5"); // 不存在
    cout << ret << endl;

}

执行结果

keys 

在使用keys命令前,我们需要先了解什么是插入迭代器,也就是keys命令的返回值类型。

 

 STL中有物种迭代器。我们之前有用过sort,就是用的随机访问迭代器。

插入迭代器本质也是一种 输出迭代器。

通常一个迭代器主要是表示一个 位置。

而插入迭代器则是 位置 + 动作。

比如刚刚的 back_insert_iterator。不过像这样的迭代器构造函数写起来比较麻烦,于是一般会使用一些辅助的函数来进行构造。

对于插入迭代器来说,最核心的操作就是 赋值运算符的重载 = 。 

比如it是一个插入迭代器(比如 back_insert_iterator),然后it2是一个普通迭代器,那么it = it2的操作,就相当于获取到it2指向的元素,然后把这个元素插入到it指向的容器末尾。

为什么要这样设计迭代器呢?

主要还是为了解耦合,比如keys命令这里它看不见容器,容器也看不见keys,它们是以迭代器为中间介质运作的。

代码:

void test4(sw::redis::Redis& redis) {
    std::cout << "keys" << std::endl;
    redis.flushall();

    redis.set("key", "111");
    redis.set("key2", "222");
    redis.set("key3", "333");
    redis.set("key4", "444");
    redis.set("key5", "555");
    redis.set("key6", "666");

    // keys 的第二个参数, 是一个 "插入迭代器". 咱们需要先准备好一个保存结果的容器. 
    // 接下来再创建一个插入迭代器指向容器的位置. 就可以把 keys 获取到的结果依次通过刚才的插入迭代器插入到容器的指定位置中了. 
    std::vector<std::string> result;
    auto it = std::back_inserter(result);
    redis.keys("*", it);
    printContainer(result);
}

可见这里的it,我们用的back_inserter来构造了一个插入迭代器,用一个vector<string>容器构造的 

 另外对于打印这个迭代器的内容,专门封装了一个函数

template<typename T>
inline void printContainer(const T& container) {
    for (const auto& elem : container) {
        std::cout << elem << std::endl;
    }
}

expire / ttl 

在expire中,

 我们需要传入一个超时时间,而这个时间的类型是std::chrono::seconds,在C++11中,我们可以直接使用字面值常量来传参。

 代码:

void test5(sw::redis::Redis& redis)
{
    using namespace std::chrono_literals; // 包含之后就可以使用字面值常量了(C++11)

    cout << "expire and ttl" << endl;
    redis.flushall();

    redis.set("key1","11");
    // 10s => std::chrono::seconds(10) 
    redis.expire("key1",10s);
    std::this_thread::sleep_for(2s);
    auto ret = redis.ttl("key1");
    cout << ret << endl;
}

执行结果

type 

代码:

void test6(sw::redis::Redis& redis)
{
    cout << "type " << endl;
    redis.flushall();

    redis.set("key1","11");
    auto ret = redis.type("key1");
    cout << ret << endl;

    redis.hset("key2","jkm","666");
    ret = redis.type("key2");
    cout << ret << endl;
}

 执行结果

到这里我们发现redis-plus-plus提供的各种函数,跟我们之前学过的redis命令是相匹配的。 

 string类型

刚刚对于一些简单的命令的介绍主要是为了熟悉C++的一些语法,毕竟命令使用起来都大差不差,并且像 back_insert_iterator 或者OptionalString类型这样的用法熟悉之后,就可以针对类型来了解命令了。

get / set

代码:

void test1(sw::redis::Redis& redis)
{
    cout << "get and set" << endl;
    redis.flushall();

    redis.set("key1","11");
    redis.set("key2","22");

    auto ret = redis.get("key1"); // 这里的返回值类型是sw::redis::OptionalString
    if(ret)
    {
        cout << ret.value() << endl;
    }

    ret = redis.get("key2");
    if(ret)
    {
        cout << ret.value() << endl;
    }
}

 

set 带超时时间的版本 

void test2(sw::redis::Redis& redis)
{
    cout << "set 带有超时时间版本的使用" << endl;
    redis.flushall();

    //redis.set("key1","11",std::chrono::seconds(10));
    redis.set("key1","11",10s);
    std::this_thread::sleep_for(2s); // 或者 2000ms

    auto ret = redis.ttl("key1"); // long long
    cout << ret << endl;
}

 

set NX 和 XX 

 如果单用一个set,那么需要搭配过期时间的版本来使用

void test3(sw::redis::Redis& redis)
{
    cout << "set 的NX 和 XX " << endl;
    redis.flushall();

    // set 的重载版本中, 没有单独提供 NX 和 XX 的版本, 必须搭配过期时间的版本来使用.
    redis.set("key1","11",0s,sw::redis::UpdateType::EXIST);

    auto ret = redis.get("key1");

    if(ret)
    {
        cout << ret.value() << endl;
    }
    else 
    {
        cout << "设置出错" << endl;
    }
}

mset 

mset有两个重载版本

1.使用初始化列表,里面用pair的形式设置 key val 

2.使用容器迭代器的方式

通过传入容器的begin和end来进行初始化

void test4(sw::redis::Redis& redis)
{
    cout << "mset" << endl;
    redis.flushall();

    // 使用初始化列表描述多个键值对
    //redis.mset({std::make_pair("key1","11"),std::make_pair("key2","22")});

    // 把多个键值对提前组织到容器中. 以迭代器的形式告诉 mset
    std::vector<std::pair<std::string,std::string>> keys = {
        {"key1","11"},
        {"key2","22"}
    };
    redis.mset(keys.begin(),keys.end());

    auto ret = redis.get("key2");
    if(ret)
    {
        cout << ret.value() << endl;
    }
}

 

mget 

void test5(sw::redis::Redis& redis)
{
    cout << "mget" << endl;
    redis.flushall();

    redis.mset({std::make_pair("key1","11"),std::make_pair("key2","22"),std::make_pair("key3","33")});

    std::vector<sw::redis::OptionalString> res;
    auto it = std::back_inserter(res);
    redis.mget({"key1","key2","key3"},it); 
    
    printContainer(res);
}

 对于std::vector<sw::redis::OptionalString> 专门重载了一下printContainer()

// 函数重载实现特化
inline void printContainer(const std::vector<sw::redis::OptionalString>& container) {
    for (const auto& elem : container) {
        std::cout << elem.value() << std::endl;
    }
}

getrange / setrange 

void test6(sw::redis::Redis& redis)
{
    cout << "getrange and setrange" << endl;
    redis.flushall();

    redis.set("key1","jkMMdaisuki");
    std::string ret = redis.getrange("key1",0,-1); // 这里返回值就是string,可以直接打印
    cout << ret << endl;

    redis.setrange("key1",2,"JJ");
    ret = redis.getrange("key1",0,-1); // 这里返回值就是string,可以直接打印
    cout << ret << endl;
}

 

incr / decr 

void test7(sw::redis::Redis& redis)
{
    cout << "incr and decr" << endl;
    redis.flushall();

    redis.set("key1","100");
    long long ret = redis.incr("key1"); // long long
    cout << ret << endl;

    ret = redis.decr("key1");
    cout << ret << endl;
}

 

list类型 

 lpush / lrange

可以用初始化列表的方式来插入多个val 

void test1(sw::redis::Redis& redis)
{
    cout << "lpush and lrange" << endl;
    redis.flushall();

    // 插入单个元素
    redis.lpush("key", "111");

    // 插入一组元素, 基于初始化列表
    redis.lpush("key",{"1","2","3","4"});

    // 插入一组元素, 基于迭代器
    vector<string> values = {"555", "666", "777"};
    redis.lpush("key", values.begin(), values.end());

    // lrange 获取到列表中的元素
    vector<string> res;
    auto it = std::back_inserter(res);
    redis.lrange("key",0,-1,it);

    printContainer(res);
}

 

rpush 

用法与lpush一致,不多介绍

void test2(sw::redis::Redis& redis)
{
    cout << "rpush " << endl;
    redis.flushall();

    // 用法跟lpush一致
    // 插入一组元素, 基于初始化列表
    redis.rpush("key",{"1","2","3","4"});

    // lrange 获取到列表中的元素
    vector<string> res;
    auto it = std::back_inserter(res);
    redis.lrange("key",0,-1,it);

    printContainer(res);
}

 

lpop / rpop 

void test3(sw::redis::Redis& redis)
{
    cout << "lpop and rpop" << endl;
    redis.flushall();

    // 插入一组元素, 基于初始化列表
    redis.rpush("key1",{"1","2","3","4"});

    auto ret = redis.lpop("key1");
    if(ret)
    {
        cout << "lpop : " << ret.value() << endl;
    }

    ret = redis.rpop("key1");
    if(ret)
    {
        cout << "rpop : " << ret.value() << endl;
    }
}

 

blpop 

阻塞版本

void test4(sw::redis::Redis& redis)
{
    cout << "blpop" << endl;
    redis.flushall();

    // blpop 的返回值是sw::redis::OptionalStringPair,这是一个pair结构的
    auto ret = redis.blpop("key1"); // 不写timeout,默认为0表示阻塞等待

    if(ret)
    {
        cout << "key : " << ret->first << endl;
        cout << "emlm : " << ret->second << endl;
    }
    else 
    {
        cout << "等待超时" << endl;
    }
}

当没有元素时:

阻塞住了。

另起一个客户端进行插入一个元素时

客户端立马就把元素删除了。

llen 

void test5(sw::redis::Redis& redis)
{
    cout << "llen" << endl;
    redis.flushall();

    // 插入一组元素, 基于初始化列表
    redis.rpush("key1",{"1","2","3","4"});

    long long ret = redis.llen("key1"); // long long
    cout << ret << endl;
}

 

set类型 

sadd / smembers 

 

void test1(sw::redis::Redis& redis)
{
    cout << "sadd and smembers" << endl;
    redis.flushall();

    // 一次插入一个值
    redis.sadd("key","11");

    // 初始化列表一次插入多个值
    redis.sadd("key",{"22","33","44"});

    // 容器迭代器的方式
    set<string> elems = {"55","66"};
    redis.sadd("key",elems.begin(),elems.end());

    // 获取所有元素 smembers
    // 此处用来保存 smembers 的结果, 使用 set 可能更合适. 
    set<string> res;
    // 由于此处 set 里的元素顺序是固定的. 指定一个 result.end() 或者 result.begin() 或者其他位置的迭代器, 都无所谓
    auto it = std::inserter(res,res.end()); // 这里就用的是inserter函数构造迭代器了
    redis.smembers("key",it);

    printContainer(res);
}

如果我们用set容器调用std::back_inserter来构造插入迭代器(输出迭代器)的话,是会报错的 

 

因为在set容器中没有push_back方法。 

 

sismember 

void test2(sw::redis::Redis& redis)
{
    cout << "sismember" << endl;
    redis.flushall();

    // 初始化列表一次插入多个值
    redis.sadd("key",{"22","33","44"});

    bool ret = redis.sismember("key","33");
    cout << "ret : " << ret << endl;
}

 

 

 scard

void test3(sw::redis::Redis& redis)
{
    cout << "scard" << endl;
    redis.flushall();

    // 初始化列表一次插入多个值
    redis.sadd("key",{"22","33","44"});

    long long len = redis.scard("key");
    cout << "len : " << len << endl;
}

 

 

spop 

void test4(sw::redis::Redis& redis)
{
    cout << "spop" << endl;
    redis.flushall();

    // 初始化列表一次插入多个值
    redis.sadd("key",{"22","33","44"});

    auto ret = redis.spop("key"); // 返回值sw::redis::OptionalString,因为有可能key不存在
    if(ret)
    {
        cout << "ret : " << ret.value() << endl;
    }
}

 

注意 spop是随机删除的,所以我这里的执行结果每次都可能不一样。 

 

sinter 

void test5(sw::redis::Redis& redis)
{
    cout << "sinter" << endl;
    redis.flushall();

    redis.sadd("key1",{"11","22","33"});
    redis.sadd("key2",{"22","33","44"});

    set<string> res;
    auto it = std::inserter(res,res.end());
    redis.sinter({"key1","key2"},it);

    printContainer(res);
}

 

 

sinterstore 

void test6(sw::redis::Redis& redis)
{
    cout << "sinterstore" << endl;
    redis.flushall();

    redis.sadd("key1",{"11","22","33"});
    redis.sadd("key2",{"22","33","44"});

    redis.sinterstore("key3",{"key1","key2"});

    // 获取key3的元素
    set<string> res;
    auto it = std::inserter(res,res.end());
    redis.smembers("key3",it);

    printContainer(res);
}

 

 

hash类型 

 

 hset / hget

void test1(sw::redis::Redis& redis)
{
    cout << "hset and hget" << endl;
    redis.flushall();

    // 一次性插入一个值
    redis.hset("key","f1","11");
    redis.hset("key",std::make_pair("f2","22"));

    // 使用初始化列表
    redis.hset("key",{std::make_pair("f3","33"),std::make_pair("f4","44")});

    // 使用容器
    vector<std::pair<string,string>> fields = {
        std::make_pair("f5","55"),
        std::make_pair("f6","66")
    };
    redis.hset("key",fields.begin(),fields.end());

    // 获取元素
    auto ret = redis.hget("key","f2");
    if(ret)
    {
        cout << "ret: " << ret.value() << endl;
    }
}

 

 

hexits 

void test2(sw::redis::Redis& redis)
{
    cout << "hexists" << endl;
    redis.flushall();

    // 使用初始化列表
    redis.hset("key",{std::make_pair("f3","33"),std::make_pair("f4","44")});

    bool ret = redis.hexists("key","f3");
    cout << "ret: " << ret << endl;
}

 

 

 hdel

void test3(sw::redis::Redis& redis)
{
    cout << "del" << endl;
    redis.flushall();

    // 使用初始化列表
    redis.hset("key",{std::make_pair("f3","33"),std::make_pair("f4","44"),std::make_pair("f5","55")});

    long long ret = redis.hdel("key","f3");
    cout << "ret: " << ret << endl;

    ret = redis.hdel("key",{"f4","f5"});
    cout << "ret: " << ret << endl;
}

 

 

hkeys / hvals 

void test4(sw::redis::Redis& redis)
{
    cout << "hkeys and hvals" << endl;
    redis.flushall();

    // 使用初始化列表
    redis.hset("key",{std::make_pair("f3","33"),std::make_pair("f4","44"),std::make_pair("f5","55")});

    vector<string> resKeys;
    auto itKesy = std::back_inserter(resKeys);
    redis.hkeys("key",itKesy);
    printContainer(resKeys);

    vector<string> resVals;
    auto itVals = std::back_inserter(resVals);
    redis.hvals("key",itVals);
    printContainer(resVals);
}

 

 

hmset / hmget 

void test5(sw::redis::Redis& redis)
{
    cout << "hmset and hmget" << endl;
    redis.flushall();

    redis.hset("key",{
        std::make_pair("f3","33"),std::make_pair("f4","44"),
        std::make_pair("f5","55")
        }); // 用法和set差不多

    vector<string> res;
    auto it = std::back_inserter(res);
    redis.hmget("key",{"f3","f4","f5"},it);
    printContainer(res);
}

 

 

zset类型 

 

 

 zadd / zrange

void test1(sw::redis::Redis& redis)
{
    cout << "zadd and zrange" << endl;
    redis.flushall();

    redis.zadd("key","亚索",99.5);
    redis.zadd("key","狐臭",0.5);
    redis.zadd("key","阿狸",100);

    // 还有容器插入的方式,这里就不多介绍了
    
    // zrange 支持两种主要的风格:
    // 1. 只查询 member, 不带 score
    // 2. 查询 member 同时带 score
    // 关键就是看插入迭代器指向的容器的类型. 
    // 指向的容器只是包含一个 string, 就是只查询 member
    // 指向的容器包含的是一个 pair, 里面有 string 和 double, 就是查询 member 同时带有 score
    vector<string> members;
    auto it = std::back_inserter(members);
    redis.zrange("key",0,-1,it);
    printContainer(members);

    vector<std::pair<string,double>> membersWithCores;
    auto it2 = std::back_inserter(membersWithCores);
    redis.zrange("key",0,-1,it2); // 如果迭代器的元素是pair类型的,就会把分数也带上
    printContainer(membersWithCores);
}

 另外针对pair类型的容器打印

inline void printContainer(const std::vector<std::pair<std::string,double>>& container) 
{
    for(const auto& p : container) 
    {
        std::cout << p.first << " " << p.second << std::endl;
    }
}

 

zcard 

void test2(sw::redis::Redis& redis)
{
    cout << "zcard" << endl;
    redis.flushall();

    redis.zadd("key","亚索",99.5);
    redis.zadd("key","狐臭",0.5);
    redis.zadd("key","阿狸",100);

    long long ret = redis.zcard("key");
    cout << "ret: " << ret << endl;
}

 

 

zrem 

void test3(sw::redis::Redis& redis)
{
    cout << "zrem" << endl;
    redis.flushall();

    redis.zadd("key","亚索",99.5);
    redis.zadd("key","狐臭",0.5);
    redis.zadd("key","阿狸",100);

    long long ret = redis.zrem("key","狐臭"); // 也可以删除多个
    cout << "ret: " << ret << endl;
}

 

 

zscore 

void test4(sw::redis::Redis& redis)
{
    cout << "zscore" << endl;
    redis.flushall();

    redis.zadd("key","亚索",99.5);
    redis.zadd("key","狐臭",0.5);
    redis.zadd("key","阿狸",100);

    auto ret = redis.zscore("key","阿狸");
    if(ret)
    {
        cout << "ret: " << ret.value() << endl;
    }
    else 
    {
        cout << "该值不存在" << endl;
    }
}

 

 

zrank 

void test5(sw::redis::Redis& redis)
{
    cout << "zrank" << endl;
    redis.flushall();

    redis.zadd("key","亚索",99.5);
    redis.zadd("key","狐臭",0.5);
    redis.zadd("key","阿狸",100);

    auto ret = redis.zrank("key","阿狸"); // 返回值是sw::redis::OptionalLongLong
    if(ret)
    {
        cout << "ret: " << ret.value() << endl;
    }
    else 
    {
        cout << "该值不存在" << endl;
    }
}

 

 

 这就是redis-plus-plus的一些基本的使用方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2213248.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一场被真话包场的前端面试记录……

​ 面试官&#xff1a;等很久了吧&#xff0c;我是故意的&#xff01;这不是要装的忙一点&#xff0c;让领导看看嘛。前端小李&#xff1a;看你们这公司不大&#xff0c;破事倒是不少&#xff01;要不是到门口了&#xff0c;我都想回去。面试官&#xff1a;没错&#xff0c;我们…

为什么kafka处理速度这么快啊!?

Apache Kafka 是一个开源的分布式事件流平台&#xff0c;具有高吞吐量、可扩展性和持久性等特点。目前被广泛使用&#xff0c;本文将介绍kafka的组成部分、特点&#xff0c;并且解释为什么kafka能够快速处理。 一、基本组成部分 1. 消息&#xff08;Message&#xff09;&#x…

【AAOS】Android Automotive 14模拟器源码下载及编译

源码下载 repo init -u https://android.googlesource.com/platform/manifest -b android-14.0.0_r20 repo sync -c --no-tags --no-clone-bundle 源码编译 source build/envsetup.sh lunch sdk_car_x86_64-trunk_staging-eng make -j8 运行效果 emualtor Home All apps …

Nature 正刊丨镊子时钟的通用量子运算和基于ancilla的读出

01摘要 通过利用纠缠来提高测量精度是量子计量学长期追求的目标1,2。然而&#xff0c;在存在噪声的情况下获得量子理论允许的最佳灵敏度是一个突出的挑战&#xff0c;需要最佳的探测态生成和读出策略3,4,5,6,7。中性原子光学钟8是测量时间的主要系统&#xff0c;在纠缠产生方面…

【C++贪心 分治】1717. 删除子字符串的最大得分|1867

本文涉及知识点 贪心 分治 LeetCode1717. 删除子字符串的最大得分 给你一个字符串 s 和两个整数 x 和 y 。你可以执行下面两种操作任意次。 删除子字符串 “ab” 并得到 x 分。 比方说&#xff0c;从 “cabxbae” 删除 ab &#xff0c;得到 “cxbae” 。 删除子字符串"…

IEEE出版:第十二届信息系统与计算技术国际学术会议参会邀请

第十二届信息系统与计算技术国际会议&#xff08;ISCTech 2024&#xff09;将于2024年11月8-11日在西安举办&#xff0c;聚焦信息系统与计算技术领域&#xff0c;邀请专家学者参与交流。投稿论文将经严格审稿并出版至IEEE Xplore&#xff0c;支持EI等检索。会议涵盖多个主题&am…

编译cifx的驱动代码报错“netlink/cli/utils.h: No such file or directory”

ethercat编译时遇到netlink/cli/utils.h: No such file or directory 确认已安装对应的库&#xff1a; sudo apt-get install libnl-3-devsudo apt-get install libnl-genl-3-dev依旧编译失败&#xff0c;找到文件所在路径。 发现该路径在系统默认的库查找路径内 选择创建软链…

MySQL数据库操作——(2)

3 DML&#xff08;数据操作&#xff09; 3.1 添加数据 insert语法&#xff1a; 向指定字段添加数据 insert into 表名 (字段名1, 字段名2) values (值1, 值2); 全部字段添加数据 insert into 表名 values (值1, 值2, ...); 批量添加数据&#xff08;指定字段&#xff09; ins…

雪花啤酒:以AI数智化重新书写啤酒产业

作者|斗斗 编辑|皮爷 出品|产业家 在中国&#xff0c;雪花啤酒的名字刻在了每个人脑中。就像在泰山南天门&#xff0c;必须拿一瓶“勇闯天涯”拍照打卡一样&#xff0c;已经成为了一个“必选项”。 1993年&#xff0c;华润集团收购了沈阳雪花啤酒厂55%股份&#xff0c;…

挑战高薪!腾讯/华为招聘提到的PMP证书来了!

想必大家伙都知道&#xff1a;想要挑战高薪工作&#xff0c;除了要有过硬的专业能力外&#xff0c;不断进行自我提升也非常重要&#xff01; 现在招聘中&#xff0c;很多与岗位匹配的证书&#xff0c;也被纳入了基本条件中&#xff0c;比如被频繁提到的PMP证书&#xff0c;无论…

什么是上网行为管理?一文为你揭晓答案!

在当今这个数字化时代&#xff0c;互联网已成为人们工作、生活和学习中不可或缺的一部分。 然而&#xff0c;随着网络使用的普及&#xff0c;如何有效管理和控制用户的上网行为成为了一个重要议题。 上网行为管理&#xff0c;作为一种综合性的网络安全和管理工具&#xff0c;正…

123-基于AD9273的64路50Msps的超声侦测FMC子卡

一、产品概述 本板卡系我公司自主研发&#xff0c;采用8片AD9273&#xff0c;实现了64路模拟信号输入采集。板卡设计满足工业级要求。可用于水声侦测、医疗超声检测等。如图 1所示&#xff1a; 二、板卡介绍 模拟输入&#xff1a;两个J30J-66连接器数字输出&#xff1a;FMC连接…

【AIGC】如何通过ChatGPT快速学习Java、解决bug、搞定技术解决方案(完整教程)

目录 1、快速编写代码需求&#xff1a;创建一个Java方法&#xff0c;从ftp服务器下载文件 2、解决bug3、优化代码4、自学Java新技术5、推荐技术解决方案[如何直接使用ChatGPT4o、o1、OpenAI Canvas](https://www.nezhasoft.cn/)编程功能的提升 现在真的是进入了 “编码不用手&a…

起吊机革新:协议转换器解锁安全与效率

重工起吊机设备在工业生产中扮演着至关重要的角色&#xff0c;但其在实际应用中面临着一系列痛点问题。这些问题不仅影响了起吊机的性能和安全性&#xff0c;还限制了生产效率的提升。我们自主研发的MG协议转换器能够高效解决这些痛点&#xff0c;同时MG协议转换器作为一种关键…

路由器概述

一、路由器的工作原理 根据路由表转发数据 二、路由表与其形成 2.1路由表 &#xff08;1&#xff09;概念 路由&#xff1a;从源主机到目的主机的转发过程路由表&#xff1a;路由器中维护的路由条目的集合&#xff1b;路由器根据路由表做路径选择 &#xff08;2&#xff…

光控资本:每天股票最佳买卖时间,什么时候交易股票最好?

一般来说&#xff0c;炒股每天最佳的生意时刻为下午的2&#xff1a;30-3&#xff1a;00&#xff0c;即尾盘买入&#xff0c;其原因如下&#xff1a; 1、能逃避大盘的系统性危险 投资者在尾盘买入个股&#xff0c;可以更好防备大盘暴降引发的资金被套的危险。 2、削减持股时刻…

001 Qt_从零开始创建项目

文章目录 前言什么是QtQt的优点Qt的应用场景创建项目小结 前言 本文是Qt专栏的第一篇文章&#xff0c;该文将会向你介绍如何创建一个Qt项目 什么是Qt Qt 是⼀个 跨平台的 C 图形⽤⼾界⾯应⽤程序框架 。它为应⽤程序开发者提供了建⽴艺术级图形界⾯所需的所有功能。它是完全…

多台服务器分布式定时调度的几种方案

背景&#xff1a;现在有多个后端服务器&#xff0c;并且在代码中定义了一个定时任务&#xff0c;希望这个定时任务在一个时间只在一个服务器上执行&#xff0c;涉及到分布式调度&#xff0c;调研了一下总结出几种方案&#xff1a; 1.mysql的内置GET_LOCK GET_LOCK方法的介绍 …

【学术会议-1】携手共创新纪元:加入2024年11月计算机科学与技术学术会议,探索前沿科技,分享智慧,共同塑造未来!

【学术会议-1】携手共创新纪元&#xff1a;加入2024年11月计算机科学与技术学术会议&#xff0c;探索前沿科技&#xff0c;分享智慧&#xff0c;共同塑造未来&#xff01; 【学术会议-1】携手共创新纪元&#xff1a;加入2024年11月计算机科学与技术学术会议&#xff0c;探索前…

【C++】创建TCP客户端

目录 一、实现发送字符串功能 二、实现接收字符串功能 三、客户端接收乱码问题 四、客户端发送乱码问题 五、客户端接收到数据时进行回调 六、子线程接收数据 七、发送Json格式数据 源码 一、实现发送字符串功能 头文件 #pragma once #include <iostream> #inc…