网站建设模块怎么使用,免费设计logo的app,广州市口碑全网推广报价,延边住房和城乡建设局网站模板是C中泛型编程的基础#xff0c;一个模板就是一个创建类或函数的蓝图。
1.定义模板
模板适用于唯一的差异是参数的类型#xff0c;函数体完全一致的情况。
1.1、函数模板
我们可以定义一个通用的函数模板用来生成针对特定类型的函数版本。
模板定义以关键字template…模板是C中泛型编程的基础一个模板就是一个创建类或函数的蓝图。
1.定义模板
模板适用于唯一的差异是参数的类型函数体完全一致的情况。
1.1、函数模板
我们可以定义一个通用的函数模板用来生成针对特定类型的函数版本。
模板定义以关键字template开始后跟模板参数列表。
templatetypename T int compare(const T v1, const T v2)
{...}
在模板定义中模板参数列表不能为空。
模板参数列表类似于函数参数列表模板参数表示在类或函数定义中用到的类型或值。
当使用模板时我们隐式或显式指定模板实参将其绑定到模板参数上。
当我们调用一个函数模板时编译器通常用函数实参为我们推断模板类型。
编译器用推断出的模板参数来为我们实例化一个特定版本的函数。
compare(1,0); //实参类型为int编译器推断出的类型为int将它绑定到模板参数T上
这些由编译器生成的特定版本的函数通常被称为模板的实例。
一般来说可以将类型参数看作类型说明符类型参数可以用来指定返回类型或函数的参数类型以及在函数体内用于变量声明或类型转换。
templatetypename T T foo(T* p) //指定返回类型和函数参数类型
{T tmp *p; //声明变量return tmp;
}
每个类型参数前必须使用关键字class或typename。
允许在模板中定义非类型参数一个非类型参数表示一个值而非一个类型。
当一个模板被实例化时非类型参数会被用户所提供的或编译器推断出的值常量表达式替代。
templateunsigned N, unsigned M int compare(const char(p1)[N],const char(p2)[M]) {}
//N和M都是char数组的长度是字面常量
一个非类型参数可以是一个整数或者是一个指向对象或函数类型的指针或左值引用。
绑定到指针或引用非类型参数的实参必须具有静态的生存期static变量。
函数模板可以声明为inline或constexpr的说明符放在模板参数列表之后返回类型之前。
templatetypename T inline T min(const T, const T);
模板程序应该尽量减少实参类型的要求。
只有当我们实例化出模板的一个特定版本时编译器才会生成代码。
我们将类定义和函数声明放在头文件中而普通函数和类的成员函数的定义放在源文件中。
函数模板和类模板成员函数的定义通常放在头文件中。
模板直到实例化时才会生成代码因此大多数编译错误在实例化期间报告。
作为调用者应保证传递给模板的实参支持模板所要求的操作以及操作能在模板内正确运行。
1.2、类模板
与函数模板不同编译器不能为类模板推断模板参数类型我们需要指定模板的具体类型。
templatetypename T class blob{...
};blobint a; //空blobint
blobstring b {hi,hello,good}; //有3个元素的blobstring
类模板的每个实例都形成一个独立的类不会与其他类有任何关联也不会有特殊访问权限。
我们可以在类模板内部也可以在类模板外部为其定义成员函数且定义在内部的函数被隐式声明为内联函数。
类模板的成员函数本身是一个普通函数但类模板的每个实例都有自己版本的成员函数。
定义在类模板之外的成员函数必须以关键字template开始后接类模板参数列表。
templatetypename T void blobT::compare(const T,constT) {...}
//定义在类外部的成员函数compare
与其他任何定义在类模板外的成员一样构造函数的定义要以模板参数开始。
templatetypename T blobT::blob() : data(std::make_sharedstd::vectorT()) {}
默认情况下一个类模板的成员函数只有当程序用到它时才会进行实例化。
当我们使用一个类模板类型时必须提供模板实参但在类模板自己的作用域中我们可以直接使用模板名而不提供实参。
templatetypename T class blobstr{//...blobstr operator(); //返回的是blobstr而不是blobstrTblobstr operator--();
};
当我们在类模板外定义其成员时遇到类名时表示进入类的作用域因此在函数体内可直接使用模板名而不必指明模板实参。
templatetypename T blobstrT blobstrT::operator(int)
{ blobstr ret *this; //直接使用模板名*this;return ret;
} 若一个类模板包含一个非模板友元则友元被授权可以访问所有的模板实例。
若友元本身为模板则类可以授权所有的友元模板实例也可以只授权给特定实例。
若友元的声明用类模板的模板形参作为自己的模板实参则友元关系被限定在用相同类型实例化的版本。
templatetypename class blobstr; //前置声明在blob中声明友元所需要的
templatetypename class blob;
templatetypename T class blob{friend class blobstrT; //友元关系限定在相同类型T的版本...
};
若友元的声明使用与类本身不同的模板参数则类将类模板的每个实例声明为自己的友元。
class total{templatetypename T friend class pal;//pal的所有实例都是total的友元这种情况无须前置声明
};
在新标准中我们可以将模板类型参数声明为友元。
templatetypename type class bar{friend type; //将访问权限授予用来实例化bar的类型...
};
//对于某个类型名该类将成为bartype的友元
类模板的一个实例定义了一个类类型因此可以通过typedef来引用已实例化的类。
由于模板不是一个类型我们不能定义一个typedef来引用一个模板。
typedef blobT strblob; //错误 新标准允许我们为类模板定义一个类型别名,一个模板类型别名是一族类的别名。
templatetypename T using twin pairT,T;
twinstring authors; //authors是一个pairstring,string
当我们定义一个模板类型别名时可以固定一个或多个模板参数。
templatetypename T using part pairT,unsigned;
partstring book; //book是一个pairstring,unsigned
类模板可以声明static成员。
模板类的每个static数据成员必须有且只有一个定义但是类模板的每个实例都有一个独有的static对象。
templatetypename T class foo{
private:static std::size_t n;
};foostring fs; //实例化static成员foostring::n
fooint is; //实例化static成员fooint::n
我们可以通过类类型对象来访问一个类模板的static成员使用作用域运算符直接访问成员。
fooint fi;
auto ct fooint::n; //实例化fooint::n
ct foo.n; 1.3、模板参数
一个模板参数名的可用范围是在其声明之后至模板声明或定义结束之前。
模板参数会隐藏外层作用域中生命的相同的名字在模板内不能重用模板参数名。
typedef double A;
templatetypename A,typename B void f(A a,B b)
{A tmp a; //tmp的类型为模板参数A的类型而非doubledouble B; //错误重声明模板参数B
}
模板的声明必须包含模板参数定义中的模板参数的名字不必与声明中相同。
templatetypename T int compare(const T, const T); //模板声明templatetypename type type compare(const type a,const type b) {...} //模板定义
一个给定模板的每个声明和定义必须有相同数量和种类类型或非类型的参数。
一个特定文件所需要的所有模板的声明通常一起放置在文件开始位置。
为了处理模板编译器必须知道名字表示一个类型还是一个数据成员。
T::size_type * p
//编译器不知道size_type是模板T的类型成员还是一个数据成员 为了解决上述问题C语言默认假定通过作用域运算符访问的名字是数据成员。 若我们希望通知编译器一个名字表示类型时必须使用关键字typename来实现。
templatetypename T template T::value_type top(const T c) //显式表示value_type是类型
{ return typename T::value_type(); }
在新标准中我们可以为函数和类模板提供默认实参。
templatetypename T, typename F lessT int compare(const T v1,const T v2,F f F())
{} //给类型F提供默认实参
与函数默认实参一致对于一个模板参数只有当它右侧的所有参数都有默认实参时它才能有默认实参。
无论何时使用一个类模板我们都必须在模板名之后接上尖括号。
若我们希望使用默认实参则在模板名之后跟一个空尖括号即可。
templateclass T int class numbers {...};numbers average; //T为int
numbersdouble prec; //T为double 1.4、成员模板 类普通类或类模板可以包含本身是模板的成员函数成员模板成员模板不能是虚函数。
class debugdelete{
public:templatetypename 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*) 对于类模板我们可以为其定义成员模板。
templatetypename T class blob{templatetypename It blob{It b,It e); //构造函数接受不同类型的迭代器
};
当我们在类模板外定义一个成员模板时必须同时为类模板和成员模板提供模板参数列表。
templatetypename T templatetypename It
blobT::blob(It b,It e) : data(std::make_sharedstd::vectorT(b,e)) {}
//类模板的参数列表在前后跟成员自己的模板参数列表
为了实例化一个类模板的成员模板我们必须同时提供类和函数模板的实参。
int ia[] {1,2,3,4,5,6};
blobint al(begin(ia),end(ia)); //a1的定义实例化了blobint::blob(int*,int*)的版本 1.5、控制实例化
当两个或多个独立编译的源文件使用了相同的模板并提供了相同的模板参数时每个文件中就都会有该模板的一个实例造成了额外的开销。
在新标准中我们可以通过显式实例化来避免这种问题的发生。
当编译器遇到extern模板声明时它不会在本文件中生成实例化代码但这些模板类型必须在程序其他位置文件进行实例化。
extern template class blobstring; //blobstring的代码不会在本文件生成
blobstring sa1,sa2; //实例化会在其他位置出现
对于一个给定的实例化版本可能有多个extern声明但必须只有一个定义。 对于每个实例化声明在程序中某个位置必须有其显式的实例化定义。
一个类模板的实例化定义会实例化该模板的所有成员包括内联的成员函数。
2.模板实参推断
对于函数模板编译器从函数实参来确定模板实参的过程被称为模板实参推断。
2.1、类型转换与模板类型参数
编译器通常不是对实参进行类型转换而是直接生成一个新的模板实例。
能在调用时自动应用于函数模板的类型转换有如下两项
const转换可以将一个非const对象的引用指针传递给一个const的引用指针形参。数组或函数指针转换若函数形参不是引用类型则可以对数组或函数类型的实参应用到正常的指针转换。数组实参转换为指向首元素的指针、函数实参转换为函数类型的指针
templatetypename T T foa(T,T);
templateTypename 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)
若希望对函数实参进行正常的类型转换可以将函数模板定义为两个类型参数。
templatetypename A, typename B int compare(const A v1, const B v2) {...}long lg;
compare(lg,1024); //正确调用compare(long,int)
若函数参数类型不是模板参数则对实参进行正常的类型转换。
templatetypename 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、函数模板显式实参
当函数返回类型与参数列表中任何类型都不相同时编译器将无法推断出模板实参的类型。
我们可以通过定义表示返回类型的第三个模板参数从而允许用户控制返回类型。
templatetypename T1,typename T2,typename T3 T1 sum(T2,T3);
//T1作为返回类型编译器无法推断T1它未出现在函数参数列表中
在sum中没有任何函数实参的类型可用来推断T1的类型因此每次调用sum时都必须为T1提供了一个显式模板实参。
auto val sumlong long(1024,2.30); //long long sum(int,double)
显式模板实参按由左至右的顺序与对应的模板参数匹配只有最右侧参数的显式模板参数可忽略。
对于模板类型参数已经显式指定了的函数实参也可以进行正常的类型转换。
long lg;
compare(lg,1024); //错误模板参数不匹配
comparelong(lg,1024); //正确显式指定模板类型参数实例化compare(long,long);
2.3、尾置返回类型与类型转换
当我们知道函数的返回类型与所处理的序列的元素类型相同可以使用尾置操作来指定返回类型。
尾置返回出现在参数列表之后它可以直接使用函数的参数。
//尾置返回允许我们在参数列表之后声明返回类型
templatetypename T auto fcn(It beg,It end)- decltype(*beg)
{ return *beg; }
所有迭代器操作都不会生成元素只能生成元素的引用为了得到元素类型可以使用标准库中的类型转换模板。
类型转换模板定义在头文件type_traits中。
标准类型转换模板 对modT其中mod为若T为则modT::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 Tmake_unsigned 带符号类型 否则 unsigned X T remove_extent X[n] 否则 X T remove_all_extents X[n1][n2] 否则 X T
templatetypename It
auto fcn2(It beg,It end) - typename remove_referencedecltype(*beg)::type
{ return *beg; }
//remove_reference::type脱去引用剩下元素的类型本身
//在返回类型的声明中使用typename来告知编译器type是一个类型 2.4、函数指针和实参推断
用一个函数模板初始化一个函数指针或为一个函数指针赋值时编译器使用指针的类型来推断模板实参。
templatetypename 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(compareint); //正确通过使用显式模板实参来消除func调用的歧义
2.5、模板实参推断和引用
编译器会应用正常的引用绑定规则。
当一个函数参数时模板类型参数的一个普通左值引用时只能传递给它一个左值。
templatetypename T void f1(T); //实参必须是一个左值
f1(i); //i是一个int模板参数类型T为int
f1(ci); //ci是一个const int模板参数类型为const int
f1(5); //错误实参不是左值当函数参数本身是const时T的类型推断的结果不会是一个const类型const已经是函数参数类型的一部分。
templatetypename T void f2(const T);
f2(i); //i是一个int模板参数T推断为int
f2(ci); //ci是一个const int模板参数T推断为int
f2(5); //一个const引用参数可以绑定到一个右值T是int
当一个函数参数是一个右值引用我们可以传递一个右值给它推断出的T的类型是该右值实参的类型。
templatetypename T void f3(T);
f3(5); //模板参数T是int 通常我们不能将一个右值引用绑定到一个左值上但C语言定义了两个例外规则
当我们将一个右值传递给函数的右值引用参数且该右值引用指向模板类型参数时编译器推断模板类型参数为实参的右值引用类型。形成了一个引用的引用 int 若我们间接创建了一个引用的引用这些引用形成了“折叠”通过折叠规则来处理上述问题。 折叠规则
X 、X 、X --- X类型X --- X
推断出的模板类型参数 自带的函数参数 实际上用到的实例化类型参数
引用折叠只能应用于间接创建的引用的引用例如类型别名或模板参数。
templatetypename T void f3(T);f3(i); //实参是一个左值模板参数T是int
f3(ci); //实参是一个左值模板参数T是const int
这两个例外规则暗示着可以将任意类型的实参传递给T类型的函数参数。
2.6、理解std::move
标准库中的move函数接受一个左值并获得一个绑定到左值上的右值引用。
标准库中的move函数的定义
//通过引用折叠函数参数可以与左值或右值的实参匹配
templatetypename T typename remove_referenceT::type move(T t)
{//typename表示作用域运算符后的type是类型而不是数据return static_casttypename remove_referenceT::type(t);//typename remove_referenceT::type 去除引用并加上右值引用//static_cast 改变t的引用类型
}
不能隐式地将一个左值转换成右值引用但可以用static_cast显式地将一个左值转换成一个右值引用。
2.7、转发
某些函数需要将其一个或多个实参连同类型不变地转发给其他函数需要保持被转发实参的所有性质。
若一个函数参数是指向模板类型参数的右值引用它对应的实参的const属性和左值/右值属性都会得到保持。
templatetypename 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会保持实参类型的所有细节。
templatetypename F,typename T1,typename T2void flip(F f,T1 t1,T2 t2)
{ f(std::forwardT2(t2),std::forwardT1(t1));
}flip(g,i,42);
//i将以int类型传递给g42将以int类型传递给g
3.重载与模板
函数模板可以被另一个模板或一个普通非模板函数重载。
若有多个函数提供同样好的匹配则
如果同样好的函数只有一个非模板函数则选择此函数。非模板版本优先如果同样好的函数没有非模板函数而有多个函数模板则其中一个模板比其他模板更特例化则选择该模板。特例版本优先
templatetypename T string debug_rep(const T t) {...} //接受一个const对象的引用
templatetypename T string debug_rep(T* p) {...} //接受一个模板参数T的指针string s(hello);
cout debug_rep(s) endl;
//第一个版本提供debug_rep(const string*); 第二个版本提供debug_rep(string*)
//第一个版本需要进行普通指针到const指针的转换而第二个版本是精确匹配数组到指针的转换的操作对于函数匹配来说这种转换被认为是精确匹配的。
在定义任何函数之前记得声明所有重载的函数版本这样就不必担心编译器由于未遇到你希望调用的函数而实例化一个并非你所需的版本。
4.可变参数模板
一个可变参数模板就是一个接受可变数量参数的模板函数或模板类。
可变数量参数被称为参数包模板参数包或函数参数包。
使用一个省略号来指出一个模板参数或函数参数表示一个包。
在函数参数列表中若一个参数的类型是一个模板参数包则此参数也是一个函数参数包。
templatetypename T,typename ...arg void foo(const T t,const arg ...rest);
//arg是一个模板参数包rest是一个函数参数包对于一个可变参数模板编译器会推断包中参数的数目。
当我们需要知道包中有多少元素时可以使用sizeof...运算符来计算包中的元素。
cout sizeof...(arg) endl; //类型参数的数目
cout sizeof..(rest) endl; //函数参数的数目
4.1、编写可变参数函数模板
可变参数函数通常是递归的。
先调用处理包中第一个实参然后用剩余实参调用自身为了终止递归还需定义非可变参数的函数。
templatetypename T ostream print(ostream os,const T t) //用来终止递归
{ return os t; }templatetypename 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机制来编写函数实现其实参不变地传递给其他函数。
//每个函数参数都是一个指向其对应实参的右值引用
templateclass ...args inline emplace_back(args...arg)
{ alloc.construct(first_free,std::forwardargs(arg)...); }
//forwardargs(arg)... 即扩展了模板参数包args也扩展了函数参数包argemplace_back(10,c);
//扩展出 std::forwardint(10) std::forwardchar(c)
可变参数函数通常将它们的参数转发给其他函数。
5.模板特例化
当我们不希望使用模板版本时可以定义类或函数模板的一个特例化版本。
templatesize_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 hashsales_data {......};//特例化一个sales_data版本的hash
类似其他任何类我们可以在类内或类外定义特例化版本的成员。
类模板的特例化不必为所有模板参数提供实参因此类模板的部分特例化本身是一个模板。
templateclass T struct remove_reference {...}; //通用模板
templateclass T struct remove_referenceT {...}; //左值引用版本
templateclass T struct remove_referenceT {...}; //右值引用版本
部分特例化版本的模板参数列表是原始模板的参数列表的一个子集或是一个特例化版本。
允许只特例化特定成员函数而不是特例化整个模板。
templatetypename T struct foo
{foo(struct T t T()):mem(t) {}void bar() {...}
};template void fooint::bar() {...} //先特例化int模板再特例化fooint的成员bar