[c++] std::future, std::promise, std::packaged_task, std::async

news2024/11/18 16:32:39

std::promise 进程间通信,std::packaged_task 任务封装,std::async 任务异步执行;std::future 获取结果。

1 std::promise

1.1 线程间同步

std::promise 可以用于线程间通信。

如下代码是 std::promise 中的示例代码。

std::promise - cppreference.com

(1)accumulate_promise 用于在线程间传递数据

(2)barrier 是 void 类型的,可以单纯地做线程之间的等待与唤醒

(3)std::fututre 是一个基本的组成元素,从名字也可以看出来,future 代表未来的结果。std::promise 只能使用一次,promise set_value 只能调用一次;std::future 调用 get() 也只能调用一次,如果再次调用 get(),那么会抛异常,在调用 get() 之前可以使用 valid() 来做判断,如果 valid() 是 true 的话,那么就可以调用 get();否则不能调用 get()。

#include <chrono>
#include <future>
#include <iostream>
#include <numeric>
#include <thread>
#include <vector>
#include <unistd.h>

void accumulate(std::vector<int>::iterator first,
                std::vector<int>::iterator last,
                std::promise<int> accumulate_promise)
{
    int sum = std::accumulate(first, last, 0);
    accumulate_promise.set_value(sum); // Notify future
}

void do_work(std::promise<void> barrier)
{
    std::cout << "before sleep\n";
    std::this_thread::sleep_for(std::chrono::seconds(1));
    barrier.set_value();
    std::cout << "after promise set value\n";
}

int main()
{
    // Demonstrate using promise<int> to transmit a result between threads.
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
    std::promise<int> accumulate_promise;
    std::future<int> accumulate_future = accumulate_promise.get_future();
    std::thread work_thread(accumulate, numbers.begin(), numbers.end(),
                            std::move(accumulate_promise));

    // future::get() will wait until the future has a valid result and retrieves it.
    // Calling wait() before get() is not needed
    // accumulate_future.wait(); // wait for result
    std::cout << "before get, future valid: " << accumulate_future.valid() << std::endl;
    std::cout << "result=" << accumulate_future.get() << '\n';
    std::cout << "after get, future valid: " << accumulate_future.valid() << std::endl;
    work_thread.join(); // wait for thread completion

    // Demonstrate using promise<void> to signal state between threads.
    std::promise<void> barrier;
    std::future<void> barrier_future = barrier.get_future();
    std::thread new_work_thread(do_work, std::move(barrier));

    std::cout << "before wait\n";
    barrier_future.wait();
    std::cout << "after wait\n";
    new_work_thread.join();
    return 0;
}

1.2 线程间同步 —— 条件变量

条件变量是一个基础的线程间同步机制,在 c 和 c++ 中都有使用。条件变量经常用于生产者线程和消费者线程之间通信的场景。

1.2.1 std::condition_variable

如下是使用条件变量时经常使用的方式,第一个形参是 std::unique_lock<> 类型的锁,第二个形参是 wait() 返回的条件。

template< class Predicate >
void wait( std::unique_lock<std::mutex>& lock, Predicate pred );

(1)条件满足之后,即使不进行 notify,wait() 也会返回

(2)条件不满足的时候,即使进行 notify(),wait() 也不会返回

这就是使用条件变量的时候最典型的用法,因为条件变量可能存在惊群问题(本人没有复现过),可能被唤醒的时候,条件还没有满足。这种用法就类似于在 c 语言使用条件变量的时候的如下代码。

while(!condition) {

  wait();

}

 

#include <condition_variable>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
#include <unistd.h>

std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;

void worker_thread()
{
    // wait until main() sends data
    std::unique_lock<std::mutex> lk(m);
    cv.wait(lk, []{ return ready; });

    // after the wait, we own the lock
    std::cout << "Worker thread is processing data\n";
    data += " after processing";

    // send data back to main()
    processed = true;
    std::cout << "Worker thread signals data processing completed\n";

    // manual unlocking is done before notifying, to avoid waking up
    // the waiting thread only to block again (see notify_one for details)
    lk.unlock();
    cv.notify_one();
}

int main()
{
    std::thread worker(worker_thread);

    data = "Example data";
    // send data to the worker thread
    {
        std::lock_guard<std::mutex> lk(m);
        sleep(2);
        std::cout << "after sleep\n";
        ready = true;
        std::cout << "after set value\n";
        std::cout << "main() signals data ready for processing\n";
    }

    std::cout << "before notify one\n";
    sleep(2);
    cv.notify_one();
    std::cout << "after notify one\n";
    // wait for the worker
    {
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{ return processed; });
    }
    std::cout << "Back in main(), data = " << data << '\n';
    worker.join();
}

1.2.2 pthread_cond_t

pthread 中的条件变量,使用方式如下。也是需要一个 mutex 和条件变量在一块使用。

wait 端调用的函数:

        pthread_mutex_lock(&mutex);
        while (!condition) {
            pthread_cond_wait(&cond, &mutex);
        }
        pthread_mutex_unlock(&mutex);

唤醒端需要调用的函数:

        pthread_mutex_lock(&mutex);
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);

在 wait 端,首先要加锁,然后进行 wait;在 wait 的时候,会释放锁,所以在 signal 端加锁是可以加成功的。

#include <stdio.h>
#include <pthread.h>

#define MAX_COUNT 10

int shared_resource = 0; // 全局共享资源
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 互斥锁
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 条件变量

void *producer(void *arg) {
    while (1) {
        pthread_mutex_lock(&mutex);
        while (shared_resource >= MAX_COUNT) { // 当共享资源达到最大值时等待
            pthread_cond_wait(&cond, &mutex);
        }
        shared_resource++; // 增加共享资源值
        printf("Produced: %d\n", shared_resource);
        pthread_cond_signal(&cond); // 唤醒消费者线程
        pthread_mutex_unlock(&mutex);
        sleep(1); // 模拟生产过程
    }
    return NULL;
}

void *consumer(void *arg) {
    while (1) {
        pthread_mutex_lock(&mutex);
        while (shared_resource <= 0) { // 当共享资源为0时等待
            pthread_cond_wait(&cond, &mutex);
        }
        shared_resource--; // 减少共享资源值
        printf("Consumed: %d\n", shared_resource);
        pthread_cond_signal(&cond); // 唤醒生产者线程
        pthread_mutex_unlock(&mutex);
        sleep(1); // 模拟消费过程
    }
    return NULL;
}

int main() {
    pthread_t producer_thread, consumer_thread;
    // 创建生产者线程和消费者线程
    pthread_create(&producer_thread, NULL, producer, NULL);
    pthread_create(&consumer_thread, NULL, consumer, NULL);
    // 等待线程结束
    pthread_join(producer_thread, NULL);
    pthread_join(consumer_thread, NULL);
    // 销毁互斥锁和条件变量
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    return 0;
}

在使用条件变量的时候要注意,当条件变量被 wait 的时候,对条件变量进行 destroy,那么 destroy 会阻塞住,直到 wait 返回之后,destroy 才会返回。如下代码是 destroy 一个正在被 wait 的条件变量,这个时候 destroy 会阻塞住。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

void *consumer(void *arg) {
    pthread_mutex_lock(&mutex);
    pthread_cond_wait(&cond, &mutex);
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main() {
    pthread_t consumer_thread;
    pthread_create(&consumer_thread, NULL, consumer, NULL);
    sleep(1);

    pthread_mutex_destroy(&mutex);
    printf("before destroy condition\n");
    pthread_cond_destroy(&cond);
    printf("after destroy condition\n");
    return 0;
}

2 std::packaged_task

std::package_task,从名字也可以看出来,表示一个打包的任务。这个任务自己并不能执行,而是需要显式的调用来执行。

如下是官网的示例代码。

std::packaged_task - cppreference.com

(1)task 能够封装的任务类型包括 lambda 表达式,bind 的函数,也可以是一个单纯的函数

(2)与 std::promise 类似,std::packaged_task 也可以获取一个 std::future,std::future 可以获取到 std::package_task 执行的结果

#include <cmath>
#include <functional>
#include <future>
#include <iostream>
#include <thread>

// unique function to avoid disambiguating the std::pow overload set
int f(int x, int y) { return std::pow(x, y); }

void task_lambda()
{
    std::packaged_task<int(int, int)> task([](int a, int b)
    {
        return std::pow(a, b);
    });
    std::future<int> result = task.get_future();

    // 封装一个 lamda 表达式,可以将 task 当成函数名来直接调用
    task(2, 9);

    std::cout << "task_lambda:\t" << result.get() << '\n';
}

void task_bind()
{
    std::packaged_task<int()> task(std::bind(f, 2, 11));
    std::future<int> result = task.get_future();

    // 封装的 bind() 函数,可以直接调用
    task();

    std::cout << "task_bind:\t" << result.get() << '\n';
}

void task_thread()
{
    std::packaged_task<int(int, int)> task(f);
    std::future<int> result = task.get_future();
    // 非 bind 方式,不能这样直接调用
    // std::cout << "directly call: " << task(2, 10) << std::endl;
    std::thread task_td(std::move(task), 2, 10);
    task_td.join();

    std::cout << "task_thread:\t" << result.get() << '\n';
}

int main()
{
    task_lambda();
    task_bind();
    task_thread();
    return 0;
}

 

2.1 异常传递

std::packaged_task中抛出的异常,保存在 std::future 中,可以通过 get() 来获取。

#include <cmath>
#include <functional>
#include <future>
#include <iostream>
#include <thread>

int f(int x, int y) {
  throw std::runtime_error("An error occurred in the task!");
  return std::pow(x, y);
}

void task_lambda()
{
    std::packaged_task<int(int, int)> task([](int a, int b)
    {
        throw std::runtime_error("An error occurred in the task!");
        return std::pow(a, b);
    });
    std::future<int> result = task.get_future();

    task(2, 9);

    try {
      std::cout << "task_lambda:\t" << result.get() << '\n';
    } catch (std::exception &e) {
      std::cout << "lambda exception: " << e.what() << std::endl;
    }
}

void task_bind()
{
    std::packaged_task<int()> task(std::bind(f, 2, 11));
    std::future<int> result = task.get_future();

    task();

    try {
      std::cout << "task_bind:\t" << result.get() << '\n';
    } catch (std::exception &e) {
      std::cout << "bind exception: " << e.what() << std::endl;
    }
}

void task_thread()
{
    std::packaged_task<int(int, int)> task(f);
    std::future<int> result = task.get_future();
    std::thread task_td(std::move(task), 2, 10);
    task_td.join();

    try {
      std::cout << "task_thread:\t" << result.get() << '\n';
    } catch (std::exception &e) {
      std::cout << "task thread exception: " << e.what() << std::endl;
    }
}

int main()
{
    task_lambda();
    task_bind();
    task_thread();
    return 0;
}

2.2 void 类型的 std::future

valid 是 true,也可以 get,不过 get 之后也是 void。

#include <cmath>
#include <functional>
#include <future>
#include <iostream>
#include <thread>

void f(int x, int y) {
  std::cout << "x " << x << ", y " << y << std::endl;
}

void task_lambda()
{
    std::packaged_task<void(int, int)> task([](int a, int b)
    {
      std::cout << "a " << a << ", b " << b << std::endl;
    });
    auto result = task.get_future();

    task(2, 9);

    try {
      std::cout << "task_lambda:\t" << result.valid() << '\n';
      result.get();
    } catch (std::exception &e) {
      std::cout << "lambda exception: " << e.what() << std::endl;
    }
}

void task_bind()
{
    std::packaged_task<void()> task(std::bind(f, 2, 11));
    std::future<void> result = task.get_future();

    task();

    try {
      std::cout << "task_bind:\t" << result.valid() << '\n';
      result.get();
    } catch (std::exception &e) {
      std::cout << "bind exception: " << e.what() << std::endl;
    }
}

void task_thread()
{
    std::packaged_task<void(int, int)> task(f);
    std::future<void> result = task.get_future();
    std::thread task_td(std::move(task), 2, 10);
    task_td.join();

    try {
      std::cout << "task_thread:\t" << result.valid() << '\n';
      result.get();
    } catch (std::exception &e) {
      std::cout << "task thread exception: " << e.what() << std::endl;
    }
}

int main()
{
    task_lambda();
    task_bind();
    task_thread();
    return 0;
}

3 std::async

std::async 提供了异步执行的方式。上一节的 std::packaged_task 只是封装了一个 task,但是这个 task 自己不会执行,std::async 相当于在 std::packaged_task 的基础上增加了自动执行的机制。

std::async 的第一个形参有两个可选值:std::launch::async 和 std::launch::deferred,第一个标志会自动异步执行;第二个标志,不会自动异步执行,需要调 wait() 同步执行。

#include <algorithm>
#include <future>
#include <iostream>
#include <mutex>
#include <numeric>
#include <string>
#include <vector>

std::mutex m;

struct X
{
    void foo(int i, const std::string& str)
    {
        std::lock_guard<std::mutex> lk(m);
        std::cout << str << ' ' << i << '\n';
    }

    void bar(const std::string& str)
    {
        std::lock_guard<std::mutex> lk(m);
        std::cout << str << '\n';
    }

    int operator()(int i)
    {
        std::lock_guard<std::mutex> lk(m);
        std::cout << i << '\n';
        return i + 10;
    }
};

template<typename RandomIt>
int parallel_sum(RandomIt beg, RandomIt end)
{
    auto len = end - beg;
    if (len < 1000)
        return std::accumulate(beg, end, 0);

    RandomIt mid = beg + len / 2;
    auto handle = std::async(std::launch::async,
                             parallel_sum<RandomIt>, mid, end);
    int sum = parallel_sum(beg, mid);
    return sum + handle.get();
}

int main()
{
    std::vector<int> v(10000, 1);
    std::cout << "The sum is " << parallel_sum(v.begin(), v.end()) << '\n';

    X x;
    // Calls (&x)->foo(42, "Hello") with default policy:
    // may print "Hello 42" concurrently or defer execution
    auto a1 = std::async(&X::foo, &x, 42, "Hello");
    // Calls x.bar("world!") with deferred policy
    // prints "world!" when a2.get() or a2.wait() is called
    auto a2 = std::async(std::launch::deferred, &X::bar, x, "world!");
    // Calls X()(43); with async policy
    // prints "43" concurrently
    auto a3 = std::async(std::launch::async, X(), 43);
    a2.wait();                     // prints "world!"
    std::cout << a3.get() << '\n'; // prints "53"
} // if a1 is not done at this point, destructor of a1 prints "Hello 42" here

3.1 使用 std::sync 注意事项

3.1.1 要获取 std::async 的返回值

如下代码,并没有获取 std::async 的返回值,这样的话两个任务是串行执行的,而不是异步并行执行的。

#include <algorithm>
#include <future>
#include <iostream>
#include <mutex>
#include <numeric>
#include <string>
#include <vector>
#include <unistd.h>

std::mutex m;

struct X
{
    void foo()
    {
        for (int i = 0; i < 10; i++) {
          std::cout << "foo()" << std::endl;
          sleep(1);
        }
    }

    void bar()
    {
        for (int i = 0; i < 10; i++) {
          std::cout << "bar()" << std::endl;
          sleep(1);
        }
    }
};

int main()
{
    X x;
    std::async(std::launch::async, &X::foo, &x);
    std::async(std::launch::async, &X::bar, &x);
    return 0;
}

官方解释:

3.1.2 异常传递

使用 std::async 的时候,如果任务中抛异常,异常会保存在 std::future 中,可以通过 std::future 来获取。

#include <algorithm>
#include <future>
#include <iostream>
#include <mutex>
#include <numeric>
#include <string>
#include <vector>
#include <unistd.h>

std::mutex m;

struct X
{
    int bar()
    {
        throw std::runtime_error("An error occurred in the task!");
        return 10;
    }
};

int main()
{
    X x;
    auto f1 = std::async(std::launch::async, &X::bar, &x);

    try {
      f1.get();
    } catch (std::exception &e) {
      std::cout << "f1 get exception: " << e.what() << std::endl;
    }
    return 0;
}

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

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

相关文章

我的NPI项目之设备系统启动(九) -- 高通平台启动阶段和功能详解

接触到一个新的平台最终要的一件事莫过于搞清楚如何boot起系统&#xff0c;那就要弄清楚系统开机各阶段的具体工作内容。这里最好的指导就是高通的启动guide。 对于系统的镜像和启动阶段我已经做了简单的介绍&#xff0c;详见: 镜像和启动阶段的说明 那今天我就以电源为例&a…

树的初步了解及堆,堆的topk问题,堆排序

目录 前言 一&#xff1a;树 1.树的概念 2.树的基础概念知识 3.在树中孩子节点和父节点知一求一 4.树的表示方法 二&#xff1a;二叉树 1.二叉树的概念 2.二叉树的特性 3.满二叉树 4.完全二叉树 三&#xff1a;堆 1.堆的定义 2.堆的实现&#xff1a;数组实现…

牛客网-SQL大厂面试题-2.平均播放进度大于60%的视频类别

题目&#xff1a;平均播放进度大于60%的视频类别 DROP TABLE IF EXISTS tb_user_video_log, tb_video_info; CREATE TABLE tb_user_video_log (id INT PRIMARY KEY AUTO_INCREMENT COMMENT 自增ID,uid INT NOT NULL COMMENT 用户ID,video_id INT NOT NULL COMMENT 视频ID,start…

【小白刷leetcode】第15题

【小白刷leetcode】第15题 动手刷leetcode&#xff0c;正在准备蓝桥&#xff0c;但是本人算法能力一直是硬伤。。。所以做得一直很痛苦。但是不熟练的事情像练吉他一样&#xff0c;就需要慢速&#xff0c;多练。 题目描述 看这个题目&#xff0c;说实在看的不是很懂。索性我们直…

YOLOv9算法原理——使用可编程梯度信息学习想要学习的内容

前言 2023年1月发布YOLOv8正式版后&#xff0c;经过一年多的等待&#xff0c;YOLOv9终于面世了&#xff01;YOLO是一种利用图像全局信息进行目标检测的系统。自从2015年Joseph Redmon、Ali Farhadi等人提出了第一代模型以来&#xff0c;该领域的研究者们已经对YOLO进行了多次更…

OceanBase4.2版本 Docker 体验

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

文件包含例子

一、常见的文件包含函数 php中常见的文件包含函数有以下四种&#xff1a; include() require() include_once() require()_once() include与require基本是相同的&#xff0c;除了错误处理方面: include()&#xff0c;只生成警告&#xff08;E_WARNING&#xff09;&#x…

Unity Live Capture 中实现面部捕捉同步模型动画

Unity Face Capture 是一个强大的工具&#xff0c;可以帮助你快速轻松地将真实人脸表情捕捉到数字模型中。在本文中&#xff0c;我们将介绍如何在 Unity Face Capture 中实现面部捕捉同步模型动画。 安装 |实时捕获 |4.0.0 (unity3d.com) 安装软件插件 安装 Live Capture 软件…

数据资产管理解决方案:构建高效、安全的数据生态体系

在数字化时代&#xff0c;数据已成为企业最重要的资产之一。然而&#xff0c;如何有效管理和利用这些数据资产&#xff0c;却是许多企业面临的难题。本文将详细介绍数据资产管理解决方案&#xff0c;帮助企业构建高效、安全的数据生态体系。 一、引言 在信息化浪潮的推动下&a…

为什么单线程的 Redis 能那么快?

大家好我是苏麟 , 给大家找一些好的文章看看 . 原文文章 : 03 高性能IO模型&#xff1a;为什么单线程Redis能那么快&#xff1f; (lianglianglee.com) Redis 为什么用单线程&#xff1f; 要更好地理解 Redis 为什么用单线程&#xff0c;我们就要先了解多线程的开销。 多线程的…

力扣hot100:416.分割等和子集(组合/动态规划/STL问题)

组合数问题 我们思考一下&#xff0c;如果要把数组分割成两个子集&#xff0c;并且两个子集的元素和相等&#xff0c;是否等价于在数组中寻找若干个数使之和等于所有数的一半&#xff1f;是的&#xff01; 因此我们可以想到&#xff0c;两种方式&#xff1a; ①回溯的方式找到t…

基于SpringBoot+Vue交流和分享平台的设计与实现(源码+部署说明+演示视频+源码介绍)

您好&#xff0c;我是码农飞哥&#xff08;wei158556&#xff09;&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。&#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精通…

Android Studio实现内容丰富的安卓宠物用品商店管理系统

获取源码请点击文章末尾QQ名片联系&#xff0c;源码不免费&#xff0c;尊重创作&#xff0c;尊重劳动。 项目编号128 1.开发环境android stuido jdk1.8 eclipse mysql tomcat 2.功能介绍 安卓端&#xff1a; 1.注册登录 2.系统公告 3.宠物社区&#xff08;可发布宠物帖子&#…

如何将Git拉取项目后,将SSH验证方式修改为HTTPS?

首先在打开项目所在位置的Git BashGUI 查找当前的远程仓库URL&#xff1a; 打开终端或命令提示符&#xff0c;导航到你的项目目录&#xff0c;并使用以下命令查看当前配置的远程仓库URL&#xff1a; git remote -v这会显示如下格式的输出&#xff1a; origin gitgithub.com:用…

从政府工作报告探讨计算机行业的发展

从政府工作报告探计算机行业发展 政府工作报告作为政府工作的全面总结和未来规划&#xff0c;不仅反映了国家整体的发展态势&#xff0c;也为各行各业提供了发展的指引和参考。随着信息技术的快速发展&#xff0c;计算机行业已经成为推动经济社会发展的重要引擎之一。因此&…

什么是 KNIME Hub(2024)

什么是 KNIME Hub KNIME Hub 是一个中央存储库和协作平台&#xff0c;它是用来促进与 KNIME Analytics Platform(分析平台,AP)相关的工作流、节点、组件和扩展的共享和管理。它既充当工作流存储库又充当协作空间&#xff0c;使用户能够发现和利用可合并到其数据分析项目中的各种…

前端Prettier 插件的使用配置(详细)

各个参数代表的意思:printWidth&#xff1a;每行代码的最大长度限制。 tabWidth&#xff1a;选项用于控制制表符的宽度。 useTabs&#xff1a;指定是否使用制表符代替空格。 semi&#xff1a;指定是否在语句的末尾添加分号。 singleQuote&#xff1a;指定是否使用单引号或双引号…

控制学习_正弦波无刷直流力矩电机建模、控制带宽讨论与选择

无刷电机通过电子换向器实现定子的磁场旋转&#xff0c;去电刷后使用寿命大幅提升&#xff0c;是现在更流行的选择。三相无刷电机则是无刷电机中比较流行的一款。三相无刷电机的驱动方式有多种&#xff0c;最简单的被称为梯形波驱动、方波驱动或正弦波驱动。而正弦波驱动技术可…

Redis中的HyperLogLog以及HyperLogLog原理

大家在学习redis的过程中&#xff0c;除了String&#xff0c;list&#xff0c;hash&#xff0c;set&#xff0c;zset这五种基本的数据结构&#xff0c;一定还会接触到几种高级的数据结构&#xff0c;比如bitmap&#xff0c;geo&#xff0c; 还有今天我们要说的hyperloglog&…

【OJ】string类题目

个人主页 &#xff1a; zxctscl 如有转载请先通知 题目 1. 415字符串相加1.1 分析1.2 代码 2. 344反转字符串2.1 分析2.2 代码 3. HJ1字符串最后一个单词的长度3.1 分析3.2 代码 4. 387.字符串中的第一个唯一字符4.1 分析4.2 代码 5. 125验证回文串5.1 分析5.2 代码 1. 415字符…