C++模板实参推断

news2024/11/17 5:28:40

模板实参推断

我们已经看到,对于函数模板,编译器利用调用中的函数实参来确定其模板参数。

从函数实参来确定模板实参的过程被称为模板实参推断。

也就是说,只有函数参数才配有模板实参推断,函数返回类型是不配有的

在模板实参推断过程中, 编译器使用函数调用中的实参类型来寻找模板实参,用这些模板实参生成的函数版本与给定的函数调用最为匹配。

类型转换与模板类型参数

与非模板函数一样,我们在一次调用中传递给函数模板的实参被用来初始化函数的形参。

如果一个函数形参的类型使用了模板类型参数,那么它采用特殊的初始化规则。

只有很有限的几种类型转换会自动地应用于这些实参。

编译器通常不是对实参进行类型转换,而是生成一个新的模板实例。

与往常一样,顶层const无论是在形参中还是在实参中,都会被忽略。

在其他类型转换中,能在调用中应用于函数模板的包括如下两项。

  1. const转换:可以将一个非const对象的引用(或指针)传递给一个const的引用(或指针)形参。
  2. 数组或函数指针转换:如果函数形参不是引用类型,则可以对数组或函数类型的实参应用正常的指针转换。一个数组实参可以转换为一个指向其首元素的指针。类似的,一个函数实参可以转换为一个该函数类型的指针。

其他类型转换,如算术转换、派生类向基类的转换以及用户定义的转换,都不能应用于函数模板。

作为一个例子,考虑对函数fobj和fref的调用。fobj函数拷贝它的参数,而fref的参数是引用类型:

#include<iostream>
using namespace std;
template <typename T> 
T fobj(T a, T b) 
{
	cout << "调用 fobj(T,T)" << endl; 
	return a;
}// 实参被拷贝
template <typename T> 
T fref(const T& a, const T& b) 
{ 
	cout << "调用fref (const T&, const T&)" << endl;
	return a;
}// 引用
int main()
{
	string s1("a value");
	const string s2("another value");
	fobj(s1, s2); //调用 fobj(string, string);const被忽略
	fref(s1, s2); // 调用fref (const string&, const string)
		//将s1转换为const是允许的
	
	int a[10], b[42];
	fobj(a, b); //调用f(int*, int*)
	fref(a, b);//错误:数组类型不匹配
}

在第一对调用中,我们传递了一个string和一个const string。虽然这些类型不严格匹配,但两个调用都是合法的。

在fobj调用中,实参被拷贝,因此原对象是否是const没有关系。

在fxef调用中,参数类型是const的引用。对于一个引用参数来说,转换为const是允许的,因此这个调用也是合法的。


在下一对调用中,我们传递了数组实参,两个数组大小不同,因此是不同类型。

在fobj 调用中,数组大小不同无关紧要。两个数组都被转换为指针。fobj中的模板类型为int*。

但是,fref调用是不合法的,如果形参是一个引用,则数组不会转换为指针。a和b的类型是不匹配的,因此调用是错误的。


 

将实参传递给带模板类型的函数形参时,能够自动应用的类型转换只有const转换及数组或函数到指针的转换。

使用相同模板参数类型的函数形参

一个模板类型参数可以用作多个函数形参的类型。

由于只允许有限的几种类型转换,因此传递给这些形参的实参必须具有相同的类型。如果推断出的类型不匹配,则调用就是错误的。

例如,我们的compare函数接受两个const T参数,其实参必须是相同类型:

template<class T>
void compare(T a,T b)
{}
int main()
{
	long lng;
	compare(lng, 1024);//错误:不能实例化compare(long, int)
}

此调用是错误的,因为传递给compare的实参类型不同。

从第一个函数实参推断出的模板实参为long,从第二个函数实参推断出的模板实参为int。这些类型不匹配,因此模板实参推断失败。

如果希望允许对函数实参进行正常的类型转换,我们可以将函数模板定义为两个类型参数:

template<class A,class B>
void compare(A a,B b)
{}

现在用户可以提供不同类型的实参了:

	long lng;
	compare(lng, 1024);// 正确:调用 compare(long, int)

正常类型转换应用于普通函数实参

函数模板可以有用普通类型定义的参数,即,不涉及模板类型参数的类型。

这种函数实参不进行特殊处理;它们正常转换为对应形参的类型。
例如,考虑下面的模板:

template <typename T>

ostream &print(ostream sos, const T &obj)

{return os << obj;}

第一个函数参数是一个已知类型ostream&。第二个参数obj则是模板参数类型。

由于os的类型是固定的,因此当调用print时,传递给它的实参会进行正常的类型转换:

print(cout, 42);// 实例化print(ostream, int)
	ofstream f ("output"); 
	print(f,10); //使用print (ostream&, int);将f转换为ostream&

在第一个调用中,第一个实参的类型严格匹配第一个参数的类型。此调用会实例化接受一
个ostream&和一个int的print版本。

在第二个调用中,第一个实参是一个ofstream,它可以转换为ostream&。由于此参数的类型不依赖于模板参数,因此编译器会将f隐式转换为ostream。

#include<iostream>
#include<fstream>
using namespace std;
template <typename T>
ostream& print(ostream &os, const T& obj)
{
	return os << obj;
}
int main()
{

	print(cout, 42);// 实例化print(ostream, int)
		ofstream f ("output"); 
		print(f,10); //使用print (ostream&, int);将f转换为ostream&
}

 

如果函数参数不是模板参数,则对实参进行正常的类型转换

 函数模板显式实参

在某些情况下,编译器无法推断出模板实参的类型。

其他一些情况下,我们希望允许用户控制模板实例化。

当函数返回类型与参数列表中任何类型都不相同时,这两种情况最常出现。

指定显式模板实参

作为一个允许用户指定使用类型的例子,我们将定义一个名为sum 的函数模板,它接受两个不同类型的参数。

我们希望允许用户指定结果的类型。这样,用户就可以选择合适的精度。

我们可以定义表示返回类型的第三个模板参数,从而允许用户控制返回类型:

//编译器无法推断T1,它未出现在函数参数列表中
template <typename T1, typename T2, typename T3>
T1 sum(T2, T3)
{
}

在本例中,没有任何函数实参的类型可用来推断T1的类型。

每次调用sum时调用者都必须为T1提供一个显式模板实参。我们提供显式模板实参的方式与定义类模板实例的方式相同。

显式模板实参在尖括号中给出,位于函数名之后,实参列表之前

此调用显式指定T1的类型。而T2和T3的类型则由编译器从i和lng的类型推断出来。

显式模板实参按由左至右的顺序与对应的模板参数匹配;第一个模板实参与第一个模板参数匹配,第二个实参与第二个参数匹配,依此类推。

只有尾部(最右)参数的显式模板实参才可以忽略,而且前提是它们可以从函数参数推断出来。(意思就是返回类型不能忽略,因为它不能从函数参数中推导出来)

如果我们的 sum 函数按照如下形式编写:

// 糟糕的设计:用户必须指定所有三个模板参数
template <typename T1, typename T2, typename T3>
T3 alternative_sum(T2, T1){}

则我们总是必须为所有三个形参指定实参:

	//错误:不能推断前几个模板参数
	auto val3 = alternative_sum<long long>(i, lng);
	// 正确:显式指定了所有三个参数
	auto va12 = alternative_sum<long long, int, long>(i, lng);

这是因为只有最尾部(最右)参数的显式模板实参才可以忽略,而且前提是它们可以从函数参数中推导出来

 第一个调用忽略了最右边那个模板实参,即T3,但是T3是函数返回类型,是不能提供函数参数推断出来的,所以这个不能省略

正常类型转换应用于显式指定的实参

对于用普通类型定义的函数参数,允许进行正常的类型转换。

出于同样的原因,对于模板类型参数已经显式指定了的函数实参,也进行正常的类型转换:

#include<iostream>

using namespace std;
template <typename T> 
T compare(T a, T b) 
{
	return a;
}


int main()
{

		long lng=8;
		compare(lng, 1024); // 错误:模板参数不匹配
		compare<long>(lng, 1024); // 正确:实例化compare(long, long)
			compare<int>(lng,1024); // 正确:实例化compare(int, int)
}

 

如我们所见,第一个调用是错误的,因为传递给compare的实参必须具有相同的类型。

如果我们显式指定模板类型参数,就可以进行正常类型转换了。

因此,调用compare<long>等价于调用一个接受两个const long&参数的函数。int类型的参数被自动转化为long。在第三个调用中,T被显式指定为int,因此lng被转换为int


 

尾置返回类型与类型转换

当我们希望用户确定返回类型时,用显式模板实参表示模板函数的返回类型是很有效的。

但在其他情况下,要求显式指定模板实参会给用户增添额外负担,而且不会带来什么好处。

例如,我们可能希望编写一个函数,接受表示序列的一对迭代器和返回序列中一个元素的引用:

template <typename It>
??? &fen(It beg, It end)
{
// 处理序列
return *beg;//返回序列中一个元素的引用
}

我们并不知道返回结果的准确类型,但知道所需类型是所处理的序列的元素类型;

vector<int> vi = { 1, 2, 3, 4, 5 };
vector<string> ca = {"hi", "bye"};
	auto& i = fcn(vi.begin(),vi.end());// fcn应该返回int&
	auto& s = fcn(ca.begin(), ca.end());// fcn应该返回string

此例中,我们知道函数应该返回*beg,而且知道我们可以用decltype(*beg)来获取此表达式的类型。

但是,在编译器遇到函数的参数列表之前,beg都是不存在的。为了定义此函数,我们必须使用尾置返回类型。由于尾置返回出现在参数列表之后,它可以使用函数的参数:

template <typename It>
auto fcn(It beg, It end) -> decltype(*beg)
{// 处理序列
	return *beg;// 返回序列中一个元素的引用
}

此例中我们通知编译器fcn的返回类型与解引用beg参数的结果类型相同。

解引用运算符返回一个左值,因此通过decltype推断的类型为beg表示的元素的类型的引用。

因此,如果对一个 string 序列调用fcn,返回类型将是string&。如果是int序列,则返回类型是int&。

进行类型转换的标准库模板类

有时我们无法直接获得所需要的类型。

例如,我们可能希望编写一个类似 fcn的函数,但返回一个元素的值而非引用。

在编写这个函数的过程中,我们面临一个问题:对于传递的参数的类型,我们几乎一无所知。

在此函数中,我们知道唯一可以使用的操作是迭代器操作,而所有迭代器操作都不会生成元素,只能生成元素的引用。

为了获得元素类型,我们可以使用标准库的类型转换模板。这些模板定义在头文件type_traits中,这个头文件中的类通常用于所谓的模板元程序设计。类型转换模板在普通编程中也很有用。

在本例中,我们可以使用remove_reference来获得元素类型。

remove reference模板有一个模板类型参数和一个名为type的(public)类型成员。如果我们用一个引用类型实例化remove reference,则type将表示被引用的类型。

例如,如果我们实例化 remove reference<int&>,则type成员将是int。类似的,如果我们实例化remove reference<string&>,则type成员将是string,依此类推。更一般的,给定一个迭代器 beg:

remove_reference<decltype (*beg)>::type 

将获得 beg 引用的元素的类型:decltype(*beg)返回元素类型的引用类型,remove_reference::type脱去引用,剩下元素类型本身。

组合使用remove_reference、尾置返回及decltype,我们就可以在函数中返回元素值的拷贝:

//为了使用模板参数的成员,必须用typename
template <typename It>
auto fcn2(It beg, It end) -> typename remove_reference<decltype(*beg)>::type
{
	//处理序列
	return *beg; //返回序列中一个元素的拷贝 
}


注意,type 是一个类的成员,而该类依赖于一个模板参数。因此,我们必须在返回类型的声明中使用typename来告知编译器,type表示一个类型

标准类型转换模板
对 Mod<T>,其中 Mod 为若T为则Mod<T>::type为
remove_referenceX&或X&& 
否则 

X

T

add_constX&、const X或函数
否则

T

const T

add_lvalue_reference

X

X&&

否则

T

X&

T&

add_rvalue_reference

X&或者X&&

否则

T

T&&

remove_pointer

X*

否则

X

T

add_pointer

X&或者X&&

否则

X*

T*

make_signed

unsigned X

否则

X

T

make_unsigned

带符号类型

否则

unsigned X

T

remove_extent

X[n]

否则

X

T

remove_all_extents

X[n1][n2]...

否则

X

T

表中描述的每个类型转换模板的工作方式都与remove_reference类似。

每个模板都有一个名为type的public成员,表示一个类型。

此类型与模板自身的模板类型参数相关,其关系如模板名所示。

如果不可能(或者不必要)转换模板参数,则type成员就是模板参数类型本身。

例如,如果T是一个指针类型,则remove_pointer<T>::type是T指向的类型。如果T不是一个指针,则无须进行任何转换,从而type具有与T相同的类型

函数指针和实参推断

当我们用一个函数模板初始化一个函数指针或为一个函数指针赋值时,编译器使用指针的类型来推断模板实参。

例如,假定我们有一个函数指针,它指向的函数返回int,接受两个参数,每个参数都是指向const int的引用。

我们可以使用该指针指向compare的一个实例:

template <typename T>
int compare(const T&, const T&){}
// pf1指向实例 int compare(const int&, const int&)
int (*pfl) (const int&, const int&) = compare;


pf1中参数的类型决定了T的模板实参的类型。在本例中,T的模板实参类型为int。指针pfl指向compare的int版本实例。如果不能从函数指针类型确定模板实参,则产生错误:

#include <string>
using namespace std;

template <typename T>
int compare(const T& a, const T& b) {
    // ... some comparison logic ...  
    return 0;
}

int main() {
   

    // 下面是两个func的重载版本  
    void func(int(*)(const string&, const string&));
    void func(int(*)(const int&, const int&));

   
    func(compare);  // 或者 func(compare<int>);  
   
    return 0;
}
void func(int(*)(const string&, const string&)) {}
void func(int(*)(const int&, const int&)) {}



这段代码的问题在于,通过func的参数类型无法确定模板实参的唯一类型。对func的调用既可以实例化接受int的compare版本,也可以实例化接受string的版本。由于不能确定 func的实参的唯一实例化版本,此调用将编译失败。


我们可以通过使用显式模板实参来消除func调用的歧义:

// 正确:显式指出实例化哪个 compare版本
func (compare<int>);// 传递 compare(const int&, const int&)

此表达式调用的func版本接受一个函数指针,该指针指向的函数接受两个const int&参数。

#include <string>
using namespace std;

template <typename T>
int compare(const T& a, const T& b) {
    // ... some comparison logic ...  
    return 0;
}

int main() {
   

    // 下面是两个func的重载版本  
    void func(int(*)(const string&, const string&));
    void func(int(*)(const int&, const int&));

   
    func(compare<int>);  // 或者 func(compare<int>);  
   
    return 0;
}
void func(int(*)(const string&, const string&)) {}
void func(int(*)(const int&, const int&)) {}

当参数是一个函数模板实例的地址时,程序上下文必须满足:对每个模板参数,能唯一确定其类型或值。

模板实参推断和引用

为了理解如何从函数调用进行类型推断,考虑下面的例子:

template <typename T> void f(T &p);

其中函数参数p是一个模板类型参数T的引用,非常重要的定记住两点:

  1. 编译器会应用正常的引用绑定规则;
  2. const是底层的,不是顶层的。

从左值引用函数参数推断类型

当一个函数参数是模板类型参数的一个普通(左值)引用时(即,形如T&),

绑定规则告诉我们,只能传递给它一个左值(如,一个变量或一个返回引用类型的表达式)。

实参可以是const类型,也可以不是。如果实参是const的,则T将被推断为const类型。

#include <string>
using namespace std;

template <typename T> void f1(T&) {}// 实参必须是一个左值
// 对fl的调用使用实参所引用的类型作为模板参数类型

int main() {
    int i;
    const int ci = 1;
    f1(i); // i是一个int;模板参数类型T是int
       f1(ci); // ci是一个const int;模板参数T是const int
    f1(5); // 错误:传递给一个&参数的实参必须是一个左值
}

 

 


如果一个函数参数的类型是const T&,正常的绑定规则告诉我们可以传递给它任何类型的实参——一个对象(const或非const)、一个临时对象或是一个字面常量值。

当函数参数本身是const时,T的类型推断的结果不会是一个const类型。

const已经是函数参数类型的一部分;因此,它不会也是模板参数类型的一部分:

template <typename T> void f2(const T&) {}//可以接受一个右值
// f2 中的参数是const & ;实参中的const是无关的
// 在每个调用中,f2的函数参数都被推断为 const int&

int main() {

    int i;
    const int ci = 1;
    f2(i); // i是一个int;模板参数T是int
    f2(ci);// ci是一个const int,但模板参数T是int
    f2(5); // 一个const & 参数可以绑定到一个右值;T是int
}

从右值引用函数参数推断类型

当一个函数参数是一个右值引用(即,形如T&&)时,正常绑定规则告诉我们可以传递给它一个右值。

当我们这样做时,类型推断过程类似普通左值引用函数参数的推断过程。

推断出的T的类型是该右值实参的类型:

template<typename T> 
void f3(T&&) {}

    f3(42);//实参是一个int类型的右值;模板参数T是int


引用折叠和右值引用参数

假定i是一个int对象,我们可能认为像f3(i)这样的调用是不合法的。毕竟,是一个左值,而通常我们不能将一个右值引用绑定到一个左值上。

但是,C++语言在正常绑定规则之外定义了两个例外规则,允许这种绑定。这两个例外规则是move这种标准库设施正确工作的基础。

第一个例外规则影响右值引用参数的推断如何进行。

当我们将一个左值(如i)传递给函数的右值引用参数,且此右值引用指向模板类型参数(如T&&)时,编译器推断模板类型参数为实参的左值引用类型。

因此,当我们调用f3(i)时,编译器推断T的类型为int&,而非int.

template<typename T> 
void f3(T&&) {}

    int i;
    f3(i);

T被推断为int&看起来好像意味着f3的函数参数应该是一个类型int 的右值引用

通常,我们不能(直接)定义一个引用的引用。但是,通过类型别名或通过模板类型参数间接定义是可以的。

在这种情况下,我们可以使用第二个例外绑定规则:如果我们间接创建一个引用的引用,则这些引用形成了“折叠”。在所有情况下(除了一个例外),引用会折叠成一个普通的左值引用类型。

在新标准中,折叠规则扩展到右值引用。

只在一种特殊情况下引用会折叠成右值引用:右值引用的右值引用。

总的来说即,对于一个给定类型X;

  • X& &,X&  &&和X&&  &都折叠成类型X&
  • 类型X&& &&折叠成X&&

引用折叠只能应用于间接创建的引用的引用,如类型别名或模板参数。

如果将引用折叠规则和右值引用的特殊类型推断规则组合在一起,则意味着我们可以对一个左值调用f3。

当我们将一个左值传递给f3 的(右值引用)函数参数时,编译器推断T为一个左值引用类型:

template<typename T> 
void f3(T&&) {}


    int i;
    const int ci = 1;
    f3(i);//  实参是一个左值;模板参数T是int &
    f3(ci);// 实参是一个左值;模板参数T是一个 const int&



当一个模板参数T被推断为引用类型时,折叠规则告诉我们函数参数T&&折叠为一个左值引用类型。

例如,f3(i)的实例化结果可能像下面这样:

//无效代码,只是用于演示目的
void f3<int&>(int& &&);// 当T是int&时,函数参数为int& &&

f3的函数参数是T&&且T是int&,因此T&&是int&&&,会折叠成int&

因此,即使f3的函数参数形式是一个右值引用(即,T&&),此调用也会用一个左值引用类型(即,int&)实例化f3:

void f3<int&>(int&);//当少是int&时,函数参数折登为int&

这两个规则导致了两个重要结果:

  • 如果一个函数参数是一个指向模板类型参数的右值引用(如,T&&),则它可以被绑定到一个左值;且
  • 如果实参是一个左值,则推断出的模板实参类型将是一个左值引用,且函数参数将做实例化为一个(普通)左值引用参数(T&)

另外值得注意的是,这两个规则暗示,我们可以将任意类型的实参传递给T&&类型的函数参数。对于这种类型的参数,(显然)可以传递给它右值,而如我们刚刚看到的,也可以传递给它左值。

如果一个函数参数是指向模板参数类型的右值引用(如,T&&),则可以传递给它任意类型的实参。如果将一个左值传递给这样的参数,则函数参数被实例化为一个普通的左值引用(T&)

编写接受右值引用参数的模板函数

模板参数可以推断为一个引用类型,这一特性对模板内的代码可能有令人惊讶的影响:

template <typename T>
void f3(T&& val)
{

    T t = val ://拷贝还是排定一个引用 ?
    t = fcn(t);// 赋值只改变t还是既改变t又改变val?
    if (val == t) (/*...*/)//若T是引用类型,则一直为true
}

当我们对一个右值调用f3时,例如字面常量42,T为int。在此情况下,局部变量t的类型为int,且通过拷贝参数val的值被初始化。当我们对t赋值时,参数val保持不变。

另一方面,当我们对一个左值i调用f3时,则T为int&。当我们定义并初始化局部变量t时,赋予它类型int&。因此,对t的初始化将其绑定到val。当我们对t赋值时,也同时改变了val的值。在f3的这个实例化版本中,if判断永远得到true。

当代码中涉及的类型可能是普通(非引用)类型,也可能是引用类型时,编写正确的代码就变得异常困难(虽然remove_reference这样的类型转换类可能会有帮助)。

在实际中,右值引用通常用于两种情况:模板转发其实参或模板被重载

目前应该注意的是,使用右值引用的函数模板通常使用下面这种方式来进行重载:

template<typename T> void f(T&&); //绑定到非const右值
template <typename T> void f(const T&);// 左值和const右值


与非模板函数一样,第一个版本将绑定到可修改的右值,而第二个版本将绑定到左值或const右值。
 

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

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

相关文章

每日面经分享(python part1)

Python中的深拷贝和浅拷贝的区别是什么&#xff1f; a. 浅拷贝创建一个新的对象&#xff0c;但其中的可变元素仍然共享引用。只有对象的第一层被复制&#xff0c;而更深层次的嵌套对象仍然是引用。更改其中一个对象的属性会影响到其他对象。 b. 深拷贝创建一个完全独立的新对象…

营销中的归因人工智能

Attribution AI in marketing 归因人工智能作为智能服务的一部分&#xff0c;是一种多渠道算法归因服务&#xff0c;根据特定结果计算客户互动的影响和增量影响。有了归因人工智能&#xff0c;营销人员可以通过了解每个客户互动对客户旅程每个阶段的影响来衡量和优化营销和广告…

MT3017 上色

思路&#xff1a;使用分治&#xff0c;在每个连续区域递归调用heng()和shu() #include <bits/stdc.h> using namespace std; int n, m; int h[5005];int shu(int l, int r) {return r - l 1; } int heng(int l, int r) {int hmin 0x3f3f3f3f;for (int i l; i < r;…

银行数字化转型导师坚鹏:银行数字化转型给总行带来的9大价值

银行数字化转型给总行带来的9大价值 银行数字化转型对总行的深远影响是多方面的&#xff0c;银行数字化转型导师坚鹏从以下9个方面进行详细分析&#xff0c;相信能够给您带来重要价值。 1. 客户价值 银行数字化转型可以利用大数据、智能化风控模型为客户设计、提供“千人千面…

多模态系列-综述Video Understanding with Large Language Models: A Survey

本文是LLM系列文章,针对《Video Understanding with Large Language Models: A Survey》的翻译。 论文链接:https://arxiv.org/pdf/2312.17432v2.pdf 代码链接:https://github.com/yunlong10/Awesome-LLMs-for-Video-Understanding 大型语言模型下的视频理解研究综述 摘要…

Python学习笔记-Flask接收post请求数据并存储数据库

1.引包 from flask import Flask, request, jsonify from flask_sqlalchemy import SQLAlchemy 2.配置连接,替换为自己的MySQL 数据库的实际用户名、密码和数据库名 app Flask(__name__) #创建应用实列 app.config[SQLALCHEMY_DATABASE_URI] mysqlpymysql://ro…

Linux笔记之制作基于ubuntu20.4的最小OpenGL C++开发docker镜像

Linux笔记之制作基于ubuntu20.4的最小OpenGL C开发docker镜像 —— 2024-04-03 夜 code review! 文章目录 Linux笔记之制作基于ubuntu20.4的最小OpenGL C开发docker镜像1.这里把这本书的例程代码放在了Dockerfile所在的文件夹内以使镜像预装例程代码2.创建Dockerfile3.构建Do…

c++前言

目录 1. 什么是 C 2. C 发展史 3. C 的重要性 4. 如何学习 C 5. 关于本门课程 1. 什么是C C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的问题&#xff0c;规模较大的 程序&#xff0c;需要高度的抽象和建模时&#xff0c; C 语言则不合适…

ShardingJdbc+Mybatis实现多数据源

Mybatis多数据源 这个是对shardingjdbc应用的一个升级&#xff0c;如果对于shardingjdbc的整合还没看过之前的文章的&#xff0c;可以先看看文章https://blog.csdn.net/Think_and_work/article/details/137174049?spm1001.2014.3001.5501 整合步骤 1、依赖 和全新项目的单…

【异常错误】 Expected to have finished reduction in the prior iteration before star、find_unused_parameters

运行代码时出现了错误&#xff1a; RuntimeError: Expected to have finished reduction in the prior iteration before starting a new one. This error indicates that your module has parameters that were not used in producing loss. You can enable unused parameter …

VSCODE使用VSIX安装扩展

VSCode安装扩展特别慢&#xff0c;使用命令行安装告别龟速&#xff1a; code --install-extension当然&#xff0c;我这个是在WSL 的linux上安装的&#xff0c;Windows一样的。 VSCode扩展商店网页链接&#xff1a;https://marketplace.visualstudio.com/vscode

Ceph分布式存储系统以及高可用原理

Ceph分布式存储系统以及高可用原理 1. Ceph原理和架构1.1 分布式存储系统抽象1.2 Ceph基本组件 2 Ceph中的策略层2.1 CRUSH进行数据分发和定位2.2 PG(Placement Group): 集群管理的基本单元2.3 PG的代理primary OSD2.4 轻量级的集群元数据ClusterMap2.5 对PG的罗辑分组&#xf…

面试总结------2024/04/04

1.面试官提问&#xff1a;你说你在项目中使用springsecurity jwt 实现了登录功能&#xff0c;能简单讲一下怎么实现的吗&#xff1f; 2.使用RabbitMQ实现订单超时取消功能 订单状态定义 首先&#xff0c;我们需要定义订单的不同状态。在这个示例中&#xff0c;我们可以定义以下…

分享three.js实现乐高小汽车

前言 Web脚本语言JavaScript入门容易&#xff0c;但是想要熟练掌握却需要几年的学习与实践&#xff0c;还要在弱类型开发语言中习惯于使用模块来构建你的代码&#xff0c;就像小时候玩的乐高积木一样。 应用程序的模块化理念&#xff0c;通过将实现隐藏在一个简单的接口后面&a…

shell的编写

文章目录 1.框架2.命令行3.获取用户命令字符串4.命令行字符串分割5.执行命令和内建命令6.完整代码&#xff1a; 1.框架 我们知道shell是一直存在的&#xff0c;所以首先我们第一步就是要搭建一个框架&#xff0c;使其一直存在。 那么也很简单&#xff0c;一个while循环就可以完…

(科研实践篇)大模型相关知识

1.embedding 1.介绍&#xff1a; embedding就是用一个低纬的向量表示一个物品。而这个embedding向量的实质就是使距离相似的向量所对应的物品具有相似的含义&#xff08;参考皮尔逊算法和cos余弦式子&#xff1a;计算相似度&#xff09;简单来说&#xff0c;就是用空间去表示…

1.Docker简介和安装

1 Docker 简介 1.1 Docker 是什么&#xff1f; docker是一个开源的应用容器引擎。 1.2 容器是什么&#xff1f; 容器是一种轻量级的虚拟化技术 &#xff0c;它是一个由应用运行环境、容器基础镜像组成的集合。 以 Web 服务 Nginx 为例&#xff0c;如下图所示&#xff1a;Ngin…

【并发编程】CountDownLatch

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;并发编程 ⛺️稳中求进&#xff0c;晒太阳 CountDownLatch 概念 CountDownLatch可以使一个获多个线程等待其他线程各自执行完毕后再执行。 CountDownLatch 定义了一个计数器&#xff0c;…

贝锐蒲公英企业路由器双机热备,保障异地组网可靠、不中断

对于关键业务&#xff0c;比如&#xff1a;在线支付系统、远程医疗监控系统、重要数据中心等&#xff0c;一旦网络发生故障&#xff0c;可能导致巨大的损失或影响&#xff0c;因此需确保网络拥有极高的可靠性、稳定性和容错能力。 面对此类场景和需求&#xff0c;贝锐蒲公英异…

优秀网站收藏——持续更新

1、Uiverse.io 官网&#xff1a;Open-Source UI elements for any project Uiverse.io是一个开源免费的UI组件库&#xff0c;直接使用HTML和CSS组成&#xff0c;可以方便的使用在任何前端框架上。它包含了丰富的UI组件类型&#xff0c;如按钮、复选框、开关、卡片、加载动画、…