16.C++中的多线程

news2026/4/2 3:40:12

文章目录

    • bind函数
    • std::thread
    • std::mutex
    • 死锁
    • std::lock_guard VS std::unique_lock
    • std::condition_variable
    • std::atomic
    • std::promise和std::future
    • std::ref
      • reference


欢迎访问个人网络日志🌹🌹知行空间🌹🌹


bind函数

有时候需要根据需要对函数的参数进行定制,这时就需要使用functional.h头文件中提供的bind函数,其作用是给函数设定默认值,并使用placeholder设置函数中的参数,然后返回一个函数对象。

#include <iostream>
#include <functional>

void substract(int a, int b, int c)
{
    std::cout << a - b - c << std::endl;
}

int main(int argc, char **argv)
{
    // 方式1
    auto f = std::bind(substract,
                      std::placeholders::_1,
                      2,
                      std::placeholders::_2);
    f(10, 12); // -4
    // 方式2
    auto ff = std::bind(substract,
                      std::placeholders::_2,
                      2,
                      std::placeholders::_1);
    ff(10, 12); // 0
    return 0;
}

在以上示例程序中,

  • 方式1, _1对应的是函数substract的参数a,参数b的默认值是2,_2对应的是函数substract的参数b
  • 方式1, _1对应的是函数substract的参数c,参数b的默认值是2,_2对应的是函数substract的参数c

此外,当对类的成员函数使用bind函数,进行参数定指时,第一个参数需要传入指向对象的指针,如在类成员函数中传入this,在类外传入对象指针,以使得bind后的函数还能访问类的成员。

#include <iostream>
#include <functional>

class A {
    public:
        int value = 12;
        void substract(int a, int b, int c)
        {
            std::cout << a - b - c - this->value << std::endl;
        }
};

int main(int argc, char **argv)
{
    A a;
    auto f = std::bind(&A::substract,
                      &a,
                      std::placeholders::_1,
                      2,
                      std::placeholders::_2);
    f(10, 12); // -16
    
    auto ff = std::bind(&A::substract,
                      &a,
                      std::placeholders::_2,
                      2,
                      std::placeholders::_1);
    ff(10, 12); // -12
    return 0;
}

std::thread

thread类表示单个可执行线程,通过多个线程可以使几个函数同时运行。

线程在构造关联的线程对象后,从作为构造函数参数提供的顶级函数开始,立即执行。

顶级函数可以通过共享变量或std::promise和主线程之间交换返回值,这可能需要使用std::mutexstd::atomic来做线程同步。

使用join函数,可以在主线程结束前阻塞主线程以等待子线程先退出。

但是,使用detach后会结束主线程和子线程之间的关联关系,使得子线程不再joinble,不能使用join()阻塞主线程。

#include <iostream>
#include <functional>
#include <thread>
#include <unistd.h>
class A {
    public:
        A() {
            t1 = std::thread(std::bind(&A::increment, this));
        }
        int value = 12;
        void increment() {
            int v = 0;
            for(int i=0; i< 1000; i++) {
                v += i;
                std::cout << v << std::endl;
                usleep(1000000);
            }
        }
        ~A() {
            if(t1.joinable())
                t1.join();
        }
    private:
        std::thread t1;

};

int main(int argc, char **argv)
{
    A a;
    return 0;
}

注意,上述代码,需要在类的析构函数中调用join函数,否则,程序主线程结束时会杀死子线程,导致程序退出,报如下错误:

terminate called without an active exception

std::mutex

mutex具有并发执行代码时的互斥(互斥)的功能,可以显式避免数据竞争。

当一个线程访问时,先给该线程所获取的资源上锁,防止其他线程修改资源,一个线程访问结束时,通过解锁释放资源。

假如没有std::mutex,

#include <iostream>
#include <functional>
#include <thread>
#include <unistd.h>
class A {
    public:
        A() {
            t1 = std::thread(std::bind(&A::increment, this));
            t2 = std::thread(std::bind(&A::increment, this));
        }
        int value = 12;
        void increment() {
            for(int i=0; i< 1000; i++) {
                value++;
                std::cout << value << std::endl;
                usleep(10);
            }
        }
        ~A() {
            if(t1.joinable())
                t1.join();
            if(t2.joinable())
                t2.join();

            std::cout << "A.value" << value << std::endl;
        }
    private:
        std::thread t1;
        std::thread t2;

};

int main(int argc, char **argv)
{
    A a;
    return 0;
}

上述代码,期望最后打印的A.value2012,但代码的实际运行中,其输出有可能是

2011
A.value2011

为什么会这样呢?自增操作value++不是原子操作,而是由多条汇编指令完成的。多个线程对同一个变量进行读写操作就会出现不可预期的操作。

#include <iostream>
#include <functional>
#include <thread>
#include <unistd.h>
#include <mutex>
class A {
    public:
        A() {
            t1 = std::thread(std::bind(&A::increment, this));
            t2 = std::thread(std::bind(&A::increment, this));
        }
        int value = 12;
        void increment() {
            for(int i=0; i< 1000; i++) {
                mtx.lock();
                value++;
                mtx.unlock();
                std::cout << value << std::endl;
                usleep(10);
            }
        }
        ~A() {
            if(t1.joinable())
                t1.join();
            if(t2.joinable())
                t2.join();

            std::cout << "A.value" << value << std::endl;
        }
    private:
        std::thread t1;
        std::thread t2;
        std::mutex mtx;

};

int main(int argc, char **argv)
{
    A a;
    return 0;
}

如上,使用mutex利用锁来保护共享变量,访问时上锁,访问结束释放锁。

上述中调用了互斥量的lock函数,上锁不成功的话线程会被阻塞,其还有另外个函数try_lock,此线程在上锁不成功时也不阻塞当前线程。

死锁

使用mutex存在死锁问题,考虑,线程1和线程2共用互斥量m,线程1调用m.lock()上锁后抛出了异常没有来得及执行m.unlock()释放锁,这时候线程2将一直处于等待状态,导致死锁。

#include <iostream>
#include <functional>
#include <thread>
#include <unistd.h>
#include <mutex>
class A {
    public:
        A() {
            t1 = std::thread(std::bind(&A::increment, this, 1));
            t2 = std::thread(std::bind(&A::increment, this, 2));
        }
        int value = 12;
        void increment(int id) {
            try {
                for(int i=0; i< 1000; i++) {
                    std::cout << "[id:" << id << "]waiting...\n";
                    mtx.lock();
                    value++;
                    if(value > 13)
                        throw std::runtime_error("throw excption....");
                    mtx.unlock();
                    std::cout << value << std::endl;
                    usleep(10);
                }
            } catch (const std::exception& e){
                 std::cout << "id:" << id << ", " << e.what() << std::endl;
            }
        }
        ~A() {
            if(t1.joinable())
                t1.join();
            if(t2.joinable())
                t2.join();

            std::cout << "A.value" << value << std::endl;
        }
    private:
        std::thread t1;
        std::thread t2;
        std::mutex mtx;

};

int main(int argc, char **argv)
{
    A a;
    return 0;
}

如上代码,将导致线程1一直处于等待状态:

d$ ./main 
[id:1]waiting...
13
[id:2]waiting...
id:2, throw excption....
[id:1]waiting...

std::lock_guard VS std::unique_lock

避免死锁的一种方式是使用std::lock_guard,std::lock_guard对象构造时,自动调用mtx.lock()进行上锁,对象析构时,自动调用mtx.unlock()释放锁。

#include <main.h>
#include <memory>
#include <iostream>
#include <functional>
#include <thread>
#include <unistd.h>
#include <mutex>
class A {
    public:
        A() {
            t1 = std::thread(std::bind(&A::increment, this, 1));
            t2 = std::thread(std::bind(&A::increment, this, 2));
        }
        int value = 12;
        void increment(int id) {
            try {
                for(int i=0; i< 1000; i++) {
                    std::cout << "[id:" << id << "]waiting...\n";
                    std::lock_guard<std::mutex> lock(mtx);
                    value++;
                    if(value > 13)
                        throw std::runtime_error("throw excption....");
                    std::cout << value << std::endl;
                    usleep(10);
                }
            } catch (const std::exception& e){
                 std::cout << "id:" << id << ", " << e.what() << std::endl;
            }
        }
        ~A() {
            if(t1.joinable())
                t1.join();
            if(t2.joinable())
                t2.join();

            std::cout << "A.value" << value << std::endl;
        }
    private:
        std::thread t1;
        std::thread t2;
        std::mutex mtx;

};

int main(int argc, char **argv)
{
    A a;
    return 0;
}

将代码改成如上形式,发现就不会死锁了,抛出异常离开lock_guard作用域时会调用其析构函数,自动释放锁。

std::unique_lockstd::lock_guard功能类似,都支持在构造时自动上锁,在析构时自动解锁,其与std::lock_guard区别在于,std::unique_lock支持在构造时推迟上锁,可以选择在需要时手动lock(),且还能保证在析构时释放锁

std::mutex mtx;
lck = std::unique_lock<std::mutex>(mtx, std::defer_lock);

// 手动上锁
std::lock(lck1);

std::condition_variable

条件变量的作用是用于多线程之间的线程同步。

线程同步是指线程间需要按照预定的先后顺序进行的行为,比如我想要线程1完成了某个步骤之后,才允许线程2开始工作,这个时候就可以使用条件变量来达到目的。

std::condition_variable 对象的某个 wait 函数被调用的时候,它使用 std::unique_lock(通过 std::mutex 来锁住当前线程。当前线程会一直被阻塞,直到另外一个线程在相同的 std::condition_variable 对象上调用了 notify 函数来唤醒当前线程。

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

class A {
    public:
        A() {
            t1 = std::thread(std::bind(&A::increment, this, 1));
            t2 = std::thread(std::bind(&A::add5, this, 2));
        }
        int value = 12;
        void increment(int id) {
            for(int i=0; i< 20; i++) {
                std::cout << "[id:" << id << "]waiting...\n";
                std::lock_guard<std::mutex> lock(mtx);
                value++;
                std::cout << value << std::endl;
                if(i % 10 == 0)
                    updated.notify_one();
                usleep(10);
            }
            flag = false;
        }
        void add5(int id) {
            while(true)
            {
                std::unique_lock<std::mutex> lock(mtx);
                updated.wait(lock);
                std::cout << "[id:" << id << "]waiting...\n";
                value += 5;
                std::cout << value << std::endl;
            }
        }

        ~A() {
            if(t1.joinable())
                t1.join();
            if(t2.joinable())
                t2.join();

            std::cout << "A.value" << value << std::endl;
        }
    private:
        std::thread t1;
        std::thread t2;
        bool flag = true;
        std::condition_variable updated;
        std::mutex mtx;

};

int main(int argc, char **argv)
{
    A a;
    return 0;
}

以上代码,线程2在while循环中调用了updated.wait(lock)函数,会一直处于阻塞状态,直到收到notify的信号,

[id:1]waiting...
30
[id:1]waiting...
31
[id:1]waiting...
32
[id:1]waiting...
33
[id:1]waiting...
34
[id:1]waiting...
35
[id:1]waiting...
36
[id:1]waiting...
37
[id:2]waiting...
42
  • std::condition_variable::notify_all解除当前等待此条件的所有线程的阻塞。
  • std::condition_variable::notify_one解除当前等待此条件的线程之一的阻塞。如果没有线程在等待,则该函数不执行任何操作。如果有多个线程等待此条件,notify_one无法指定选择哪个线程。

std::atomic

对于线程间共享的变量,进行读取和写入操作时,常用的同步方式就是加锁,但是每一次循环都要加锁解锁会导致程序开销很大。

为了提高性能,C++11提供了原子类型(std::atomic<T>),它提供了多线程间的原子操作。

可以把原子操作理解成一种不需要用互斥量加锁(无锁)来实现多线程并发编程的方式。

原子类型是封装了一个值的类型,它的访问保证不会导致数据的竞争,并且可以用于在不同的线程之间同步内存访问。

从效率上来说,原子操作要比互斥量的方式效率要高。互斥量的加锁一般是针对一个代码段,而原子操作针对的一般都是一个变量。

原子操作,一般都是指“不可分割的操作”;是一系列不可被 CPU 上下文交换的机器指令,这些指令组合在一起就形成了原子操作。

原子类型是无锁类型,但是无锁不代表无需等待,因为原子类型内部使用了 CAS(Compare and swap) 循环,当大量的冲突发生时,仍然需要等待,总体比使用锁性能要好。

std::atomic类模板,允许用户使用自定义类型创建一个原子变量(除了标准原子类型之外),需要满足一定的标准才可以使用std::atomic<>,为了使用std::atomic<UDT>(UDT是用户定义类型),这个类型必须有拷贝赋值运算符。这就意味着这个类型不能有任何虚函数或虚基类,以及必须使用编译器创建的拷贝赋值操作。

#include <main.h>
#include <memory>
#include <iostream>
#include <functional>
#include <thread>
#include <unistd.h>
#include <mutex>
#include <condition_variable>
#include <atomic>

class A {
    public:
        A() {
            flag.store(true);
            t1 = std::thread(std::bind(&A::increment, this, 1));
            t2 = std::thread(std::bind(&A::add5, this, 2));
        }
        int value = 12;
        void increment(int id) {
            for(int i=0; i< 20; i++) {
                std::cout << "[id:" << id << "]waiting...\n";
                std::unique_lock<std::mutex> lock(mtx);
                value++;
                std::cout << value << std::endl;
                std::cout << "i=" << i << std::endl;
                if(i % 10 == 0)
                    updated.notify_one();
                usleep(10);
            }
            updated.notify_one(); // 这里需要notify,否则线程2可能一直阻塞
            flag.store(false); 
            std::cout << "1" << (flag.load() ? "Y\n" : "N\n"); 
        }
        void add5(int id) {
            while(flag.load())
            {
                std::unique_lock<std::mutex> lock(mtx);
                updated.wait(lock);
                std::cout << "[id:" << id << "]waiting...\n";
                value += 5;
                std::cout << value << std::endl;
            }
        }

        ~A() {
            if(t1.joinable())
                t1.join();
            if(t2.joinable())
                t2.join();

            std::cout << "A.value" << value << std::endl;
        }
    private:
        std::thread t1;
        std::thread t2;
        std::atomic<bool> flag;
        std::condition_variable updated;
        std::mutex mtx;

};

int main(int argc, char **argv)
{
    A a;
    return 0;
}

如上代码store()改变原子变量的值,load()获取原子变量的值。

std::promise和std::future

std::promise类型的对象可以和std::future类型结合使用,实现在线程之间传递类型为T(泛型)的值。调用future.get()函数时,当promise没有给值之前,future.get调用所在的线程将一直处于阻塞状态。

void printInt(std::future<int> &fut)
{
    std::cout << "printInt func is waiting value...\n";
    int x = fut.get();
    std::cout << "x: " << x << std::endl;
}

int main(int argc, char **argv)
{
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();
    std::thread t1(printInt, std::ref(fut));

    prom.set_value(1);
    t1.join();
    return 0;
}

如上代码,演示了如何使用std::promisestd::future

promise可以翻译成诺言,承诺在未来会给future对象一个值,future对象在没有获得承诺的值之前,会一直等待。future对象只有从promise对象中获取才有意义。没有承诺的未来没有意义,什么都不会有。

promise对象只能set_value一次,否则将报错,

void printInt(std::future<int> &fut)
{
    std::cout << "printInt func is waiting value...\n";
    int x = fut.get();
    std::cout << "x: " << x << std::endl;
}

int main(int argc, char **argv)
{
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();
    std::thread t1(printInt, std::ref(fut));

    prom.set_value(1);
    prom.set_value(2); // exception
    t1.join();
    return 0;
}

以上代码,将报错如下:

terminate called after throwing an instance of 'std::future_error'
  what():  std::future_error: Promise already satisfied
printInt func is waiting value...
Aborted (core dumped)

future对象也只能获取一次值

void printInt(std::future<int> &fut)
{
    int idx = 0;
    while(idx < 2)
    {
        std::cout << "printInt func is waiting value...\n";
        int x = fut.get();
        std::cout << "x: " << x << std::endl;
        idx += 1;
    }
}

int main(int argc, char **argv)
{
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();
    std::thread t1(printInt, std::ref(fut));
    prom.set_value(1);
    t1.join();
    return 0;
}

以上代码将报错如下:

printInt func is waiting value...
x: 1
printInt func is waiting value...
terminate called after throwing an instance of 'std::future_error'
  what():  std::future_error: No associated state
Aborted (core dumped)

std::ref

值得注意的是当使用‵std::bind修改的函数参数或std::thread绑定的函数参数是引用类型时,必须使用std::ref来创建一个模拟引用类型的对象,这是因为,在std::bindstd::thread`中传递的参数是按值传递,会进行复制,而普通的引用类型不支持复制。

使用std::ref会返回一个模拟引用的类型std::reference_warper,这个类型支持复制。

struct Box {
    int x;
    int y;
};

void printInt(Box &v)
{
    int idx = 0;
    while(idx < 2)
    {
        std::cout << "printInt func is waiting value...\n";
        v.x = 100;
        std::cout << "x: " << v.x << std::endl;
        idx += 1;
    }
}

int main(int argc, char **argv)
{
    Box b;
    b.x = 1000;
    std::thread t1(printInt, std::ref(b));
    t1.join();

    std::cout << "b.x: " << b.x << std::endl;
    return 0;
}
// printInt func is waiting value...
// x: 100
// printInt func is waiting value...
// x: 100
// b.x: 100

假如没有使用std::ref在编译的时候就会报错:

/usr/include/c++/9/thread:120:44: error: static assertion failed: std::thread arguments must be invocable after conversion to rvalues

欢迎访问个人网络日志🌹🌹知行空间🌹🌹


reference

  • 1.https://cplusplus.com/reference/functional/bind/
  • 2.https://zhuanlan.zhihu.com/p/91062516#:~:text=%E5%AE%9A%E4%B9%89%E4%B8%80%E4%B8%AAstd%3A%3Amutex,%E9%98%BB%E5%A1%9E%EF%BC%8C%E7%9B%B4%E5%88%B0%E5%8A%A0%E9%94%81%E6%88%90%E5%8A%9F%E3%80%82
  • 3.https://stackoverflow.com/questions/20516773/stdunique-lockstdmutex-or-stdlock-guardstdmutex
  • 4.https://cplusplus.com/reference/condition_variable/condition_variable/notify_one/
  • 5.https://juejin.cn/post/7086226046931959838
  • 6.https://cplusplus.com/reference/future/promise/
  • 7.https://stackoverflow.com/questions/11004273/what-is-stdpromise

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

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

相关文章

Docker 常用指令集合,更换镜像(Ubantu)

1.更换镜像 先进入root用户 cat /etc/docker/daemon.json 查看有没有镜像创建目录,创建并编辑damon,json文件 mkdir -p /etc/docker vim /etc/docker/daemon.json# 填写内容 {"registry-mirrors": ["https://h5rurp1p.mirror.aliyuncs.com"] } 重新启…

在工作中保持情绪稳定:策略与实践

一、引言 近期发生的新闻热点再度引发公众对稳定情绪和心理健康的关注。在快节奏的现代生活中&#xff0c;我们常常面临各种压力和挑战&#xff0c;这可能会导致情绪波动&#xff0c;甚至影响我们的工作和生活质量。有时候我们遇到的最大的敌人&#xff0c;不是运气也不是能力…

Kubernetes的介绍(组件、Pod)和 安装使用

目录 Kubernetes是什么&#xff1f; 跟Kubernetes相似的软件&#xff1a; k8s里有哪些组件&#xff1f; 官方网站&#xff1a;Kubernetes 组件 | Kubernetes master上的Control Plane组件 什么是组件&#xff1f; Pod是什么呢&#xff1f; 1、kube-apiserver …

Oracle的CentOS安装

1.CentOS环境 阿里云轻量型服务器 2核-4g内存-80G系统盘 2.Oracle下载 Oracle下载 Oracle 数据库免费 CPU 限制 Oracle 数据库免费版自动将自身限制为两个内核进行处理。例如&#xff0c;在具有 2 个双核 CPU&#xff08;四个核&#xff09;的计算机上&#xff0c;如果大量…

【在线文件管理】响应式文件管理AngularJS

目录 1.功能展示截图 2.实现代码 2.1HTML页面代码 2.2后台代码 2.2.1项目结构 2.2.2项目代码 其他问题 1.功能展示截图 项目中需要用到文件管理来自由控制文件的上传、下载、删除等&#xff0c;就想到做一个简单的在线文件管理功能。 支持在线编辑&#xff1a; 2.实现代…

Java基础---SPI

目录 典型回答 从面向接口编程说起 接口位于调用方所在的包中 接口位于实现方所在的包中 注意 如何定义一个SPI SPI的实现原理 SPI的应用场景 典型回答 Java中区分 API 和 SPI&#xff0c;通俗的讲&#xff1a;API 和 SPI 都是相对的概念&#xff0c;他们的差别只在语义…

从零开始的Android逆向工程,开启对应用程序内部的探索之旅

Android逆向有那些发展方向 安全评估&#xff1a; 逆向工程可以帮助安全专家分析和评估Android应用程序的安全性。通过逆向应用程序&#xff0c;发现潜在的漏洞和安全隐患&#xff0c;并提供改进建议&#xff0c;以加强应用程序的安全性。 应用改进和优化&#xff1a; 逆向…

java编码转换过程

常见的JAVA程序包括以下类别&#xff1a; *直接在console上运行的类(包括可视化界面的类) *JSP代码类&#xff08;注&#xff1a;JSP是Servlets类的变型&#xff09; *Servelets类 *EJB类 *其它不可以直接运行的支持类 这些类文件中&#xff0c;都有可能含有中文字符串&…

KDE缺少全屏启动器的解决办法

我记得以前KDE是有一个全局搜索的启动起来着&#xff0c;但是重装了一次之后发现只剩下一个半屏的了。解决方案如下 sudo apt-get install plasma-widgets-addons

三菱PLC 红绿灯 步进指令 STL

自己写的红绿灯。 有启动、停止两个按钮。 南北通行4S&#xff0c;东西通行5S。 链接: https://caiyun.139.com/m/i?0E5CJEoVGt4D0 提取码:kVOA SET(启动,启动标志); RST(启动,停止标志); SET(停止,停止标志); RST(停止,启动标志); RST(LDP(TRUE,停止),T0); RST(LDP(TRUE…

范德波尔方程可视化

Van der Pol方程如下所示 d x d t y d y d t − x ( 1 − x 2 ) y \begin{equation} \begin{aligned} \frac{dx}{dt} & y \\ \frac{dy}{dt} & -x(1-x^2)y \end{aligned} \end{equation} dtdx​dtdy​​y−x(1−x2)y​​​ 相应的程序如下 为了观看长期趋势&…

VUE项目打包成apk

在我们的开发需求中&#xff0c;可能会遇到需要将vue项目中的H5代码打包成一个安卓的app&#xff0c;那么我为大家介绍一套保姆级的解决方案&#xff0c;看完你就会。 VUE HBuilder 1.准备工作&#xff1a; 需要下载一个HBuilder X编辑器&#xff0c;不过我相信大家身为前端…

最适合新手的SpringBoot+SSM项目《苍穹外卖》实战—(四)集成 Swagger

文章目录 Swagger 介绍集成 Swagger常用注解 黑马程序员最新Java项目实战《苍穹外卖》&#xff0c;最适合新手的SpringBootSSM的企业级Java项目实战。 Swagger 介绍 Swagger 是一个开源的 API 设计工具&#xff0c;它可以用于描述、设计、开发和测试 RESTful API。 它提供了一…

【C++11】 线程库的使用

文章目录 1 线程库的基本使用1.1 thread1.2 this_thread1.3 线程函数参数 2 mutex2.1 mutex的基本使用2.2 mutex系列锁2.3 lock_guard与unique_lock 3 原子操作4 条件变量 1 线程库的基本使用 1.1 thread 在C11之前&#xff0c;涉及到多线程问题&#xff0c;都是和平台相关的&…

研究一下「pnpm」这个神奇的包管理工具

最近搬砖 &#x1f9f1; 在搞前端项目部署优化 &#x1f3a1;&#xff0c;大部分项目的包管理工具都已经从 npm/yarn 替换成了 pnpm&#xff0c;整体来看无论是在 install 或是 build 阶段都提速了不少 &#x1f680;&#xff0c;借此时机&#xff0c;做个总结&#xff01;&…

TypeScript 中【类型断言】得使用方法

类型断言的概念 有些时候开发者比TS本身更清楚当前的类型是什么&#xff0c;可以使用断言&#xff08;as&#xff09;让类型更加精确和具体。 类型断言&#xff08;Type Assertion&#xff09;表示可以用来手动指定一个值的类型。 类型断言语法&#xff1a; 值 as 类型 或 <…

vue3 实现多层级列表

文章目录 需求背景解决效果index.vue视频效果 需求背景 需要在统一个列表下&#xff0c;实现商品和规格得管理和联动 解决效果 index.vue <!--/*** author: liuk* date: 2023/7/7* describe: 商品列表 */--> <template><div class"container">&…

textarea自适应高度二——(设置隐藏div获取高度和仿element-ui组件)

文章目录 前言一、通过隐藏div的方式来设置文本域自适应高度1. 新增一个文本域样式一个的dom&#xff0c;但是里面的textarea改为div2. 隐藏div的class3.设置文本域高度的方法 二、仿element-ui组件设置textarea自适应高度1.element-ui中自适应效果2. 看源码&#xff0c;盘逻辑…

病毒专题丨 plugx病毒

一、病毒简述 之前分析了一下&#xff0c;分析的较为简单&#xff0c;这次又详细分析了一下。 文件名称 00fbfaf36114d3ff9e2c43885341f1c02fade82b49d1cf451bc756d992c84b06 文件格式 RAR 文件类型(Magic) RAR archive data, v5 文件大小 157.74KB SHA256 00fbfaf36114d3ff9e…

【编程中的数学】:冰雹猜想

今天和大家分享一个令人着迷的数学谜题——冰雹猜想。这个谜题曾在1976年引起轰动&#xff0c;当时《华盛顿邮报》以头版头条刊登了一篇关于它的报道。让我们一起探索这个数学游戏的奥秘。 70年代中期&#xff0c;美国一所名牌大学的校园内兴起了一种数学游戏&#xff0c;这个游…