当前位置: 首页 > news >正文

萃取的实现(三)

探测成员

        基于SFINAE,判断一个给定类型T,是否含有名为x的成员。

探测类型成员

        判断一个给定类型T,是否含有类型成员size_type,源码如下:

#include <type_traits>
#include <iostream>
#include <vector>template <typename ...>
using void_t = void;template <typename , typename = void_t<>>
struct has_sizetype_t : std::false_type {};template <typename T>
struct has_sizetype_t<T, void_t<typename T::size_type>> : std::true_type {};int main(int argc, char **argv) {std::cout << (has_sizetype_t<int>::value ? "true" : "false") << std::endl;std::cout << (has_sizetype_t<std::vector<int>>::value ? "true" : "false") << std::endl;return 0;
}

        注意:如果类型成员为private成员,萃取模板没有访问该类型的特殊权限,has_sizetype_t会返回false

        对于普通类型,上述模板能够准确做出判断,但如果模板参数为引用类型,如std::vector<int>&,上述模板处理结果确是false。为了能兼容这种情况,可以在偏特化使用std::removereference,如下:

template <typename T>
struct has_sizetype_t<T, void_t<typename std::remove_reference<T>::type::size_type>> : std::true_type {};

        对于注入类的名字,上述检测模板也会返回true,如下:

struct size_type {};
struct size_able : size_type {};//...
std::cout << (has_sizetype_t<size_able>::value ? "true" : "false") << std::endl;

探测任意类型成员

         has_sizetype_t能够有效的判断一个类型T是否包含类型成员size_type,如果把size_type换成其他类型呢?最简单也是最暴力的做法,就是仿照has_sizetype_t再实现一个萃取模板。但为每个类型成员都实现一个萃取模板,不太合理,把类型成员参数化,才是最优的选择。然而不幸的是,目前还没有语言机制可以被用来描述“潜在” 的名字。但是可以通过宏来实现这一功能,源码如下:

#include <type_traits>
#include <iostream>
#include <string>
#include <vector>template <typename ...>
using void_t = void;#define define_has_member_type(member_type) \template <typename , typename = void_t<>> \struct has_##member_type##_t : std::false_type {}; \\template <typename T> \struct has_##member_type##_t<T, void_t<typename std::remove_reference<T>::type::member_type>> : std::true_type {};//struct has_##member_type##_t<T, void_t<typename T::member_type>> : std::true_type {};define_has_member_type(size_type)
define_has_member_type(str_type)class mystring {
public:using str_type = std::string;
};class myint {
public:using int_type = int;
};int main(int argc, char **argv) {std::cout << (has_str_type_t<mystring&&>::value ? "true" : "false") << std::endl;std::cout << (has_str_type_t<myint>::value ? "true" : "false") << std::endl;std::cout << (has_size_type_t<int>::value ? "true" : "false") << std::endl;std::cout << (has_size_type_t<std::vector<int>>::value ? "true" : "false") << std::endl;std::cout << (has_size_type_t<std::vector<int>&&>::value ? "true" : "false") << std::endl;return 0;
}

探测非类型成员

        对上述萃取模板做简单的修改,便可以得到探测非类型成员的萃取模板,如下:

#include <type_traits>
#include <iostream>template <typename ...>
using void_t = void;#define define_has_member(member) \template <typename, typename = void_t<>> \struct has_##member##_t : std::false_type {}; \\template <typename T> \struct has_##member##_t<T, void_t<decltype(&T::member)>> : std::true_type {};enum myenum {failed = 0,succeeded = 1
};class myclass {
public:int value;myenum status;static int instance_number;
};class myclass2 {
public:using status = myenum;
};define_has_member(value)
define_has_member(status)
define_has_member(instance_number)int main(int argc, char **argv) {std::cout << (has_status_t<myclass2>::value ? "true" : "false") << std::endl;std::cout << (has_status_t<std::true_type>::value ? "true" : "false") << std::endl;std::cout << (has_value_t<myclass>::value ? "true" : "false") << std::endl;std::cout << (has_status_t<myclass>::value ? "true" : "false") << std::endl;std::cout << (has_instance_number_t<myclass>::value ? "true" : "false") << std::endl;return 0;
}

        偏特化模板参数中为什么使用void_t<decltype(&T::member)>,而非void_t<decltype(T::member)>?因为使用后者探测数据成员没有问题,但探测成员函数就无能为力了。使用该萃取模板,还需要注意以下几点:

  • 成员必须是非类型成员以及非枚举成员,否则&T::member失效,所以has_kind_type_t,has_ultrasonic_t均检测失败

  • 成员必须是public成员

  • member必须没有歧义,对于has_kind_t<bat>::value,has_name_t<bat>::value,has_move_t<bat>::value,编译其无法确认kind为mammals::kind,还是bird::kind,name为mammals::name,还是bird::name,move为mammals::move,还是bird::move,所以均检测失败

  • 如果存在重载函数,检测失败,如bat拥有两个search版本,所以has_search_t检测失败

        针对重载函数探测失效的问题,目前的方案只能针对不同的函数实现不同的的萃取模板。如下:

template <typename, typename = void_t<>>
struct has_search_void_t : std::false_type {};template <typename T>
struct has_search_void_t<T, void_t<decltype(std::declval<T>().search())>> : std::true_type {};template <typename, typename = void_t<>>
struct has_search_int_t : std::false_type {};template <typename T>
struct has_search_int_t<T, void_t<decltype(std::declval<T>().search(0))>> : std::true_type {};

        下面是两种失败的尝试方案:

//方案1
//ERROR:
//: error: expected ')'
//    std::cout << (has_search_t<bat, int>(nullptr)::value ? "true" : "false") << std::endl;
//to match this '('
//    std::cout << (has_search_t<bat, int>(nullptr)::value ? "true" : "false") << std::endl;
template <typename T, typename ...Args, typename = void_t<decltype(std::declval<T>().search(std::declval<Args>()...))>>
constexpr std::true_type has_search_t(void *);template <typename, typename ...Args>
constexpr std::false_type has_search_t(...);//...
std::cout << (has_search_t<bat, int>(nullptr)::value ? "true" : "false") << std::endl;//方案2
//ERROR:
//template parameter pack must be the last template parameter
//    template <typename, typename ...Args, typename = void_t<>>
//error: expected expression
//    struct has_search_t <T, ...Args, void_t<decltype(std::declval<T>().search(std::declval<Args>()...))>>: std::true_type {};
//expected unqualified-id
//    struct has_search_t <T, ...Args, void_t<decltype(std::declval<T>().search(std::declval<Args>()...))>>: std::true_type {};
template <typename, typename = void_t<>>
struct has_search_void_t : std::false_type {};template <typename T>
struct has_search_void_t<T, void_t<decltype(std::declval<T>().search())>> : std::true_type {};

         除了类的类型成员,变量成员,函数成员外,还可以用于探测表达式,甚至是多个表达式的组合,下面是两个例子:

template <typename ...>
using void_t = void;//表达式
template <typename, typename, typename = void_t<>>
struct has_less_t : std::false_type {};template <typename T1, typename T2>
struct has_less_t<T1, T2, void_t<decltype(std::declval<T1>() < std::declval<T2>())>> : std::true_type {};//表达式组合
template <typename, typename = void_t<>>
struct has_calc_t : std::false_type {};template <typename T>
struct has_calc_t<T, void_t<decltype(std::declval<T>() + std::declval<T>(), std::declval<T>() - std::declval<T>(), std::declval<T>() * std::declval<T>(), std::declval<T>() / std::declval<T>())>> : std::true_type {};

用泛型 Lambda 探测成员

        使用萃取的实现(二)【将泛型 Lambdas 用于 SFINAE】中的lambda表达式,也可以实现成员探测,源码如下:

constexpr auto has_size_type = is_valid([](auto &&x)->typename std::decay_t<decltype(x)>::size_type {});
template <typename T>
using has_size_type_t = decltype(has_size_type(std::declval<T>()));constexpr auto has_begin = is_valid([](auto &&x)->decltype(x.begin()){});
//constexpr auto has_begin = is_valid([](auto &&x)->decltype(std::declval<decltype(x)>().begin()){});
template <typename T>
using has_begin_t = decltype(has_begin(std::declval<T>()));int main(int argc, char **argv) {std::cout << (has_size_type_t<std::string>::value ? "true" : "false") << std::endl;std::cout << (has_size_type_t<int>::value ? "true" : "false") << std::endl;std::cout << (has_begin_t<std::string>::value ? "true" : "false") << std::endl;std::cout << (has_begin_t<int>::value ? "true" : "false") << std::endl;return 0;
}

其它的萃取技术

If-Then-Else

        如何根据数值的大小,自动选择合适的整型类型?首先定义一个模板if_then_else模拟选择行为,该模板接收三个模板参数,并且第一个参数为bool常量表达式,剩余的两个参数为类型参数,由第一个参数的值决定哪个类型参数生效;然后,定义一个参数为整型数值的模板,调用if_then_else,推断出合适的类型(由于实现不知道数值的合适类型,因此模板参数的类型使用了auto,需要c++17才能支持)。代码如下:

template <bool COND, typename TrueType, typename FalseType>
struct if_then_else { using type = TrueType; };template <typename TrueType, typename FalseType>
struct if_then_else<false, TrueType, FalseType> { using type = FalseType; };template <auto N>
struct small_int {using type = typename if_then_else<N <= std::numeric_limits<char> ::max(), char,typename if_then_else<N <= std::numeric_limits<short> ::max(), short,typename if_then_else<N <= std::numeric_limits<int> ::max(), int,typename if_then_else<N <= std::numeric_limits<long>::max(), long,typename if_then_else<N <= std::numeric_limits<long long>::max(), long long,void>::type>::type>::type>::type>::type;
};

        需要注意的是,和常规的 C++ if-then-else 语句不同,在最终做选择之前,then 和 else 分支 中的模板参数都会被计算,因此两个分支中的代码都不能有问题,否则整个程序就会有问题。 

探测不抛出异常的操作

        有时候我们需要判断一些操作是否会抛出异常,这时我们需要用到noexcept操作符,该操作符对表达式进行编译时检查,如果表达式不抛出任何异常返回true,否则返回false。主要应用场景如下:

  • 显示指明函数是否会抛出异常:noexcept和noexcept(true)等效,表示该函数不抛出任何异常;noexcept(false)表示有可能抛出异常
  • 阻止异常传播和扩散,出现异常直接调用std::terminate终止进程
#include <iostream>void func1() {throw 1;
}void func2() {func1();
}void func3() noexcept {func1();
}int main(int argc, char **argv) {try {func1();} catch(...) {std::cout << "func1" << std::endl;}try {func2();} catch(...) {std::cout << "func2" << std::endl;}try {func3(); //阻止异常传播,程序crash} catch(...) {std::cout << "func3" << std::endl;}return 0;
}
  • 用于模板,增强c++泛型能力
#include <type_traits>
#include <iostream>template <typename T, typename = std::void_t<>>
struct has_noexcept_move_constructor : std::false_type {};template <typename T>
struct has_noexcept_move_constructor<T, std::void_t<decltype(T(std::declval<T>()))>> : std::bool_constant<noexcept(T(std::declval<T>()))> {};class A {
public:A(A &&) {throw 1;}
};class B {
public:B(B &&) = default;
};int main(int argc, char **argv) {std::cout << (has_noexcept_move_constructor<A>::value ? "true" : "false") << std::endl;std::cout << (has_noexcept_move_constructor<B>::value ? "true" : "false") << std::endl;return 0;
}

萃取的便捷性

        关于萃取最大的问题便是繁琐,对于萃取类型的使用需要添加::type后缀,而且在依赖上下文中,还需要一个typename前缀,例如:

template <typename T>
void print_type_name() {std::cout << typeid(typename std::remove_reference<T>::type).name() << std::endl;
}

别名模板和萃取

        使用using创建别名,可以简化类型名称,如下:

template <typename T>
using rmref = typename std::remove_reference<T>::type;template <typename T>
void print_type_name() {std::cout << typeid(rmref<T>).name() << std::endl;
}

        从c++14开始,直接为之引入了相应的别名模板,以_t结尾,对于“typename std::remove_reference<T>::type”,其别名为“std::remove_reference_t<T>”。

变量模板和萃取

        对于返回数值的萃取需要使用一个::value来生成萃取的结果,如下:

template <typename T1, typename T2>
void print_diff() {std::cout << (std::is_same<T1, T2>::value ? "true" : "false") << std::endl;
}

        针对上面这种情况,可以使用constexpr修饰的变量模板进行简化(需要在C++14之后),如下:

template <typename T1, typename T2>
constexpr bool issame = std::is_same<T1, T2>::value;template <typename T1, typename T2>
void print_diff() {std::cout << (issame<T1, T2> ? "true" : "false") << std::endl;
}

        从c++17开始,直接引入了与之对应的变量模板,以_v结尾,对于“std::is_same<T1, T2>::value”,对应的变量模板为“std::is_same_v<T1, T2>”。

类型分析

判断基础类型

        判断一个类型是否是基础类型,如int是否是基础类型。代码如下:

template <typename T>
struct is_fundamental : std::false_type {};template <>
struct is_fundamental<int> : std::true_type {};template <>
struct is_fundamental<double> : std::true_type {};int main(int argc, char **argv) {std::cout << (is_fundamental<double>::value ? "true" : "false") << std::endl;std::cout << (is_fundamental<int>::value ? "true" : "false") << std::endl;return 0;
}

         为了能够正确的识别出每一个基础类型,只能针对每个基础类型实现一遍is_fundamental。通过宏尽管能少些一些代码,但本质上依旧是对基础类型的穷举。

#define IS_FUNDAMENTAL(T) template <> struct is_fundamental<T> : std::true_type {};IS_FUNDAMENTAL(float)
IS_FUNDAMENTAL(unsigned long)

判断复合类型

        复合类型是由其它类型构建出来的类型。简单的复合类型包含指针类型,左值以及右值引用 类型,指向成员的指针类型(pointer-to-member types),和数组类型。

//指针
template <typename T>
struct is_pointer : std::false_type {};
template <typename T>
struct is_pointer<T *> : std::true_type {};//左值引用
template <typename T>
struct is_lvalue_reference : std::false_type {};
template <typename T>
struct is_lvalue_reference<T &> : std::true_type {};//右值引用
template <typename T>
struct is_rvalue_reference : std::false_type {};
template <typename T>
struct is_rvalue_reference<T &&> : std::true_type {};//数组
template <typename T>
struct is_array : std::false_type {};
template <typename T>
struct is_array<T[]> : std::true_type {};
template <typename T, std::size_t N>
struct is_array<T[N]> : std::true_type {};//指向成员的指针
template <typename T>
struct is_member_pointer : std::false_type {};
template <typename T, typename C>
struct is_member_pointer<T C::*> : std::true_type {};struct S {int a = 10;int b = 20;float func1(void) { return 0; }float func2(void) { return 1; }
};using mem_data_ptr_t = int S::*;
using mem_func_ptr_t = float S::*;int main(int argc, char **argv) {std::cout << (is_pointer<int>::value ? "true" : "false") << std::endl;std::cout << (is_pointer<int *>::value ? "true" : "false") << std::endl;int *pi = &argc;std::cout << (is_pointer<decltype(pi)>::value ? "true" : "false") << std::endl;std::cout << std::endl;std::cout << (is_lvalue_reference<int>::value ? "true" : "false") << std::endl;std::cout << (is_lvalue_reference<int &>::value ? "true" : "false") << std::endl;int &lri = argc;std::cout << (is_lvalue_reference<decltype(lri)>::value ? "true" : "false") << std::endl;std::cout << std::endl;std::cout << (is_rvalue_reference<int>::value ? "true" : "false") << std::endl;std::cout << (is_rvalue_reference<int &&>::value ? "true" : "false") << std::endl;std::cout << (is_rvalue_reference<decltype(std::list<int>())>::value ? "true" : "false") << std::endl;//不太确定为什么decltype(std::list<int>())得到的就不是右值引用类型std::cout << typeid(decltype(std::list<int>())).name() << std::endl;std::list<int> li;std::cout << (is_rvalue_reference<decltype(std::move(li))>::value ? "true" : "false") << std::endl;std::cout << typeid(decltype(std::move(li))).name() << std::endl;std::cout << std::endl;std::cout << (is_array<int>::value ? "true" : "false") << std::endl;std::cout << (is_array<int[]>::value ? "true" : "false") << std::endl;std::cout << (is_array<int [3]>::value ? "true" : "false") << std::endl;int arr[10]; //int arr[0]; decltype(arr)得到的类型时A0_i,不是数组std::cout << (is_array<decltype(arr)>::value ? "true" : "false") << std::endl;std::cout << typeid(int[3]).name() << std::endl;std::cout << typeid(decltype(arr)).name() << std::endl;std::cout << std::endl;using mem_string_ptr_t = std::string S::*;//虽然这个类型不存在,但结果依然返回了true,标注库意识如此std::cout << (is_member_pointer<mem_string_ptr_t>::value ? "true" : "false") << std::endl;std::cout << (is_member_pointer<mem_data_ptr_t>::value ? "true" : "false") << std::endl;std::cout << (is_member_pointer<mem_func_ptr_t>::value ? "true" : "false") << std::endl;}

识别函数类型

        在讨论如何识别函数类型时,先考虑如何定义一个函数类型。下面是函数类型定义的相关代码:

#include <iostream>int func_instance(int arg) {std::cout << arg << std::endl;return 0;
}int main(int argc, char **argv) {typedef int (func_t)(int);func_t *func1 = func_instance; ✅func1(12);typedef int (*func_pt)(int);func_pt func2 = func_instance; ✅func2(14);return 0;
}

         通过上面的例子,可以看出:只要不同函数类型的返回值和入参一致,既可以表示同一个类型,名称不是特别重要。因此,识别函数类型时,只用到了返回值和入参,只要类型符合Ret(Args...)格式,该类型就是函数类型。这个格式很像去掉了名称的函数类型定义。下面是函数类型识别源码:

#include <type_traits>
#include <iostream>
#include <cstdio>template <typename T>
struct is_function : std::false_type { constexpr static int flag = 0; };template <typename Ret, typename ...Args>
struct is_function<Ret(Args...)> : std::true_type { constexpr static int flag = 1; };template <typename Ret, typename ...Args>
struct is_function<Ret(Args..., ...)> : std::true_type { constexpr static int flag = 2; };class A {
public:A(){}static A &GetInstance() { static A _inst; return _inst; }void Set(int) {}int Get(void) const { return 0; }int Func(int a) const & { return a; }
};struct B
{static int foo();int fun() const&;
};int main(int argc, char **arg) {std::cout << (is_function<int(int)>::value ? "true" : "false") << " " << is_function<int(int)>::flag << std::endl;std::cout << (is_function<decltype(printf)>::value ? "true" : "false") << " " << is_function<decltype(printf)>::flag << std::endl;std::cout << (is_function<decltype(&A::Set)>::value ? "true" : "false") << " " << is_function<decltype(&A::Set)>::flag << std::endl;std::cout << (is_function<const int(void)>::value ? "true" : "false") << " " << is_function<const int(void)>::flag << std::endl;std::cout << (is_function<decltype(&A::Get)>::value ? "true" : "false") << " " << is_function<decltype(&A::Get)>::flag << std::endl;std::cout << (is_function<decltype(A::GetInstance)>::value ? "true" : "false") << " " << is_function<decltype(A::GetInstance)>::flag << std::endl;std::cout << (is_function<decltype(&B::fun)>::value ? "true" : "false") << " " << is_function<decltype(&B::fun)>::flag << std::endl;std::cout << (is_function<int(int)const>::value ? "true" : "false") << " " << is_function<int(int)const>::flag << std::endl;std::cout << (is_function<int(int) &>::value ? "true" : "false") << " " << is_function<int(int) &>::flag << std::endl;std::cout << (std::is_function<decltype(&A::Get)>::value ? "true" : "false") << std::endl;return 0;
}

        需要注意几个问题:

  • is_function识别的是函数类型,对于函数需要通过decltype推断出其函数类型,如decltype(printf)
  • is_function可以识别类的静态成员函数,无法识别非静态成员函数
  • is_function不能处理所有函数类型,因为有些函数还包含const和volatile,到c++17之后,还包含noexcept操作符,如int(int) const
  • 此外还应该处理函数的引用情况,关于函数的引用,解释如下(摘自c 成员函数声明()后加&或&&表示什么):

C++中,成员函数声明后添加 & 或 && 表明这个成员函数是针对左值对象还是右值对象进行操作的。具体来说,在成员函数声明后加上 & 表示该成员函数只能被左值对象调用、而加上 && 表示该成员函数只能被右值对象调用。这种技术是C++11引入的,用于支持移动语义和更精细地控制对象的行为。

        更多is_function的重载,如下(非全部):

template <typename Ret, typename ...Args>
struct is_function<Ret(Args...) const> : std::true_type { constexpr static int flag = 3; };template <typename Ret, typename ...Args>
struct is_function<Ret(Args..., ...) const> : std::true_type { constexpr static int flag = 4; };template <typename Ret, typename ...Args>
struct is_function<Ret(Args...) volatile> : std::true_type { constexpr static int flag = 5; };template <typename Ret, typename ...Args>
struct is_function<Ret(Args..., ...) volatile> : std::true_type { constexpr static int flag = 6; };template <typename Ret, typename ...Args>
struct is_function<Ret(Args...) const volatile> : std::true_type { constexpr static int flag = 7; };template <typename Ret, typename ...Args>
struct is_function<Ret(Args..., ...) const volatile> : std::true_type { constexpr static int flag = 8; };template <typename Ret, typename ...Args>
struct is_function<Ret(Args...) &> : std::true_type { constexpr static int flag = 9; };template <typename Ret, typename ...Args>
struct is_function<Ret(Args..., ...) &> : std::true_type { constexpr static int flag = 10; };template <typename Ret, typename ...Args>
struct is_function<Ret(Args...) const &> : std::true_type { constexpr static int flag = 11; };template <typename Ret, typename ...Args>
struct is_function<Ret(Args..., ...) const &> : std::true_type { constexpr static int flag = 12; };template <typename Ret, typename ...Args>
struct is_function<Ret(Args...) volatile &> : std::true_type { constexpr static int flag = 13; };template <typename Ret, typename ...Args>
struct is_function<Ret(Args..., ...) volatile &> : std::true_type { constexpr static int flag = 14; };template <typename Ret, typename ...Args>
struct is_function<Ret(Args...) const volatile &> : std::true_type { constexpr static int flag = 15; };template <typename Ret, typename ...Args>
struct is_function<Ret(Args..., ...) const volatile &> : std::true_type { constexpr static int flag = 16; };

        以上仅是基础实现,libc++,libstdc++,MS stl还有更为简单的实现,如下:

template<class T>
struct is_function : std::integral_constant<bool,!std::is_const<const T>::value && !std::is_reference<T>::value
> {};

判断class类型

        原理:只有class类型才可以被用于指向成员的指针类型,即对于T Y::*一类的类型结构,Y只能是class类型,T可以选择任何类型。

#include <type_traits>
#include <iostream>template <typename T, typename = std::void_t<>>
struct is_class : std::false_type {};template <typename T>
struct is_class<T, std::void_t<int T::*>> : std::true_type {};struct s {};
class c {};
union u {};
enum e {};int main(int argc, char **argv) {std::cout << (is_class<s>::value ? "true" : "false") << std::endl;std::cout << (is_class<c>::value ? "true" : "false") << std::endl;std::cout << (is_class<u>::value ? "true" : "false") << std::endl;std::cout << (is_class<e>::value ? "true" : "false") << std::endl;auto f = []{};std::cout << (is_class<decltype(f)>::value ? "true" : "false") << std::endl;std::cout << (is_class<uint64_t>::value ? "true" : "false") << std::endl;std::cout << (is_class<int>::value ? "true" : "false") << std::endl;return 0;
}

        需要注意两点以下两点:

  • c++语言指出,lambda 表达式的类型是“唯一的,未命名的,非枚举 class 类型”,使用is_class萃取lambda表达式,得到的结果也是true
  • int T::*表达式同样适用于 union类型 

相关文章:

萃取的实现(三)

探测成员 基于SFINAE&#xff0c;判断一个给定类型T&#xff0c;是否含有名为x的成员。 探测类型成员 判断一个给定类型T&#xff0c;是否含有类型成员size_type&#xff0c;源码如下&#xff1a; #include <type_traits> #include <iostream> #include <vect…...

nodejs版本管理,使用 nvm 删除node版本,要删除 Node.js 的某个版本详细操作

要删除 Node.js 的某个版本并保持 Node Version Manager (nvm) 的管理整洁&#xff0c;可以按以下步骤操作&#xff1a; 步骤 1&#xff1a;查看已安装的 Node.js 版本 nvm ls这会列出你通过 nvm 安装的所有 Node.js 版本。输出类似于&#xff1a; -> v18.17.1v16.20…...

openCV中如何实现滤波

图像滤波用于去除噪声和图像平滑&#xff0c;OpenCV 提供了多种滤波器&#xff1a; 1.1. 均值滤波&#xff1a; import cv2# 读取图像 image cv2.imread("example.jpg")# 均值滤波 blurred_image cv2.blur(image, (5, 5)) # (5, 5) 是滤波核的大小 滤波核大小的…...

vscode通过ssh连接服务器实现免密登录+删除

文章目录 参考&#xff1a; 1、 vscode通过ssh连接服务器实现免密登录删除&#xff08;吐血总结&#xff09;...

智能硬件新时代,EasyRTC开启物联音视频新纪元

在万物互联的时代浪潮中&#xff0c;智能硬件正以前所未有的速度融入我们的生活&#xff0c;从智能家居的便捷控制&#xff0c;到智能穿戴设备的健康监测&#xff0c;再到工业物联网的高效管理&#xff0c;智能硬件的应用场景不断拓展。而在这个智能硬件蓬勃发展的背后&#xf…...

《机器学习数学基础》补充资料:求解线性方程组的克拉默法则

《机器学习数学基础》中并没有将解线性方程组作为重点&#xff0c;只是在第2章2.4.2节做了比较完整的概述。这是因为&#xff0c;如果用程序求解线性方程组&#xff0c;相对于高等数学教材中强调的手工求解&#xff0c;要简单得多了。 本文是关于线性方程组的拓展&#xff0c;供…...

mysql的rpm包安装

(如果之前下载过mariadb&#xff0c;使用yum remove mariadb卸载&#xff0c;因为mariadb与rpm包安装的mysql有很多相似的组件和文件&#xff0c;会发生冲突&#xff0c;而源码包安装的mysql不会&#xff0c;所以不用删除源码包安装myqsl&#xff0c;只删除mariadb就可以&#…...

TailwindCss的vue3安装使用

按照官网的安装教程&#xff0c;属性最后无法生效&#xff0c;这是自我改良版&#xff0c;添加了额外步骤&#xff0c;但是每次引入新的tailwindcss属性 需要重新跑一次项目 npm下载tailwindcss npm install -D tailwindcss npx初始化tailwind配置 npx tailwindcss init 此时根…...

foobar2000设置DSP使用教程及软件推荐

foobar2000安卓中文版&#xff1a;一款高品质手机音频播放器 foobar2000安卓中文版是一款备受好评的高品质手机音频播放器。 几乎支持所有的音频格式&#xff0c;包括 MP3、MP4、AAC、CD 音频等。不论是经典老歌还是最新的流行音乐&#xff0c;foobar2000都能完美播放。除此之…...

开源多商户商城源码最新版_适配微信小程序+H5+APP+PC多端

在数字化时代&#xff0c;电子商务已经成为各行业不可或缺的一部分&#xff0c;开源多商户商城源码为中小企业和个人开发者提供了快速搭建和定制电商平台的利器。分享一款最新版的开源多商户商城源码&#xff0c;它能够适配微信小程序、H5、APP和PC等多个端口&#xff0c;满足商…...

【matlab】大小键盘对应的Kbname

matlab中可以通过Kbname来识别键盘上的键。在写范式的时候&#xff0c;遇到一个问题&#xff0c;我想用大键盘上排成一行的数字按键评分&#xff0c;比如 Kbname(1) 表示键盘上的数字1&#xff0c;但是这种写法只能识别小键盘上的数字&#xff0c;无法达到我的目的&#xff0c;…...

go语言并发的最佳实践

Go 语言的并发模型是其最强大的特性之一&#xff0c;基于 CSP&#xff08;Communicating Sequential Processes&#xff09;理论&#xff0c;通过 goroutine 和 channel 实现轻量级并发. 一、并发核心概念 1. Goroutine 在 Go 语言中&#xff0c;Goroutine 是实现并发编程的…...

超全Deepseek资料包,deepseek下载安装部署提示词及本地部署指南介绍

该资料包涵盖了DeepSeek模型的下载、安装、部署以及本地运行的详细指南&#xff0c;适合希望在本地环境中高效运行DeepSeek模型的用户。资料包不仅包括基础的安装步骤&#xff0c;还提供了68G多套独立部署视频教程教程&#xff0c;针对不同硬件配置的模型选择建议&#xff0c;以…...

重看Spring聚焦BeanDefinition分析和构造

目录 一、对BeanDefinition的理解 &#xff08;一&#xff09;理解元信息 &#xff08;二&#xff09;BeanDefinition理解分析 二、BeanDefinition的结构设计分析 &#xff08;一&#xff09;整体结构体会 &#xff08;二&#xff09;重要接口和类分析 三、构造 BeanDef…...

从MySQL5.7平滑升级到MySQL8.0的最佳实践分享

一、前言 升级需求&#xff1a;将5.7.35升级到8.0.27, 升级方式 in-place升级【关闭现有版本MySQL&#xff0c;将二进制或包替换成新版本并在现有数据目录上启动MySQL并执行升级任务的方式&#xff0c;称为in-place升级】 原版本 5.7.35 CentOS Linux release 7.9.2009 新版本…...

Node-Red

是什么? ——基于浏览器的流程编辑工具可连接设备、服务器和API应用 一、安装 1、本地安装(Windows) 在电脑上安装 Node.js 环境,然后使用 NPM 安装 Node-RED Node.js介绍: JavaScript 在不同的位置有不一样的解析器 写入 HTML 的 JS 语言,浏览器即它的解析器需要独…...

GO语言的安装以及第一个Go语言程序

1. Go语言的安装与设置 官网:golang.org 国内下载:https://studygolang.com/dl 国内镜像:https://goproxy.cn/ 2. GOland的安装 Go 1.13 及以上&#xff08;推荐&#xff09; 打开你的终端并执行 $ go env -w GO111MODULEon $ go env -w GOPROXYhttps://goproxy.cn,direc…...

年前集训总结python

1.用空格隔开输出列表list1 print(" ".jion(map(str,list1))) 2.转换函数 int() 将一个数字或字符串转换为整数。可以指定基数&#xff0c;支持从其他进制&#xff08;如二进制、十六进制&#xff09;转换为十进制。 int(101, 2) # 二进制字符串转十进制 > 5 …...

【JAVA实战】JAVA实现Excel模板下载并填充模板下拉选项数据

背景 有这样一个场景&#xff1a;前端下载Excel模板&#xff0c;进行数据导入&#xff0c;这个下载模板过程需要经过后端接口去数据库查询数据进行某些列的下拉数据填充&#xff0c;下拉填充的数据过程中会出现错误String literals in formulas can’t be bigger than 255 cha…...

【linux】在 Linux 上部署 DeepSeek-r1:32/70b:解决下载中断问题

【linux】在 Linux 上部署 DeepSeek-r1:32/70b:解决下载中断问题 【承接商业广告,如需商业合作请+v17740568442】 文章目录 【linux】在 Linux 上部署 DeepSeek-r1:32/70b:解决下载中断问题问题描述:解决方法方法一:手动中断并重启下载方法二:使用 Bash 脚本自动化下载在…...

Rust 面试题

1. Rust 中的所有权(Ownership)是什么? 回答: 所有权是 Rust 的核心概念之一,每个值在 Rust 中都有一个唯一的所有者,并且所有权在作用域内转移。当所有者离开作用域时,Rust 会自动释放该值的内存,避免了内存泄漏。 代码示例: fn main() {let s1 = String::from(&q…...

Copilot Next Edit Suggestions(预览版)

作者&#xff1a;Brigit Murtaugh&#xff0c;Burke Holland 排版&#xff1a;Alan Wang 我们很高兴向你介绍在本次 Visual Studio Code 发布中&#xff0c;关于 GitHub Copilot 的三个预览功能&#xff1a; Next Edit Suggestions&#xff08;NES&#xff09;Copilot Edits 的…...

LeetCode - 18 四数之和

题目来源 18. 四数之和 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你一个由 n 个整数组成的数组 nums &#xff0c;和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] &#xff08;若两个四元组元素一一…...

汽车零部件开发应该具备哪些编程思维?

目录 1、功能安全思维 2、实时性与确定性思维 3、可靠性和冗余思维 4、硬件软件协同思维 5、CAN总线通信思维 6、故障诊断和自诊断思维 7、功耗优化思维 8、软件更新和版本管理思维 9、用户体验与安全性思维 汽车零部件开发中&#xff0c;嵌入式软件在车辆系统中的作用…...

在Windows系统中安装Open WebUI并连接Ollama

Open WebUI是一个开源的大语言模型&#xff08;LLM&#xff09;交互界面&#xff0c;支持本地部署与离线运行。通过它&#xff0c;用户可以在类似ChatGPT的网页界面中&#xff0c;直接操作本地运行的Ollama等大语言模型工具。 安装前的核心要求&#xff1a; Python 3.11&#…...

React 与 Vue 对比指南 - 上

React 与 Vue 对比指南 - 上 本文将展示如何在 React 和 Vue 中实现常见功能&#xff0c;从基础渲染到高级状态管理 Hello 分别使用 react 和 vue 写一个 Hello World&#xff01; react export default () > {return <div>Hello World!</div>; }vue <…...

自动化办公|通过xlwings进行excel格式设置

1. 介绍 xlwings 是一个强大的 Python 库&#xff0c;可以用来操作 Excel&#xff0c;包括设置单元格格式、调整行高列宽、应用条件格式以及使用内置样式。本文将详细介绍如何使用 xlwings 进行 Excel 格式化操作&#xff0c;并附带代码示例。 2. 基础格式设置&#xff08;字…...

c# -01新属性-模式匹配、弃元、析构元组和其他类型

文章目录 **学习摘抄分享**模式匹配概述Null 检查类型测试比较离散值关系模型多个输入ObServation列表模式弃元元组和对象析构利用switch的模式进行匹配对于out的方法调用独立弃元析构元组和其他类型元组方法一方法二方法三方法四使用弃元元组的元素使用弃元的用户定义类型解构…...

银河麒麟V10SP1下qt5-12-12编译环境配置

下面的脚本在兆芯版kylin上试过,如果是本地编译,arm版应该也没有问题 a. 如果执行脚本安装有问题&#xff0c;读者可以拷贝命令多次安装 b. 建议在虚拟机环境下进行 #!/bin/bashsudo apt updatesudo apt-get install g gcc make cmake gperf bison flex libdrm-dev libxcompos…...

轻松搭建本地大语言模型(一)Ollama安装与使用

Ollama 是一款开源的本地大语言模型运行框架&#xff0c;支持在 Windows、macOS 和 Linux 系统上运行&#xff0c;能够帮助用户轻松下载和使用各种大语言模型&#xff08;例如deepseek、llama、qwen&#xff09;。本文将详细介绍 Ollama 的安装步骤&#xff0c;帮助你快速搭建本…...

【深度学习】Transformer入门:通俗易懂的介绍

【深度学习】Transformer入门&#xff1a;通俗易懂的介绍 一、引言二、从前的“读句子”方式三、Transformer的“超级阅读能力”四、Transformer是怎么做到的&#xff1f;五、Transformer的“多视角”能力六、Transformer的“位置记忆”七、Transformer的“翻译流程”八、Trans…...

浅识Linux高阶用法

&#xff08;前记&#xff1a;内容有点多&#xff0c;先看目录再挑着看。&#xff09; 问题&#xff1a;在Java面试中&#xff0c;当被提问到Linux的高阶用法以及如何使用Linux将程序、数据库、中间件等部署在云上时&#xff0c;可以从以下几个方面进行回答&#xff1a; Li…...

I²C简介

前言 IC&#xff08;Inter-Integrated Circuit, 内置集成电路&#xff09;总线是由Philips公司&#xff08;现属于恩智浦&#xff09;在上世纪80年代开发的两线式串行通信总线&#xff0c;用于连接微控制器及其外围设备&#xff0c;控制设备之间的通信。 IC总线的物理拓扑示意…...

Linux定时静默执行桌面/前台程序

Linux服务器上的程序大都在后台默默的执行&#xff0c;但有些需要在前台或桌面上运行的程序该怎么办呢&#xff1f;这就要借助虚拟显示技术&#xff0c;也就是在后台运行时提供虚拟的显示&#xff0c;使前台运行的程序也能默默的在后台运行&#xff0c;这就是所谓的以“无头模式…...

Redux中间件redux-thunk和redux-saga的具体区别是什么?

Redux 中间件是增强 Redux 功能的重要工具&#xff0c;redux-thunk 和 redux-saga 是两个常用的中间件&#xff0c;它们在处理异步操作和副作用时提供了不同的方式和理念。以下是两者的具体区别&#xff1a; 1. 概念与设计理念 redux-thunk 简洁&#xff1a;redux-thunk 是一…...

【STM32】DRV8833驱动电机

1.电机如何转动 只需要给电机两个端子加一正一负的极性就会转起来了&#xff0c;但是要注意的是不要将电机两端直接接在5v和gnd之间&#xff0c;这种电机一般要提供几百毫安的电流&#xff0c;而GPIO口只能提供几毫安&#xff0c;所以我们使用一个DRV8833来驱动 DRV8833输入口…...

【go语言规范】 使用函数式选项 Functional Options 模式处理可选配置

如何处理可选配置&#xff1f; Config Struct 方式 (config-struct/main.go) 这是最简单的方式&#xff0c;使用一个配置结构体&#xff1a; 定义了一个简单的 Config 结构体&#xff0c;包含 Port 字段创建服务器时直接传入配置对象优点&#xff1a;简单直接缺点&#xff1a…...

vue中为组建添加样式的方式

在 Vue 中&#xff0c;可以通过多种方式为 view 添加样式&#xff0c;并且支持动态绑定样式。以下是几种常见的方式&#xff1a; 1. 内联样式 直接在模板中使用 style 属性来添加样式。 <template><div style"color: red; font-size: 14px;">这是一个…...

如何简单的去使用jconsloe 查看线程 (多线程编程篇1)

目录 前言 1.进程和线程 进程 PCB 的作用 并发编程和并行编程 线程 为什么选择多线程编程 2.在IDEA中如何简单创建一个线程 1. 通过继承Thread类 2. 通过实现 Runnable 接口 3. 使用 Lambda 表达式 3.如何简单使用jconsloe去查看创建好的线程 前言 2025来了,这是第…...

机器学习·最近邻方法(k-NN)

前言 上一篇简单介绍了决策树&#xff0c;而本篇讲解与决策树相近的 最近邻方法k-NN。 机器学习决策树-CSDN博客 一、算法原理对比 特性决策树最近邻方法&#xff08;k-NN&#xff09;核心思想通过特征分割构建树结构&#xff0c;递归划分数据基于距离度量&#xff0c;用最近…...

网络安全试题

ciw网络安全试题 &#xff08;1&#xff09;&#xff08;单选题&#xff09;使网络服务器中充斥着大量要求回复的信息&#xff0c;消耗带宽&#xff0c;导致网络或系统停止正常服务&#xff0c;这属于什么攻击类型? A、拒绝服务 B、文件共享 C、BIND漏洞 D、远程过程调用 &a…...

沃丰科技大模型标杆案例 | 索尼大模型智能营销机器人建设实践

AI大模型发展日新月异&#xff0c;国内外主流大模型每月必会升级。海外AI大模型市场由美国主导&#xff0c; 各模型已形成“多强竞合”的局面。中国积极响应全球大模型技术的发展趋势&#xff0c;高校、研究院所等科研机构、互联网企业&#xff0c;人工智能企业均不同程度地投入…...

​ ​rust学习四、控制语句

rust的控制语句和大部分语言没有什么区别&#xff0c;都是熟悉的for,while,loop,if。 比较不同的是&#xff0c;在绝大部分非常流行的语言中都有的switch&#xff0c;rust是没有的。 诸如c/c,java,javascript,c#。连PL/SQL都有case when语句。 一、基本的for、while、if&…...

会员购交易系统架构演进

本期作者 1.背景 会员购是B站2017年推出的IP消费体验服务平台&#xff0c;在售商品以手办、漫画、JK制服等贴合平台生态的商品为主。随着业务发展&#xff0c;会员购从最开始的预售&#xff0c;现货拓展到全款预售&#xff0c;盲盒&#xff0c;众筹等多种售卖方式&#xff0c;销…...

在Linux系统下修改Docker的默认存储路径

在Linux系统下修改Docker的默认存储路径可以通过多种方法实现&#xff0c;下边是通过修改daemon.json文件方式实现 查看当前Docker存储路径 使用命令 docker info | grep "Docker Root Dir" 查看当前Docker的存储路径&#xff0c;默认为 /var/lib/docker 停止Docker…...

【人工智能】释放数据潜能:使用Featuretools进行自动化特征工程

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 特征工程是机器学习流程中至关重要的一步,它直接影响模型的性能。然而,手动特征工程既耗时又需要领域专业知识。Featuretools是一个强大的…...

shell脚本备份PostgreSQL数据库和库下表

注意&#xff1a; 以下为对PostgreSQL13.16版本数据库备份shell脚本参考请确认备份节点上psql和pgdump的版本不至于太低&#xff0c;建议>13.16该脚本目前是对于整库、&#xff08;默认针对public这个schema&#xff0c;如果有其他schema&#xff0c;请自行添加一层循环&am…...

java面试笔记(一)

1. 一万个string类型的数据&#xff0c;设计一个算法如何按照String长度来排序 以使用 Arrays.sort() 方法&#xff0c;并结合一个自定义的比较器。以下是实现的示例代码: public class StringLengthSort {public static void main(String[] args) {// 定义一万个字符串的示例…...

网络分析仪E5071C的回波损耗测量

回波损耗&#xff08;Return Loss&#xff09;是评估射频/微波元件&#xff08;如滤波器、天线、电缆等&#xff09;信号反射特性的关键参数&#xff0c;反映端口阻抗匹配性能。E5071C矢量网络分析仪&#xff08;VNA&#xff09;通过以下步骤实现高精度回波损耗测量&#xff1a…...

sql注入中,如果information_schema被过滤,该怎么绕过

目录 一、sys.schema_auto_increment_columns 1.功能 2.利用思路 二、sys.schema_table_statistics_with_buffer 1.功能 2.利用思路 三、mysql.innodb_table_stats和mysql.innodb_index_stats 1.mysql.innodb_table_stats 1.1功能 2.mysql.innodb_index_stats 2.1功…...