C++模板函数重载规则细说

引言

模板编程,指的是可以我们可以将函数或者类的数据类型抽离出来,做到类型无关性。我们关注的对象,是普通函数、普通类。如下面的这个经典的模板函数:

template<typename T>
bool compare(T t1,T t2) {return t1 >t2;
}

我们可以使用一份代码,来判断两个相同的类型的对象,t1是否大于t2。

而模板元编程,则是对模板函数、模板类本身,进行编程。继续上面的代码例子,假如有一些类型,他并没有>运算符,只有<=运算符,那么我们需要重载两个模板函数,对这两个类型的数据进行分类:

// 函数
template<typename T>
bool compare(T t,T t2) {return t > t2;
}
// 函数
template<typename T>
bool compare(T t,T t2) {return t <= t1;
}

拥有>运算符的类型进入函数1,拥有<=运算符进入函数2。我们这里对模板类型进行判断、选择的过程,就是模板元编程。可以说,模板编程,是将数据类型从函数或者类抽离出来;而模板元编程,则是对类型进行更加细致的划分,分类别进行处理。

这个时候可能有读者会有疑问:这不就是类型识别吗?我用typeid也可以实现啊,例如以下代码:

template<typename T> 
void show(T t) {if(typeid(T).hash_code()==...) {t.toString();} else {t.toType();}
}

这种写法是错误的。上面代码例子中无法通过编译,原因是T类型无法同时拥有toString()和toType()函数,即使我们的代码只会运行其中一个路径。其次:

  • typeid在多动态库环境下,会出现不一致的问题,并不是非常可靠。
  • typeid只能对已有的数据类型进行判断,无法判断新增类型。
  • 会导致函数臃肿,判断条件众多,代码不够优雅。

原因有很多,这里列举了几条,一句话总结就是不可靠、不适用、不优雅。因此我们才需要模板元编程。

那么,如何在模板中实现对类型的判断并分类处理呢?我们接着往下看。

文章内容略长,我非常建议你完整阅读,但是如果时间比较紧,可以选择性阅读章节:

开始:从一个具体的例子从0到1解析模板元编程

模板函数重载匹配规则+模板匹配规则:介绍模板编程最核心的两个规则,他是整个模板元编程依赖的基础

最后的章节进行全文的总结

开始

我们先从一个例子来看模板元编程是如何工作的。我们创建一个类HasToString,其作用是判断一个类型是否有toString成员函数,使用的代码如下:

template<typename T> HasToString{...}
class Dog {
};
class Cat {
public:std::string toString() const{return "cat";}
};
std::cout << "Dog:" << HasToString<Dog>::value << std::endl;  // 输出
std::cout << "Cat:" << HasToString<Cat>::value << std::endl;  // 输出

通过类HasToString,我们可以判断一个类型是否有toString这个成员函数。好,接下来让我们看一下HasToString是如何实现的:

// 判断一个类型是否有 toString 成员函数
template<typename T>
class HasToString {template<typename Y, Y y>class Helper {};template<typename U = T>constexpr static bool hasToString(...) {return false;}template<typename U = T>constexpr static bool hasToString(Helper<std::string (U::*)() const,&U::toString>*)  {return true;}
public:const static bool value = hasToString<T>(nullptr);
};

好家伙,这也太复杂了!!完全没看懂。你是否有这样的感觉呢?如果你是第一次接触,感觉比较复杂很正常,现在我们无需完全理解他,下面我们一步步慢慢说。

首先有两个c++的其他知识先解释一下:constexpr关键字和成员函数指针,了解的读者可以直接跳过。

constexpr:表示一个变量或者函数为编译期常量,在编译的时候可以确定其值或者函数的返回值。在上面的代码中,const static bool value 需要在编译器确定其值,否则不能在类中直接复制。因此我们给hasToString函数增加了constexpr关键字。

成员函数指针:我们可以获取一个对象的成员函数指针,而在合适的时候,调用此函数。如下代码

std::string (Cat::*p)() const = &Cat::toString; // 获取Cat的函数成员指针
Cat c;
std::string value = (c.*p)(); // 通过成员函数指针调用c的成员函数

可以看到成员函数指针的声明语法和函数指针很相似,只是在前面多了Cat::表示是哪个类的指针。

这里仅简单介绍,其他更详细的内容,感兴趣可以百度一下了解。

好,我们第一步先看到HasToStringvalue变量,他是一个const static bool类型,表示T类型是否有toString函数的结果。他的值来源于hasToString<T>(nullptr),我们继续看到这个函数。

hasToString是一个返回值为bool类型的模板函数,由于其为constexpr static类型,使得其返回值可以直接赋值给value。他有两个重载实例:

  • 第一个重载函数的参数为函数参数包
  • 第二个重载函数的参数为Helper对象的的指针

我们暂时先不管Helper的内容,当我们调用hasToString<T>(nullptr)时,他会选择哪个重载函数?答案是不管T类型如何,都会先进入第二个重载函数。原因是,第二个重载函数相比第一个更加特例化:实参与形参均为指针类型,根据模板函数匹配规则,他的优先级更高,因此会选择第二个重载函数进行匹配。

到这里,我们已经可以明确,在编译时,不管T的类型如何,均会调用到hasToString的第二个重载函数。这个时候,我们看到模板类Helper,他的模板类型很简单,第一个模板参数是Y,而第二个模板参数则为第一个模板类型的对象值。

看到hasToString第二个重载函数,其参数为一个Helper类型指针。其中,Helper的第一个模板类型描述了成员函数toString的函数类型,第二个模板参数获取模板类型U的成员函数toString的指针。这一步可以保证类型U拥有成员函数toString,且类型为我们所描述的函数类型。

好,到这里就可能有两种情况:

  • 假如类型U拥有toString成员函数,那么函数匹配正常,hasToString实例化成功。
  • 假如类型U没有toString成员函数,此时会匹配失败,因为&U::toString无法通过编译。这个时候,根据c++的模板匹配规则,匹配失败并不会直接导致崩溃,而是会继续寻找可能的函数重载

对于类型Dog,他没有toString成员函数,hasToString第二个重载函数匹配失败,此时会继续寻找hasToString的其他重载类型。到了第一个重载类型,匹配成功,类型Dog匹配到hasToString第一个重载函数。

这里就是我们整个HasToString的重点:他成功将含toString成员函数的类型,与不含toString成员函数的类型成功分到两个不同重载函数中去,完成我们判断的目的。

这,就是模板元编程。

好了,对于一开始我们觉得很复杂的代码,我们也基本都了解了,可以先暂时松一口气,先来回顾一下上面的内容:

// 判断一个类型是否有 toString 成员函数
template<typename T>
class HasToString {template<typename Y, Y y>class Helper {};template<typename U = T>constexpr static bool hasToString(...) {return false;}template<typename U = T>constexpr static bool hasToString(Helper<std::string (U::*)() const,&U::toString>*)  {return true;}
public:const static bool value = hasToString<T>(nullptr);
};
  • 我们创建了一个模板类HasToString来判断一个类型是否拥有toString成员函数,并将结果存储在静态常量value中。
  • value的值来源于静态模板函数hasToString的判断,我们将该函数设置为constexpr类型,因此可以直接将返回值赋值给value
  • 利用模板函数重载匹配规则,将函数调用优先匹配到hasToString的第二个重载函数进行匹配。
  • 我们创建了Helper辅助模板类,来描述我们需要的成员函数类型,并获取类型的成员函数。
  • 利用模板匹配规则,匹配失败的类型,将进入hasToString的第一个重载函数进行匹配,实现类型的选择。

整个过程最核心的部分,是模板函数hasToString的重载与匹配。而其所依赖的,是我们重复提到模板函数重载匹配规则、模板匹配规则,那么接下来,我们来聊聊这个匹配规则的内容。

模板函数重载匹配规则

模板函数重载匹配规则,他规定着,当我们调用一个具有多个重载的模板函数时,该选择哪个函数作为我们的调用对象。与普通函数的重载类似,但是模板属性会增加一些新的规则。

模板函数重载匹配规则可以引用《c++ primer》中的一段话来总结:

对于一个调用,其候选函数包括所有模板实参推断成功的函数模板实例。

候选的函数模板总是可行的,因为模板实参推断会排除任何不可行的模板。

与往常一样,可行函数(模板与非模板)按类型转换 (如果对此调用需要的话)来排序。当然,可以用于函数模板调用的类型转换是非常有限的。

与往常一样,如果恰有一个函数提供比任何其他函数都更好的匹配,则选择此函数。 但是,如果有多个函数提供同样好的匹配,则:

  • 如果同样好的函数中只有一个是非模板函数,则选择此函数。
  • 如果同样好的函数中没有非模板函数,而有多个函数模板,且其中一个模板比其他模板更特例化,则选择此模板。
  • 否则,此调用有歧义。

看着有点不知所以然,我们一条条来看。这里我给整个过程分为三步:

第一步:模板函数重载匹配会将所有可行的重载列为候选函数。

举个例子,我们现在有以下模板函数以及调用:

template<typename T> void show(T t) {...}  // 形参为T
template<typename T> void show(T* t) {...} // 形参为T*
int i =;
show(i);
show(&i);

代码中模板函数show有两个重载函数,其形参不同。当调用show(i)时,第一个重载函数T可以匹配为int类型,第二重载函数,无法完成int类型到指针类型的匹配,因此本次调用的候选重载函数只有第一个重载函数。

第二个调用show(&i),第一个重载函数T可以匹配为int*类型,第二个重载函数T可以匹配为int类型,因此本地调用两个重载函数都是候选函数。

选择候选函数是整个匹配过程的第一步,过滤掉那些不符合的重载函数,再进行后续的精确选择。

第二步:候选可行函数按照类型转换进行排序

匹配的过程中,可能会发生类型转换,需要类型转换的优先级会更低。看下面代码:

template<typename T> void show(T* t) {...}       // 形参为T*
template<typename T> void show(const T* t) {...} // 形参为const T*
int i =;
show(&i);

show两个重载函数均作为候选函数。第一个函数的形参会被匹配为int*,而第二个重载函数会被匹配为const int*,进行了一次非const指针到const指针的转换。因此前者的优先级会更高。

类型转换,主要涉及volatile和const转换,上面的例子就是const相关的类型转换。类型转换是匹配过程中的第二步。

此外,还有char*到std::string的转换,也属于类型转换。字符串字面量,如"hello"属于const char*类型,编译器可以完成到std::string的转化。

第三步:若第二步存在多个匹配函数,非模板函数优先级更高;若没有非模板函数,则选择特例化更高的函数。

到了这一步,基本选择出来的都是精确匹配的函数了。但是却存在多个精确匹配的函数,需要按照一定规则进行优先级排序。看下面例子代码:

template<typename T> void show(T t) {...}  // 形参为T
template<typename T> void show(T* t) {...} // 形参为T*
void show(int i) {...} // 非模板函数
int i =;
show(i);
show(&i);

在上面代码中,show(i)的调用,有两个精确匹配的函数,第一个和第三个重载函数。但是,第三个重载函数为非模板函数,因此其优先级更高,选择第三个重载函数。

show(&i)调用中,可以精确匹配到第一个和第二个重载函数。但是第二个函数相比第一个会更加特例化,他描述的形参就是一个指针类型。因此选择第二个重载函数版本。

到此基本就能选择最佳匹配的重载函数版本。若最后出现了多个最佳匹配,则本地调用时有歧义的,调用失败。

这里需要注意的一点是,引用不属于特例化的范畴,例如以下的代码在调用时是有歧义的:

template<typename T> void show(T t) {...}  // 形参为T
template<typename T> void show(T& t) {...} // 形参为T&
int i =;
show(i); // 调用失败,无法确定重载版本

好了,这就是整个模板函数重载的匹配过程,主要分三步:

  • 选择所有可行的候选重载函数版本
  • 根据是否需要进行类型转换进行排序
  • 优先选择非模板类型函数;若无非模板函数则选择更加特例化的模板函数。若出现多个最佳匹配函数则调用失败

了解了模板函数重载的匹配过程,那么我们就能在进行模板元编程的时候,对整体的匹配过程有把握。除了模板函数重载匹配规则,还有一个重要的规则需要介绍:模板匹配规则。

模板匹配规则

模板,有两种类型,模板函数和模板类。模板类没有和模板函数一样的重载过程,且在使用模板类时需要指定其模板类型,因此其貌似也不存在匹配过程?不,其实也存在一种场景具有类似的过程:默认模板参数。看下面的例子:

template<typename T,typename U = int>
struct Animal {};
template<typename T>
struct Animal<T,int> {};
Animal<int> animal;

模板类Animal有两个模板参数,第二个模板参数的默认类型为int。代码中特例化了<T,int>类型,与第二个模板参数的默认值保持一致。当我们使用Animal<int>实例化时,Animal两个模板参数被转化为<int,int>,模板匹配会选择特例化的版本,也就是template<typename T> struct Animal<T,int>版本。这个过程有点类似我们前面的模板函数重载匹配过程,但是本质上是不同的,模板类的匹配过程不涉及类型转换,完全是精确类型匹配。但在行为表现上有点类似,因此在这里补充说明一下。

这里我们要介绍一个更加重要的规则:SFINAE法则

这个法则很简单:模板替换导致无效代码,并不会直接抛出错误,而是继续寻找合适的重载。我们还是通过一个例子来理解:

// 判断一个类型是否有 toString 成员函数
template<typename T>
class HasToString {template<typename Y, Y y>class Helper {};template<typename U = T>constexpr static bool hasToString(...) {return false;}template<typename U = T>constexpr static bool hasToString(Helper<std::string (U::*)() const,&U::toString>*)  {return true;}
public:const static bool value = hasToString<T>(nullptr);
};

这是我们前面的例子,当我们调用hasToString<T>(nullptr)时,模板函数hasToString的两个重载版本都是精确匹配,但是后者为指针类型,更加特例化,因此优先选择第二个重载版本进行替换。到这里应该是没问题的。

但是,如果我们的类型T不含toString成员函数,那么在这个部分Helper<std::string (U::*)() const,&U::toString>会导致替换失败。这个时候,按照SFINAE法则,替换失败,并不会抛出错误,而是继续寻找其他合适的重载。在例子中,虽然第二个重载版本替换失败了,但是第一个重载版本也是精确匹配,只是因为优先级没有第二个高,这个时候会选择第一个重载版本进行替换。

前面我们在讲模板函数重载规则时提到了候选函数,在匹配完成后发生替换失败时,会在候选函数中,按照优先级依次进行尝试,直到匹配到替换成功的函数版本。

这一小节前面提到的模板类的默认模板参数场景,也适用SFINAE法则。看下面的例子:

class Dog {};
template<typename T,typename U = int>
struct Animal {};
template<typename T>
struct Animal<T, decltype(declval<T>().toString(),int)> {};
Animal<Dog> animal;

代码中有一个关键字std::declval,有些读者可能并不熟悉。

declval的作用是构建某个类型的实例对象,但是又不能真正去执行构建过程,一般结合decltype使用。例如代码中的例子,我们利用declval构建了类型T的实例,并调用了其toString的成员函数。使用decltype保证这个过程并不会被执行,仅做类型获取,或者匹配的过程。更详细的建议读者搜索资料进一步了解,declval是c++14以后的新特性,如果是c++11则无法使用。

根据前面的内容,我们知道Animal<Dog>会匹配到特例化的版本,但是由于Dog类型没有toString成员函数,会导致替换失败。这时候会回到第一个非特例化的版本,进行替换。

好了,通过这两个例子,读者应该也能理解SFINAE法则的内容。模板重载匹配规则,是整个模板元编程中最核心的内容,利用这个规则,就可以在整个匹配的流程的不同的重载中,函数重载或者类特例化,选择我们需要的类型,并将其他不需要的类型根据匹配流程继续寻找匹配的目标,从而完成我们对数据类型的选择

这个过程其实有点类似于流转餐厅:厨师放下的食物是数据类型,每个客户是重载版本,流水线是模板匹配规则流程,每个客户选择自己喜爱的食物,并将不感兴趣的食物利用流水线往后传,每个食物最终都到了感兴趣的客户中。当然如果最终无人感兴趣,则意味着匹配出错。

使用

到此,我们对于模板元编程核心内容就了解完成了。那么在实际中如何去使用呢?这里给出笔者的一些经验。

首先,必须要明确目的,不要为了使用技术而使用技术。模板元编程,能完成的功能是,在模板重载中实现对类型的判断与选择。当我们有这个需求的时候,可以考虑使用模板元编程,这里举几个常见场景。

我们回到我们最开始的那个例子:比较大小。假如一个类型拥有<操作,采用<运算符进行比较,否则采用>=运算符进行比较。这里我们采用默认模板参数的方式进行编写:

template<typename T,typename U = int>
struct hasOperate {constexpr static bool value = false;
};
template<typename T>
struct hasOperate<T, decltype(declval<T>() < declval<T>(),int())> {constexpr static bool value = true;
};

这样通过value值就可以获取到结果。那么我们很容易写出下面的代码:

template<typename T> bool compare(const T& t,const T& t2) {if(hasOperate<T>::value) {return t < t2;} else {return t >= t1;}
}

好了,大功告成。运行一下,诶,怎么编译不过?这个问题在文章前面有简单提到。对于类型T,他可能只有两种操作符其中的一种,例如以下类型:

class A {
public:explicit A(int num) : _num(num){}bool operator<(const A& a) const{return _num < a._num;}int _num;
};

A类型只有<操作符,并没有>=操作符,上面的模板函数实例化之后会变成下面的代码:

bool compare(const A& t,const A& t2) {if(hasOperate<A>::value) {return t < t2;} else {return t >= t1;  // 这里报错,找不到>=操作符}
}

代码中,即使我们的else逻辑不会运行到,但编译器会检查所有关于类型A的调用,再抛出找不到操作符的错误。那么我们该如何操作呢,有两个思路。

第一个思路是直接在hasOperate结构体中,分别编写各自的处理函数。这样能解决一些问题,但是局限性比较大,不够灵活。

另一个思路就是我要给你介绍的一个非常好用工具类std::enable_if。有了它之后我们可以这么使用:

template<typename T>
bool compare(typename std::enable_if<hasOperate<T>::value,T>::type t,T t2) {return t < t2;
}
template<typename T>
bool compare(typename std::enable_if<!hasOperate<T>::value,T>::type t,T t2) {return t >= t1;
}

感觉有点不太理解,没事,我们先来了解一下他。enable_if的实现代码很简单:

template<bool enable,typename T> 
struct enable_if {};
template<typename T> 
struct enable_if<true,T> {using type = T;
};

他是一个模板结构体,第一个参数是一个布尔值,第二个是一个泛型T。其特例化了布尔值为true的场景,并增加了一个type别名,反之如果布尔值为false,则没有这个type类型。

回到我们前面使用代码,我们使用hasOperate<T>::value来获取该类型是否拥有指定操作符,如果没有则获取不到type类型,那么整个替换过程就会失败,需要继续寻找其他的重载。这样就实现对类型的选择。

系统库中,还提供了很多类型判断接口可以和enable_if一起使用。例如判断一个类型是否为指针std::is_pointer<>、数组std::is_array<>等。例如我们可以创建一个通用的析构函数,根据是否为数组类型进行析构:

template<typename T> void deleteAuto(typename std::enable_if<std::is_array<T>::value,T>::type t) {delete[] t;
}
template<typename T> void deleteAuto(typename std::enable_if<!std::is_array<T>::value,T>::type t) {delete t;
}
int array[];
int *pointer = new int();
deleteAuto<decltype(array)>(array);    // 使用数组版本进行析构
deleteAuto<decltype(pointer)>(pointer);// 使用指针版本进行析构

结合模板具体化与enable_if,也可以实现对一类数据的筛选。例如我们需要对数字类型进行单独处理。首先需要编写判断类型是否为数组类型的代码:

template<typename T> constexpr bool is_num() { return false; }
template<> constexpr bool is_num<int>() { return true; }
template<> constexpr bool is_num<float>() { return true; }
template<> constexpr bool is_num<double>() { return true; }
...

注意这里的函数必须要声明为constexpr,这样才能在enable_if中使用。补充好所有我们认为是数字的类型,就完成了。使用模板类也是可以完成这个任务的:

template<typename T> struct is_num {constexpr static bool value = false;
};
template<> struct is_num<int> {constexpr static bool value = true;
};
... // 补充其他的数字类型

使用静态常量来表示这个类型是否为数字类型。静态常量也可以使用标准库的类,减少代码量,如下:

template<typename T> struct is_num : public false_type {};
template<> struct is_num<int> : public true_type{};
... // 补充其他的数字类型

改为继承的写法,但原理上是一样的。

有了以上的判断,就可以使用enable_if来分类处理我们的逻辑了:

template<typename T> void func(typename std::enable_if<is_num<T>(),T>::type t) {//...
}
template<typename T> void func(typename std::enable_if<!is_num<T>(),T>::type t) {//...
}

使用enable_if的过程中,还需要特别注意,避免出现重载歧义,或者优先级问题导致编程失败。

最后,再补充一点关于匹配过程的类型问题。还是上面判断是否是数字的例子,看下面的代码:

int i =;
int &r = i;
func<decltype<r>>(r); // 无法判断是数字类型

在我们调用func<decltype<i>>(i);时,i的类型是const int,而我们具体化是template<> constexpr bool is_num<int>() { return true; },他的模板类型是int,这是两个不同的类型,无法对应。因此判断此类型为非数字类型。

导致这个问题不止有const,还有volatile和引用类型。如int&、volatile int等。解决这个问题的方法有两个:

  • 在具体化中,增加const int等类型,但是枚举所有的类型非常繁杂且容易遗忘。
  • 在匹配之前,对数据类型进行去修饰处理。

第二种方法,c++提供函数处理。std::remove_reference<T>::type移除类型的引用,std::remove_cv<T>::type移除类型的const volatile修饰。因此我们在调用前可以如此处理:

template<typename T>
using remove_cvRef = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
int i =;
int &r = i;
func<remove_cvRef<decltype<r>>(r); // 移除引用修饰,转化为int类型

关于类型推断相关的问题这里不多展开,但要特别注意由于类型修饰导致的匹配失败问题。

最后

文章真的长呀,如果你能坚持看到这里,说明你是一个非常坚持且对编程有强烈兴趣的人,希望这篇文章让你在c++模板的路上有所帮助。

那么接下来我们再来回顾一下这篇文章的内容。

  • 我们先介绍了模板元编程要解决的场景与问题
  • 然后我们从一个具体的模板元编程例子展开,一步步学习了模板元编程的整体内容
  • 接下来针对其核心:模板函数重载匹配规则以及模板规则进一步了解
  • 最后再给出在使用方面的一些经验供参考

模板元编程他要解决的最核心的问题就是:对模板类型的判断与选择。而其所依赖的最核心的内容是模板函数重载匹配规则以及SFINAE法则,他是我们模板元编程得以实现的基础。需要注意,整个元编程发生在编译期,任何的函数调用都无法通过编译。其次需要类型的推断导致的匹配错误问题,而且此错误比较隐蔽难以发现。

最后,模板元编程十分强大,但涉及的相关内容多,容易出错。只有当我们十分确定要使用模板元编程解决的问题,再去使用他。切不可为了使用而使用,成为自己炫技的工具,这会给代码留下很多的隐患。

参考

C++之std::declval-CSDN博客

C++之std::enable_if-CSDN博客

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

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

相关文章

matlab设置colorbar标题的两种方式

%% 第一种 figure; A rand(3,4,3); A1 A(:,:,1); A2 A(:,:,2); A3 A(:,:,3); contourf(A1,A2,A3,30); colormap(jet);colorbar; my_handlecolorbar; my_handle.Label.String depth/km; my_handle.Label.FontSize 15;%% 第二种 figure; A rand(3,4,3); A1 A(:,:,1); A2 …

新概念英语第二册(2)

【New words and expressions】生词和短语&#xff08;5&#xff09; until prep. 直到 outside adv. 外面 ring v. &#xff08;铃、电话等&#xff09;响&#xff08;rang, rung&#x…

隔壁小孩馋哭了都要问我要的MySQL数据库攻略

1 Mysql数据库 1.1 数据库概念 数据 描述事物的符号记录 包括数字、文字、图形、图像声音、档案记录等 以“记录”形式按统一的格式进行存储 表 将不同的记录组织在一起 用来存储具体数据 数据库 标的合集&#xff0c;是存储数据的仓库 以定的组织方式存储打的相互有…

linux Systemd为裸机goLand应用保驾护航

这里是weihubeats,觉得文章不错可以关注公众号小奏技术&#xff0c;文章首发。拒绝营销号&#xff0c;拒绝标题党 背景 线上有一个Goland的应用程序&#xff0c;goland语言和java不同&#xff0c;如果有任何异常就直接挂掉退出&#xff0c;异常处理要实现 try catch也比较麻烦…

等级保护安全的管理机构与管理制度

目录 安全管理机构的控制点 岗位设置 人员配备 授权和审批 沟通和合作 审核和检查 安全管理制度的控制点 安全管理制度 指定和发布 评审和修订 安全管理机构的控制点 岗位设置 人员配备 授权和审批 沟通和合作 审核和检查 安全管理制度的控制点 安全管理制度 指定…

云卷云舒:算力网络+云原生(中):探索构建算力网络数据库

一、导言 1、基础要求&#xff1a;算力网络时代&#xff0c;数据类型、范围充分延展和爆发&#xff0c;数据库也要适应起来&#xff0c;分布式数据库是起步要求&#xff1b; 2、近期需求&#xff1a;通过云服务的方式提供算网时代&#xff0c;尤其是智能大模型所需要的向量数…

042、文本与语言模型

之——nlp基础 目录 之——nlp基础 杂谈 正文 1.文本预处理 2.语言模型 3.语言模型实现 杂谈 在语言模型中&#xff0c;需要对文本进行预处理&#xff0c;进行数字化的一系列操作&#xff0c;而后才能进行网络的拟合。 以前的相关&#xff1a;词性判断 正文 1.文本预处…

简易的555函数信号发生器电路图

函数信号发生器是一种信号发生装置&#xff0c;能产生某些特定的周期性时间函数波形&#xff08;正弦波、方波、三角波、锯齿波和脉冲波等&#xff09;信号&#xff0c;频率范围可从几个微赫到几十兆赫。除供通信、仪表和自动控制系统测试用外&#xff0c;还广泛用于其他非电测…

基于深度学习的安全帽检测识别系统(含UI界面、yolov5、Python代码、数据集)

项目介绍 项目中所用到的算法模型和数据集等信息如下&#xff1a; 算法模型&#xff1a;     yolov5 yolov5主要包含以下几种创新&#xff1a;         1. 添加注意力机制&#xff08;SE、CBAM、CA等&#xff09;         2. 修改可变形卷积&#xff08;DySnake-主…

输入url后回车发生了什么(持续更新)

在大多数情况下&#xff0c;浏览器被认为是单线程的。也就是说&#xff0c;他们从头到尾执行一项任务&#xff0c;然后再开始另一项任务。为了实现流畅的交互&#xff0c;开发人员的目标是确保高性能的站点交互&#xff0c;从平滑滚动到触摸响应。渲染时间是关键&#xff0c;确…

Wireshark网络工具来了

Wireshark是网络包分析工具。网络包分析工具的主要作用是尝试捕获网络包&#xff0c;并尝试显示包的尽可能详细的情况。 Wireshark是一个免费开源软件&#xff0c;不需要付费&#xff0c;免费使用&#xff0c;可以直接登陆到Wireshark的官网下载安装。 在windows环境中&#x…

FPFA.一种二倍频电路代码描述以及测量详情

一、前言 1、因为需要倍频电路所以找了个二倍频的电路&#xff0c;通过fpga实际测量发现经过倍频后的电路峰值降低。不过这个也正常&#xff0c;因为该电路只要过触发点就会开始发生波形变化&#xff0c;而电路的触发值不是峰值。​​​​​​​ 2、继续对电路做倍频后信号做二…

LeetCode_Java判断给出的字符串是否是合法的括号序列

public class BracketSequence {public static void main(String[] args) {System.out.println(isValid("([])"));}public static boolean isValid(String s) {if(snull || s.length()0){return true;}//新建栈&#xff0c;用以存储左括号Stack<Character> sta…

嵌入式开发常见的3个C语言技巧

​1.操作寄存器 在嵌入式开发中&#xff0c;常常要操作寄存器&#xff0c;对寄存器进行写入&#xff0c;读出等等操作。每个寄存器都有自己固有的地址&#xff0c;通过C语言访问这些地址就变得尤为重要。 #define GSTATUS1 (*(volatile unsigned int *)0x560000B0)在这里…

国外加固Appdome环境检测与绕过

文章目录 前言第一部分&#xff1a;定位检测逻辑的通用思路1. 通过linux“一切皆文件”思路定位2. 分析现有检测软件猜测可能检测点3. 通过正向开发思路定位4. 通过activity及弹窗定位 第二部分&#xff1a;检测结果展示整体流程1. Jni反射调用doDispath完成广播发送2. NativeB…

第11章 GUI Page439 步骤十二 为图元编号 支持直线

运行效果&#xff1a; 关键代码&#xff1a; 为IItem类新增三个函数&#xff0c;两个纯虚 为直线类&#xff0c;新增一个_index成员&#xff0c;并实现GetIndex和SetIndex两个函数 窗口类新增一个 _item_id成员&#xff0c;并初始化 在将图元压入队列之前&#xff0c;设置图元…

多继承与多重继承

多继承与多重继承 实验介绍 多继承与多重继承虽然只相差一个字,但是却是两个不同的概念。实验首先是要区分多继承与多重继承,其次是要学习多继承与多重继承的使用方式。 知识点 多继承与多重继承概念继承构造函数多继承与多重继承概念 多继承与多重继承可以从字面上理解。…

IDEA2018升级2023,lombok插件不兼容导致get/set方法无法使用

1、问题 最近了解到一款叫CodeGeeX 的智能编程助手&#xff0c;想要试用一下&#xff0c;但是IDEA2018版本太低了&#xff0c;没有CodeGeeX插件&#xff0c;于是打算将IDEA升级到2023.2.5版本&#xff0c;具体升级过程略过&#xff0c;升级完成后&#xff0c;启动项目&#xf…

图像去噪综述

传统方法 最经典最新的图像去噪算法 (360doc.com) 图像去噪算法综述 - 百度文库 (baidu.com) 传统图像去噪总结_传统图像降噪算法-CSDN博客 图像降噪有哪些方法&#xff1f; (qq.com) 深度学习方法 综述 | 图像去噪方法比较 (qq.com)

【强化学习】PPO:近端策略优化算法

近端策略优化算法 《Proximal Policy Optimization Algorithms》 论文地址&#xff1a;https://arxiv.org/pdf/1707.06347.pdf 一、 置信域方法(Trust Region Methods) ​ 设 π θ o l d \pi_{\theta_{old}} πθold​​是先前参数为 θ o l d \theta_{old} θold​的策略网…