C++20 高级编程

文章目录

  • 前言
  • 前奏
    • lambda
    • 浅谈std::ref的实现
    • 浅谈is_same
    • 浅谈std::function的实现
    • std::visit 与 std::variant 与运行时多态
    • SFINAE
    • 类型内省
    • 标签分发 (tag dispatching)
    • 编译时多态
      • 奇异递归模板模式 (Curiously Recurring Template Pattern,CRTP)
    • 三路比较操作符 (飞船操作符) <=>
    • enable_shared_from_this 模板类
  • 概念约束
    • 定义概念
    • requires 表达式
  • 模板元编程
  • constexpr 元编程
  • Ranges 标准库
  • 协程
  • 软件设计六大原则 SOLID
  • To be continue....

前言

  • C++20 是C++在C++11 之后最大的一次语言变革, 其中引入了大量具有革命性的新特性.
  • 本节包含了C++20中相当重要的四大特性: 概念约束, ranges(范围)标准库, 协程以及模块
    • 概念约束:
             是一个编译期谓词, 它根据程序员定义的接口规范对类型,常量等进行编译时检查,以便在泛型编程中为使用者提供更好的可读性与错误信息.
    • ranges标准库:
             对现有的标准库进行了补充,它以函数式编程范式进行编程, 将计算任务分解成一系列灵活的原子操作, 使得代码的正确性更容易推理.
    • 协程:
             是一种可挂起, 可恢复的通用函数, 它的切换开销是纳秒级的, 相对其他方案而言占用的资源极低, 并且可以非侵入式地为已有库扩展协程接口, 它常常用于并发编程, 生成器, 流处理, 异常处理等.
    • 模块:
             解决了传统的头文件编译模型的痛点: 依赖顺序导致头文件难以组合, 重复解析, 符号覆盖等问题, 从语言层面为程序员提供了模块化的手段.
  • 涉及了一些元编程的概念

前奏

lambda

  • lambda其实是由编译器生成的一个匿名类
  • 如果lambda包含在捕获列表内, 那么捕获将在对应的匿名类中生成成员变量与构造函数来存储捕获;
  • 对于无捕获的lambda而言, 其生成的匿名类中拥有一个非虚的函数指针类型转换操作符, 能够将lambda转换成函数指针. 这个不难理解, 因为无状态的 lambda 表达式可以赋给无状态的函数指针;
  • 在C++20 中泛型 lambda 也支持以模板参数形式提供, 这样就能保证两个形参类型一致.
    auto add=[]<typename T>(T a,T b){return a+b;};
    cout<<add(1,2)<<endl;
  • 模板函数只有实例化之后才能传递, 而泛型 lambda 是一个对象,可以按值传递, 在调用时根据实际传参进行实例化 模板函数 operator(),从而延迟了实例化的时机, 大大提高了灵活性. 标准库的一些算法通常要求对函数对象进行组合, 此时泛型 lambda 将能通过编译, 而模板函数不行.

浅谈std::ref的实现

  • 就是说构造一个新的对象(reference_wrapper是一个类模板)并在内部保存原来传入的变量的地址 _f 和类型 type
  • 重载类型转换为原来变量的引用类型, 有了_f 和 type, 这样可以在需要的时候自动将reference_wrapper转换为原来传入的变量实体的引用;
  • reference_wrapper 她本身可能会被 decay 但内部存储的 _f 值始终不会变, 始终可以在需要的时候自动转换为 type&
  • 我们可以自己简单实现一下:
    template<typename T>
    class my_ref{
    public:
        T * _f;
      explicit  my_ref(T& var): _f(addressof(var)){
    
        };
        operator T& () { return *_f; };
    //如果传入的是一个可调用对象
        template<class... Args>
        auto&& operator()(Args&& ...args){
    	 return (*_f)(args...);
        }
    };
    
  • 参考: https://zhuanlan.zhihu.com/p/581739392

浅谈is_same

  • 这是C++11引入的, 其实很简单, 模板特化就行了. 让编译器决定. 考虑: 万一我要运行时才能确定类型呢?
    template<class T, class U> struct is_same : std::false_type {};
    //偏特化版本 待确认一个模板参数
    template<class T> struct is_same<T, T> : std::true_type {};
    

浅谈std::function的实现

  • 有点类似上面的std::ref
  • 主要是对函数参数列表类型的获取, 可以使用模板特化来达到目的 C++ Template -> [5] -> 自由函数与模板可变参数

std::visit 与 std::variant 与运行时多态

  • 用法参考:
    https://zhuanlan.zhihu.com/p/676918348
    https://zhuanlan.zhihu.com/p/670189611
  • std::variant 行为像是一个类型安全的联合体。它存储了一系列类型,并能够在运行时安全地处理这些类型之一。
  • 它保留足够的空间来存储其可能的任何类型,通常是这些类型中最大者的大小。此外,std::variant 还需要额外的存储空间来跟踪当前存储的类型。
  • 为了维护类型安全,std::variant 使用一个内部索引来标记当前激活的类型。当访问或修改 std::variant 的值时,它会检查这个索引,并确保操作符合当前激活的类型。
  • std::visit 的第一个参数传入一个可调用对象,后面传入的是可调用对象的参数(可以用variant)。 std::visit 的工作原理依赖于编程语言或编译器的内部机制,这些机制通常对程序员透明。其中一种可能的实现方式是使用 “vtable”(Virtual Table,虚函数表)。 总之, std::visit 会在运行时查找函数地址。每当创建一个 std::variant 对象的时候,就会产生一个与之关联的 vtable,同来存储这个 std::variant 中存储的相关信息。
  • visit 编译时静态绑定 (第二个参数中各类型对应的第一个参数中可调用函数版本绑定到类型对应的索引 (用vtable来存储映射关系) )
  • visit 运行时动态绑定 (检查variant中当前激活的类型的索引). 从而决定要调用的函数版本
  • subtype多态例子:
    #include <iostream>
    #include <memory>
    #include <cmath>
    namespace Subtype
    {
        struct Shape
        {
            virtual ~Shape() = default;
            virtual double getArea() const = 0;
            virtual double getPerimeter() const = 0;
        };
        
        struct Circle: Shape
        {
            Circle(double r): r_(r) {}
            double getArea() const override
            {
                return M_PI * r_ * r_;
            }
            double getPerimeter() const override
            {
                return 2 * M_PI * r_;
            }
        private:
            double r_;
        };
        
        struct Rectangle: Shape
        {
            Rectangle(double w, double h): w_(w), h_(h) {}
            double getArea() const override
            {
                return w_ * h_;
            }
            double getPerimeter() const override
            {
                return 2 * (w_ + h_);
            }
        private:
            double w_;
            double h_;
        };
    }
    using namespace Subtype;
    
    int main(int argc, char** argv) {
        std::unique_ptr<Shape> shape = std::make_unique<Circle>(2);
        // shape area: 12.5664 perimeter: 12.5664
        std::cout << "shape area: " << shape->getArea()
            << " perimeter: " << shape->getPerimeter() << std::endl;
    
        shape = std::make_unique<Rectangle>(2, 3);
        // shape area: 6 perimeter: 10
        std::cout << "shape area: " << shape->getArea()
            << " perimeter: " << shape->getPerimeter() << std::endl;
    
        return 0;
    }
    
  • ad-hoc多态例子:
    #include <variant>
    #include <cmath>
    #include <iostream>
    namespace Adhoc
    {
        struct Circle
        {
            double r;
        };
        // Circle的一系列操作
        double getArea(const Circle& c)
        {
            return M_PI * c.r * c.r;
        }
        double getPerimeter(const Circle& c)
        {
            return 2 * M_PI * c.r;
        };
    
        struct Rectangle
        {
            double w;
            double h;
        };
        // Rectangle的一系列操作
        double getArea(const Rectangle& r)
        {
            return r.w * r.h;
        }
        double getPerimeter(const Rectangle& r)
        {
            return 2 * (r.w + r.h);
        };
    
        // 通过加法类型定义一个统一的类型Shape,其拥有不同的形状,从而实现运行时多态
        using Shape = std::variant<Circle, Rectangle>;
    
        // 统一类型Shape的一系列多态行为
        double getArea(const Shape& s)
        {
            return std::visit([](const auto & data)
            {
                return getArea(data);
            }, s);
        }
        double getPerimeter(const Shape& s)
        {
            return std::visit([](const auto & data)
            {
                return getPerimeter(data);
            }, s);
        };
    }
    using namespace Adhoc;
    
    int main(int argc, char** argv) {
        Shape shape = Circle{2};
        // shape area: 12.5664 perimeter: 12.5664
        std::cout << "shape area: " << getArea(shape)
            << " perimeter: " << getPerimeter(shape) << std::endl;
    
        shape = Rectangle{2, 3};
        // shape area: 6 perimeter: 10
        std::cout << "shape area: " << getArea(shape)
            << " perimeter: " << getPerimeter(shape) << std::endl;
        return 0;
    }
    
  • image

    subtype 多态和 ad-hoc 多态的表现形式对比

    多态形式定义多态调用
    subtype 多态Abstract* objobj->method()
    ad-hoc 多态Abstract objmethod(obj)

SFINAE

  • substitution failure is not an error

  • 典型的用法是利用enable_if
    enable_if:

    template<bool, typename _Tp = void>
      struct enable_if
      { };
    
    // Partial specialization for true.
    template<typename _Tp>
      struct enable_if<true, _Tp>//第二个参数默认为void
      { typedef _Tp type; };
    

    如果是false, 那么第一个空类没有type成员, 这时直接使用其type将报错
    但在SFINAE决策上下文环境中, 这种情况可以做为一种决策条件:

    下面情况下, 编译器如果根据实参推断发现enable_if<false>没有定义成员类型type, 将导致替换失败, 将其从候选集中删除, 从而达到我们的目的

    template<class T, enable_if_t<is_integral_v<T>>* = nullptr>
    void test(T t)
    {cout << "is integral" << endl;};
    
    template<class T, enable_if_t<is_floating_point_v<T>>* = nullptr>
    void test(T t)
    {cout << "is floating_point" << endl;};
    
  • Tips: 函数重载的过程中只看函数的声明, 如果它被决策为最佳可行函数, 但模板函数体内发生了模板参数替换失败, 那么就会在实例化过程中产生编译错误, 而不是SFINAE

  • Tips: SFINAE机制在其他条件相同情况下, 总是会优先选择更明确更具体的类型版本, 这也是模板特化能起作用的原因

类型内省

  • 检查对象的类型或属性的一种能力
  • 在C++中类型萃取也可以视作内省
  • 就能实现把各种类型各种分离与组合, <type_trait> 实现了这些功能
  • 比如 C++ Template -> [5] -> 自由函数与模板可变参数 中也是利用了类型内省 类似的还有数组 template<class E,size_t N> someArr<E[N]>{…}

标签分发 (tag dispatching)

  • 除了 enable_if 之外的编译时多态手段还有标签分发, 这也是C++社区著名的管用手法;

  • 标签常常是一个空类, 没有别的什么, 只是当作一种类型. 好让编译器在SFINAE决策中把它作为参考依据, 匹配出最合适的版本

  • 辅助类 true_type 和 false_type 类型也可视作标签, 他们把 true 和 false 包装成两种类型 这样在编译时就能决策出最合适的版本

  • 示例:

    template<class T>bool numEqImpl(T l, T r, true_type)
    {
        cout << "is floating" << endl;
        return true;
    }
    template<class T>bool numEqImpl(T l, T r, false_type)
    {
        cout << "is not floating" << endl;
        return false;
    }
    template<class T> enable_if_t<is_arithmetic_v<T>, bool> numEq(T l, T r)
    {
        return numEqImpl(l, r, is_floating_point<T> {});//标签分发
    }
    

    怎么样, 是不是逐渐理解一切了?

    标准库中在<iterator>中定义了如下迭代器标签
    input_iterator_tag;
    forward_iterator_tag;
    bidirectional_iterator_tag;
    random_access_iterator_tag;
    并且提供了配套的元函数 iterator_traits, 输入迭代器,输出相关属性;
    例如类型成员::iterator_category 存储的是迭代器的种类标签,::value_type 是解引用后的类型, ::difference_type 为迭代器作差后的类型 ptrdiff_t(通常是long类型的别名)

    iterator_traits<myIterator>::iterator_category{}实际调用时这样开始分发 ,有了标签就不用浪费内存真的去构造一个实体iterator对象了(大部分标签实现为空类)
    看看advance函数 它也利用了迭代器标签来实现

    也可以尝试利用 if constexpr 来实现advance

编译时多态

除了前面多多少少讲的一些编译时多态手段, 还有下面这些

  • 奇异递归模板模式 (Curiously Recurring Template Pattern,CRTP)

    • 代码复用: 由于子类派生于模板基类, 因此可以复用基类的方法
    • 编译时多态: 由于基类是一个模板类, 能够获得传递进来的派生类, 进而可以调用派生类的方法, 达到多态的效果. 与运行时多态相比没有虚表的开销.
    • 通常是如下形式:
     template<class T> struct Base{/*...*/};
     //派生类
     struct Derived : Base<Derived>{/*...*/};
    

    看一个利用 CRTP 形成静态多态的例子:

     #include <iostream>
     using namespace std;
     
     template<typename Derived>
     struct Animal {
         void bark() { static_cast<Derived&>(*this).barkImpl(); }
     };
     
     class Cat: public Animal<Cat> {
         friend Animal;
         void barkImpl() { cout << "Miaowing!" << endl; }
     };
     
     class Dog: public Animal<Dog> {
         friend Animal;
         void barkImpl() { cout << "Bow wow!" << endl; }
     };
     
     template<typename T>
     void play(Animal<T>& animal) { animal.bark(); }
     
     int main(int argc, char** argv) {
         Cat cat; play(cat); // Miaowing!
         Dog dog; play(dog); // Bow wow!
     
         return 0;
     }
    

    从play的实现来看, 它接受一个统一的抽象接口类, 而调用的时候根据实际类型是Cat还是Dog进行多态调用, 由于类型是在编译时确定的, 所以多态调用也是在编译时绑定的. 为何不把play写成模板函数的形式? 这样无需模板基类Animal<T>也能实现静态多态.
           原因在于CRTP的模板基类可以起到接口类的作用, 它明确列出了该派生体系中所支持的一组操作, 换句话说Animal<T>其实是对它所接受的类型,所支持的方法的集合进行约束, 而普通的模板函数对模板参数没有约束, 需要使用高级技巧(如 enable_if)进行约束.
           C++演进的过程中考虑了这一点, 在 C++20 中提供 concept 特性对模板参数进行约束.

    Visitor Pattern (访问者模式)
    虽然现代C++使用 std::variant 与 std::visit 来代替访问者模式
    但先辈们想出来的这个复合了奇异递归模板模式的模式…其思想…高内聚低耦合总是现代C++所追求的…

    点击查看代码
    #include <iostream>
    #include <string>
    #include <memory>
    #include <vector>
    #include <variant>
    
    using namespace std;
    
    struct VideoFile;
    struct TextFile;
    
    struct Visitor {
        virtual void visit(VideoFile&) = 0;
        virtual void visit(TextFile&) = 0;
        virtual ~Visitor() = default;
    };
    
    struct Elem {
        virtual void accept(Visitor& visit) = 0;
        virtual ~Elem() = default;
    };
    
    template<typename T>
    struct dump;
    
    template<typename Derived>
    struct AutoDispatchElem: Elem {
        void accept(Visitor& visitor) override
        { visitor.visit(static_cast<Derived&>(*this)); }
    };
    
    struct VideoFile: AutoDispatchElem<VideoFile> { /* ... */ };
    struct TextFile: AutoDispatchElem<TextFile> { /* ... */ };
    
    int main(int argc, char** argv) {
        std::vector<std::unique_ptr<Elem>> elems;
        elems.emplace_back(new VideoFile);
        elems.emplace_back(new TextFile);
    
        struct ConcreteVisitor: Visitor {
            void visit(VideoFile&) override
            { cout << "process VideoFile ..." << endl; }
            void visit(TextFile&) override
            { cout << "process TextFile ..." << endl; }
        } visitor;
    
        for (auto&& e: elems) e->accept(visitor);
    
        return 0;
    }
    
    

三路比较操作符 (飞船操作符) <=>

  • C++20 引入了三路比较操作符, 这样只需要实现一个直观的<=>操作符,就可以直接得到六个比较操作符<,==,!=,>,<=,>=
    image

    点击查看代码
    #include <iostream>
    #include <tuple>
    #include <cassert>
    
    using namespace std;
    
    namespace crtp {
    template<typename Derived>
    struct Comparable {
        friend bool operator==(const Derived& lhs, const Derived& rhs)
        { return lhs.tie() == rhs.tie(); }
        friend bool operator!=(const Derived& lhs, const Derived& rhs)
        { return !(lhs == rhs); }
    
        friend bool operator< (const Derived& lhs, const Derived& rhs)
        { return lhs.tie() < rhs.tie(); }
        friend bool operator> (const Derived& lhs, const Derived& rhs) { return rhs < lhs; }
        friend bool operator<=(const Derived& lhs, const Derived& rhs) { return !(lhs > rhs); }
        friend bool operator>=(const Derived& lhs, const Derived& rhs) { return !(lhs < rhs); }
    };
    
    struct Point: Comparable<Point> {
        Point(int x, int y): x(x), y(y) { }
        int x; int y;
        auto tie() const { return std::tie(x, y); }
    };
    }
    
    namespace classical {
    struct Point {
        int x; int y;
        friend bool operator==(const Point& lhs, const Point& rhs)
        { return std::tie(lhs.x, lhs.y) == std::tie(rhs.x, rhs.y); }
        friend bool operator<(const Point& lhs, const Point& rhs)
        { return std::tie(lhs.x, lhs.y) < std::tie(rhs.x, rhs.y); }
    
        friend bool operator!=(const Point& lhs, const Point& rhs) { return !(lhs == rhs); }
        friend bool operator> (const Point& lhs, const Point& rhs) { return rhs < lhs;     }
        friend bool operator<=(const Point& lhs, const Point& rhs) { return !(lhs > rhs);  }
        friend bool operator>=(const Point& lhs, const Point& rhs) { return !(lhs < rhs);  }
    };
    }
    
    namespace modern {
    struct Point { // C++20
        int x; int y;
        friend auto operator<=>(const Point& lhs, const Point& rhs) = default;
    };
    }
    
    #define PointTest() assert((Point{1,2} >= Point{1,0})); \
                        assert((Point{1,2} <= Point{5,0})); \
                        assert((Point{1,2} == Point{1,2})); \
                        assert((Point{1,2} != Point{1,3})); \
                        assert((Point{1,2} <  Point{1,3})); \
                        assert((Point{3,2} >  Point{1,3}))
    
    int main(int argc, char** argv) {
        {
            using crtp::Point;
            PointTest();
        }
    
        {
            using classical::Point;
            PointTest();
        }
    
        {
            using modern::Point;
            PointTest();
        }
    
        return 0;
    }
    
    

enable_shared_from_this 模板类

  • 异步编程中存在一种场景, 需要在类中将该类的对象注册到某个回调类或函数中, 不能简单的将this传到回调类中, 很可能因为回调时该对象被释放而导致野指针访问, 也不能直接将this构造成shared_ptr, 因为无法直接通过裸指针获得引用计数信息, 强行构造也会导致对象内存被多次释放.
    struct Obj {
        void submit() {
            executor.submit([this] { this->onComplete(); });//内存风险
        }
        void onComplete() {/*...*/}
    };
    
    那么如果获得裸指针(this) 的引用计数信息呢? 标准库提供了enable_shared_from_this 模板基类 通过CRTP在父类中存储子类的指针信息与引用计数信息, 并提供 shared_from_this 和 weak_from_this 以获得智能指针.
    struct Obj : enable_shared_from_this<Obj> {
        void submit() {
            executor.submit([obj = weak_from_this()] {
                if (auto spObj = obj.lock()) spObj->onComplete();
            });
        }
        void onComplete() {/*...*/}
    };
    
    为在类内处理它本身引用计数逻辑提供了便利

概念约束

  • C++20 起对 concept 特性进行了标准化, 主流的编译器也提供了支持
    将一类数据类型和对它的一组操作所满足的公理集称为 concept: 不仅需要从语法上满足要求, 还需要从语义层面上满足.
  • concept 拥有强大的表达力, 程序员能够非常简单的定义一个概念, 也可以对概念库中已有的概念进行组合. 概念支持重载, 能够消除对变通方案(enable_if等技巧)的依赖, 大大降低了元编程的难度, 也简化了泛型编程.

定义概念

  • 是一个对类型约束的编译期谓词, 给定一个类型判断其能否满足语法和语义要求, 这对泛型编程而言极为重要.比如, 给定模板参数T, 对它的要求如下:

    1. 一种迭代器类型 Iterator<T>
    2. 一种数字类型 Number<T>
      Iterator 和 Number 就是概念
  • 基本定义语法:

    template<被约束的模板参数列表>
    concept 概念名 = 约束表达式;

    概念和模板 using 的别名很类似, 前者是对布尔表达式的别名, 而后者是对模板类型的别名.
    约束表达式 可以简单理解为 布尔常量表达式
    如果在定义概念时约束表达式类型不为 bool 类型, 将引发一个编译错误, 而不是返回 false

    //Atomic constraint must be of type 'bool' (found 'int')
    template <typename T> concept Foo=1;
    

    >.GCC编译器定义concept时不会报错, 在求值的时候才进行类型检查;

  • 在概念标准库<concepts>中可以看到:

    template<typename T>
    concept integral = is_integral_v<T>;
    template<typename T>
    concept floating_point = is_floating_point_v<T>;
    

    简单使用展示:

    static_assert(floating_point<float>);
    static_assert(! floating_point<int>);
    
  • 约束表达式中使用 && 与 ||
    合取(conjunction)与析取(disjunction)操作与逻辑表达式中的与或运算类似, 也是一个短路操作符.

    template <typename T>
    concept Foo = is_integral_v<typename T::type>||sizeof(T)>1;
    static_assert(Foo<double>);//ok,因为 sizeof(T)>1 满足(true)
    
  • 原子约束
    在依次对每个条件检查时, 首先检查表达式是否合法, 若不合法则该约束不满足,返回false 否则进一步对约束进行求值判断是否满足.
    对可变模板参数形成的约束表达式, 既不是约束合取也不是约束析取

    //要求每个模板参数都含有类型成员type, 否则整个表达式为false,
    //因为原子约束先检查整个表达式是否合法, 如果有一个不合法, 那么整个表达式就为 false 
    //在整个表达式全部合法的前提下, 才有意义谈|| && 才进入求值判断阶段
    // || 表示有一个参数符合就返回true,&& 表示所有参数符合才返回true
    template <typename... Ts>
    concept Foo1 = (is_integral_v<typename Ts::type>||...);
    

    如果你仔细看看这个表达式结构…oh my gosh! 这可不就是个折叠表达式么? C++ Template >> [ 5 ].模板的可变参数->Variadic Expressions

    非可变模板参数如何形成原子约束?

    template <typename T>
    concept Foo = bool(is_integral_v<typename T::type>||sizeof(T)>1);//原子约束
    
  • 中间层
    上面有关可变模板参数的 concept 想要表达 “至少一个模板参数存在类型成员 type 且类型成员为整型” 怎么办? 很简单, 创建一个约束中间层就行了
    concept 的返回值总可能合法

    template <typename T>//中间层
    concept Midal = is_integral_v<typename T::type>;
    template <typename... Ts>
    concept Foo1 = (Midal<Ts> ||...);
    
  • negation (逻辑否定)

    template<typename T>concept C1 = is_integral_v<typename T::type>;
    template<typename T>concept C2 =! is_integral_v<typename T::type>;
    //T存在type且为整数类型时 C3 为 false, 反之只要C1中有一条不满足则C1为false, C3这时就为true
    template<typename T>concept C3 =! C1<T>;
    
    struct Foo{using type = float ;};
    //static_assert(C1<int>);//C1 表达: 要求T存在关联类型type,且 关联类型为整数类型
    //static_assert(C1<Foo>);
    static_assert(C2<Foo>);//C2 表达: 要求T存在关联类型type,且 关联类型不为整数类型
    static_assert(C3<Foo>);//C3 表达: 要求T不存在关联类型type,或 关联类型不为整数类型
    static_assert(C3<int>);//其实是把C1当作一个整体来看,只看结果
    

    注意两者语义的细微差别

requires 表达式

模板元编程

constexpr 元编程

Ranges 标准库

协程

软件设计六大原则 SOLID

  • Single responsibility principle
  • Open Closed Principle
  • Liskov Substitution Principle (LSP)
  • Interface Segregation Principle
  • Dependence Inversion Principle
  • Law of Demeter
  • https://baike.baidu.com/starmap/view?nodeId=294b5d3c8cc7452ad5c9cdba&lemmaTitle=开闭原则&lemmaId=2828775&starMapFrom=lemma_starMap&fromModule=lemma_starMap

To be continue…

https://netcan.github.io/

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯2024/1/28----十二届省赛题笔记

题目要求&#xff1a; 2、 竞赛板配置要求 2.1将 IAP15F2K61S2 单片机内部振荡器频率设定为 12MHz。 2.2键盘工作模式跳线 J5 配置为 KBD 键盘模式。 2.3扩展方式跳线 J13 配置为 IO 模式。 2.4 请注意 &#xff1a; 选手需严格按照以上要求配置竞赛板&#xff0c;编写和调…

C语言基础13

今天是学习嵌入式相关内容的第十四天&#xff0c;以下是今日所学内容 1.结构体: 1.结构体类型定义 2.结构体变量的定义 3.结构体元素的访问 4.结构体的存储 内存对齐 结构体整体的大小必须为最大基本类型长度的整数倍 5.结构体作为函数参数 值传递 练习:定…

数据中心IP代理是什么?有何优缺点?海外代理IP全解

海外代理IP中&#xff0c;数据中心代理IP是很热门的选择。这些代理服务器为用户分配不属于 ISP&#xff08;互联网服务提供商&#xff09;且来自第三方云服务提供商的 IP 地址&#xff0c;是分配给位于数据中心的服务器的 IP 地址&#xff0c;通常由托管和云公司拥有。 这些 I…

使用Huggingface镜像站hf-mirror.com下载资源

前言 在使用Huggingface的过程中&#xff0c;有时我们可能会遇到无法访问官方网站huggingface.co的情况&#xff0c;这可能是由于网络监管或者网络连接问题所致。然而&#xff0c;幸运的是&#xff0c;我们可以通过hf-mirror.com这个Huggingface镜像站来解决这个问题。本篇博客…

shell脚本之多行重定向 免交互 expect ssh scp; 字符处理

多行重定向 使用I/O重定向的方式将命令列表提供给交互式程序 标准输入的一种替代品 Here Document 是标准输 入的一种替代品&#xff0c;可以帮助脚本开发人员不必使用临时文件来构建输入信息&#xff0c;而是直接就地 生产出一个文件并用作命令的标准输入,Here Document 可…

TypeScript(十) Map对象、元组、联合类型、接口

1. Map对象 1.1. 简述 Map对象保存键值对&#xff0c;并且能够记住键的原始插入顺序。   任何值都可以作为一个键或一个值。 1.2. 创建 Map 使用Map类型和new 关键字来创建Map&#xff1a; 如&#xff1a; let myMap new Map([["key1", "value1"],[&…

Prometheus---图形化界面grafana(二进制)

前言 Prometheus是一个开源的监控以及报警系统。整合zabbix的功能&#xff0c;系统&#xff0c;网络&#xff0c;设备。 proetheus可以兼容网络&#xff0c;设备。容器的监控。告警系统。因为他和k8s是一个项目基金开发的产品&#xff0c;天生匹配k8s的原生系统。容器化和云原…

iOS App审核状态和审核时间管理指

引言 对于一款开发完成并准备上架的 iOS 应用程序来说&#xff0c;通过苹果公司的审核是非常重要的一步。苹果公司会对应用程序进行严格的检查&#xff0c;以确保应用程序的质量和安全性。本文将介绍 iOS 应用程序审核的流程和时间&#xff0c;希望能够帮助开发者更好地了解和…

《Is dataset condensation a silver bullet for healthcare data sharing?》

一篇数据浓缩在医疗数据集应用中的论文。 其实就是在医疗数据集上使用了data condensation的方法&#xff0c;这里使用了DM的方式&#xff0c;并且新增了浓缩时候使用不同的网络。 1. 方法 数据浓缩DC的目的是&#xff1a; E x ∼ P D [ L ( φ θ O ( x ) , y ) ] ≃ E x ∼…

CPU-Cache结构查看

参考【Ubuntu 查看 CPU 缓存】 本文主要介绍cpu的cache查看&#xff0c;以供读者能够理解该技术的定义、原理、应用。 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;计算机杂记 &#x1f380;CSDN主页 发狂的小花…

【昕宝爸爸小模块】深入浅出详解之常见的语法糖

深入浅出详解之常见的语法糖 一、&#x1f7e2;关于语法糖的典型解析二、&#x1f7e2;如何解语法糖&#xff1f;2.1&#x1f7e2;糖块一、switch 支持 String 与枚举2.2&#x1f4d9;糖块二、泛型2.3&#x1f4dd;糖块三、自动装箱与拆箱2.4&#x1f341;糖块四、方法变长参数…

什么是图形组态软件?可视化组态工具的特点

组态软件的定义 组态软件主要作为SCADA系统及其他控制系统的上位机人机界面的开发平台&#xff0c;为用户提供快速地构建工业自动化系统数据采集和实时监控功能服务。它使用灵活的组态方式&#xff0c;提供快速构建工业自动控制系统监控功能的通用层次的软件工具。 组态软件的…

【学网攻】 第(17)节 -- 命名ACL访问控制列表

系列文章目录 目录 前言 一、ACL(访问控制列表)是什么&#xff1f; 二、实验 1.引入 总结 文章目录 【学网攻】 第(1)节 -- 认识网络【学网攻】 第(2)节 -- 交换机认识及使用【学网攻】 第(3)节 -- 交换机配置聚合端口【学网攻】 第(4)节 -- 交换机划分Vlan【学网攻】 第…

对于this.$nextTick代码的理解

我们都知道DOM的更新是异步的,Vue的绑定原理就是用数据区驱动视图,视图也能驱动数据&#xff0c;两者是双向绑定的。 如何立马获取到更新之后的DOM呢&#xff1f; 可以使用: <template><div class"" ref"aa">{{ a }}<button click"f…

TortoiseSVN各版本汉化包下载

首先进入下载版本列表 1.下载地址&#xff1a;https://sourceforge.net/projects/tortoisesvn/files ​ 2.选择自己版本进入​ 3.选择Language Packs进入&#xff0c;选择对应语言包下载。 ​ 4.在TortoiseSVN根目录下点击安装即可。 ​

Leetcode—1265. 逆序打印不可变链表【中等】Plus

2024每日刷题&#xff08;一零三&#xff09; Leetcode—1265. 逆序打印不可变链表 实现代码 /*** // This is the ImmutableListNodes API interface.* // You should not implement it, or speculate about its implementation.* class ImmutableListNode {* public:* v…

Django模型(六)

一、其它查询 文档:https://docs.djangoproject.com/zh-hans/4.1/ref/models/querysets/#count 1.1、排序 Queryset.order_by(*fields) 默认情况下,QuerySet 返回的结果是按照模型 Meta 中的 ordering 选项给出的排序元组排序的 可以通过使用 order_by 方法在每个 QueryS…

《QDebug 2024年1月》

一、Qt Widgets 问题交流 1. 二、Qt Quick 问题交流 1.Repeator 的 delegate 在 remove 移除时的注意事项 Qt Bug Tracker&#xff1a;https://bugreports.qt.io/browse/QTBUG-47500 Repeator 在调用 remove 函数之后&#xff0c;对应的 Item 会立即释放&#xff0c;后续就…

百川终入海 ,一站式海量数据迁移工具 X2Doris 正式发布

在大数据分析领域&#xff0c;Apache Doris 作为广受认可的开源实时数据仓库&#xff0c;已经在越来越多行业用户的真实业务场景中得到广泛应用&#xff0c;成为许多企业数据分析基础设施的重要基座。尤其在过去一年多的时间里&#xff0c;越来越多企业选择基于 Apache Doris 进…

【Linux驱动】块设备驱动(一)—— 注册块设备

针对块设备驱动将分为两部分介绍&#xff0c;第一部分是注册块设备&#xff0c;即将块设备成功添加到内核&#xff1b;第二部分是介绍如何读写块设备&#xff0c;因为没有实际块设备&#xff0c;这里选择使用内存来模拟块设备。 一、认识块设备 1、什么是块设备 块设备针对的…
最新文章