【Redis】Redis C++使用
一、Redis的自定义网络协议
1.1 为什么可以编写出一个自定义的Redis客户端
为什么我们可以编写出一个自定义的Redis客户端?因为Redis公开了自己的自定义协议。而对于一些其他软件的客户端,我们无法编写出一个自定义的Redis客户端,因为他们没有公开自己的自定义协议,但是我们可以通过一些抓包/逆向的手段猜测其应用层协议是什么样子的!!
在网络通信过程中,会用到很多的“协议”,比如数据链路层的以太网协议,网络层的IP协议,传输层的TCP/UDP协议,应用层的协议更多!!虽然业界中有很多成熟的应用层协议:HTTP等~~但是此处更多的时候,都会“自定义”应用层协议,Redis此处的应用层协议就是自定义的协议(传输层还是基于TCP)~~


1.2 RESP protocol spec
RESP 协议的优点:
- 简单好实现
- 快速进行解析
- 肉眼可读
传输层这里基于TCP,但是又和TCP没有强耦合。
请求和响应之间的通信模型是一问一答的形式~~(客户端给服务器发送一个请求,服务器返回一个响应)


我们需要根据上述规则进行字符串的编写,然后将这个字符串写入到 tcp socket 中。因此,redis客户端服务器要做的工作是:
- 按照上述格式,构造出字符串,往 socket 中写入
- 从 socket 中读取字符串,按照上述格式解析

二、安装 redis-plus-plus
由于 redis-plus-plus 依赖了 hiredis(C语言版本的redis客户端库),所以我们要先下载 hiredis 库(可以直接使用包管理器来安装)。
但是 redis-plus-plus 本体,只能编译安装了。如果是编译安装,使用 Ubuntu 比 使用 centos 简单很多。redis-plus-plus 本身功能比较简陋,比较原始,写起来也比较麻烦,实际开发中很少会手写 makefile。通过程序来生成 makefile,cmake就是一个生成 makefile 的工具。
先来看一下如何使用 CMake 编译程序:
- 创建一个 build 目录是习惯做法,并非是必须,目的是为了让编译生成的临时文件都放到build下面,避免污染源代码目录~
- cmake .. 这个操作是生成 makefile,此处的 .. 指向的是刚才 CMakeLists.txt 文件所在的目录~~
- make 进行编译
- make install 把刚才的库拷贝到系统目录
三、进行 ping 命令验证
#include <sw/redis++/redis++.h>
1.包含 redis-plus-plus 的头文件,如果我们不知道将这个库下载到哪里,我们可以使用 find 命令进行查找,命令如下所示:
find /XXX(路径) -name XXX
2.创建了一个 Redis 对象
sw::redis::Redis redis("tcp://127.0.0.1:6379);

3.进行 ping 命令

4.使用 Makefile 编译程序
编译程序的时候,需要引入一些库文件(需要知道这些库文件的目录):
- redis++ 自己的静态库
- hiredis 的静态库
- 线程库

四、Redis 的通用命令的使用
一览整个Redis通用命令的使用:
get/set
exists
del
keys
expire/ttl
type
4.1 get/set

在 C++ 中,std::string 是可以修改,既能读,也能写。但是 StringView 是只读的(不能修改),针对只读操作,做很多的优化工作,效率比 std::string 更高。在 C++17 标准库中,也提供了一个 std::string_view。这里是为了兼容 C++11,14,17,所以自己封装了一个类型。StringView 中的各个操作和string类似,只不过只是包含了一些只读方法。
在 Java 中的 String 就是类似于 StringView 只读的,Java 中要想使用可修改的字符串,要使用 StringBuilder 或者 StringBuffer。
对于 get 来说,有可能获取不到元素,这时应该返回什么类型呢??
如果直接使用 std::string 来表示,不方便来表现这个 nil(无效值),如果使用 std::string* 来表示,是可以使用 nullptr 表示无效的,但是返回指针又涉及到内存归谁管~~

因此,作者就自己封装了一个类型,此处的 Optional 可以表示 “非法值” 或者 “无效值”。在 Boost 中,很早就引入了 optional 类型,C++14版本中,就正式归纳标准库了。
在使用 Optional 类型的时候,有可能出现以下这个错误:

此处不需要给这个 Optional 类型搞一个 << 重载,只需要把 Optional 里面包含的元素取出来即可~~

void test1(sw::redis::Redis& redis) {std::cout << "get 和 set 的使用" << std::endl;// 清空一下数据库, 避免之前残留的数据有干扰. redis.flushall();// 使用 set 设置 keyredis.set("key1", "111");redis.set("key2", "222");redis.set("key3", "333");// 使用 get 获取到 key 对应的 valueauto value1 = redis.get("key1");// optional 可以隐式转成 bool 类型, 可以直接在 if 中判定. 如果是无效元素, 就是返回 falseif (value1) {std::cout << "value1=" << value1.value() << std::endl;}auto value2 = redis.get("key2");if (value2) {std::cout << "value2=" << value2.value() << std::endl;}auto value3 = redis.get("key3");if (value3) {std::cout << "value3=" << value3.value() << std::endl;}auto value4 = redis.get("key4");if (value4) {std::cout << "value4=" << value4.value() << std::endl;}
}
4.2 exists
void test2(sw::redis::Redis& redis) {std::cout << "exists" << std::endl;redis.flushall();redis.set("key", "111");redis.set("key3", "111");auto ret = redis.exists("key");std::cout << ret << std::endl;ret = redis.exists("key2");std::cout << ret << std::endl;ret = redis.exists({"key", "key2", "key3"});std::cout << ret << std::endl;
}
对于 exists 命令来说,我们可以一次性查看多个键值,我们可以使用初始化列表传参,代码如下:
redis.exists({"key", "key2", "key3"});
4.3 del
void test3(sw::redis::Redis& redis) {std::cout << "del" << std::endl;// 清除库非常必要的! redis.flushall();redis.set("key", "111");redis.set("key2", "111");// redis.del("key");auto ret = redis.del({"key", "key2", "key3"});std::cout << ret << std::endl;ret = redis.exists({"key", "key2"});std::cout << ret << std::endl;
}
4.4 keys
keys 命令不可以随便使用,否则会影响其他命令的执行,因为 Redis 是单线程。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 获取到的结果依次通过刚才的插入迭代器插入到容器的指定位置中了. vector<string> result;auto it = std::back_inserter(result);redis.keys("*", it);printContainer(result);
}
4.5 expire/ttl
void test5(sw::redis::Redis& redis) {using namespace std::chrono_literals;std::cout << "expire and ttl" << std::endl;redis.flushall();redis.set("key", "111");// 10s => std::chrono::seconds(10)redis.expire("key", 10s);std::this_thread::sleep_for(3s);auto time = redis.ttl("key");std::cout << time << std::endl;
}
在使用睡眠函数的时候,由于不同系统之间的单位不同,我们更好的选择是使用线程库中的睡眠函数:sleep_for。
Linux的sleep和Windows的Sleep,都属于系统函数,是和系统相关的,同样的功能,在不同系统中可能是完全不同的函数~
4.6 type
void test6(sw::redis::Redis& redis) {std::cout << "type" << std::endl;redis.flushall();redis.set("key", "111");string result = redis.type("key");std::cout << "key: " << result << std::endl;redis.lpush("key2", "111");result = redis.type("key2");std::cout << "key2: " << result << std::endl;redis.hset("key3", "aaa", "111");result = redis.type("key3");std::cout << "key3: " << result << std::endl;redis.sadd("key4", "aaa");result = redis.type("key4");std::cout << "key4: " << result << std::endl;redis.zadd("key5", "吕布", 99);result = redis.type("key5");std::cout << "key5: " << result << std::endl;
}
五、string类型的操作
5.1 get/set
void test1(Redis& redis) {std::cout << "get 和 set" << std::endl;redis.flushall();redis.set("key", "111");auto value = redis.get("key");if (value) {std::cout << "value: " << value.value() << std::endl;}redis.set("key", "222");value = redis.get("key");if (value) {std::cout << "value: " << value.value() << std::endl;}
}
5.2 set带有超时时间
void test2(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;
}
5.3 set NX/XX
void test3(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;}
}
5.4 mset
void test4(Redis& redis) {std::cout << "mset" << std::endl;redis.flushall();// 第一种写法, 使用初始化列表描述多个键值对// redis.mset({ std::make_pair("key1", "111"), std::make_pair("key2", "222"), std::make_pair("key3", "333") });// 第二种写法, 可以把多个键值对提前组织到容器中. 以迭代器的形式告诉 msetvector<std::pair<string, string>> keys = {{"key1", "111"},{"key2", "222"},{"key3", "333"}};redis.mset(keys.begin(), keys.end());auto value = redis.get("key1");if (value) {std::cout << "value: " << value.value() << std::endl;}value = redis.get("key2");if (value) {std::cout << "value: " << value.value() << std::endl;}value = redis.get("key3");if (value) {std::cout << "value: " << value.value() << std::endl;}
}
5.5 mget
void test5(Redis& redis) {std::cout << "mget" << std::endl;redis.flushall();vector<std::pair<string, string>> keys = {{"key1", "111"},{"key2", "222"},{"key3", "333"}};redis.mset(keys.begin(), keys.end());vector<sw::redis::OptionalString> result;auto it = std::back_inserter(result);redis.mget({"key1", "key2", "key3", "key4"}, it);printContainerOptional(result);
}
5.6 getrange/setrange
void test6(Redis& redis) {std::cout << "getrange 和 setrange" << std::endl;redis.flushall();redis.set("key", "abcdefghijk");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;
}
5.7 incr/decr
incr 和 decr 得到的是 long long 类型(使用这个更多一些),get 得到的是 OptionalString 类型,需要手动转成数字~C++中把字符串转成数字,也有很多种方法。
void test7(Redis& redis) {std::cout << "incr 和 decr" << std::endl;redis.flushall();redis.set("key", "100");long long result = redis.incr("key");std::cout << "result: " << result << std::endl;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;
}
六、list类型的操作
6.1 lpush/prange
void test1(Redis& redis) {std::cout << "lpush 和 lrange" << std::endl;redis.flushall();// 插入单个元素redis.lpush("key", "111");// 插入一组元素, 基于初始化列表redis.lpush("key", {"222", "333", "444"});// 插入一组元素, 基于迭代器vector<string> values = {"555", "666", "777"};redis.lpush("key", values.begin(), values.end());// lrange 获取到列表中的元素vector<string> results;auto it = std::back_inserter(results);redis.lrange("key", 0, -1, it);printContainer(results);
}
6.2 rpush
void test2(Redis& redis) {std::cout << "rpush" << std::endl;redis.flushall();// 插入单个元素redis.rpush("key", "111");// 插入多个元素, 基于初始化列表redis.rpush("key", {"222", "333", "444"});// 插入多个元素, 基于容器vector<string> values = {"555", "666", "777"};redis.rpush("key", values.begin(), values.end());// 使用 lrange 获取元素vector<string> results;auto it = std::back_inserter(results);redis.lrange("key", 0, -1, it);printContainer(results);
}
6.3 lpop/rpop
void test3(Redis& redis) {std::cout << "lpop 和 rpop" << std::endl;redis.flushall();// 构造一个 listredis.rpush("key", {"1", "2", "3", "4"});auto 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;}
}
6.4 blpop
void test4(Redis& redis) {using namespace std::chrono_literals;std::cout << "blpop" << std::endl;redis.flushall();auto result = redis.blpop({"key", "key2", "key3"}, 10s);if (result) {std::cout << "key:" << result->first << std::endl;std::cout << "elem:" << result->second << std::endl;} else {std::cout << "result 无效!" << std::endl;}
}

6.5 llen
void test5(Redis& redis) {std::cout << "llen" << std::endl;redis.flushall();redis.lpush("key", {"111", "222", "333", "444"});long long len = redis.llen("key");std::cout << "len: " << len << std::endl;
}
对于 redis-plus-plus 这个库来说,接口风格的设计是非常统一的。
- 当一个函数参数需要传递多个值的时候,往往都是支持初始化列表或者一对迭代器的方式来进行实现的
- 当一个函数的返回值需要表示多个数据的时候,也往往会借助插入迭代器来实现往一个容器中添加元素的效果
- 当某些场景涉及到无效值的时候,往往会搭配 std::optional 来使用
七、set类型的操作
7.1 sadd/smembers
void test1(Redis& redis) {std::cout << "sadd 和 smembers" << std::endl;redis.flushall();// 一次添加一个元素redis.sadd("key", "111");// 一次添加多个元素(使用初始化列表)redis.sadd("key", {"222", "333", "444"});// 一次添加多个元素(使用迭代器)set<string> elems = {"555", "666", "777"};redis.sadd("key", elems.begin(), elems.end());// 获取到上述元素// 此处用来保存 smembers 的结果, 使用 set 可能更合适. vector<string> result;// auto it = std::back_inserter(result);// 由于此处 set 里的元素顺序是固定的. 指定一个 result.end() 或者 result.begin() 或者其他位置的迭代器, 都无所谓~~auto it = std::inserter(result, result.end());redis.smembers("key", it);printContainer(result);
}

7.2 sismember
void test2(Redis& redis) {std::cout << "sismember" << std::endl;redis.flushall();redis.sadd("key", {"111", "222", "333", "444"});bool result = redis.sismember("key", "555");std::cout << "result: " << result << std::endl;
}
7.3 scrad
void test3(Redis& redis) {std::cout << "scard" << std::endl;redis.flushall();redis.sadd("key", {"111", "222", "333"});long long result = redis.scard("key");std::cout << "result: " << result << std::endl;
}
7.4 spop
void test4(Redis& redis) {std::cout << "spop" << std::endl;redis.flushall();redis.sadd("key", {"111", "222", "333", "444"});auto result = redis.spop("key");if (result) {std::cout << "result: " << result.value() << std::endl;} else {std::cout << "result 无效!" << std::endl;}
}
7.5 sinter
void test5(Redis& redis) {std::cout << "sinter" << std::endl;redis.flushall();redis.sadd("key1", {"111", "222", "333"});redis.sadd("key2", {"111", "222", "444"});set<string> result;auto it = std::inserter(result, result.end());redis.sinter({"key1", "key2"}, it);printContainer(result);
}
7.6 sinterstore
void test6(Redis& redis) {std::cout << "sinterstore" << std::endl;redis.flushall();redis.sadd("key1", {"111", "222", "333"});redis.sadd("key2", {"111", "222", "444"});long long len = redis.sinterstore("key3", {"key1", "key2"});std::cout << "len: " << len << std::endl;set<string> result;auto it = std::inserter(result, result.end());redis.smembers("key3", it);printContainer(result);
}
八、hash类型的操作
8.1 hset/hget
void test1(Redis& redis) {std::cout << "hset 和 hget" << std::endl;redis.flushall();redis.hset("key", "f1", "111");redis.hset("key", std::make_pair("f2", "222"));// hset 能够一次性插入多个 field-value 对!!redis.hset("key", {std::make_pair("f3", "333"),std::make_pair("f4", "444")});vector<std::pair<string, string>> fields = {std::make_pair("f5", "555"),std::make_pair("f6", "666")};redis.hset("key", fields.begin(), fields.end());auto result = redis.hget("key", "f3");if (result) {std::cout << "result: " << result.value() << std::endl;} else {std::cout << "result 无效!" << std::endl;}
}
8.2 hexists
void test2(Redis& redis) {std::cout << "hexits" << std::endl;redis.flushall();redis.hset("key", "f1", "111");redis.hset("key", "f2", "222");redis.hset("key", "f3", "333");bool result = redis.hexists("key", "f4");std::cout << "result: " << result << std::endl;
}
8.3 hdel
void test3(Redis& redis) {std::cout << "hdel" << std::endl;redis.flushall();redis.hset("key", "f1", "111");redis.hset("key", "f2", "222");redis.hset("key", "f3", "333");long long result = redis.hdel("key", "f1");std::cout << "result: " << result << std::endl;result = redis.hdel("key", {"f2", "f3"});std::cout << "result: " << result << std::endl;long long len = redis.hlen("key");std::cout << "len: " << len << std::endl;
}
8.4 hkeys/hvals
void test4(Redis& redis) {std::cout << "hkeys 和 hvals" << std::endl;redis.flushall();redis.hset("key", "f1", "111");redis.hset("key", "f2", "222");redis.hset("key", "f3", "333");vector<string> fields;auto itFields = std::back_inserter(fields);redis.hkeys("key", itFields);printContainer(fields);vector<string> values;auto itValues = std::back_inserter(values);redis.hvals("key", itValues);printContainer(values);
}
8.5 hmget/hmset
void test5(Redis& redis) {std::cout << "hmget 和 hmset" << std::endl;redis.flushall();redis.hmset("key", {std::make_pair("f1", "111"),std::make_pair("f2", "222"),std::make_pair("f3", "333")});vector<std::pair<string, string>> pairs = {std::make_pair("f4", "444"),std::make_pair("f5", "555"),std::make_pair("f6", "666")};redis.hmset("key", pairs.begin(), pairs.end());vector<string> values;auto it = std::back_inserter(values);redis.hmget("key", {"f1", "f2", "f3"}, it);printContainer(values);
}
九、zset类型的操作
void test1(Redis& redis) {std::cout << "zadd 和 zrange" << std::endl;redis.flushall();redis.zadd("key", "吕布", 99);redis.zadd("key", {std::make_pair("赵云", 98),std::make_pair("典韦", 97)});vector<std::pair<string, double>> members = {std::make_pair("关羽", 95),std::make_pair("张飞", 93)};redis.zadd("key", members.begin(), members.end());// zrange 支持两种主要的风格:// 1. 只查询 member, 不带 score// 2. 查询 member 同时带 score// 关键就是看插入迭代器指向的容器的类型. // 指向的容器只是包含一个 string, 就是只查询 member// 指向的容器包含的是一个 pair, 里面有 string 和 double, 就是查询 member 同时带有 scorevector<string> memberResults;auto it = std::back_inserter(memberResults);redis.zrange("key", 0, -1, it);printContainer(memberResults);vector<std::pair<string, double>> membersWithScore;auto it2 = std::back_inserter(membersWithScore);redis.zrange("key", 0, -1, it2);printContainerPair(membersWithScore);
}void test2(Redis& redis) {std::cout << "zcard" << std::endl;redis.flushall();redis.zadd("key", "zhangsan", 90);redis.zadd("key", "lisi", 91);redis.zadd("key", "wangwu", 92);redis.zadd("key", "zhaoliu", 93);long long result = redis.zcard("key");std::cout << "result: " << result << std::endl;
}void test3(Redis& redis) {std::cout << "zrem" << std::endl;redis.flushall();redis.zadd("key", "zhangsan", 90);redis.zadd("key", "lisi", 91);redis.zadd("key", "wangwu", 92);redis.zadd("key", "zhaoliu", 93);redis.zrem("key", "zhangsan");long long result = redis.zcard("key");std::cout << "result: " << result << std::endl;
}void test4(Redis& redis) {std::cout << "zscore" << std::endl;redis.flushall();redis.zadd("key", "zhangsan", 90);redis.zadd("key", "lisi", 91);redis.zadd("key", "wangwu", 92);redis.zadd("key", "zhaoliu", 93);auto score = redis.zscore("key", "zhangsan");if (score) {std::cout << "score: " << score.value() << std::endl;} else {std::cout << "score 无效" << std::endl;}
}void test5(Redis& redis) {std::cout << "zrank" << std::endl;redis.flushall();redis.zadd("key", "zhangsan", 90);redis.zadd("key", "lisi", 91);redis.zadd("key", "wangwu", 92);redis.zadd("key", "zhaoliu", 93);auto rank = redis.zrank("key", "zhaoliu");if (rank) {std::cout << "rank: " << rank.value() << std::endl;} else {std::cout << "rank 无效" << std::endl;}
}
相关文章:
【Redis】Redis C++使用
一、Redis的自定义网络协议 1.1 为什么可以编写出一个自定义的Redis客户端 为什么我们可以编写出一个自定义的Redis客户端?因为Redis公开了自己的自定义协议。而对于一些其他软件的客户端,我们无法编写出一个自定义的Redis客户端,因为他们没…...
每日一题(小白)暴力娱乐篇19
样例: 6 1 1 4 5 1 4 输出: 56 66 52 44 54 64 分析题意可以得知,就是接收一串数字,将数字按照下标每次向右移动一位(末尾循环到第一位),每次移动玩计算一下下标和数字的乘积且累加。 ①接收…...
25级总分413数学一142专业124东南大学820考研经验电子信息通信工程,真题,大纲,参考书。
我是南京理工大学的本科生,25 考研一战东大,政治 69,英一 78,数一 142,专业课(820)124,总分 413。我从 3 月正式开始备考,专业课跟着无线电论坛jenny 老师进行学习&#…...
一个基于Django的进销存管理系统Demo实现
第一步:创建 Django 项目 bash 复制 django-admin startproject inventory_system cd inventory_system python manage.py startapp erp 第二步:定义数据模型(models.py) python 复制 from django.db import models from d…...
【数据结构_6上篇】有关链表的oj题
import java.util.*;/* public class ListNode {int val;ListNode next null;ListNode(int val) {this.val val;} }*/ public class Partition {public ListNode partition(ListNode pHead, int x) {// write code here//1.首先要判断链表是否为空的情况if(pHead null){retu…...
【QT】QWidget 概述与核心属性(API)
🌈 个人主页:Zfox_ 🔥 系列专栏:Qt 目录 一:🔥 控件概述 🦋 控件体系的发展阶段 二:🔥 QWidget 核心属性 🦋 核心属性概览🦋 用件可用(…...
微服务篇——SpringCloud
服务注册 Spring Cloud5大组件有哪些? 服务注册和发现是什么意思?Spring Cloud如何实现服务注册发现? nacos与eureka的区别 负载均衡 如何实现负载均衡? Ribbon负载均衡的策略有哪些? 如何自定义负载均衡的策略&…...
如何使用PyCharm自动化测试
如何使用PyCharm自动化测试 1.打开PyCharm右击文件,点击新建项目 按照如图配置,然后点击创建 2.创建好后,点击文件,然后点击设置 按照如图步骤,查看selenium和webdriver-manager是否存在 3.以上都完成后按照如图创…...
记一次某网络安全比赛三阶段webserver应急响应解题过程
0X01 任务说明 0X02 靶机介绍 Webserver(Web服务器)是一种软件或硬件设备,用于接收、处理并响应来自客户端(如浏览器)的HTTP请求,提供网页、图片、视频等静态或动态内容,是互联网基础设施的核心…...
c# 新建不重名的唯一文件夹
在源文件夹内创建唯一目标文件夹 string newFolder GetUniqueFolderName(sourceFolder); Directory.CreateDirectory(newFolder); /// <summary>/// 生成唯一文件夹名称(格式:新建文件夹、新建文件夹1、新建文件夹2...)…...
复现QGIS-MCP教程
由于Claude国内下载不了尝试使用Cursor 下载安装Cursor Cursor - The AI Code Editor 本示例安装的是0.46版本 UV安装 简介 安装 安装成功 配置环境变量 验证 下载代码 git clone gitgithub.com:jjsantos01/qgis_mcp.git QGIS插件安装 文件拷贝 您需要将 qgis_mcp_plu…...
SQL:JOIN 完全指南:从基础到实战应用
JOIN 是 SQL 中最重要也最常用的操作之一,它允许我们从多个表中获取关联数据。本文将全面解析 SQL 中的各种 JOIN 类型,包括 INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL JOIN 以及 CROSS JOIN,并通过实际示例展示它们的应用场景。 一、JOIN 基础概念 1.1 什么是 JOIN JOIN…...
docker部署certimateSSL证书管理自动续签
开源certimate项目地址:https://github.com/usual2970/certimate 官方文档地址:https://docs.certimate.me/docs/introduction 与certbot对比 维度CertbotCertimate安装方式通过包管理器(如apt/yum)安装支持二进制文件、Docker…...
R语言——直方图
参考资料:学习R 如果我们要研究一个连续变量的分布,直方图是最佳的选择。 1、base绘图 在base中可以使用hist函数绘制直方图,与plot函数一样,它没有data参数,须把数据框置于with中。 data(obama_vs_mccain,package&q…...
第4节:Python编程基础 - Python语法快速回顾
1. Python简介与环境配置 Python是一种高级、解释型、通用的编程语言,由Guido van Rossum于1991年首次发布。 它以简洁明了的语法和强大的功能而闻名,广泛应用于Web开发、数据分析、人工智能、科学计算等领域。 1.1 Python的特点 简单易学:…...
十四、C++速通秘籍—函数式编程
目录 上一章节: 一、引言 一、函数式编程基础 三、Lambda 表达式 作用: Lambda 表达式捕获值的方式: 注意: 四、函数对象 函数对象与普通函数对比: 五、函数适配器 1、适配普通函数 2、适配 Lambda 表达式 …...
蓝桥杯 2025 C++组 省 B 题解
可分解的正整数 算法:思维 因为可以有负数 所以除了1以外的任何数都可以构造 当这个数为x构造方法为 -(x-1) -(x-2) -(x-3) ....-1 0 1...x-3 x-2 x-1 x 除了x,x以前的数都会被负数抵消 #include <bits/stdc.h> #define ll long long ll a…...
如何让老电脑运行快些(极限榨干老电脑硬件)
要让老电脑运行更快,可以通过增加虚拟内存、优化系统设置和硬件升级等方法实现。以下是具体建议: 1. 增加虚拟内存(适合硬盘空间大的老电脑) 虚拟内存(页面文件)是硬盘上的一部分空间,用于扩展…...
博物馆小程序怎么做?从0到1打造数字化文化窗口
博物馆小程序怎么做?从0到1打造数字化文化窗口 一、行业痛点:传统博物馆的数字化困局 在数字化浪潮下,传统博物馆普遍面临三大挑战: 客流受限:线下接待能力有限,难以触达更广泛人群 互动单一…...
信息学奥赛一本通 1498:Roadblocks | 洛谷 P2865 [USACO06NOV] Roadblocks G
【题目链接】 ybt 1498:Roadblocks 洛谷 P2865 [USACO06NOV] Roadblocks G 【题目考点】 1. 图论:严格次短路径 严格次短路的路径长度必须大于最短路的路径长度。 非严格次短路的路径长度大于等于最短路的路径长度。 【解题思路】 每个交叉路口是一…...
1.5-APP的架构\微信小程序的架构
1.5-APP的架构\微信小程序的架构 APP的三种开发架构: 原生态APP类型 APP-开发架构-原生态-IDEA 演示:remusic项目源码 NP管理器: http://normalplayer.top/ HttpCanary:https://github.com/mingww64/HttpCanary-SSL-Magisk 安全影…...
【服务器端表单字符验证】
文章目录 一、实验目的二、核心代码实现三、调试关键问题四、总结 一、实验目的 掌握JSP表单验证在服务器端的实现技术,实现对用户输入字符的非空及长度为5的验证,返回对应提示信息并优化用户交互。 二、核心代码实现 前端表单 <form action"…...
【玩转全栈】—— Django 连接 vue3 保姆级教程,前后端分离式项目2025年4月最新!!!
本文基于之前的一个旅游网站,实现 Django 连接 vue3,使 vue3 能携带 CSRF Token 发送 axios 请求给后端,后端再响应数据给前端。想要源码直接滑倒底部。 目录 实现效果 解决跨域 获取 csrf-token 什么是 csrf-token ? CSRF攻击的…...
react中通过 EventEmitter 在组件间传递状态
要在 Reply 组件中通过 statusChangeEvent 发送状态值,并在 Select 组件中接收这个状态值 status,你可以按照以下步骤实现: //Event.jsimport EventEmitter from events;export const statusChangeEvent new EventEmitter();// 工单状态切换…...
数据库实验:分组查询与聚集函数的使用
目录 引言一、GROUP BY核心规则与常见错误二、高级分组选项:ROLLUP、CUBE与GROUPING SETS三、窗口函数:在原始行中显示分组聚合结果四、UNION ALL合并结果集:解决冗余查询问题五、实验体会 结语 (附上实验中表格的信息)…...
【mllm】——x64模拟htp的后端无法编译debug
mllm, qnn, x64 code:https://github.com/UbiquitousLearning/mllm 1. 问题 通过自定义qualcomm graph使用高通的htp后端进行llm推理,网络暂时只有mllm,和https://github.com/chraac/llama.cpp。qualcomm是支持x64模拟htp推理的,这样比较好d…...
计算机网络- UDP协议详解
UDP协议详解 5. UDP协议详解5.1 UDP协议基础5.1.1 UDP的基本概念5.1.2 UDP报文结构5.1.3 UDP校验和计算5.1.4 UDP在Linux内核中的实现UDP套接字的创建UDP数据的发送UDP数据的接收UDP校验和的计算 5.1.5 UDP的实际应用音视频流媒体在线游戏DNS查询VoIP通信网络时间同步 5.2 UDP编…...
Open-TeleVision源码解析——宇树摇操方案的重要参考:VR控制人形机器人采集数据
前言 本来针对Open-TeleVision的源码解析,是打算放在此文《从宇树摇操avp_teleoperate到unitree_IL_lerobot:如何基于宇树人形进行二次开发》中的,但考虑到为避免篇幅过长,故独立成此文 第一部分 Open-TeleVision的源码解析 如本…...
Embedding质量评估、空间塌缩、 Alignment Uniformity
Embedding质量的评估和空间塌缩的解决是自然语言处理(NLP)和推荐系统领域的关键问题。以下是综合多篇研究的总结: 一、Embedding质量评估方法 基准测试与任务指标 MTEB/C-MTEB:使用多语言或中文的基准测试集(如58个数据…...
[题解] Educational Codeforces Round 168 (Rated for Div. 2) E - level up
链接 思路 1 注意到在 k ∈ [ 1 , n ] k \in [1,n] k∈[1,n] 可以得到的最高等级分别为: n , n 2 , n 3 . . . . . n n n,\frac{n}{2},\frac{n}{3}.....\frac{n}{n} n,2n,3n.....nn, 总的个数是一个调和级数, s u m n ∗ ln n sumn*\ln n sumn∗lnn, 完全可以处…...
