C/C++开发,无可避免的多线程(篇四).线程与函数的奇妙碰撞

news2024/11/16 11:32:40

一、函数、函数指针及函数对象

        1.1 函数

        函数(function)是把一个语句序列(函数体, function body)关联到一个名字和零或更多个函数形参(function parameter)的列表的 C++ 实体,可以通过返回或者抛出异常终止。。

// 函数名:func
// 形参列表拥有一个形参,具有名字 cnt 和类型 int
// 返回类型是 void
void func(int cnt)
{                      // 函数体开始
    std::cout << (cnt % 2);
}                      // 函数体结束

        函数声明可以在任何作用域中出现,但函数定义只能在命名空间作用域出现,或对于成员和友元函数,可以在类作用域中出现。在类体中声明而不带 friend 说明符的函数是类成员函数。这种函数拥有许多附加性质。

        每个函数都具有一个类型,它由函数的返回类型,所有形参的类型(形参列表),函数是否为 noexcept (C++17 起),以及对于非静态成员函数的 cv 限定性和引用限定性 (C++11 起)构成。函数类型同样拥有语言链接。不存在有 cv 限定的函数类型(不要与如 int f() const; 这样的 cv 限定函数类型,或如 std::string const f(); 这样的返回 cv 限定类型的函数相混淆)。如果有任何 cv 限定符被添加到到函数类型的别名,那么它会被忽略。

         1.2 函数指针

        函数不能按值传递或被其他函数所返回。可以有指向/到除主函数以外的所有函数 (C++20 前)所有非标准库函数和几个标准库函数 (C++20 起)的指针和引用,它们可以用于这些函数自身无法被使用的地方。因此我们说这些函数“可取址”。

void (*pf)() = &func;
pf();

        函数指针能以非成员函数或静态成员函数的地址初始化。由于存在函数到指针的隐式转换,取址运算符可以忽略。不同于函数或函数的引用,函数指针是对象,从而能存储于数组、被复制、被赋值等。

void f(int);
void (*p1)(int) = &f;
void (*p2)(int) = f; // 与 &f 相同

        更多关于函数指针说明见本专栏关于函数指针的博文:c/c++开发,无可避免的函数指针使用案例_py_free-物联智能的博客-CSDN博客

        1.3 函数对象

        函数调用表达式还支持函数指针以及重载了函数调用运算符及可转换为函数指针的任何类类型的值(包括 lambda 表达式) (C++11 起)。这些类型被统称为函数对象 (FunctionObject)。

        函数对象是指可以像函数一样被调用的对象,它可以重载函数调用运算符(),并且可以像函数一样接受参数和返回值。任何定义了函数调用操作符的对象都是函数对象。C++ 支持创建、操作新的函数对象,同时也提供了许多内置的函数对象。

        函数对象可以作为参数传递给函数,也可以作为返回值返回给调用者。函数对象可以用于实现一些特定的算法,例如排序、查找等。在c/c++标准库中,函数对象被广泛应用于算法和容器中,例如sort()函数中可以传递一个函数对象作为排序规则,或者在map容器中可以使用一个函数对象作为比较器来进行元素的排序和查找。此外,函数对象还可以用于实现自定义的操作符重载,例如可以定义一个函数对象来实现自定义的加法操作。

        函数对象和函数指针都可以作为函数的参数或返回值,但它们的实现方式不同。函数对象是一个类,它重载了函数调用运算符(),可以像函数一样被调用。而函数指针是一个指向函数的指针,可以通过指针调用函数。另外,函数对象可以保存状态,而函数指针不可以。

二、线程与函数

        线程是程序中的一条执行路径,通过执行多个线程,可以让程序在同一时间执行多个任务,提高程序的并发性和效率。

        函数指针是指指向函数的指针变量,可以将函数作为参数传递给其他函数,或者将函数作为返回值返回给调用者;函数对象是一个类,它重载了 () 运算符,可以像函数一样被调用。它们都是 c/c++语言中非常实用的特性,可以更好地实现复杂的功能。在多线程编程中,函数指针或函数对象可以作为线程的入口函数,让线程执行特定的任务。

        2.1 线程通过函数体入口获得真正任务执行语句

        下面来看一下线程与函数指针或函数对象结合,完成执行任务的例子:

//test1.h
#ifndef _TEST_1_H_
#define _TEST_1_H_

void func_ptr_test(void);

#endif //_TEST_1_H_
//test1.cpp
#include "test1.h"

#include <thread>
#include <atomic>
#include <iostream>
#include <functional>

std::atomic<int> cnt{0};
void func(){ cnt++;};
typedef void (*pfunc)();
using pfunc_us = void (*)();
//using pfunc_us=pfunc;

void func_ptr_test(void)
{
    //函数引用
    std::thread t1{func}; // OK
    t1.join();
    std::cout << "cnt = " << cnt << "\n";
     //函数指针
    void (*pf)() = &func;
    std::thread t2{pf}; // OK
    t2.join();
    std::cout << "cnt = " << cnt << "\n";
    //函数指针别名
    pfunc pfc = &func;//= func;
    std::thread t3{pfc}; // OK
    t3.join();
    std::cout << "cnt = " << cnt << "\n";
    //函数指针别名
    pfunc_us pfus = &func;//= func;
    std::thread t4{pfus}; // OK
    t4.join();
    std::cout << "cnt = " << cnt << "\n";
    //函数对象,Lambda 表达式,闭包
    auto f = [&]{cnt++;};
    std::thread t5{f}; // OK
    t5.join();
    std::cout << "cnt = " << cnt << "\n";
    //函数包裹器
    std::function<void()> f_display = [&]{cnt++;};
    std::thread t6{f}; // OK
    t6.join();
    std::cout << "cnt = " << cnt << "\n";
    //函数包裹器
    std::function<void()> f_f = &func;
    std::thread t7{f_f}; // OK
    t7.join();
    std::cout << "cnt = " << cnt << "\n";
    //函数包裹器
    std::function<void()> f_fb = std::bind(func);
    std::thread t8{f_fb}; // OK
    t8.join();
    std::cout << "cnt = " << cnt << "\n";
};
//main.cpp
#include "test1.h"

int main(int argc, char* argv[])
{
    func_ptr_test();
    return 0;
};

        在这个例子可以看到,通过函数引用、函数指针及函数指针别名、函数对象Lambda 表达式、函数对象包裹器等均可作为入口地址传递给线程类,线程运行是通过函数入口地址开始执行语句的。线程执行任务的真正实现内容是在传递进去函数体内完成。

        编译g++ main.cpp test*.cpp -o test.exe -std=c++11,运行程序:

        2.2 Lambda 表达式与函数

         在上述例子中,有两个很新颖的应用Lambda 表达式和std::function类模板。先来看Lambda 表达式,它能够捕获作用域中的变量的无名函数对象。

        标准库为其提供了一系列支持:

//语法支持
/*(1),完整声明,捕获,包含零或更多个捕获符的逗号分隔列表,可以默认捕获符(capture-default)起始 
*由说明符、 异常说明、 属性和尾随返回类型 按顺序组成,每个组分均非必需
* (C++20 起)向闭包类型的 operator() 添加约束 
*默认捕获符有&(以引用隐式捕获被使用的自动变量)和=(以复制隐式捕获被使用的自动变量)。
*/
[ 捕获 ] ( 形参 ) lambda说明符 约束(可选) { 函数体 }  
    
/*(2),省略形参列表:函数不接收实参,如同形参列表是 ()*/
[ 捕获 ] { 函数体 }                                          //(C++23 前)
[ 捕获 ] lambda说明符 { 函数体 }                              //(C++23 起) 

/*(3),与(1) 相同,但指定泛型 lambda 并显式提供模板形参列表
*模板形参列表不能为空(不允许 <>)。*/            
[ 捕获 ] <模板形参> 约束(可选)( 形参 ) lambda说明符 约束(可选) { 函数体 }     //(C++20 起) 

/*(4),与 (2) 相同,但指定泛型 lambda 并显式提供模板形参列表。
*模板形参列表不能为空(不允许 <>)。*/
[ 捕获 ] <模板形参> 约束(可选) { 函数体 }                      // (C++20 起)(C++23 前) 
[ 捕获 ] <模板形参> 约束(可选) lambda说明符 { 函数体 }         //(C++23 起) 

        lambda 表达式是纯右值表达式,它的类型是独有的无名非联合非聚合类类型,被称为闭包类型(closure type),它(对于 ADL 而言)在含有该 lambda 表达式的最小块作用域、类作用域或命名空间作用域声明。

//闭包类型::operator()(形参)
返回类型 operator()(形参) const { 函数体 }  //(未使用关键词 mutable) 
返回类型 operator()(形参) { 函数体 }        //(使用了关键词 mutable) 
template<模板形参>返回类型 operator()(形参) const { 函数体 }  //(C++14 起)(泛型 lambda) 
template<模板形参>返回类型 operator()(形参) { 函数体 }        //(C++14 起)(泛型 lambda,使用了关键词 mutable) 

        前面例子,因为采用了auto关键字,它是一个泛型 lambda(当以 auto 为形参类型或显式提供模板形参列表 (C++20 起)时,该 lambda 为泛型 lambda)。它捕获([&])一个函数的引用,传入线程是,线程会获得函数体{cnt++;},即创建线程后,将执行cnt++;语句后,线程就完成了执行任务。

// 泛型 lambda,operator() 是无形参的模板
auto f = [&]{cnt++;};
//auto f = [&](void){cnt++;};
std::thread t5{f};

        更多的关于lambda 表达式的用法及格式要求不是本博文关注重点,就不在这里展开:

Lambda 捕获标识符:空、...、初始化器、&、&...、&初始化器、this、*this、...初始化器、&...初始化器
/*注,初始化器:(表达式)、=表达式、{表达式}*/
闭包类型::捕获
闭包类型::operator 返回类型(*)(形参)()
闭包类型::闭包类型()
闭包类型::operator=(const 闭包类型&)
闭包类型::~闭包类型()

        2.3 函数包装器-std::function类模板

        std::function类模板是定义在标准库头文件 <functional>,此头文件是函数对象库的一部分并提供标准散列函数。该头文件包含很多函数对象相关类模板及函数模板,本文就只讲述std::function和std::bind。


function    (C++11) 包装具有指定函数调用签名的任意可复制构造类型的可调用对象(类模板) 
bind        (C++11)  绑定一或多个实参到函数对象 

        类模板 std::function 是通用多态函数包装器。 std::function 的实例能存储、复制及调用任何可复制构造 (CopyConstructible) 的可调用 (Callable) 目标——函数、 lambda 表达式、 bind 表达式或其他函数对象,还有指向成员函数指针和指向数据成员指针。

//std::function,定义于头文件 <functional>,(C++11 起) 
template< class >class function;                     
template< class R, class... Args > class function<R(Args...)>; 

        std::function 满足可复制构造 (CopyConstructible) 和可复制赋值 (CopyAssignable) 。类模板std::function的成员:

成员类型
类型                定义 
result_type         R 
argument_type       //(C++17 中弃用)(C++20 中移除) 若 sizeof...(Args)==1 且 T 是 Args... 中首个且唯一的类型,则为 T 
first_argument_type//(C++17 中弃用)(C++20 中移除) 若 sizeof...(Args)==2 且 T1 是 Args... 中二个类型的第一个,则为 T1 
second_argument_type//(C++17 中弃用)(C++20 中移除) 若 sizeof...(Args)==2 且 T2 是 Args... 中二个类型的第二个,则为 T2 

成员函数
(构造函数)      构造新的 std::function 实例(公开成员函数) 
(析构函数)      析构 std::function 实例(公开成员函数) 
operator=      为内容赋值(公开成员函数) 
swap           交换内容(公开成员函数) 
assign         (C++17 中移除)  为内容赋值一个新的目标(公开成员函数) 
operator bool  检查是否包含了有效的目标(公开成员函数) 
operator()     调用其目标(公开成员函数) 

目标访问
target_type    获得 std::function 所存储的目标的typeid(公开成员函数) 
target         获得指向 std::function 所存储的目标的指针(公开成员函数) 

非成员函数
std::swap(std::function)    (C++11)特化 std::swap 算法(函数模板) 
operator==                   比较 std::function 和 nullptr(函数模板) 
operator!=                  (C++20 中移除)比较 std::function 和 nullptr(函数模板) 

辅助类
std::uses_allocator<std::function>    (C++11) (C++17 前)特化std::uses_allocator类型特性(类模板特化) 

        类模板 std::function的声明定义:

namespace std {
  template<class> class function; // 不定义
 
  template<class R, class... ArgTypes>
  class function<R(ArgTypes...)> {
  public:
    using result_type = R;
 
    // 构造/复制/销毁
    function() noexcept;
    function(nullptr_t) noexcept;
    function(const function&);
    function(function&&) noexcept;
    template<class F> function(F);
 
    function& operator=(const function&);
    function& operator=(function&&);
    function& operator=(nullptr_t) noexcept;
    template<class F> function& operator=(F&&);
    template<class F> function& operator=(reference_wrapper<F>) noexcept;
 
    ~function();
 
    // function 修改器
    void swap(function&) noexcept;
 
    // function 容量
    explicit operator bool() const noexcept;
 
    // function 调用
    R operator()(ArgTypes...) const;
 
    // function 目标访问
    const type_info& target_type() const noexcept;
    template<class T>       T* target() noexcept;
    template<class T> const T* target() const noexcept;
  };
 
  template<class R, class... ArgTypes>
    function(R(*)(ArgTypes...)) -> function<R(ArgTypes...)>;
 
  template<class F> function(F) -> function</* see description */>;
 
  // 空指针比较函数
  template<class R, class... ArgTypes>
    bool operator==(const function<R(ArgTypes...)>&, nullptr_t) noexcept;
 
  // 特化的算法
  template<class R, class... ArgTypes>
    void swap(function<R(ArgTypes...)>&, function<R(ArgTypes...)>&) noexcept;
}

        从std::function类名板声明可以看出,其模板参数是由一个泛型返回类型R和多个泛型参数类型ArgTypes...组成,最终构成类似于lambda表达式的指向R(*)(ArgTypes...){};函数体。

三、线程与成员函数

         3.1 函数转发包装器- std::bind

        讲述std::function类模板时,涉及到一个重要的函数模板 std::bind ,它可以生成 函数f 的转发调用包装器。调用此包装器等价于以一些绑定到 args 的参数调用函数 f上。

/*std::bind,定义于头文件 <functional>
*参数:
*1)f - 可调用 (Callable) 对象(函数对象、指向函数指针、到函数引用、指向成员函数指针或指向数据成员指针) 
*2)args - 要绑定的参数列表,未绑定参数为命名空间 std::placeholders 的占位符 _1, _2, _3... 所替换 
*返回值:
*未指定类型 T 的函数对象,满足 std::is_bind_expression<T>::value == true 。
*/
template< class F, class... Args > 
/*unspecified*/ bind( F&& f, Args&&... args );  (C++11 起)(C++20 前) 

template< class F, class... Args > 
constexpr /*unspecified*/ bind( F&& f, Args&&... args );  (C++20 起) 

template< class R, class F, class... Args > 
/*unspecified*/ bind( F&& f, Args&&... args );  (C++11 起)(C++20 前) 

template< class R, class F, class... Args > 
constexpr /*unspecified*/ bind( F&& f, Args&&... args );  (C++20 起) 

        简单来说,函数模板 std::bind就是将函数(包括操作符)、成员函数(包括操作符)进行函数转换包装给调用者使用,相当于使用函数对象指针一样方便。调用指向非静态成员函数指针或指向非静态数据成员指针时,首参数必须是引用或指针(可以包含智能指针,如 std::shared_ptr 与 std::unique_ptr),指向将访问其成员的对象。因此可以将std::bind指向的对象作为函数入口地址传递给线程,和将函数引用或指针传递是一样效果。

        3.2 std::bind与线程

//test2.h
#ifndef _TEST_2_H_
#define _TEST_2_H_

void cfunc_ptr_test(void);

#endif //_TEST_2_H_
//test2.cpp
#include "test2.h"

#include <thread>
#include <iostream>
#include <functional>
#include <memory>

struct ATest {
    void print1(int i) const { 
        std::cout << "ATest::print1 i = " << i << '\n'; 
    };
};

struct CTest {
    CTest(int num) : num_(num) {};
    void operator()(int i) const
    {
        std::cout << "CTest::operator i = " << i << '\n';
    };
    void print_add(int i) const { 
        std::cout << "CTest::print_add num_+i = " << num_+i << '\n'; 
    };
    static void print2(int i)
    {
        std::cout << "CTest::print2 2*i = " << 2*i << '\n';
    };
    struct BTest
    {
        void print1(int i) const { 
            std::cout << "CTest::BTest::print1 i = " << i << '\n'; 
        };
    };
    int num_;
    ATest at;
    BTest bt;
};

void cfunc_ptr_test(void)
{
    //绑定到成员函数
    CTest ct(1);
    //f1类型:void (CTest::*(CTest*,int))(int i) const
    auto f1 = std::bind(&CTest::print_add, &ct, 10);//10为函数传入参数,&ct为传入地址
    std::thread t1{f1}; // OK
    t1.join();
    //绑定到成员函数,符号索引参数
    using std::placeholders::_1;
    auto f2 = std::bind(&CTest::print_add, &ct, _1);
    std::thread t2{f2,2 }; // OK,2为函数传入参数
    t2.join();
    //绑定到成员操作符
    auto f3 = std::bind(&ct, &ct, _1);  //CTest::operator()
    // auto f3 = std::bind(&CTest::operator(), &ct, _1); 
    std::thread t3{f2,3 }; // OK,3为函数传入参数
    t3.join();
    //绑定智能指针指向的成员函数
    std::unique_ptr<CTest> pct(new CTest(1));
    auto f4 = std::bind(&CTest::print_add, &*pct.get(), _1);
    std::thread t4{f4,4 }; // OK,4为函数传入参数
    t4.join();
    //绑定智能指针指向的成员函数
    auto f5 = std::bind(&CTest::operator(), &*pct.get(), _1);//CTest::operator()
    std::thread t5{f5,5 }; // OK,5为函数传入参数
    t5.join();
    //静态成员函数作为线程入口
    // auto f6 = &CTest::print2;
    // std::thread t6{f6,6 }; // OK,6为函数传入参数
    std::thread t6{&CTest::print2,6 }; // OK,6为函数传入参数
    t6.join();
    //
    auto f7 = std::bind(&ATest::print1, &ct.at, _1);
    // f7(7);
    std::thread t7{f7,7 }; // OK,7为函数传入参数
    t7.join();
    //
    auto f8 = std::bind(&CTest::BTest::print1, &ct.bt, _1);
    // f8(8);
    std::thread t8{f8,8 }; // OK,8为函数传入参数
    t8.join();
    //绑定到成员函数,直接在bind内初始化一个对象传入
    auto f9 = std::bind(&CTest::print_add, new CTest(1), _1);//10为函数传入参数,&ct为传入地址
    std::thread t9{f9,9}; // OK
    t9.join();
};
//main.cpp
#include "test2.h"

int main(int argc, char* argv[])
{
    cfunc_ptr_test();
    return 0;
};

        另外,标准库还有和std::bind相差不大的函数转发调用转发调用包装:std::bind_front,这是C++20的标准。调用此包装等价于绑定首 sizeof...(Args) 个参数到 args 再调用函数 f,这里就不细说了,有兴趣的自行查看C++20资料。

        编译g++ main.cpp test2.cpp -o test.exe -std=c++11,运行生产程序:

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

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

相关文章

MongoDB分片教程

一、概述分片是一种将数据分布在多个 机器。MongoDB使用分片来支持具有非常大数据的部署 集和高吞吐量操作。具有大型数据集或高吞吐量应用程序的数据库系统可以 挑战单个服务器的容量。例如&#xff0c;高查询率可以 耗尽服务器的 CPU 容量。工作集大小大于 系统的 RAM 会给磁…

初学者的第一个Linux驱动

软件环境&#xff1a;Ubuntu20.04 Linux内核源码&#xff1a;3.4.39 硬件环境&#xff1a;GEC6818 什么是驱动&#xff1f;简单来说就是让硬件工作起来的程序代码。 Linux驱动模块加载有两种方式&#xff1a; 1、把写好的驱动代码直接编译进内核。 2、把写好的驱动代码编…

Linux24 -- tcp相关概念、多个客户端链接服务端代码

一、tcp相关概念 tcp协议特点&#xff1a;面向连接的、可靠的、流式服务 建立链接&#xff1a;三次握手&#xff0c;发送 SYN 断开链接&#xff1b;四次挥手&#xff0c;发送 FIN tcp、udp都同属于传输层&#xff0c;在网络层使用ip协议&#xff0c;都要将数据交给IP协议&am…

零拷贝技术-内核源码剖析

在网络编程中&#xff0c;如果我们想要提供文件传输的功能&#xff0c;最简单的方法就是用read将数据从磁盘上的文件中读取出来&#xff0c;再将其用write写入到socket中&#xff0c;通过网络协议发送给客户端。ssize_t read(int fd, void *buf, size_t count); ssize_t write(…

学习记录---latent code 潜在编码

文章目录参考文献1. 什么是潜在编码&#xff1f;2.什么是潜在空间&#xff1f;3.同类潜在编码的相似性4.潜在编码的应用4.1 Antoencoders4.2 Generative models5.结论个人学习总结&#xff0c;持续更新中……参考文献 [1] 快速理解深度学习中的latent code潜在编码 1. 什么是…

[一篇读懂]C语言十一讲:单链表的删除和单链表真题实战

[一篇读懂]C语言十一讲&#xff1a;单链表的删除和单链表真题实战1. 与408关联解析及本节内容介绍1 本节内容介绍2. 单链表的删除操作实战3. 单链表真题解读与解题设计1 题目解读2 解题设计第一阶段&#xff1a;双指针找中间结点第二阶段&#xff1a;原地逆置第三阶段&#xff…

ubuntu16.04 python代码自启动和可执行文件自启动

1 python代码自启动 参考 https://blog.csdn.net/qq_38288618/article/details/104096606 准备好python文件 test.py import time c1 while 1:time.sleep(1)cc1print(c)运行 sudo chmod 777 test.py python3 test.py准备run.sh 文件 #!/bin/bash gnome-terminal -x bash -…

【Spring6】IoC容器之基于XML管理Bean

3、容器&#xff1a;IoC IoC 是 Inversion of Control 的简写&#xff0c;译为“控制反转”&#xff0c;它不是一门技术&#xff0c;而是一种设计思想&#xff0c;是一个重要的面向对象编程法则&#xff0c;能够指导我们如何设计出松耦合、更优良的程序。 Spring 通过 IoC 容…

C语言学习笔记——指针(初阶)

前言 指针可以说是C语言基础语法中最难的理解的知识之一&#xff0c;很多新手&#xff08;包括我&#xff09;刚接触指针时都觉得很难。在我之前发布的笔记中都穿插运用了指针&#xff0c;但是我一直没有专门出一期指针的笔记&#xff0c;这是因为我确实还有些细节至今还不太清…

STM32之关门狗

看门狗介绍在由单片机构成的微型计算机系统中&#xff0c;由于单片机的工作常常会受到来自外界电磁场的干扰&#xff0c;造成程序的跑飞&#xff0c;而陷入死循环&#xff0c;程序的正常运行被打断&#xff0c;由单片机控制的系统无法继续工作&#xff0c;会造成整个系统的陷入…

vue3+rust个人博客建站日记5-所有界面

没有数据的前端&#xff0c;是没有灵魂的。明明标题是vue3 rust &#xff0c;但日记撰写至今&#xff0c;似乎只有第一篇提及了Rust&#xff0c;这可不行。是时候一股作气&#xff0c;完成大部分页面绘制工作了&#xff01; 最后再说一次&#xff0c;时间要加速了。 ——普奇神…

EPICS S7nodave手册

第一章&#xff1a;介绍 本手册分为6章(不算次介绍部分)。第一章介绍s7nodave用于EPICS的设备支持的概念和特新。第二章描述启动一个使用s7nodave的IOC项目所需要的几步。第三章描述s7nodave支持的IOC shell命令。之后&#xff0c;第四章解释s7nodave支持的各种记录类型。最后…

【算法】期末复盘,酒店住宿问题——勿向思想僵化前进

文章目录前言题目描述卡在哪里代码&#xff08;C&#xff09;前言 省流&#xff1a;一个人也可以住双人间&#xff0c;如果便宜的话。 害&#xff01;尚正值青春年华&#xff0c;黄金岁月&#xff0c;小脑瓜子就已经不灵光咯。好在我在考试的最后一分钟还是成功通过了这题&am…

Jetpack Compose 中的 CompositionLocal

要在可组合函数之间共享数据时&#xff0c;可以通过参数传递显式地调用&#xff0c;这通常是最简单和最好的方式。 但随着参数越来越多&#xff0c;组件也越来越多&#xff0c;并且有些数据还需要保持私有性&#xff0c;这时这种方式就会显得很繁琐臃肿&#xff0c;难以维护。…

vscode插件推荐

文章目录前言一、vscode插件推荐&#xff1f;1、 Chinese (Simplified) (简体中文) Language Pack for Visual Studio Code2、Auto Close Tag3、Auto Import3、Error Lens4、vscode-icons5、ES7 React/Redux/React-Native snippets6、GitLens — Git supercharged7、JavaScript…

【FPGA】Verilog:时序电路应用 | 序列发生器 | 序列检测器

前言&#xff1a;本章内容主要是演示Vivado下利用Verilog语言进行电路设计、仿真、综合和下载 示例&#xff1a;序列发生器与序列检测器 ​ 功能特性&#xff1a; 采用 Xilinx Artix-7 XC7A35T芯片 配置方式&#xff1a;USB-JTAG/SPI Flash 高达100MHz 的内部时钟速度 存储器…

车道线检测CondLaneNet论文和源码解读

CondLaneNet: a Top-to-down Lane Detection Framework Based on Conditional Convolution Paper&#xff1a;https://arxiv.org/pdf/2105.05003.pdf code&#xff1a;GitHub - aliyun/conditional-lane-detection 论文解读&#xff1a; 一、摘要 这项工作作为车道线检测任…

js垃圾回收机制

内存的生命周期 ]S环境中分配的内存&#xff0c;一般有如下生命周期 1.内存分配:当我们声明变量、函数、对象的时候&#xff0c;系统会自动为他们分配内存 2.内存使用:即读写内存&#xff0c;也就是使用变量、函数等 3.内存回收: 使用完毕&#xff0c;由垃圾回收器自动回收不再…

MySQL实战解析底层---事务到底是隔离的还是不隔离的

目录 前言 “快照”在 MVCC 里是怎么工作的&#xff1f; 更新逻辑 前言 讲事务隔离级别的时候提到过&#xff0c;如果是可重复读隔离级别&#xff0c;事务 T 启动的时候会创建一个视图 read-view之后事务 T 执行期间&#xff0c;即使有其他事务修改了数据&#xff0c;事务 T…

​ ​​ ​IIS之FTP服务器 部署 (图文详细) 千锋

目录 概述 部署 步骤&#xff1a; 二重新配置FTP服务器 概述 1、File Transfor Protocol 文件传输协议 2、端口号&#xff1a; TCP 20/21 3、工作方式&#xff1a; 1)主动模式 2&#xff09;被动模式 部署 步骤&#xff1a; 配置静态IP 安装IIS-ftp软件 使用默认站…