C++ primer 第十六章

模板是C++中泛型编程的基础,一个模板就是一个创建类或函数的蓝图。

1.定义模板

模板适用于唯一的差异是参数的类型,函数体完全一致的情况。

1.1、函数模板

我们可以定义一个通用的函数模板用来生成针对特定类型的函数版本

模板定义以关键字template开始,后跟模板参数列表

template<typename T> int compare(const T& v1, const T& v2)
{...}

在模板定义中,模板参数列表不能为空。 

模板参数列表类似于函数参数列表,模板参数表示在类或函数定义中用到的类型或值

当使用模板时,我们(隐式或显式)指定模板实参将其绑定到模板参数上。

当我们调用一个函数模板时,编译器通常用函数实参为我们推断模板类型。

编译器用推断出的模板参数来为我们实例化一个特定版本的函数。

compare(1,0); //实参类型为int,编译器推断出的类型为int,将它绑定到模板参数T上

这些由编译器生成的特定版本的函数通常被称为模板的实例。 

一般来说,可以将类型参数看作类型说明符,类型参数可以用来指定返回类型或函数的参数类型,以及在函数体内用于变量声明或类型转换

template<typename T> T foo(T* p)  //指定返回类型和函数参数类型
{
    T tmp = *p;  //声明变量
    return tmp;
}

每个类型参数前必须使用关键字class或typename。 

允许在模板中定义非类型参数一个非类型参数表示一个值而非一个类型

当一个模板被实例化时,非类型参数会被用户所提供的或编译器推断出的值(常量表达式)替代。

template<unsigned N, unsigned M> int compare(const char(&p1)[N],const char(&p2)[M]) {}
//N和M都是char数组的长度,是字面常量

一个非类型参数可以是一个整数,或者是一个指向对象或函数类型的指针或左值引用。

绑定到指针或引用非类型参数的实参必须具有静态的生存期(static变量)。

函数模板可以声明为inline或constexpr的,说明符放在模板参数列表之后,返回类型之前。

template<typename T> inline T min(const T&, const T&);

模板程序应该尽量减少实参类型的要求。 

只有当我们实例化出模板的一个特定版本时,编译器才会生成代码。

我们将类定义和函数声明放在头文件中,而普通函数和类的成员函数的定义放在源文件中。

函数模板和类模板成员函数的定义通常放在头文件中。

模板直到实例化时才会生成代码,因此大多数编译错误在实例化期间报告。

作为调用者应保证传递给模板的实参支持模板所要求的操作,以及操作能在模板内正确运行。

1.2、类模板

与函数模板不同,编译器不能为类模板推断模板参数类型,我们需要指定模板的具体类型。

template<typename T> class blob{
 ...
};

blob<int> a;   //空blob<int>
blob<string> b = {"hi","hello","good"}; //有3个元素的blob<string>

类模板的每个实例都形成一个独立的类,不会与其他类有任何关联,也不会有特殊访问权限。

我们可以在类模板内部,也可以在类模板外部为其定义成员函数,且定义在内部的函数被隐式声明为内联函数。

类模板的成员函数本身是一个普通函数,但类模板的每个实例都有自己版本的成员函数。

定义在类模板之外的成员函数必须以关键字template开始,后接类模板参数列表。

template<typename T> void blob<T>::compare(const T&,constT&) {...}
//定义在类外部的成员函数compare

与其他任何定义在类模板外的成员一样,构造函数的定义要以模板参数开始。

template<typename T> blob<T>::blob() : data(std::make_shared<std::vector<T>>()) {}

默认情况下,一个类模板的成员函数只有当程序用到它时才会进行实例化。 

当我们使用一个类模板类型时必须提供模板实参,但在类模板自己的作用域中,我们可以直接使用模板名而不提供实参。

template<typename T> class blobstr{
     //...
     blobstr& operator++(); //返回的是blobstr&,而不是blobstr<T>&
     blobstr& operator--();
};

当我们在类模板外定义其成员时,遇到类名时表示进入类的作用域,因此在函数体内可直接使用模板名而不必指明模板实参。

template<typename T> blobstr<T> blobstr<T>::operator(int)
{  
    blobstr ret = *this;   //直接使用模板名
    ++*this;
    return ret;
}

 若一个类模板包含一个非模板友元,则友元被授权可以访问所有的模板实例。

若友元本身为模板,则类可以授权所有的友元模板实例,也可以只授权给特定实例。 

友元的声明用类模板的模板形参作为自己的模板实参,则友元关系被限定在用相同类型实例化的版本。

template<typename> class blobstr;  //前置声明,在blob中声明友元所需要的
template<typename> class blob;
template<typename T> class blob{
   friend class blobstr<T>;   //友元关系限定在相同类型T的版本
   ...
};

友元的声明使用与类本身不同的模板参数,则类将类模板的每个实例声明为自己的友元。 

class total{
    template<typename T> friend class pal;
  //pal的所有实例都是total的友元,这种情况无须前置声明
};

在新标准中,我们可以将模板类型参数声明为友元。

template<typename type> class bar{
    friend type;   //将访问权限授予用来实例化bar的类型
    ...
};
//对于某个类型名,该类将成为bar<type>的友元

类模板的一个实例定义了一个类类型,因此可以通过typedef来引用已实例化的类。

由于模板不是一个类型,我们不能定义一个typedef来引用一个模板。

typedef blob<T> strblob; //错误

 新标准允许我们为类模板定义一个类型别名,一个模板类型别名是一族类的别名。

template<typename T> using twin = pair<T,T>;
twin<string> authors; //authors是一个pair<string,string>

当我们定义一个模板类型别名时,可以固定一个或多个模板参数。

template<typename T> using part = pair<T,unsigned>;
part<string> book;  //book是一个pair<string,unsigned>

类模板可以声明static成员。

模板类的每个static数据成员必须有且只有一个定义,但是类模板的每个实例都有一个独有的static对象。

template<typename T> class foo{
private:
   static std::size_t n;
};

foo<string> fs;  //实例化static成员foo<string>::n
foo<int> is;  //实例化static成员foo<int>::n

我们可以通过类类型对象来访问一个类模板的static成员,使用作用域运算符直接访问成员。

foo<int> fi;
auto ct = foo<int>::n; //实例化foo<int>::n
ct = foo.n; 

1.3、模板参数 

一个模板参数名的可用范围是在其声明之后,至模板声明或定义结束之前。

模板参数会隐藏外层作用域中生命的相同的名字,在模板内不能重用模板参数名

typedef double A;
template<typename A,typename B> void f(A a,B b)
{
    A tmp = a;   //tmp的类型为模板参数A的类型,而非double
    double B;   //错误,重声明模板参数B
}

模板的声明必须包含模板参数,定义中的模板参数的名字不必与声明中相同。 

template<typename T> int compare(const T&, const T&); //模板声明

template<typename type> type compare(const type& a,const type& b) {...} //模板定义

一个给定模板的每个声明和定义必须有相同数量和种类(类型或非类型)的参数。

一个特定文件所需要的所有模板的声明通常一起放置在文件开始位置。

为了处理模板,编译器必须知道名字表示一个类型还是一个数据成员。

T::size_type * p
//编译器不知道size_type是模板T的类型成员还是一个数据成员

 为了解决上述问题,C++语言默认假定通过作用域运算符访问的名字是数据成员

 若我们希望通知编译器一个名字表示类型时,必须使用关键字typename来实现。

template<typename T> template T::value_type top(const T& c) //显式表示value_type是类型
{ return typename T::value_type(); }

在新标准中,我们可以为函数和类模板提供默认实参。

template<typename T, typename F = less<T>> int compare(const T& v1,const T& v2,F f = F())
{} //给类型F提供默认实参

与函数默认实参一致,对于一个模板参数,只有当它右侧的所有参数都有默认实参时,它才能有默认实参。

无论何时使用一个类模板,我们都必须在模板名之后接上尖括号。

若我们希望使用默认实参,则在模板名之后跟一个空尖括号即可。

template<class T = int> class numbers {...};

numbers<> average;   //T为int
numbers<double> prec; //T为double

 1.4、成员模板

 类(普通类或类模板)可以包含本身是模板的成员函数(成员模板),成员模板不能是虚函数

class debugdelete{
public:
    template<typename T> void operator()(T* p) const
      { 
        os << "delete ptr" << std::endl;
        delete p;
      }
};

double* p = new double;  
debugdelete d;  //创建对象
d(p);  //实例化operator()(double*)版本,释放p
int* ip = new int;
debugdelete()(ip); //在一个临时的debugdelete对象上调用operator()(int*)

 对于类模板,我们可以为其定义成员模板。

template<typename T> class blob{
    template<typename It> blob{It b,It e); //构造函数接受不同类型的迭代器
};

当我们在类模板外定义一个成员模板时,必须同时为类模板和成员模板提供模板参数列表。 

template<typename T> template<typename It>
blob<T>::blob(It b,It e) : data(std::make_shared<std::vector<T>>(b,e)) {}
//类模板的参数列表在前,后跟成员自己的模板参数列表

为了实例化一个类模板的成员模板,我们必须同时提供类和函数模板的实参。

int ia[] = {1,2,3,4,5,6};
blob<int> al(begin(ia),end(ia)); //a1的定义实例化了blob<int>::blob(int*,int*)的版本

 1.5、控制实例化

当两个或多个独立编译的源文件使用了相同的模板,并提供了相同的模板参数时,每个文件中就都会有该模板的一个实例,造成了额外的开销。

在新标准中,我们可以通过显式实例化来避免这种问题的发生。

当编译器遇到extern模板声明时,它不会在本文件中生成实例化代码,但这些模板类型必须在程序其他位置(文件)进行实例化。

extern template class blob<string>;  //blob<string>的代码不会在本文件生成
blob<string> sa1,sa2; //实例化会在其他位置出现

对于一个给定的实例化版本,可能有多个extern声明,但必须只有一个定义。 

 对于每个实例化声明,在程序中某个位置必须有其显式的实例化定义。

一个类模板的实例化定义会实例化该模板的所有成员,包括内联的成员函数。

2.模板实参推断

对于函数模板,编译器从函数实参来确定模板实参的过程被称为模板实参推断

2.1、类型转换与模板类型参数

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

在调用时自动应用于函数模板的类型转换有如下两项:

  • const转换:可以将一个非const对象的引用(指针)传递给一个const的引用(指针)形参。
  • 数组或函数指针转换:若函数形参不是引用类型,则可以对数组或函数类型的实参应用到正常的指针转换。(数组实参转换为指向首元素的指针、函数实参转换为函数类型的指针)
template<typename T> T foa(T,T);
template<Typename T> T fob(const T&,const T&);

string s1("hello");
const string s2("world");
foa(s1,s2); //调用foa(string,string);const被忽略
fob(s1,s2); //调用fob(const string&,const string&); s1转换为const的

int a[5];
int b[10];
foa(a,b);  //调用foa(int*,int*); 数组实参转换为指向首元素的指针
fob(a,b);  //错误,两个数组大小不同,因此类型不同,数组类型不匹配

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

 由于只允许有限的几种类型转换,因此传递给函数形参的实参必须具有相同的类型。

long lg;
compare(lg,1024); //错误,不能实例化compare(long,int)

若希望对函数实参进行正常的类型转换,可以将函数模板定义为两个类型参数。 

template<typename A, typename B> int compare(const A& v1, const B& v2) {...}

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

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

template<typename T> ostream& print(ostream& os,const T& obj)
{ return os << obj; }

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

2.2、函数模板显式实参 

当函数返回类型与参数列表中任何类型都不相同时,编译器将无法推断出模板实参的类型。

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

template<typename T1,typename T2,typename T3> T1 sum(T2,T3);
//T1作为返回类型,编译器无法推断T1,它未出现在函数参数列表中

在sum中,没有任何函数实参的类型可用来推断T1的类型,因此每次调用sum时都必须为T1提供了一个显式模板实参

auto val = sum<long long>(1024,2.30); //long long sum(int,double)

显式模板实参按由左至右的顺序与对应的模板参数匹配,只有最右侧参数的显式模板参数可忽略。

对于模板类型参数已经显式指定了的函数实参,也可以进行正常的类型转换。

long lg;
compare(lg,1024); //错误,模板参数不匹配
compare<long>(lg,1024); //正确,显式指定模板类型参数,实例化compare(long,long);

2.3、尾置返回类型与类型转换

当我们知道函数的返回类型与所处理的序列的元素类型相同,可以使用尾置操作来指定返回类型。

尾置返回出现在参数列表之后,它可以直接使用函数的参数。

//尾置返回允许我们在参数列表之后声明返回类型
template<typename T> auto fcn(It beg,It end)-> decltype(*beg)
{ return *beg; }

所有迭代器操作都不会生成元素,只能生成元素的引用,为了得到元素类型,可以使用标准库中的类型转换模板

类型转换模板定义在头文件type_traits中。

标准类型转换模板
对mod<T>,其中mod为若T为则mod<T>::type为
remove_reference

X&或X&&

否则

X

T

add_const

X&,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

template<typename It> 
auto fcn2(It beg,It end) -> typename remove_reference<decltype(*beg)>::type
{ return *beg; }
//remove_reference::type脱去引用,剩下元素的类型本身
//在返回类型的声明中使用typename来告知编译器,type是一个类型

 2.4、函数指针和实参推断

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

template<typename T> int compare(const T&,const T&);
int (*p)(const int&,const int&) = compare;
//p中参数的类型决定了T的模板实参的类型,T的模板实参类型为int

若不能从函数指针类型确定模板实参,则将产生错误。

当参数是一个函数模板实例的地址时,对于每个模板参数,必须唯一确定其类型或值

void func(int(*)(const int&, const int&));
void func(int(*)(const string&, const string&));

func(compare); //错误,通过func的参数类型无法确定模板实参的唯一类型

func(compare<int>); //正确,通过使用显式模板实参来消除func调用的歧义

2.5、模板实参推断和引用 

编译器会应用正常的引用绑定规则。

当一个函数参数时模板类型参数的一个普通左值引用时,只能传递给它一个左值。

template<typename T> void f1(T&); //实参必须是一个左值
f1(i);  //i是一个int,模板参数类型T为int
f1(ci); //ci是一个const int,模板参数类型为const int
f1(5);  //错误,实参不是左值

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

template<typename T> void f2(const T&);
f2(i);  //i是一个int,模板参数T推断为int
f2(ci); //ci是一个const int,模板参数T推断为int
f2(5);  //一个const引用参数可以绑定到一个右值;T是int

当一个函数参数是一个右值引用,我们可以传递一个右值给它,推断出的T的类型是该右值实参的类型。

template<typename T> void f3(T&&);
f3(5); //模板参数T是int

 通常我们不能将一个右值引用绑定到一个左值上,但C++语言定义了两个例外规则:

  • 当我们将一个右值传递给函数的右值引用参数,且该右值引用指向模板类型参数时,编译器推断模板类型参数为实参的右值引用类型。(形成了一个引用的引用 int& &&)
  • 若我们间接创建了一个引用的引用,这些引用形成了“折叠”,通过折叠规则来处理上述问题。

 折叠规则:

  • X& &、X& &&、X&& &  ---> X&
  • 类型X&& && ---> X&&

推断出的模板类型参数 + 自带的函数参数 = 实际上用到的实例化类型参数

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

template<typename T> void f3(T&&);

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

这两个例外规则暗示着可以将任意类型的实参传递给T&&类型的函数参数。

2.6、理解std::move

标准库中的move函数接受一个左值,并获得一个绑定到左值上的右值引用。

标准库中的move函数的定义:

//通过引用折叠,函数参数可以与左值或右值的实参匹配
template<typename T> typename remove_reference<T>::type&& move(T&& t)
{
     //typename表示作用域运算符后的type是类型,而不是数据
     return static_cast<typename remove_reference<T>::type&&>(t);
     //typename remove_reference<T>::type&& 去除引用,并加上右值引用
     //static_cast 改变t的引用类型
}

不能隐式地将一个左值转换成右值引用,但可以用static_cast显式地将一个左值转换成一个右值引用。

2.7、转发 

某些函数需要将其一个或多个实参连同类型不变地转发给其他函数,需要保持被转发实参的所有性质。

若一个函数参数是指向模板类型参数的右值引用,它对应的实参的const属性和左值/右值属性都会得到保持。

template<typename F,typename T1,typename T2> void flip(F f,T1&& t1,T2&& t2)
{ f(t2,t1); } 

void g(int&& i,int& j) { cout << i << " " << j << endl; }

当我们试图通过flip来调用g时,参数t2将被传递给g的右值引用参数,将会发生错误。

flip(g,i,42); //错误,不能从一个左值实例化int&&
//t2是一个有名称的变量,有身份的右值表达式是左值

在调用中使用std::forward的新标准库设施来传递flip的参数,它能保持原始实参的类型

forward必须通过显式模板实参来调用。

当用于一个指向模板参数类型的右值引用函数参数时,forward会保持实参类型的所有细节。

template<typename F,typename T1,typename T2>void flip(F f,T1&& t1,T2&& t2)
{ 
    f(std::forward<T2>(t2),std::forward<T1>(t1));
}

flip(g,i,42);
//i将以int&类型传递给g,42将以int&&类型传递给g

3.重载与模板 

函数模板可以被另一个模板或一个普通非模板函数重载。

若有多个函数提供同样好的匹配,则:

  • 如果同样好的函数只有一个非模板函数,则选择此函数。(非模板版本优先
  • 如果同样好的函数没有非模板函数,而有多个函数模板,则其中一个模板比其他模板更特例化,则选择该模板。(特例版本优先
template<typename T> string debug_rep(const T& t) {...} //接受一个const对象的引用
template<typename T> string debug_rep(T* p) {...}  //接受一个模板参数T的指针

string s("hello");
cout << debug_rep(&s) << endl; 
//第一个版本提供debug_rep(const string*&); 第二个版本提供debug_rep(string*)
//第一个版本需要进行普通指针到const指针的转换,而第二个版本是精确匹配

数组到指针的转换的操作对于函数匹配来说,这种转换被认为是精确匹配的。

在定义任何函数之前,记得声明所有重载的函数版本,这样就不必担心编译器由于未遇到你希望调用的函数而实例化一个并非你所需的版本。

4.可变参数模板

一个可变参数模板就是一个接受可变数量参数的模板函数或模板类。

可变数量参数被称为参数包(模板参数包或函数参数包)。

使用一个省略号来指出一个模板参数或函数参数表示一个包。

在函数参数列表中,若一个参数的类型是一个模板参数包,则此参数也是一个函数参数包。

template<typename T,typename ...arg> void foo(const T& t,const arg& ...rest);
//arg是一个模板参数包,rest是一个函数参数包

对于一个可变参数模板,编译器会推断包中参数的数目。

当我们需要知道包中有多少元素时,可以使用sizeof...运算符来计算包中的元素。

cout << sizeof...(arg) << endl;  //类型参数的数目
cout << sizeof..(rest) << endl; //函数参数的数目

4.1、编写可变参数函数模板 

可变参数函数通常是递归的。

先调用处理包中第一个实参,然后用剩余实参调用自身,为了终止递归,还需定义非可变参数的函数。

template<typename T> ostream& print(ostream& os,const T& t) //用来终止递归
{  return os << t; }

template<typename T,typename ...args>
ostream& print(ostream& os,const T& t,const args&...rest)
{
    os << t << " ";  //打印第一个实参
    return print(os,rest...);  //递归调用,打印其他实参
    //rest中的第一个实参被绑定t,剩余实参形成下一个print调用的参数包
}

当定义可变参数版本的print时,非可变参数版本的声明必须在作用域中。否则可变参数将会无限递归。

4.2、包扩展

对于一个参数包,只有获取其大小的操作和扩展它的操作。

扩展一个包就是将它分解为构成的元素,对每个元素应用模式,获得扩展后的列表。

在模式右边放置一个省略号(...)来触发扩展操作。

return print(os,rest...); //扩展rest

C++语言允许更复杂的扩展模式,编写第二个可变参数函数,对每个实参调用这个可变参数函数

return print(os,debug_rep(rest)...);
//上式等于 print(os,debug_rep(a1),debug_rep(a2),...,debug_rep(an))
//扩展结果将是一个逗号分隔的debug_rep调用列表

扩展中的模式会独立地应用于包中的每个元素。 

 4.3、转发参数包

在新标准下,我们可以组合使用可变参数模板forward机制来编写函数,实现其实参不变地传递给其他函数

//每个函数参数都是一个指向其对应实参的右值引用
template<class ...args> inline emplace_back(args&&...arg)
{  alloc.construct(first_free++,std::forward<args>(arg)...);  }
//forward<args>(arg)... 即扩展了模板参数包args,也扩展了函数参数包arg

emplace_back(10,'c');
//扩展出 std::forward<int>(10)  std::forward<char>(c)

可变参数函数通常将它们的参数转发给其他函数。

5.模板特例化 

当我们不希望使用模板版本时,可以定义类或函数模板的一个特例化版本。

template<size_t n,size_t m> int compare(const char(&)[n],const char(&)[m]);
//专门处理字符串字面常量,函数参数都是非类型模板参数

一个模板特例化版本就是模板的一个独立的定义,在其中一个或多个模板参数被指定为特定的类型。 

当我们特例化一个函数模板时,必须为原模板中的每个模板参数都提供实参。

使用关键字template后跟一个空尖括号来指出正在实例化一个模板,这操作使得该特例化版本与模板保持相同的优先级。 

template<> int compare(const char* const &p1,const char* const &p2) {}
//compare的特例化版本,处理字符数组的指针

一个特例化版本本质上是一个模板的实例,而非函数名的一个重载版本。 

模板及其特例化版本应该声明在同一个头文件中。所有同名模板的声明应放在前面,然后是这些模板的特例化版本。

除了特例化函数模板,还可以特例化类模板。

template<> struct hash<sales_data> {......};//特例化一个sales_data版本的hash

类似其他任何类,我们可以在类内或类外定义特例化版本的成员。

类模板的特例化不必为所有模板参数提供实参,因此类模板的部分特例化本身是一个模板。 

template<class T> struct remove_reference {...};  //通用模板
template<class T> struct remove_reference<T&> {...}; //左值引用版本
template<class T> struct remove_reference<T&&> {...}; //右值引用版本

部分特例化版本的模板参数列表是原始模板的参数列表的一个子集或是一个特例化版本。

允许只特例化特定成员函数而不是特例化整个模板。

template<typename T> struct foo
{
    foo(struct T& t = T()):mem(t) {}
    void bar() {...}
};

template<> void foo<int>::bar() {...} //先特例化int模板,再特例化foo<int>的成员bar

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

二分图

数据结构、算法总述&#xff1a;数据结构/算法 C/C-CSDN博客 二分图&#xff1a;节点由两个集合组成&#xff0c;且两个集合内部没有边的图。换言之&#xff0c;存在一种方案&#xff0c;将节点划分成满足以上性质的两个集合。 染色法 目的&#xff1a;验证给定的二分图是否可…

房间采光不好怎么改造?这里有6个实用的解决方案!福州中宅装饰,福州装修

当装修中遇到房间采光不好的问题&#xff0c;可以从以下几个方面来解决&#xff1a; ①引入自然光源 尽可能减少光线阻碍物&#xff0c;例如可以考虑打通一些非承重墙&#xff0c;扩大窗户的面积&#xff0c;让阳光直接穿过阳台照射到室内。同时&#xff0c;也可以考虑在某些没…

YOLOV8逐步分解(2)_DetectionTrainer类初始化过程

接上篇文章yolov8逐步分解(1)--默认参数&超参配置文件加载继续讲解。 1. 默认配置文件加载完成后&#xff0c;创建对象trainer时&#xff0c;需要从默认配置中获取类DetectionTrainer初始化所需的参数args&#xff0c;如下所示 def train(cfgDEFAULT_CFG, use_pythonFalse…

17.注释和关键字

文章目录 一、 注释二、关键字class关键字 我们之前写的HelloWorld案例写的比较简单&#xff0c;但随着课程渐渐深入&#xff0c;当我们写一些比较难的代码时&#xff0c;在刚开始写完时&#xff0c;你知道这段代码是什么意思&#xff0c;但是等过了几天&#xff0c;再次看这段…

图片标注编辑平台搭建系列教程(3)——画布拖拽、缩放实现

简介 标注平台很关键的一点&#xff0c;对于整个图片为底图的画布&#xff0c;需要支持缩放、拖拽&#xff0c;并且无论画布位置在哪里&#xff0c;大小如何&#xff0c;所有绘制的点、线、面的坐标都是相对于图片左上角的&#xff0c;并且&#xff0c;拖拽、缩放&#xff0c;…

从零开始学习在VUE3中使用canvas(六):线条样式(线条宽度lineWidth,线条端点样式lineCap)

一、线条宽度lineWidth 1.1简介 值为一个数字 const ctx canvas.getContext("2d"); ctx.lineWidth 6; 1.2效果展示 1.3全部代码 <template><div class"canvasPage"><!-- 写一个canvas标签 --><canvas class"main" r…

图像处理与视觉感知---期末复习重点(5)

文章目录 一、膨胀与腐蚀1.1 膨胀1.2 腐蚀 二、开操作与闭操作 一、膨胀与腐蚀 1.1 膨胀 1. 集合 A A A 被集合 B B B 膨胀&#xff0c;定义式如下。其中集合 B B B 也称为结构元素&#xff1b; ( B ^ ) z (\hat{B})z (B^)z 表示 B B B 的反射平移 z z z 后得到的新集合。…

冥想打坐睡觉功法

睡觉把手机放远一点&#xff0c;有电磁辐射&#xff0c;我把睡觉功法交给你&#xff0c;这样就可以睡好了。

55、Qt/事件机制相关学习20240326

一、代码实现设置闹钟&#xff0c;到时间后语音提醒用户。示意图如下&#xff1a; 代码&#xff1a; #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget), speecher(new QTextToSpeech(t…

C++超市商品管理系统

一、简要介绍 1.本项目为面向对象程序设计的大作业&#xff0c;基于Qt creator进行开发&#xff0c;Qt框架版本6.4.1&#xff0c;编译环境MINGW 11.2.0。 2.项目结构简介&#xff1a;关于系统逻辑部分的代码的头文件在head文件夹中&#xff0c;源文件在s文件夹中。与图形界面…

权限提升-Win系统权限提升篇AD内网域控NetLogonADCSPACKDCCVE漏洞

知识点 1、WIN-域内用户到AD域控-CVE-2014-6324 2、WIN-域内用户到AD域控-CVE-2020-1472 3、WIN-域内用户到AD域控-CVE-2021-42287 4、WIN-域内用户到AD域控-CVE-2022-26923 章节点&#xff1a; 1、Web权限提升及转移 2、系统权限提升及转移 3、宿主权限提升及转移 4、域控权…

Git命令上传本地项目至github

记录如何创建个人仓库并上传已有代码至github in MacOS环境 0. 首先下载git 方法很多 这里就不介绍了 1. Github Create a new repository 先在github上创建一个空仓库&#xff0c;用于一会儿链接项目文件&#xff0c;按照自己的需求设置name和是否private 2.push an exis…

指针数组的有趣程序【C语言】

文章目录 指针数组的有趣程序指针数组是什么&#xff1f;指针数组的魅力指针数组的应用示例&#xff1a;命令行计算器有趣的颜色打印 结语 指针数组的有趣程序 在C语言的世界里&#xff0c;指针是一种强大的工具&#xff0c;它不仅能够指向变量&#xff0c;还能指向数组&#…

如何利用OpenCV4.9离散傅里叶变换

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇:如何利用OpenCV4.9 更改图像的对比度和亮度 下一篇:OpenCV 如何使用 XML 和 YAML 文件的文件输入和输出 目标 我们将寻求以下问题的答案&#xff1a; 什么是傅里叶变换&#xff0c;为什…

《数据结构学习笔记---第五篇》---链表OJ练习下

step1:思路分析 1.实现复制&#xff0c;且是两个独立的复制&#xff0c;我们必须要理清指针之间的逻辑&#xff0c;注意random的新指针要链接到复制体的后面。 2.我们先完成对于结点的复制&#xff0c;并将复制后的结点放在原节点的后面&#xff0c;并链接。 3.完成random结点…

黑马鸿蒙笔记1

这里与前端类似。

斜率优化dp 笔记

任务安排1 有 N 个任务排成一个序列在一台机器上等待执行&#xff0c;它们的顺序不得改变。 机器会把这 N 个任务分成若干批&#xff0c;每一批包含连续的若干个任务。 从时刻 00 开始&#xff0c;任务被分批加工&#xff0c;执行第 i 个任务所需的时间是 Ti。 另外&#x…

PHP开发全新29网课交单平台源码修复全开源版本,支持聚合登陆易支付

这是一套最新版本的PHP开发的网课交单平台源代码&#xff0c;已进行全开源修复&#xff0c;支持聚合登录和易支付功能。 项目 地 址 &#xff1a; runruncode.com/php/19721.html 以下是对该套代码的主要更新和修复&#xff1a; 1. 移除了论文编辑功能。 2. 移除了强国接码…

linux之进程

一、背景 冯.诺依曼体系结构 输入设备键盘、鼠标、摄像头、话筒、磁盘、网卡...输出设备显示器、声卡、磁盘、网卡...CPU运算器、控制器存储器一般就是内存 数据在计算机的体系结构进行流动&#xff0c;流动过程中&#xff0c;进行数据的加工处理&#xff0c;从一个设备到另一…

网上兼职赚钱攻略:六种方式让你轻松上手

在互联网时代&#xff0c;网上兼职已经成为一种非常流行的赚钱方式。对于许多想要在家里挣钱的人来说&#xff0c;网上兼职不仅可以提供灵活的工作时间&#xff0c;还可以让他们在自己的兴趣领域中寻求机会&#xff0c;实现自己的财务自由。 在这里&#xff0c;我将为您介绍六…