C++基础与深度解析 | 模板 | 函数模板 | 类模板与成员函数模板 | concepts | 完美转发 | 模板的其他内容

news2024/11/16 15:34:45

文章目录

  • 一、函数模板
  • 二、类模板与成员函数模板
  • 三、Concepts(C++20)
  • 四、模板相关内容
    • 1.数值模板参数与模板模板参数
    • 2.别名模板与变长模板
    • 3.包展开与折叠表达式
    • 4.完美转发与lambda表达式模板
    • 5.消除歧义与变量模板

一、函数模板

  在C++中,函数模板是一种允许你编写可以处理多种数据类型的函数的方式。函数模板通过使用模板参数来实现泛型编程,这样同一个函数就可以用不同的数据类型来调用

函数模板不是函数。

使用 template 关键字引入模板

  使用 template<typename T> 或者 template<class T> 来定义一个函数模板。typenameclass 在这里可以互换使用,但通常 typename 更常用于模板参数。

template<typename T> 
void fun(T) 
{
    //...
}

函数模板的声明与定义

  在一个翻译单元中,函数声明可以包含多次,但函数定义只能包含一次。函数模板的声明和定义通常写在一起。例如:

template<typename T>
void functionTemplate(T param) {
    // 函数实现
}

这个函数模板 functionTemplate 可以接受任何类型的参数 param

函数模板参数

  函数模板中包含了两对参数:函数形参 / 实参;模板形参 / 实参。

  • 函数形参:函数定义中的参数,如上面例子中的 param
  • 函数实参:调用函数时传递给函数的具体参数值。
  • 模板形参:在模板定义中使用的类型或值的占位符,如 T
  • 模板实参:在调用模板时,提供给模板形参的具体类型或值

函数模板的显式实例化

  在C++中,函数模板的显式实例化是一种告诉编译器创建一个特定函数模板实例的操作。

  • 显式实例化的语法

    显式实例化使用模板函数名后跟尖括号内指定的类型参数来完成。例如,fun<int>(3) 告诉编译器实例化模板函数 fun 并使用 int 作为模板参数,然后调用这个实例化函数并传递整数 3 作为参数。

  • 实例化会使得编译器产生相应的函数(函数模板并非函数,不能调用)

    使用C++ Insights可知:

    image-20240606113537606

  • 编译期的两阶段处理(函数模板的实例化发生在编译期)

    • 模板语法检查

      编译器首先检查模板代码的语法是否正确

    • 模板实例化

      编译器根据提供的模板参数来生成具体的函数代码。

  • 模板必须在实例化时可见–翻译单元的一处定义原则

    模板的定义只在一个翻译单元中出现一次,以避免链接错误。

  • 与内联函数的异同

    虽然函数模板与内联函数都满足翻译单元级别的一次定义原则而非程序级别的一次定义原则,但原因是不同的。

    • 共同点:两者都可以在编译时进行优化,并且都可以在头文件中定义。
    • 不同点:内联函数不涉及类型参数,而函数模板是类型安全的泛型函数。

函数模板的重载

  在C++中,函数模板的重载指的是可以定义多个具有相同名称但模板参数不同的函数模板。当编译器尝试确定哪个函数模板实例与给定的调用匹配时,它会根据传递给函数的实参类型来解析重载。如果存在多个匹配的模板实例,编译器将选择最匹配的一个。

示例:

#include <iostream>

template<typename T>
void fun(T input)
{
	std::cout << input << std::endl;
}

template<typename T>
void fun(T* input)
{
	std::cout << *input << std::endl;
}

template<typename T, typename T2>
void fun(T input, T2 input2)
{
	std::cout << input << std::endl;
  	std::cout << input2 << std::endl;
}

int main()
{
  	double x = 3.14;
	fun<int>(3);
  	fun<double>(&x);
}

编译后的结果为

#include <iostream>

template<typename T>
void fun(T input)
{
  (std::cout << input) << std::endl;
}

/* First instantiated from: insights.cpp:25 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void fun<int>(int input)
{
  std::cout.operator<<(input).operator<<(std::endl);
}
#endif


template<typename T>
void fun(T * input)
{
  (std::cout << *input) << std::endl;
}

/* First instantiated from: insights.cpp:26 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void fun<double>(double * input)
{
  std::cout.operator<<(*input).operator<<(std::endl);
}
#endif


template<typename T, typename T2>
void fun(T input, T2 input2)
{
  (std::cout << input) << std::endl;
  (std::cout << input2) << std::endl;
}

int main()
{
  double x = 3.1400000000000001;
  fun<int>(3);
  fun<double>(&x);
  return 0;
}

模板实参的类型推导:(隐式实例化)

  在C++中,模板实参的类型推导是一个自动确定模板参数类型的过程,推导是基于函数实参(表达式)确定模板实参的过程。如果函数模板在实例化时没有显式指定模板实参,那么系统会尝试进行推导。

模板实参类型推导的基本原则:(与auto类型推导相似)

  • 当函数形参是左值引用或指针

    忽略实参表达式的引用部分,并尝试匹配表达式的类型与形参类型来确定模板实参。

    示例:

    template<typename T>
    void func(T& param) {
        // ...
    }
    int main() 
    {
        const int a = 5;
    	func(a); // T& 推导为 const int
    }
    

    image-20240606133737821

  • 当函数形参是万能引用(使用 T&& 声明)时

    模板实参的类型推导将根据实参表达式的值类别(左值或右值)来确定:

    • 如果实参是一个右值,模板实参将被推导为被推导为去掉引用的基本类型
    • 如果实参是一个左值,模板实参将被推导为左值引用类型,这将触发引用折叠规则。

    示例:

    template<typename T>
    void func(T&& param) {
        // ...
    }
    int main() 
    {
        const int a = 5;
    	func(a); // T 推导为 const int&,引用折叠指的是const int& && ==> const int&
      	func(10.1); //T 推导为double
    }
    

    image-20240606134324822

  • 当函数形参不包含引用

    模板实参的类型推导将忽略实参表达式的引用部分和顶层 const,并且:

    • 数组和函数类型将转换成相应的指针类型。
    • 其他类型将直接推导为该类型。

    示例:

    template<typename T>
    void func(T param) {
        // ...
    }
    
    void someFunction()
    {
    }
    
    int main()
    {
      	int x = 2;
      	const int& y = x;
      	func(x);	//T推导为 int,忽略引用与顶层const
      	const int* const ptr = &x;
      	func(ptr);	//T推导为const int*,忽略顶层const
        int arr[] = {1, 2, 3};
        func(arr); // T 推导为 int*,因为 arr 是数组类型
    
        void (*funcPtr)() = someFunction;
        func(funcPtr); // T 推导为 void (*)(),因为 funcPtr 是函数指针
    }
    
    

    image-20240606140314544

模板实参并非总是能够推导得到

  • 如果模板形参与函数形参的类型无关,则编译器可能无法从函数实参推断出模板实参的类型。

    例如:

    template<typename T, typename U>
    U func(T param) {
        // ...
    }
    
  • 即使相关,也不一定能进行推导,

  • 推导成功也可能存在因歧义而无法使用

    例如:

    template<typename T>
    void func(T param1, T param2) {
        // ...
    }
    
    int main()
    {
        func(3, 5.0);
    }
    

在无法推导时,编译器会选择使用缺省模板实参,可以为任意位置的模板形参指定缺省模板实参。

注意与函数缺省实参的区别,不需要保证缺省实参右边全部为缺省实参

例如:

template<typename T, typename U = int>
U func(T param) {
    // ...
}

int main()
{
    func(3);
}

显式指定部分模板实参

  即在调用模板函数时明确指定其中一些模板参数,而让编译器自动推导剩余的参数。

  • 显式指定的模板实参必须从最左边开始,依次指定

    一旦你开始显式指定模板实参,编译器将自动推导剩余未指定的模板参数。如果推导失败,将导致编译错误。

  • 模板形参的声明顺序会影响调用的灵活性

    例如:

    //这种模板形参的声明顺序会编译错误
    template<typename T, typename U>
    U func(T param) {
        // ...
    }
    
    template<typename U, typename T>
    U func(T param) {
        // ...
    }
    
    int main()
    {
        func<int>(3);
    }
    

    image-20240606143454338

函数模板自动推导时会遇到几种情况

  • 函数形参无法匹配—— SFINAE (替换失败并非错误)

    当模板参数推导失败时,如果是因为类型不匹配导致的,编译器会认为这不是一个错误,而是简单地排除这个模板实例化选项。

  • 模板与非模板同时匹配,匹配等级相同,此时选择非模板的版本

    如果模板函数和非模板函数都可以匹配同一个调用,并且它们的匹配等级相同,那么编译器将选择非模板函数。这是因为在C++中,非模板函数的优先级高于模板函数:

    例如:

    void func(int i) {
        // ...
    }
    
    template<typename T>
    void func(T t) {
        // ...
    }
    
    int main()
    {
        func(5); // 调用非模板 func(int i),因为匹配等级相同
    }
    
  • 多个模板同时匹配,此时采用偏序关系确定选择”最特殊“的版本

    如果有多个模板实例都可以匹配同一个调用,C++的重载解析规则将根据偏序关系来确定“最特殊”的版本。这通常涉及到模板参数的特化程度,更具体的模板实例会被优先选择:

    例如:

    template<typename T>
    void func(T t) {
        // ...
    }
    
    template<>
    void func<int>(int i) {
        // ...
    }
    
    int main()
    {
       func(5); // 调用模板特化 func<int>(int i),因为它更具体 
    }
    

函数模板的实例化控制:显式实例化但不调用

  • 显式实例化定义

    显式实例化定义是告诉编译器为特定的模板参数生成一个实例。这通常在模板定义的实现文件中完成。

    template 
    void fun<int>(int);
    或者
    template 
    void fun(int);
    

    image-20240606151909033

  • 显式实例化声明

    显式实例化声明用于告诉编译器存在一个显式实例化的定义。

    extern template 
    void fun<int>(int);
    或者
    extern template 
    void fun(int);
    

    如果引入了显式实例化声明,就不会产生模板实例,减轻了编译器的负担,在链接过程中也不需要将相同的实例删除掉,提升编译与链接速度。

  • 注意一处定义原则(程序级别)

    C++要求每个模板显式实例化在程序中只能有一个定义。这意味着显式实例化定义只能出现在一个编译单元(通常是一个.cpp文件)中。违反这一原则会导致链接错误。

  • 注意实例化过程(显式实例化定义)中的模板形参推导

函数模板的(完全)特化

  C++中的函数模板特化是为特定类型提供特定实现的一种方式。与函数重载不同,特化是为已经存在的模板函数提供针对特定类型的特定实现。本质上函数模板特化就是函数模板实例化。

完全特化语法:

// 模板定义
template<typename T>
void f(T t) {
    // 通用实现
}

// 函数模板的完全特化
template<>
void f<int>(int t) {
    // int 类型的具体实现
}
  • 并不引入新的(同名)名称,只是为某个模板针对特定模板实参提供优化算法

    特化不会创建新的函数名称。它只是为模板函数提供了一个针对特定参数的定制版本。这意味着特化和非特化版本在函数名上是相同的,只是参数类型不同。

  • 注意与重载的区别

    • 重载:涉及创建多个具有相同名称但参数类型或数量不同的函数。
    • 特化:为模板函数提供针对特定类型的定制实现,不增加新的函数名称。
  • 注意特化过程中的模板形参推导

    特化可以影响模板形参的推导过程。

避免使用函数模板的特化

  • 不参与重载解析,会产生反直觉的效果(重载解析是在函数模板特化之前完成的)

    函数模板特化不参与普通的重载解析过程。这意味着即使存在一个更匹配的非特化版本,编译器也可能选择特化版本,因为特化版本在重载候选中具有更高的优先级。这可能导致一些反直觉的结果。

  • 通常可以用重载代替(函数重载会参与重载解析)

    优先使用函数重载而不是模板特化。函数重载遵循标准的重载解析规则,这使得代码的行为更加可预测和直观。

  • 一些不便于重载的情况:无法建立模板形参与函数形参的关联,可以考虑一下替代方案

    • 使用if constexpr解决

      if constexpr 是C++17引入的一个特性,它允许在编译时根据模板参数的值选择执行不同的代码路径。这可以用来模拟重载的效果

      #include <type_traits>
      
      template<typename T>
      void func(T t) {
          if constexpr (std::is_same_v<T, int>) {
              // 针对 int 类型的代码
          } else {
              // 通用代码
          }
      }
      
    • 引入“假”函数形参

    • 通过类模板特化解决

      使用类模板特化,然后在类中定义需要重载的函数。这种方法可以将重载的复杂性封装在类内部:

      template<typename T>
      struct MyClass {
          void func(T t) {
              // 通用实现
          }
      };
      
      template<>
      struct MyClass<int> {
          void func(int t) {
              // 针对 int 类型的实现
          }
      };
      

函数模板的简化形式(C++20):使用auto定义模板参数类型

  在C++20中,引入了使用auto来定义函数模板参数类型的简化形式。

  • 优势:书写简捷

  • 劣势:在函数内部需要间接获取参数类型信息

image-20240606161910859

二、类模板与成员函数模板

  在C++中,类模板是一种泛型编程工具,允许你创建可以处理多种数据类型的类。

类模板不是类

  • 使用template关键字引入类模板

      使用 template<typename T>template<class T> 来声明一个类模板。typenameclass 在这里可以互换使用,但 typename 更常用于模板参数。

    template<typename T>
    class B {
        // 包括成员变量、成员函数的实现
    };
    
  • 类模板的声明与定义:翻译单元级别的一处定义原则

      类模板的声明和定义通常写在一起。类模板的定义包括成员变量、成员函数的实现等。

  • 成员函数只有在调用时才会被实例化

      类模板的成员函数只有在被调用时才会被实例化。这意味着编译器会根据成员函数调用时提供的实参来生成具体的函数实现。

    #include <iostream>
    
    template<typename T>
    class B {
    public:
        void fun(T input)
        {
            std::cout << input << std::endl;
        }
    };
    
    int main()
    {
        B<int> x;
        x.fun(3);
    }
    

    经编译器实例化后如下:

    #include <iostream>
    
    template<typename T>
    class B
    {
      
      public: 
      inline void fun(T input)
      {
        (std::cout << input) << std::endl;
      }
      
    };
    
    /* First instantiated from: insights.cpp:14 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    class B<int>
    {
      
      public: 
      inline void fun(int input)
      {
        std::cout.operator<<(input).operator<<(std::endl);
      }
      
      // inline constexpr B() noexcept = default;
    };
    
    #endif
    
    int main()
    {
      B<int> x;
      x.fun(3);
      return 0;
    }
    

    类模板中的成员函数本质上是内联函数

  • 类内类模板名称的简写

    在类模板的成员函数中,可将类模板名称进行简写

    image-20240606171534460

  • 类模板成员函数的定义(类内、类外)

    类模板的成员函数可以在类内定义(内联定义)或类外定义。

    • 类内定义

      成员函数在类模板内部定义时,不需要再次使用 template 关键字,编译器能够从上下文中推断出模板参数。

      template<typename T>
      class B {
      public:
          void memberFunc() {
              // 实现
          }
      };
      
    • 类外定义

      当成员函数在类外部定义时,需要使用模板关键字,并显式指定模板参数。

      template<typename T>
      class B {
      public:
          void memberFunc();
      };
      
      template<typename T>
      void B<T>::memberFunc() {
          // 实现
      }
      

成员函数模板

  • 类的成员函数模板

    位于类内部的函数模板,同上也可分为类内定义与类外定义

    class B {
    public:
        template<typename T>
        void func(T input);
    };
    
    template<typename T>
    void B::func(T input)
    {
        
    }
    
    int main()
    {
        B x;
        x.func<int>(3);
    }
    
  • 类模板的成员函数模板

    类模板可以包含成员模板函数,这些函数在类内或类外定义

    template<typename T>
    class B {
    public:
        template<typename T2>
        void func(T2 input);
    };
    
    template<typename T>
    template<typename T2>
    void B<T>::func(T2 input)
    {
        
    }
    
    int main()
    {
        B<int> x;
        x.func<int>(3);
    }
    

友元函数模板:(很少使用)

  • 可以声明一个函数模板为某个类(模板)的友元
  • C++11 支持声明模板参数为友元
template<typename T>
class B {
public:
    template<typename T2>
    friend void func(T2 input);
  
private:
  	int x;
};

template<typename T2>
void func(T2 input)
{
    B<int> tmp1;
  	tmp1.x;
  	
  	B<char> tmp2;
  	tmp2.x;
}

int main()
{
    func<float>(3);
}

类模板的实例化

C++中的类模板实例化与函数模板实例化在概念上是相似的,都涉及到根据提供的模板实参生成具体的类型或函数。

详细内容可参考:https://en.cppreference.com/w/cpp/language/class_template

  • 实例化过程

    类模板的实例化是通过替换模板参数来创建一个具体类的版本。这个过程可以是隐式的,也可以是显式的。

  • 隐式实例化

    当你创建一个类模板的对象或调用其成员函数时,如果模板参数没有明确指定,编译器会自动推导这些参数,从而实例化类或成员函数。

    template<typename T>
    class Box {
        T item;
    public:
        Box(T t) : item(t) {}
        T getItem() const { return item; }
    };
    
    int main() {
        Box<int> myBox(10); // 隐式实例化 Box<int>
        return 0;
    }
    
  • 显式实例化

    显式地要求编译器实例化类模板的特定版本。这通常在类模板的定义完成后进行

    template class Box<int>; // 显式实例化 Box<int>
    
  • 可以实例化整个类模板或者类模板中的某个成员函数

    • 实例化整个类模板,即为该类创建一个具体的类型版本

      Box<double> anotherBox(5.5); // 实例化 Box<double> 的对象
      
    • 实例化类模板中的某个成员函数,特别是当成员函数是模板时

      template<typename T>
      class MyClass {
      public:
          template<typename U>
          U templatedMethod(U u) {
              return u;
          }
      };
      int main()
      {
       	// 实例化 MyClass<int> 的 templatedMethod<double>
          MyClass<int> myObject;
          double result = myObject.templatedMethod<double>(3.14);   
      }
      

类模板的(完全)特化 / 部分特化

  • 完全特化

    完全特化是指为类模板的特定类型参数提供完全定制的类定义。特化版本与基础版本可以完全不同,不继承或包含基础模板的任何成员。

    // 基础模板类定义
    template<typename T>
    class MyClass {
    public:
        void func() { /* ... */ }
    };
    
    // 完全特化版本
    template<>
    class MyClass<int> {
    public:
        void func() { /* 完全不同的实现 */ }
    };
    

    MyClass<int>MyClass 的一个完全特化版本,它具有与基础模板完全不同的实现。

  • 部分特化

    部分特化或偏特化是指当类模板接受多个类型参数时,可以为其中一些参数提供特化,而其他参数保持通用。

    // 基础模板类定义,接受两个类型参数
    template<typename T1, typename T2>
    class MyClass {
    public:
        void func() { /* ... */ }
    };
    
    // 部分特化版本,T2 特化为 int
    template<typename T1>
    class MyClass<T1, int> {
    public:
        void func() { /* 针对 T1 的任意类型和 T2 为 int 的特化实现 */ }
    };
    

    MyClass<T1, int> 是一个部分特化版本,它只针对 T2int 的情况提供了定制的实现,而 T1 可以是任何类型。

类模板的实参推导:(从C++17开始)

  • 基于构造函数的实参推导

    C++17允许编译器根据构造函数的参数来推导类模板的模板参数。如果构造函数的参数能够明确地推导出模板参数的类型,编译器将自动实例化类模板。、

    template<typename T>
    class Wrapper {
    public:
        T value;
        Wrapper(T v) : value(v) {}
    };
    
    int main() {
        Wrapper w(42); // C++17 允许从 42 推导 T 为 int
    }
    

    经编译器翻译后如下:

    template<typename T>
    class Wrapper
    {
      public: 
      T value;
      inline Wrapper(T v)
      : value(v)
      {
      }
    };
    
    /* First instantiated from: insights.cpp:9 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    class Wrapper<int>
    {
      public: 
      int value;
      inline Wrapper(int v)
      : value{v}
      {
      }
    };
    
    #endif
    
    int main()
    {
      Wrapper<int> w = Wrapper<int>(42);
      return 0;
    }
    
    template<typename T>
    Wrapper(T v) -> Wrapper<T>;
    
    /* First instantiated from: insights.cpp:9 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    Wrapper(int v) -> Wrapper<int>;
    #endif
    
  • 用户自定义的推导指引

    C++17还引入了自定义的推导指引(Deduction Guide),允许开发者提供构造函数的重载来帮助编译器进行类型推导。推导指引需要在类模板的作用域外部定义,以便编译器能够在整个程序中找到并使用它们。

    template<typename T>
    class Wrapper {
    public:
        T value;
        Wrapper(T v) : value(v) {} // 普通构造函数
    };
    
    // 自定义推导指引
    template<typename T>
    Wrapper(T) -> Wrapper<T>;
    
  • 注意:引入实参推导并不意味着降低了类型限制

    即使类模板的参数可以从构造函数参数中推导出来,这并不意味着模板参数可以是任何类型。模板参数仍然需要满足类模板中定义的任何类型约束或要求。

  • C++ 17 之前的解决方案:引入辅助模板函数

    template<typename T>
    class Wrapper {
    public:
        T value;
        Wrapper(T v) : value(v) {} // 普通构造函数
    };
    
    // 辅助模板函数
    template<typename T>
    Wrapper<T> make_Wrapper(T v) {
        return Wrapper<T>(v);
    }
    
    int main() {
        auto w = make_Wrapper(42); // 使用辅助函数创建 Wrapper 实例
    }
    

    C++中有很多类似的函数,如:make_pair

  • 类模板实参推导的限制

    • 类模板实参推导只适用于构造函数。
    • 如果构造函数重载,编译器需要能够从上下文推导出应该使用哪一个构造函数。
    • 如果存在多个可能的模板实例化,编译器将尝试找到最佳匹配。

三、Concepts(C++20)

详细内容可参考:https://en.cppreference.com/w/cpp/language/constraints

  C++模板的问题:没有对模板参数引入相应的限制,会造成如下两个问题:

  • 参数是否可以正常工作,通常需要阅读代码进行理解

  • 编译报错友好性较差(vector<int&>)

    C++编译器在模板实例化失败时生成的错误信息通常很长且难以理解。这是因为模板在编译时展开,如果模板参数不符合要求,编译器需要报告所有相关的错误。例如, vector<int&> 是一个错误用法,因为 vector 需要其元素类型是可复制的,而引用类型 int& 不满足这一要求。这将导致编译错误,但错误信息可能不会直接指出问题所在。

  C++20 引入了 Concepts(概念),这是一种新的类型系统特性,用于在编译期对模板参数进行更严格的约束。Concepts 允许开发者定义模板参数必须满足的条件,这些条件被称为编译期谓词,它们返回 truefalse 来表明模板参数是否符合预期

基本概念

  • concept:编译期谓词,它定义了一组类型必须满足的要求

    #include <iostream>
    #include <type_traits>
    
    template <typename T>
    concept IsAvail = std::is_same_v<T, int> || std::is_same_v<T, float>;
    
    int main()
    {
    	return IsAvail<int>;
    }
    
  • constraints(约束):concept与 constraints ( require 从句)一起使用限制模板参数。通常置于表示模板形参的尖括号后面进行限制。

    #include <iostream>
    #include <type_traits>
    
    template <typename T>
    concept IsAvail = std::is_same_v<T, int> || std::is_same_v<T, float>;
    
    template <typename T>
      requires IsAvail<T>
    void fun(T input)
    {
    
    }
    
    int main()
    {
    	fun(3);
    }
    

concept 的定义与使用

  • 包含一个模板参数的 concept

    • 使用 requires 从句

      #include <iostream>
      #include <type_traits>
      
      template <typename T>
      concept IsAvail = std::is_same_v<T, int> || std::is_same_v<T, float>;
      
      template <typename T>
        requires IsAvail<T>
      void fun(T input)
      {
      
      }
      
      int main()
      {
      	fun(3);
      }
      
    • 直接替换 typename

      #include <iostream>
      #include <type_traits>
      
      template <typename T>
      concept IsAvail = std::is_same_v<T, int> || std::is_same_v<T, float>;
      
      template <IsAvail T>
      void fun(T input)
      {
      
      }
      
      int main()
      {
      	fun(3);
      }
      
  • 包含多个模板参数的 concept

    #include <iostream>
    #include <type_traits>
    
    template <typename T, typename T2>
    concept IsAvail = !std::is_same_v<T, T2>;
    
    template <typename T, typename T2>
    	requires IsAvail<T, T2>
    void fun(T input, T2 input2)
    {
    
    }
    
    int main()
    {
    	fun(3, 3.14);
    }
    

    用做类型 constraint 时,少传递一个参数,推导出的类型将作为首个参数

    #include <iostream>
    #include <type_traits>
    
    template <typename T, typename T2>
    concept IsAvail = !std::is_same_v<T, T2>;
    
    template <IsAvail<int> T>
    void fun(T input)
    {
    
    }
    
    int main()
    {
    	fun(3.14);
    }
    

requires表达式

注意区分requires表达式与requires从句的含义

  • requires 从句用于模板定义中,它指定了模板参数必须满足的条件。
  • requires 表达式是 requires 从句的一部分,它用于定义概念(Concepts)。
  • 简单表达式:表明可以接收的操作
  • 类型表达式:表明是一个有效的类型
  • 复合表达式:表明操作的有效性,以及操作返回类型的特性
  • 嵌套表达式:包含其它的限定表达式

requires 从句会影响重载解析与特化版本的选取

  • 只有 requires 从句有效而且返回为 true 时相应的模板才会被考虑

    当编译器进行函数调用时,它会尝试找到匹配的函数重载版本。如果一个模板的 requires 从句中的条件不满足,那么即使模板的其他部分与调用匹配,这个模板版本也不会被考虑。这意味着 requires 从句充当了一种编译期的筛选器,确保只有当条件满足时,相应的模板实例才会被考虑。

    #include <iostream>
    #include <type_traits>
    
    template <typename T>
    	requires std::is_same_v<T, float>
    void fun(T input)
    {
    	std::cout << "float";
    }
    
    template <typename T>
    	requires std::is_same_v<T, int>
    void fun(T input)
    {
    	std::cout << "int";
    }
    
    int main()
    {
    	fun(3);		//第二个模板将会被调用
    }
    
  • requires 从句所引入的限定具有偏序特性,系统会选择限制最严格的版本

    当存在多个模板特化版本时,编译器会根据 requires 从句所引入的限定来选择最合适的特化。这些限定具有偏序特性

    #include <iostream>
    #include <type_traits>
    
    template <typename T>
    concept C1 = std::is_same_v<T, int>;
    
    template <typename T>
    concept C2 = std::is_same_v<T, float> || std::is_same_v<T, int>;
    
    template <C1 T>
    void fun(T input)
    {
    	std::cout << "1";
    }
    
    template <C2 T>
    void fun(T input)
    {
    	std::cout << "2";
    }
    
    int main()
    {
    	fun(3);
    }
    

特化小技巧:在声明中引入“ A||B” 进行限制,之后分别针对 A 与 B 引入特化

#include <iostream>
#include <type_traits>

template <typename T>
	requires std::is_same_v<T, int> || std::is_same_v<T, float>
class B;

template <>
class B<int> {};

template <>
class B<float> {};

int main()
{
	B<double> x;
}

四、模板相关内容

1.数值模板参数与模板模板参数

数值模板参数

  模板可以接收(编译期常量)数值作为模板参数

  • 使用int类型的编译器常量

    其写法为:

    template <int a> 
    class Str;
    

    示例:

    template <int a>
    int fun(int x)
    {
        return x + a;
    }
    
    int main()
    {
        fun<3>(5);
    }
    
  • 使用类型与编译器常量的组合

    这种方式允许你指定一个类型 T 和一个该类型的编译期常量 value

    其语法为:

    template <typename T, T value> 
    class Str;
    

    示例:

    template <typename T, T a>
    int fun(int x)
    {
        return x + a;
    }
    
    int main()
    {
        fun<int, 3>(5);
    }
    
  • 使用 auto 关键字来简化模板参数的定义(C++17)

    在C++17中,可以使用 auto 关键字来简化模板参数的定义,使得模板参数可以自动推断为传递给它的值的类型。

    其语法为:

    template <auto value>
    class Str {
        // ...
    };
    

    示例:

    template <auto a>
    int fun(int x)
    {
        return x + a;
    }
    
    int main()
    {
        fun<3>(5);
        fun<true>(5);
    }
    
  • 接收字面值类对象与浮点数作为模板参数(C++20)

    C++20进一步扩展了模板非类型参数,允许使用字面值类对象和浮点数作为模板参数。

    其语法为:

    template <double value>
    class FloatStr {
        // ...
    };
    

    支持还不完整,有些编译器不支持

模板模板参数

  在 C++ 中,模板可以接收另一个模板作为参数

  • 模板的模板参数(C++17之前)

    即一个模板的模板参数为模板T。在 C++17 之前,模板的模板参数需要显式指定类型说明符 class

    其语法为:

    template <template<typename T> class C>
    class Str {
        // ...
    };
    
  • C++17开始允许省略类型说明符

    C++17 标准放宽了对模板的模板参数的语法要求,允许在模板的模板参数中省略类型说明符 class

    其语法为:

    template <template<typename T> typename C>
    class Str {
        // ...
    };
    

    示例:

    #include <vector>
    
    template <template<typename T> typename C>
    void fun() {
        C<int> tmp;
    };
    
    int main()
    {
        fun<std::vector>();
    }
    
  • C++17 开始,模板的模板实参考虑缺省模板实参

    如:上面的vector类模板实际有两个参数,第二个参数为缺省实参

    支持还不完整,有些编译器不支持

2.别名模板与变长模板

别名模板

  在 C++ 中,using 关键字可以用于引入别名,可以使用 using 引入别名模板。

  • 为模板本身引入别名

    template <typename T>
    class MyClass {
        // ...
    };
    
    // 为模板本身引入别名
    template <typename T>
    using MyAlias = MyClass<T> ;
    int main()
    {
        // 使用别名创建对象
        MyAlias<int> myObject;
    }
    
  • 为类模板的成员引入别名

    template <typename T>
    class MyClass {
    public:
        template <typename U>
        class InnerClass {
            // ...
        };
    
        using InnerType = InnerClass<T>; // 为 InnerClass 模板的特定实例引入别名
    };
    
    int main()
    {
        // 使用别名访问类模板的成员
        MyClass<int>::InnerType myInnerObject;
    }
    
  • 别名模板不支持特化,但可以为基于类模板的特化引入别名,以实现类似特化的功能

    template <typename T>
    class MyClass {
        // ...
    };
    
    // 特化模板
    template <>
    class MyClass<double> {
        // ...
    };
    
    int main()
    {
        // 为特化的模板引入别名
        using MyDoubleClass = MyClass<double>;
    }
    

变长模板

详细内容可参考:https://zh.cppreference.com/w/cpp/language/parameter_pack

  C++中的变长模板(Variadic Templates),也称为参数包(Parameter Packs),是一种强大的模板特性,允许模板接受任意数量的模板参数。

  • 变长模板参数与参数包

    变长模板参数使用省略号(...)来表示,可以与模板参数列表中的其他参数一起使用

    template <typename... Types>
    class Tuple {
        // Types 是一个类型参数包,可以包含任意数量的类型
    };
    
  • 变长模板参数可以是数值、类型或模板

    • 类型参数包:可以用于模板的类型参数。

      #include <iostream>
      
      template <typename ... T>
      void fun(T... args)
      {
          
      }
      
      int main()
      {
          fun<int, double, char>(3, 5.3, 'c');
      }
      

      变长函数模板可以用任意数量的函数实参调用

    • 数值参数包:可以用于模板的非类型参数。

      template <int... Values>
      void printInts() {
          // 使用递归或迭代来打印 Values 中的整数
      };
      
    • 模板参数包:可以用于模板的模板参数。

      template <template <typename> class... Templates>
      class TemplateHolder {
          // Templates 是一个模板参数包
      };
      
  • sizeof... 操作(C++11)

    sizeof... 操作符用于获取参数包中的参数数量

    template<class... Types>
    struct count
    {
        static const std::size_t value = sizeof...(Types);
    };
    
  • 注意变长模板参数的位置

    • 在主类模板中,模板形参包必须是模板形参列表的最后一个形参。特化模板没有这个限制

    • 在函数模板中,模板参数包可以在列表中更早出现,只要其后的所有形参都可以从函数实参推导或拥有默认实参即可:

    template<typename U, typename... Ts>    // OK:能推导出 U
    struct valid;
    // template<typename... Ts, typename U> // 错误:Ts... 不在结尾
    // struct Invalid;
     
    template<typename... Ts, typename U, typename=void>
    void valid(U, Ts...);    // OK:能推导出 U
    // void valid(Ts..., U); // 不能使用:Ts... 在此位置是不推导语境
     
    valid(1.0, 1, 2, 3);     // OK:推导出 U 是 double,Ts 是 {int, int, int}
    

3.包展开与折叠表达式

包展开(C++11):

  通过包展开技术操作变长模板参数。

  • 模式T后随省略号且其中至少有一个形参包的名字会被展开成零个或更多个逗号分隔的模式实例,其中形参包的名字按顺序被替换成包中的各个元素。

    示例:

    template<class... Us>
    void f(Us... pargs) {}
     
    template<class... Ts>
    void g(Ts... args)
    {
        f(&args...); // “&args...” 是包展开
                     // “&args” 是它的模式
    }
    int main()
    {
     	g(1, 0.2, "a"); // Ts... args 会展开成 int E1, double E2, const char* E3
                    // &args... 会展开成 &E1, &E2, &E3
                    // Us... 会展开成 int* E1, double* E2, const char** E3   
    }
    

    编译器会翻译成

    template<class ... Us>
    void f(Us... pargs)
    {
    }
    
    /* First instantiated from: insights.cpp:7 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    void f<int *, double *, const char **>(int * __pargs0, double * __pargs1, const char ** __pargs2)
    {
    }
    #endif
    
    
    template<class ... Ts>
    void g(Ts... args)
    {
      f(&args... );
    }
    
    /* First instantiated from: insights.cpp:12 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    void g<int, double, const char *>(int __args0, double __args1, const char * __args2)
    {
      f(&__args0, &__args1, &__args2);
    }
    #endif
    
    
    int main()
    {
      g(1, 0.20000000000000001, "a");
      return 0;
    }
    
  • 如果两个形参包在同一模式中出现,那么它们同时展开而且长度必须相同:

    template<typename...>
    struct Tuple {};
     
    template<typename T1, typename T2>
    struct Pair {};
     
    template<class... Args1>
    struct zip
    {
        template<class... Args2>
        struct with
        {
            typedef Tuple<Pair<Args1, Args2>...> type;
            // Pair<Args1, Args2>... 是包展开
            // Pair<Args1, Args2> 是模式
        };
    };
    
    int main()
    {
        typedef zip<short, int>::with<unsigned short, unsigned>::type T1;
        // Pair<Args1, Args2>... 会展开成
        // Pair<short, unsigned short>, Pair<int, unsigned int> 
        // T1 是 Tuple<Pair<short, unsigned short>, Pair<int, unsigned>>
    
        // typedef zip<short>::with<unsigned short, unsigned>::type T2;
        // 错误:包展开中的形参包包含不同长度   
    }
    

使用包展开技术操作变长模板参数的基本应用:

#include <iostream>

void fun()
{

}

template <typename U, typename... T>
void fun(U u, T... args)
{
    std::cout << u << std::endl;
    fun(args...);
}

int main()
{
    fun(1, 2, "hello", "world");
}

运行结果:

1
2
hello
world

折叠表达式(C++17):简化变长模板参数操作

详细内容可参考:https://zh.cppreference.com/w/cpp/language/fold

  • 基于逗号的折叠表达式应用

    示例:对上述代码进行改写

    #include <iostream>
    
    void fun()
    {
    
    }
    
    template <typename... T>
    void fun(T... args)
    {
        ((std::cout << args << std::endl), ...);
    }
    
    int main()
    {
        fun(1, 2, "hello", "world");
    }
    
  • 折叠表达式用于表达式求值,无法处理输入(输出)是类型与模板的情形

4.完美转发与lambda表达式模板

完美转发(C++11): std::forward 函数

  完美转发允许模板函数在转发参数时保留参数的值类别(左值或右值)。这是通过 std::forward 函数和万能引用(也称为转发引用)实现的。

  • 万能引用

    万能引用是一个使用双&&声明的引用类型。它可以接受左值、右值,或者通过模板参数推导为 T&T&&

    template <typename T>
    void func(T&& arg) {
        // arg 是一个万能引用,可以绑定到左值或右值
    }
    
  • std::forward 函数

    std::forward 是一个模板函数,用于实现完美转发。它的作用是:

    • 当模板参数 UT 相同的时候,std::forward<T>(arg)arg 视为 T 类型的左值引用或右值引用,这取决于 arg 在声明时的类型。
    • UT 不同的时候,std::forward<T>(arg)arg 视为 T 类型的右值引用。
    template <typename T>
    void func(T&& arg) {
        // 使用 std::forward 实现完美转发
        someOtherFunction(std::forward<T>(arg));
    }
    
  • 完美转发的使用场景

    完美转发通常用于模板函数或模板类中,特别是那些需要转发其参数给其他函数或构造函数的模板。这样可以保证:

    • 如果原始参数是一个左值,它在转发后仍然是左值。
    • 如果原始参数是一个右值,它在转发后仍然是右值。

示例:

#include <iostream>

void process(int& i) {
    std::cout << "process(int&)" << std::endl;
}

void process(int&& i) {
    std::cout << "process(int&&)" << std::endl;
}

template <typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg));
}

int main()
{
    int x = 3;
    wrapper(x);
    wrapper(3);
}

编译器翻译结果:

image-20240607213648027

运行结果:

process(int&)
process(int&&)

在这个示例中,wrapper 函数接受一个万能引用参数。根据传入 wrapper 的参数是左值还是右值,std::forward 将正确地将参数转发给 process 函数,保持其值类别。

lambda表达式模板(C++20):

详细内容可参考:https://zh.cppreference.com/w/cpp/language/lambda

5.消除歧义与变量模板

使用 typename 与 template 消除歧义

  • 使用 typename 表示一个依赖名称是类型而非静态数据成员

    当你在类型上下文中使用依赖名称,并且该名称表示一个类型时,你可以使用 typename 来消除歧义。这通常发生在通过模板参数访问嵌套类型时。

    template <typename T>
    class Outer {
    public:
        template <typename U>
        class Inner {
        };
    
        // 使用 typename 来消除歧义,表示 Inner 是一个类型
        typedef typename Outer<T>::Inner<int> TypedInner;
    };
    

    在这个例子中,Outer<T>::Inner<int> 是一个依赖名称,它依赖于模板参数 T。使用 typename 告诉编译器 Inner<int> 是一个类型

  • 使用 template 表示一个依赖名称是模板

    当你需要指定一个依赖名称是模板时,可以使用 template 关键字。

    template <typename T>
    class MyClass {
    public:
        template <typename U>
        void function() {
            // 使用 template 来消除歧义,表示 function 是一个模板
            MyClass<T>::template function<U>();
        }
    };
    

    在这个例子中,MyClass<T>::function<U>() 是一个依赖名称,它表示一个模板。使用 template 告诉编译器 function 是一个模板,而不是一个静态成员或类型。

  • template 与成员函数模板调用

    成员函数模板是类模板内部定义的模板。当你在类模板外部实例化一个成员函数模板时,你需要使用 template 来指定模板实例化。

    template <typename T>
    class MyClass {
    public:
        template <typename U>
        void memberFunction(U param) {
            // ...
        }
    };
    
    int main() {
        MyClass<int> myObject;
        // 调用成员函数模板,需要使用 template 来指定模板参数
        myObject.template memberFunction<double>(3.14);
    }
    

    编译器翻译成

    template<typename T>
    class MyClass
    {
      
      public: 
      template<typename U>
      inline void memberFunction(U param)
      {
      }
    };
    
    /* First instantiated from: insights.cpp:11 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    class MyClass<int>
    {
      
      public: 
      template<typename U>
      inline void memberFunction(U param);
      
      /* First instantiated from: insights.cpp:13 */
      #ifdef INSIGHTS_USE_TEMPLATE
      template<>
      inline void memberFunction<double>(double param)
      {
      }
      #endif
      
      // inline constexpr MyClass() noexcept = default;
    };
    
    #endif
    
    int main()
    {
      MyClass<int> myObject;
      myObject.memberFunction<double>(3.1400000000000001);
      return 0;
    }
    

    在这个例子中,memberFunctionMyClass 的一个成员函数模板。在 main 函数中,我们使用 template 关键字来指定 memberFunction 的模板参数 double

变量模板(C++14):

  C++14 引入了变量模板,这是一种新的模板类型,允许模板定义变量。

  • 基本形式的变量模板

    template <typename T>
    T pi = T(3.1415926);
    

    pi 是一个变量模板,它对于每个类型 T 都有一个与之对应的实例。注意,这里使用类型转换 T(3.1415926) 来确保值 3.1415926 根据模板参数 T 的类型进行适当的转换。

  • 使用变量模板

    只需要指定所需的类型

    double piDouble = pi<double>; // 使用 double 类型的 pi
    int piInt = pi<int>;          // 使用 int 类型的 pi
    
  • 其他形式的变量模板

    • 编译时常量

    • 类型属性

    • 内联变量模板

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

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

相关文章

GIGE 协议摘录 —— GVCP 协议(二)

系列文章目录 GIGE 学习笔记 GIGE 协议摘录 —— 设备发现&#xff08;一&#xff09; GIGE 协议摘录 —— GVCP 协议&#xff08;二&#xff09; GIGE 协议摘录 —— GVSP 协议&#xff08;三&#xff09; GIGE 协议摘录 —— 引导寄存器&#xff08;四&#xff09; GIGE 协议…

Allure在jenkins中无法显示的问题

jenkins中使用allure生成报告需要注意工作环境和路径的配置 前提条件&#xff1a; jenkins容器中已安装jdk和allure jenkins中配置全局工具环境&#xff1a; 项目中配置allure路径&#xff1a; 路径来源&#xff1a; Path需要选择相对路径的allure-report、allure-results

react快速开始(四)-之Vite 还是 (Create React App) CRA? 用Vite创建项目

文章目录 react快速开始(四)-之Vite 还是 (Create React App) CRA? 用Vite创建项目背景Vite 和 (Create React App) CRAVite&#xff1f;Vite 是否支持 TypeScript&#xff1f; 用Vite创建react项目参考 react快速开始(四)-之Vite 还是 (Create React App) CRA? 用Vite创建项…

劝大家:打个工而已,千万不要太老实,上周,我们单位一位兢兢业业,工作了20年的老员工,被公司辞退了...

学习资源已打包&#xff0c;需要的小伙伴可以戳这里 学习资料 在当今社会&#xff0c;职场竞争激烈&#xff0c;每个人都在努力工作&#xff0c;追求自己的目标。然而&#xff0c;随着工作经验的积累和观察的深入&#xff0c;我发现了一些工作中的现象&#xff0c;希望通过本文…

EE trade:通货膨胀时期投资什么最好

当通货膨胀来袭&#xff0c;货币购买力下降&#xff0c;闲置资金贬值速度加快。为了有效抵御通货膨胀&#xff0c;投资者需要选择能够保值甚至增值的投资工具。以下是几种在通货膨胀环境下较为理想的投资选择&#xff1a; 1. 投资股票 收益性和风险性&#xff1a;股票虽然风险…

寻找python库的安装路径

以pip库为例 要找到并修改 pip 库中的 __pip-runner__.py 文件&#xff0c;您可以按照以下步骤操作&#xff1a; 找到 pip 库的安装路径&#xff1a; 通常&#xff0c;Python 库会安装在您的虚拟环境或全局 Python 包目录中。您可以通过以下命令来找到 pip 库的路径&#xff1…

【代码随想录】【算法训练营】【第28天】 [93]复原IP地址 [78]子集 [90]子集II

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 28&#xff0c;工作的周二~ 题目详情 [93] 复原 IP 地址 题目描述 93 复原 IP 地址 解题思路 前提&#xff1a;分割问题 思路&#xff1a;回溯算法&#xff0c;确定每次递归回溯的分割位置…

【CTF-Web】XSS漏洞学习笔记(附ctfshow web316-333题目)

XSS 跨站脚本攻击 文章目录 XSS 跨站脚本攻击What is XSS&#xff1f;How to lead XSS&#xff1f;Where is the XSS&#xff1f;CookieHow to use XSS&#xff1f;反射型XSS存储型XSSDom-Based XSS Then Lets see the XSS in CTF各种过滤绕过学习web316Web317-319Web319-321We…

[经验] 蝉联一词的含义是什么 #知识分享#职场发展

蝉联一词的含义是什么 蝉联这个词起源于古代中国&#xff0c;最初是指天子连续两年以上的年号相同。后来&#xff0c;这个词被用于形容某个人或某个团体连续多次获得某种荣誉或奖项的情况。在现代生活中&#xff0c;我们常常听到某个体育运动员蝉联冠军、某个企业蝉联业绩排行榜…

编写程序提示用户输入一个数目(例如:100)、年利率(例如:5)以及月份数(例如:6),然后显示给定月份后账户上的钱数。

(财务应用程序:复利值)假设你每月向银行账户存 100美元&#xff0c;年利率为5%&#xff0c;那么每 月利率是 0.05/12-0.00417。 第一个月之后&#xff0c;账户上的值就变成:100*(10.00417)100.417 第二个月之后&#xff0c;账户上的值就变成(100100.417)*(10.00417)-201.252 第…

MySQL(三) - 基础操作

一、索引 由于我们在使用数据库的时候&#xff0c;大部分操作的都是查询操作&#xff0c;但是我们每一次进行查询都需要遍历一遍表中所有数据&#xff0c;这会花费O(n)的时间&#xff0c;因此数据引入了“索引” 也就是在底层使用了数据结构来进行优化查询的操作&#xff0c;但…

Ubuntu流体程序编译

文章目录 前言一、换源1.1 &#xff08;任意&#xff09;终端窗口输入1.2 在终端窗口依次输入下面命令 二、编译mpap程序----安装各类库2.1 安装 Eigen 库安装Lapack、VTK、SuperLU可以按照师弟文件给出的教程 2.2 安装Lapack2.3 安装VTK2.4 安装SuperLU2.5 安装其他包2.6 安装…

45-5 护网溯源 - 远控木马样本溯源

在分析恶意样本时&#xff0c;需要查看包括作者名字、ID、IP地址、域名等在内的相关信息。 把恶意样本上传到微步、360沙箱云分析&#xff1a;样本报告-微步在线云沙箱 (threatbook.com) 动态分析 运行截图 发现该木马是与一个装机软件绑定的&#xff0c;你运行正常软件的时候…

No module named _sqlite3解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

21 - 即时食物配送 II(高频 SQL 50 题基础版)

21 - 即时食物配送 II -- sum(if(order_datecustomer_pref_delivery_date,1,0))/count(*)sum(order_datecustomer_pref_delivery_date)/count(*) -- count(*),表示数据的行数&#xff0c;如果有分组&#xff0c;为分组后数据的行数select round(100*sum(if(order_datecustomer_…

天诚公租房、人才公寓NB-IOT人脸物联网智能门锁解决方案

近期&#xff0c;全国已有超70城推出商品房“以旧换新”。各地商品房“以旧换新”主要采取国企收购、市场联动、税费补贴三种模式&#xff0c;二手房和新房市场交易活跃度均有提升。 一、人才公寓掀起建设浪潮 事实上&#xff0c;旧房被收购后将被纳入保障性租赁住房&#xf…

【悬架笔记三】1/4被动悬架垂向动力学仿真+频域特性分析

1/4被动悬架 代码&#xff1a; %书第156页、159页 clc clear close all %% 一.悬架参数 ms320; mw50; Ks22000; Cs1500; Kw195000; f00.07; %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% 二.垂向振动动力学仿真 %% 二.1.状态方程 A [0 1 0 -1;.…

查询SQL03:大的国家

问题描述 如果一个国家满足下述两个条件之一&#xff0c;则认为该国是 大国 &#xff1a; 面积至少为 300 万平方公里&#xff08;即&#xff0c;3000000 km2&#xff09;&#xff0c;或者 人口至少为 2500 万&#xff08;即 25000000&#xff09; 编写解决方案找出 大国 的国…

ubuntu 挂载新SSD盘

挂载新 SSD 盘 1 识别新硬盘 使用 lsblk 命令来识别新插入的 SSD 磁盘 lsblk输出可能如下&#xff1a; 在这个例子中 nvme1n1 是新插入的 SSD 磁盘 2 分区 接下来&#xff0c;我们需要对新磁盘进行分区。这里可以使用 parted 或 fdisk 进行分区&#xff0c;现以 parted 为…

Go微服务: 关于TCC分布式事务

TCC 分布式事务 T: Try 预处理, 尝试执行&#xff0c;完成所有的业务检查&#xff0c;做好一致性&#xff0c;预留必要的业务资源&#xff0c;做好准隔离性C: Confirm 确认&#xff0c;如果所有的分支Try都成功了, 就到了这个阶段, Confirm 是真正执行业务的过程, 不做任何业务…