C++知识点梳理:C++ templates

  1. c++模板包括:类模板、类(非模板类和模板类)方法模板、函数模板、别名模板、变量模板。

    类模板模板参数列表说明:
    1)类模板定义
    类名前,需模板参数列表声明,template<>行。
    template后面的< >中提供参数列表,包括类型参数或非类型参数。
    类型参数前面使用typename/class 关键字,或者使用某个concept(概念)进行限定。
    类名后,无需参数说明<>。
    2)类方法定义
    在类外面定义方法时,方法名前需要类型限定:ClassName:: 。
    类名后面需要类型说明< T >,包括完整参数列表,所有未特化模板参数和特化模板参数。
    可能:全都是未特化参数、全部特化参数、特化参数+未特化参数
    同时在首行需要template<>类型声明。
    3)类实例化
    不需要template<>声明。
    只需要类名和后面的类型说明<>,和定义时不同,此时需要提供具体的类型。
    类型后面的< >:可省略、可为空(自动类型推导)、可包含部分参数(其他省略的自动推导)、可包含完整模板参数。
    4)模板特化时,template<>中只包含未特化、未实例化参数。
    类名后的参数列表<>中包含完整参数列表:未实例化参数和特化参数

注:类名后面的模板参数列表中,在任何是完整的参数

// 1. 类模板
// TempClass temp(1, 2);
template <typename T, typename U>
class TempClass {
pulibc:TempClass(T a, U b) {cout << "basic template\n";}
}
// 2. 类方法模板
// 2.1 非模板类(普通类)的模板方法
// NonTempClass non_temp;
// non_temp.func1(123);
// non_temp.func2(456);
class NonTempClass {
public:// 在类中直接定义模板方法template<typename T>void func1(T t) {cout << "NonTempClass::func1: " << t << endl;}// 在类中仅声明模板方法,在类外面实现template<typename T> void func2(T t);
}
// 在类外面实现模板方法
template<typename T>
void NonTempClass::func2(T t) {cout << "NonTempClass::func2: " << t << endl;
}// 2.2 模板类的模板方法
template<typename T, typename U>
class TempClass {
public:// 在类内部实现模板方法template<typename N>void func1(N n) {cout << "TempClass::func1: " << n << endl;}// 仅声明方法template<typename N> void func2(N n);
}// 在类外部定义模板方法,注意需要两个template关键字,第一个是模板类的,第二个是函数模板
template<typename T, typename U>	// <----- 类模板的类型参数,第一个template关键字
template<typename N>				// <----- 函数模板的类型参数,第二个template关键字
void TempClass<T, U>::func2(N n) {cout << "TempClass::func2: " << n << endl;
}
// 3. 函数模板
template<typename T, typename U>
void temp_func(T t, U u) {cout << "temp_func: " << t << "&" << u << endl;
}

别名模板:
(1)用于为类模板定义别名。函数模板不可用,只能为类型定义别名,typedef不能用于函数。
(2)如果类模板完全特化,所有参数都明确,则不需要提供模板参数声明template<>,就不用别名模板。
(3)如果类模板偏特化,部分参数不确定,需要提供模板参数声明:template<>,此时用模板别名。

// 4. 别名模板 alias template
// 为上面定义的TempClass模板声明一个别名模板,类似模板偏特化的语法
template<typename U>
using AliasClass = TempClass<string, U>;
// 5. 变量模板
template<typename T>
constexpr T pi { T {3.1415} };float fPi { pi<float> };
auto ldPi { pi<long double> };
  1. 编译器处理模板和选择性实例化
    1)编译器不编译模板,会检查语法错误。
    2)在模板实例化时,用具体类型替换类型参数,生成实际代码。
    3)选择性实例化:编译器不会生成所有方法的代码。
    (1)所有虚方法都会生成。
    (2)非虚方法,只有被调用的才会生成。
    4)选择性实例化缺点:忽略错误,不能及时发现。
    5)解决:显式模板实例化
Grid<int> grid;		// 选择性实例化template class Grid<int>;	// 显示实例化
  1. 3种模板参数:类型参数、非类型参数、template template参数
    这3种参数是不同维度的,尤其是类型参数和template template参数,容易混淆,在实例化时需要区分模板参数和普通类型参数,如果定义为模板参数则只能提供模板名(不能用具体类型实例化,如vector,不是vector< int >)

    1)类模板和函数模板都可以使用非类型参数。
    2)非类型参数支持有限类型:整型 / 枚举、指针 / 引用 / nullptr_t、auto / auto& / auto*、float、class(后2种在C++支持,有限制)
    3)非类型参数需要使用constexpr,可编译时求值的数值或表达式。所有模板参数都需要在编译器确定。
    4)类型参数和非类型参数都可以提供默认值。
    注意:匿名模板参数也可以设置默认值,类型或非类型参数都可以。
    5)模板非类型参数,其类型可以使用模板的类型参数指定的类型
    6)匿名参数,在模板参数列表中定义匿名参数,这些参数在模板定义中没有用到,仅用于静态分发(通过类型选择不同的模板),或者为了应用SFINAE规则(阻止生成不符合某些规则的模板)。同时,匿名模板参数可以提供默认值。

constexpr int a = 100;
constexpr int b = 200;
// 类模板,提供模板参数默认值,constexpr
template<typename T=int, int n=a+b>
struct NonTypeArgClass {NonTypeArgClass() {cout << "NonTypeArg: n=" << n << endl;}
};// 函数模板,提供模板参数默认值
template<typename T=int, int n=2>
void NonTypeArgFunc() {cout << "NonTypeArgFunc: n=" << n << endl;
}NonTypeArgClass<string, 10> nonTypeArg1;	// 实例化时提供全部参数
NonTypeArgClass<string> nonTypeArg2;		// 只提供类型参数,非类型参数使用默认值
// NonTypeArgClass<15> nonTypeArg2; 		// comiplation error,如果一个参数使用非默认值,它前面的参数也不能使用默认值,和函数参数默认值一致
NonTypeArgClass<> nonTypeArg3;				// 都使用默认值
NonTypeArgClass nonTypeArg4;				// nonTypeArg3和nonTypeArg4完全相同NonTypeArgFunc<string, 20>();				// 实例化时提供全部参数
NonTypeArgFunc<string>();					// 只提供类型参数,非类型参数使用默认值
// NonTypeArgFunc<25>(); 					// compilation error
NonTypeArgFunc();							// 和下面的调用效果完全相同
NonTypeArgFunc<>();//    NonTypeArg: n=10			// nonTypeArg1
//    NonTypeArg: n=300			// nonTypeArg2,在类定义中没有用到T,结果中体现不出差别
//    NonTypeArg: n=300			// nonTypeArg3
//    NonTypeArg: n=300			// nonTypeArg4//    NonTypeArgFunc: n=20		// NonTypeArgFunc<string, 20>();
//    NonTypeArgFunc: n=2		// NonTypeArgFunc<string>();
//    NonTypeArgFunc: n=2		// NonTypeArgFunc<>();
//    NonTypeArgFunc: n=2		// NonTypeArgFunc();
// 类模板,
// 第一个参数是类型参数,第二个参数是非类型参数
// 第二个参数的类型使用的类型是模板参数指定的类型,并且设置为T的默认值T()或T{ }
template<typename T, const T defaultVal = T()>
class Grid {...}
// template template 参数,模板的类型参数是另一个模板
// c++17后,class可用typename
template<..., template<parameter-list> class ParameterName, ...>// 如下实例化,类型重复,且没有约束,可能错误写成不同类型
Grid<int, vector<optional<int>>> grid;// 以下语法不能通过编译,vector是模板,不是类型,编译器不知道需要用int实例化vector
Grid<int, vector> grid;// vector容器定义原型
// vector模板的类型,是除去名称vector外,剩余的部分,这些可作为模板类型参数使用
template<typename E, typename Allocator = std::allocator<E>>
class vector {...}// 类模板定义
// 复制模板声明,把模板名称(vector)改为类型参数名,作为模板参数template<>
// c++17后,模板类型参数中的class可换为typename(class Container -> typename Container),
// 在模板类型中只使用关键字typename即可
// 模板参数默认类型为vector,不是vector<T>,Container是一个模板名,不是类型名,所以要对应vector
template<typename T,template<typename T, typename Allocator = std::allocator<E>> class Container = std::vector>
class Grid {
private:// 定义中使用template template参数// Container是模板的模板类型参数,它用optional<T>进行实例化// 模板Container用另一个类型参数T(相关类型)进行实例化vector<Container<optional<T>>> mData;
}// 模板方法定义
// 只需更新template<>参数列表,其他部分不变,包括方法前的类型限定,例如:
template<typename T,template<typename E, typename Allocator = std::allocator<E>> Container = std::vector>
optional Grid<T, Container>::at(int x, int y) {...}// 实例化,和其他参数类型模板相同,注意区分template template参数,只能提供模板名
// Grid模板第二个参数(Container)是模板类型,实例化时需要提供vector/deque(模板名)
// 不能是vector<int>或deque<int>,这些是具体的类型名
Grid<int, vector> grid1;
Grid<int, deque> grid2;// template template参数可多层嵌套(纯属好奇心驱动的尝试)
template<typename U, // 类型参数U// GridType的模板类型template<typename T, template<typename E, typename Allocator = std::allocator<E>> class Container = std::vector>class GridType // 类型参数GridType>
class Foo {};
// 匿名类型参数 和 匿名非类型参数,都有默认值
// 貌似提供具体的默认值没有意义,定义中用不到所以才匿名,默认值也不会被引用
// 只有使用trait提供编译时的动态值,应用SFINAE规则,控制模板在符合条件时生成,参见下面的示例。
template<typename = int, size_t = 12>
struct funcAnonymousDefault { };// 匿名模板参数,编译期默认值
// 函数模板,比较两个值是否相等,只有两个值的类型相同时才能实例化这个模板
// typename = enable_if< is_same_v<T, U> >::type 匿名参数,默认值为enable_if<>::type
//		注意:要用enable_if::type,如果没有type则无法应用SFINAE,因为enable_if<>总是一种类型;
//		错误:typename = enable_if< is_same_v<T, U> >  (末尾缺少::type)
// enable_if<arg1, arg2=void>,
//	(1)如果arg1为true,则enable_if<>::type值为arg2;
//	(2)如果arg1为false,则enable_if<>::type值为空,导致模板语法不合规,SFINAE阻止模板实例化
template<typename T, typename U,typename = enable_if< is_same_v<T, U> >::type>
bool isSameValue(T t, U u) {return t == u;
}int a = 3;
int b = 3;
double b = 3; // 编译错误,参见下面错误信息cout << "is same? : " << isSameValue(a, b) << endl;
// 输出结果
// is same? : 1// 编译错误信息:
error: no matching function for call to ‘isSameValue(int&, int&)23 |     cout << "is same? : " << isSameValue(a, b) << endl;|                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
note: candidate:template<class T, class U, class> bool isSameValue(T, U)15 | bool isSameValue(T t, U u) {|      ^~~~~~~~~~~~~~~~~~~~~~~~~~~
note:   template argument deduction/substitution failed:
error: no type named ‘type’ in ‘struct std::enable_if<false, void>13 |         typename = enable_if< !is_same_v<T, U> >::type|         ^~~~~~~~
  1. CTAD(class template argument deduction)自动推导类型参数
    1)函数模板天生支持类型推导。
    2)在较新版本c++中,类模板支持类型推导。需要构造函数中使用模板参数,在初始化过程中可以推断类型和值参数。
    如果模板参数在初始化过程中使用不到,则无法完成类型推导。
    3)通过辅助模板函数,在内部实例化模板类,支持类型自动推导。
    4)特殊:unique_ptr,shared_ptr不支持类型推导,需要用make_unique() make_shared()。
    原因:传入T*时,编译器无法确定是类型T还是T[ ]。
    5)自定义推导规则:可以自定义推导规则,规避上面的歧义。
    (1)必须定义在类定义外面。
    (2)必须和类定义在同一个namespace。
    (3)可选使用explicit关键字,规则和应用在构造函数上一样。
    6)函数模板中,返回类型不能自动推导。可让编译器推导部分参数
// 自定义推导规则
template<typename T>
struct DeductionRule {explicit DeductionRule(T t) {cout << "DeductionRule: T=" << typeid(T).name() << endl;}template<typename Iter>DeductionRule(Iter iter) {}
};// 自定义规则语法:构造函数 -> 实例化类型;
DeductionRule(int) -> DeductionRule<double>;
DeductionRule(const char*) -> DeductionRule<string>;template<typename T>
DeductionRule(Iter) -> DeductionRule<typename iterator_traits<Iter>::value_type>;// Output: 无自定义推导规则
// DeductionRule: T=i
// DeductionRule: T=PKc// Output: 添加自定义推导规则后
// DeductionRule: T=d
// DeductionRule: T=NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
// 部分参数推导
template<typename R, typename T, typename S>
R func(T t, S s);// 以下2种调用方式等效,
// 第二种省略了部分类型参数,由编译器自动推导,返回类型需明确指出
auto ret = func<long, int, int>(1, 2);
auto ret = func<long>(1, 2);
auto ret = func(1, 2); // compilation error// 不能自动推导的类型参数,通过提供默认值,可以使调用时省略全部参数
// 本质上:部分由编译器推导,部分使用默认值,都不需要手动指定
template<typename R=long, typename T, typename S>
R func(T t, S s);
auto ret = func(1, 2);
  1. 模板特化:全特化、偏特化
    1)类模板:支持全特化、偏特化
    2)函数模板:只能全特化
    (1)函数有重载机制,本质上根据不提供的类型,提供不同的实现。和模板机制类似。
    (2)在函数重载解析时,函数模板不参与重载解析。
    (3)函数模板特化不常用,可能遇到非“预期”行为。模板特化和函数重载可能误用。
    3)特化语法:
    (1)在模板参数列表(template<>)中,去掉特化的(类型/非类型)参数。(因为这个参数已经确定,不再需要占位符“变量”)
    在全特化时,只保留template<>,里面没有任何参数。
    (2)类似模板实例化的语法,在类名称或者函数名称后,增加参数列表<>,列出所有类型/非类型参数。
    (3)一种“半实例化”状态,template<>告诉编译器这是一个模板,所有参数都在类定义中类名后面列出。
    偏(部分)特化 < ---- > 偏/部分 实例化
template <typename T, typename S>
struct TempClass {TempClass(T t, S s) {cout << "basic temp class\n";}
};// 类模板偏特化:用int特化第二个参数S
template <typename T>
struct TempClass<T, int> {TempClass(T t, int s) {cout << "specialize S\n";}
};
// 类模板偏特化:用int特化第一个参数T
template <typename S>
struct TempClass<int, S> {TempClass() = default;TempClass(int t, S s) {cout << "specialize T\n";}
};
// 类模板全特化
template<>
struct TempClass<bool, bool> {TempClass(bool a, bool b) {cout << "TempClass<bool, bool>\n";}
};TempClass temp1 {1.0, 2.0};
TempClass temp2 {1, 2.0};
TempClass temp3 {1.0, 2};
TempClass temp4 {false, true};// Output:
//    basic template class
//    TempClass<int, S>
//    TempClass<T, int>
//    TempClass<bool, bool>
// 非类型参数特化
// 语法和类型参数特化一样,可用于模板递归中,作为递归结束条件
template<int i>
struct TempClass2 {TempClass2() {cout << "TempClass2: basic template, i=" << i << endl;}
};template<>
struct TempClass2<10> {TempClass2() {cout << "TempClass2: specialized template for i=10" << endl;}
};TempClass2<666> temp2_1;
TempClass2<10> temp2_2;// Output:
// TempClass2: basic template, i=666
// TempClass2: specialized template for i=10
// 函数模板全特化
template<typename T, typename S>
void TempFunc(T t, S s) {cout << "TempFunc(T t, S s)\n";
}template<>
void TempFunc<double, double>(double t, double s) {cout << "TempFunc<double, double>(double t, double s)\n";
}// 实例化函数模板并调用
TempFunc(1, 2);
TempFunc(3.0, 4);
TempFunc(5.0, 6.0);// Output:
// TempFunc(T t, S s)
// TempFunc(T t, S s)
// TempFunc<double, double>(double t, double s)
  1. 模板继承

  2. 友元 friend:类模板的友元模板函数和友元模板类
    关键:operator+后面的< T >

// 前向声明
template<typename T> class Grid;// operator+ 声明
// 运算符+用到了模板类Grid,也要定义为模板,template<typename T>
// 其中用到的类型都为模板类型Grid<T>
template<typename T>
Grid<T> operator+ (const Grid<T>& left, const Grid<T>& right);// 1. friend关键字声明友元。
// 2. operator+后面<T>告诉编译器此operator是模板。
// 3. 在Grid模板内部,使用Grid或Grid<T>是等效的(CLion验证)。
template<typename T>
class Grid {
public:friend Grid<T> operator+ <T> (const Grid& left, const Grid& right);
};// android: StrongPointer.h
template<typename T>
class sp {// ...
private:// 把sp自身和wp类声明为sp类的友元// sp和wp都是模板类,friend关键字挨着class,在class前面(位于template<>之后)template<typename Y> friend class sp;template<typename Y> friend class wp;
}
  1. 函数模板返回类型和简化的函数模板语法

    如果让编译器自动推导函数返回类型,可使用以下几种方式:
    1)auto // 去掉const和&
    2)decltype(auto) // 不会去掉const和&
    3)decltype( func() )
    4)auto add(T t, S s) -> decltype(t+s)

// Error,在前面decltype使用t和s时,尚未定义
template<typename T, typename S>
decltype(t + s) add(const T& t, const S& s)
{ return t+s; }template<typename T, typename S>
auto add(const T& t, const S& s) -> decltype(t+s)
{ return t+s; }

简化的函数模板
1)所有类型都用auto(或const auto&),省略了template<typename …>声明。
2)使用auto是编译器语法糖,效果和使用template<typename…>声明一致。
3)局限性 1:使用auto的类型每个都不同,如果需要指定多个入参为同一种类型,需要使用template的方式声明。
局限性 2: 因为auto没有类型名,在函数体内无法直接使用对应的类型,可使用decltype。

template<typename T, typename S>
decltype(auto) func(const T& t, contst S& s) { return t + s;
}decltype(auto) func(const auto& t, const auto& s) { return t + s;
}
  1. c++20 concept
    1)concept由constraints-expression(约束表达式)构成。
    2)concept表达式(concept expression),应用已有concept
    3)constraints-expression组成:
    (1)一个简单的常量表达式,返回bool值。
    (2)一种新的特殊的常量表达式,require表达式。
    4)require表达式由requirement构成
    5)requirement分为4种:简单、类型、复合、嵌套。
    6)concept expression可使用&&或||组合使用。
// concept定义语法
template<parameter-list>
concept concept-name = constraints-expressions;// concept表达式
// 应用已有的概念,例如:Incrementable<T>, convertible_to<bool>
// 类似于调用已定义函数:func(arg);
concept-name<argument-list>// 约束表达式
template<typename T>
concept C = sizeof(T) == 4;// require表达式定义语法
requires (parameter-list) { requirements; }// requirement分为4种:
// 1. simple requirement (不以requires开头,相对嵌套requirement来说)
//		可以是任意表达式,不求值,只验证编译通过(验证语法和语义功能)
template<typename T>
concept Incremental = requires(T x) {x++;++x;
}// 2. type requirement
template<typename T>
concept C = requires {typename T::value_type;		// 验证T是否有类型value_typetypename SomeTemplate<T>;	// 是否可以用T实例化SomeTemplate
}// 3. 复合 requirement	compound requirement
// 验证不会抛出异常,或者返回某种类型
// noexcept, 或->type-constraint可选的,可验证某一种或同时验证
// { }是必须的,即使一个语句,也需要有。
{ expression } noexcept -> type-constraint;template<typename T>
concept C = requires(const T x, const T y) {{ x.swap(y) } noexcept;					// noecept验证时,{}不能省略{ x.size() } -> convertible_to(size_t);	// 验证返回值时,一个语句时,{}也不可省略{ x==y } -> convertible_to<bool>;		// 不可省略{}
}// 4. 嵌套 requirement
template<typename T>
concept C = requires (T t) {// 这里不能省略requires,只用sizeof(T) == 4是不行的,不会实际校验,感觉requires类似assertrequires sizeof(T) == 4;
//	sizeof(T) == 4;  // 错误,可编译通过,逻辑不对++t;t++;
}// 5. 组合概念表达式,不是-不是-不是requirement,combined CONCEPT expression
// 使用&&,||组合
template<typename T>
concept MyConcept = Incrementable<T> && Decrementable<T>;
  1. 模板递归

  2. 可变参数模板

    1)< typename… Tn >表示0个或多个参数。
    2)…展开其左侧的‘表达式’,根据参数个数重复多次,用逗号分隔。
    3)…前后的空格可选。
    4)可变参数没有直接的遍历方式,只能通过模板递归遍历。

template<typename... Tn>
class Temp {...}

// 可变参数函数模板
// typename… Tn,声明可变类型个数
// Tn… args,声明可变参数个数
// args…,使用可变参数
// 使用完美转发perfect forward,避免参数复制和使用字面量
// 使用确定类型参数T1和可变参数结合的方式,递归分离、解析可变参数

void handleValue(int v) { cout << "int: " << v << endl; }
void handleValue(double v) { }
void handleValue(string_view v) { }// 递归终止case
void processValues() { }template<typename T1, typename... Tn>
void processValues(T1 arg1, Tn... args) {handleValue(arg1);processValues(args...);
}template<typename T1, typename... Tn>
void processValues(T1&& arg1, Tn&&... args) {handleValue(std::forward<T1>(arg1));processValues(std::forward<Tn>(args)...); // 注意此处...,在每个参数上执行forward
}// sizeof...操作符,和sizeof是不同的操作符
int count { sizeof...(args) }

使用可变参数模板实现Mixin模板类

struct Base1 {int mValue;Base1(int i): mValue(i) { }void func1() {cout << "Base1: value=" << mValue << endl;}
};struct Base2 {int mValue;Base2(int i): mValue(i) { }void func2() {cout << "Base2: value=" << mValue << endl;}
};template<typename... Cn>								// 1. 声明可变参数:typename... Cn
struct Mixin: public Cn... {							// 2. 继承可变参数:public Cn...Mixin(const Cn&... classes): Cn {classes} ... {}	// 3. 可变参数作为函数参数:Cn&... cls,virtual ~Mixin() = default;							//    以及初始化可变参数成员变量:Cn{classes}...
};// 调用
Mixin<Base1, Base2> mix { Base1{123}, Base2{456}}; // 初始化方式1auto b1 = Base1{123};
auto b2 = Base2{456};
Mixin<Base1, Base2> mix {b1, b2};  // 初始化方式2mix.func1();
mix.func2();// output:
// Base1: value=123
// Base2: value=456
  1. constexpr if
  2. 折叠表达式:fold expressions
  3. 元编程
// 模板递归,计算阶乘
template<size_t n>
struct Factorial {constexpr static int value = n * Factorial<n -1>::value;
};template<>
struct Factorial<0> {constexpr static int value = 1;
};cout << "fact: " << Factorial<1000>::value << endl;
// 模板递归,循环
template<size_t n>
struct Loop {template<typename Func>static void run(Func func) {// Loop::run和func()的执行顺序,决定是正序循环还是逆序循环Loop<n-1>::run(func);func(n);}
};template<>
struct Loop<0> {template<typename Func>static void run(Func func) {}
};Loop<5>::run([](int i){cout << "run: " << i << endl;
});
  1. traits
  2. 在一个声明中多次使用template的场景
    1)模板类的模板方法定义
    2)模板的template template参数
// 顺序并列使用2个template关键字
template<typename ClassArgType>
template<typename MethodArgType>
void Temp<ClassArgType>::func(MethodArgType data) { ... }// template中嵌套template
// 可嵌套多层,参见上面template template参数中的例子
template<typename T,template<typename E, typename Allocator<E> = std::allocator<E>> Container = std::vector>
class Grid { ... }

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

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

相关文章

CUDA API学习

1. nvtxNameCudaStream 在 NVIDIA Tools Extension (NVTX) 中,nvtxNameCudaStream 函数用于为 CUDA 流(stream)设置一个用户定义的名称,以便在可视化工具中更好地理解 CUDA 流的作用。该函数允许你将一个可读的字符串关联到 CUDA 流,从而在可视化工具中标识和跟踪不同的流…

【数据结构初阶(3)】双向带头结点循环链表

文章目录 Ⅰ 概念及结构Ⅱ 基本操作实现1. 结点的定义2. 创建头节点3. 创建新结点4. 双向链表销毁5. 双向链表打印6. 双向链表尾插7. 双向链表尾删8. 双向链表头插9. 双向链表头删10. 双向链表查找11. 在指定 pos 位置前插入新结点12. 删除指定 pos 位置的结点 Ⅲ 十分钟手搓链…

CSS中常用的伪元素选择器

伪元素&#xff0c;表示页面中一些特殊的并不真实存在的元素&#xff08;元素的位置&#xff09; 1&#xff0c;::first-letter 表示第一个字母 2&#xff0c;::first-line 表示第一行 3&#xff0c;::selection 选中的内容 4&#xff0c;::before 元素的开始位置 5&#…

​vmware虚拟机ubuntu系统配置静态ip​

把虚拟机当成服务器&#xff0c;如果虚拟机的ip是一直变化的&#xff0c;每次远程连接需要都修改连接虚拟机的ip地址&#xff0c;这肯定是麻烦的。 一、设置一下本机的VMnet8的ip 配置路径&#xff1a;控制面板->所有控制面板项->网络和共享中心 二、首先设置NAT 选自…

数据结构【DS】树的性质

度为m的树 m叉树 至少有一个节点的度m 允许所有节点的度都<m 一定是非空树&#xff0c;至少有m1个节点 可以是空树 节点数 总度数 1m叉树&#xff1a; 高度为h的m叉树 节点数最少为&#xff1a;h具有n个结点的m叉树 最大高度&#xff1a;n度为m的树&#xff1a; 具有…

【营养学01】肾脏:脱发以及白头

发&#xff0c;即头发&#xff0c;又名血余。 发之营养来源于血&#xff0c;故称“发为血之余”。 但发的生机根源于肾。 因为肾藏精&#xff0c;精能化血&#xff0c;精血旺盛&#xff0c;则毛发壮而润泽&#xff0c;故又说肾“其华在发” 黑桑葚&#xff0c;黑枸杞&#xff0…

Postman的各种参数你都用对了吗?

大家好&#xff0c;我是G探险者。 Postman我们都不陌生&#xff0c;作为一个广泛使用的 HTTP 客户端&#xff0c;平时我们使用它来测试接口&#xff0c;无非就是把接口的url放进去&#xff0c;然后根据请求类型get或者post,在不同位置传一下参数&#xff0c;除了常见的 Params…

Redis(地理空间Geospatial和HyperLogLog)

Geospatial&#xff1a; Redis中的Geospatial提供了一种存储和处理地理空间数据的能力&#xff0c;这对于许多应用非常有用。以下是Redis中的Geospatial的一些作用&#xff1a; 1. 地理位置查询&#xff1a;可以存储地理位置的坐标信息&#xff0c;并且可以通过查询指定半径范…

抛美债买中债!人民币大涨645点,美联储投降,美元要撑不住了

正当各国央行纷纷权衡其外汇储备组合时&#xff0c;一则消息震动了金融市场&#xff1a;投资者正抛售美国国债&#xff0c;转而购买中国国债。此举不仅反映了市场对未来货币政策的预期&#xff0c;也昭示了全球经济力量平衡的微妙变化。 首先&#xff0c;我们不得不提的是人民…

第2关:图的深度优先遍历

任务要求参考答案评论2 任务描述相关知识编程要求测试说明 任务描述 本关任务&#xff1a;以邻接矩阵存储图&#xff0c;要求编写程序实现图的深度优先遍历。 相关知识 图的深度优先遍历类似于树的先序遍历, 是树的先序遍历的推广&#xff0c;其基本思想如下&#xff1a; …

DITTEL控制器维修SENSITRON6-2AE

DITTEL工控产品维修包括&#xff1a;德国DITTEL平衡测试仪维修,DITTEL模块&#xff0c;过程监控模块&#xff0c;DITTEL控制器&#xff0c;平衡头&#xff0c;机电平衡头&#xff0c;显示器&#xff0c;平衡系统等产品。 DITTEL过程控制模块维修 DM6000是一个过程控制模块&…

onnx模型转换opset版本和固定动态输入尺寸

背景&#xff1a;之前我想把onnx模型从opset12变成opset12&#xff0c;太慌乱就没找着&#xff0c;最近找到了官网上有示例的&#xff0c;大爱onnx官网&#xff0c;分享给有需求没找着的小伙伴们。 1. onnx模型转换opset版本 官网示例&#xff1a; import onnx from onnx im…

关于灰度发布的总结(一)

灰度发布是指在不影响生产环节可用性的前提下&#xff0c;将软件版本部署到生产灰度区&#xff08;小范围、小流量&#xff09;&#xff0c;对其进行持续一段时间的监控及验证&#xff0c;最后根据监控验证结果决定软件新版本是否正式发布的软件发布过程。 灰度发布可以进行以…

element表格第一个列变成最后一个处理方案

解决方案&#xff1a; 翻译过来就是这样子&#xff0c;说el-table不能嵌套el-table-column以外的元素

白盒子测试总结

白盒子测试&#xff0c;也称为结构测试、透明盒测试、逻辑驱动测试或基于代码的测试&#xff0c;是一种测试用例的设计方法。白盒测试需要全面了解被测试程序的内部逻辑结构&#xff0c;并对所有逻辑路径进行测试。其基本方法包括&#xff1a; 语句覆盖&#xff1a;设计若干个…

第3关:图的广度遍历

500 任务要求参考答案评论2 任务描述相关知识编程要求测试说明 任务描述 本关任务&#xff1a;以邻接表存储图&#xff0c;要求编写程序实现图的广度优先遍历。 相关知识 广度优先遍历类似于树的按层次遍历的过程。 假设从图中某顶点v出发&#xff0c;在访问了v之后依次访…

Foodpanda API连接的艺术:无代码开发如何集成营销系统和广告推广工具

连接Foodpanda和电商平台的无代码开发 Foodpanda不仅是一家提供快速外卖服务的国际品牌&#xff0c;而且其创新的技术解决方案还能帮助电商企业优化系统运营。通过无代码开发的方法&#xff0c;即使没有专业的API开发知识&#xff0c;商家也能实现高效的电商系统和客服系统连接…

无人售货奶柜:颠覆传统零售行业的潜力黑马

无人售货奶柜&#xff1a;颠覆传统零售行业的潜力黑马 无人售货奶柜具备体积小、灵活运用空间、无需人工看守和自动结算等特点。相较于传统建店方式&#xff0c;它的成本大大降低&#xff0c;从而提高了运营效率。此外&#xff0c;无人售货奶柜独特的优势之一就是可以保持24小时…

[hive] posexplode函数

在Hive SQL中&#xff0c;posexplode是一个用于将数组&#xff08;array&#xff09;拆分为多行的函数。 它返回数组中的每个元素以及其在数组中的位置&#xff08;索引&#xff09;作为两列输出。 这是posexplode函数的语法&#xff1a; posexplode(array)其中&#xff0c;…

【YOLOX简述】

YOLOX的简述 一、 原因1. 背景2. 概念 二、 算法介绍2.1 YOLOX算法结构图&#xff1a;2.2 算法独特点2.3 Focus网络结构2.4 FPN&#xff0c;PAN2.5 BaseConv2.6 SPP2.7 CSPDarknet2.8 YOlO Head 三、预测曲线3.1 曲线 一、 原因 1. 背景 工业的缺陷检测是计算机视觉中不可缺少…