沧州网站建设icp备,平台网站开发公司组织架构,江苏网站建设效果好,wordpress编辑权限设置文章目录 模板模板的声明与定义函数模板非类型模板参数类模板类的成员函数定义构造函数的定义类的静态成员的定义类模板的实例化使用模板类型中的类型成员 默认模板参数指定显示模板实参(函数模板显示实参)引用折叠和右值引用参数可变参数模板对参数包的扩展对参数包的转发可变… 文章目录 模板模板的声明与定义函数模板非类型模板参数类模板类的成员函数定义构造函数的定义类的静态成员的定义类模板的实例化使用模板类型中的类型成员 默认模板参数指定显示模板实参(函数模板显示实参)引用折叠和右值引用参数可变参数模板对参数包的扩展对参数包的转发可变参数模板的示例模板参数传入可调用对象 虽然用了多年的C但是C泛型编程在开发工作中接触的很少就连需要使用模板的场景都很少。因为用的少模板相关的知识就是看了忘忘了看。 但是在阅读一些开源的C项目模板还是经常遇到的为了理解代码模板的基础知识还是得了然于心。这篇文章是记录了C模板基础知识 内容来自 C Primer做了归纳方便以后查看。
模板
模板是泛型编程的基础。分别有模板函数和模板类声明一个模板函数或模板类与定义一个普通函数和类一样只是并不指定明确的类型了。
模板包含了模板实例的步骤对不同类型相同模板的函数或类编译器在编译阶段生成对应的代码。不同类型的模板实例化是不同的类型。
模板的声明与定义
为了生成一个实例化版本编译器需要掌握函数模板或类模板成员函数的定义。因此与非模板代码不同模板的头文件通常即包括声明也包括定义。
函数模板和类模板成员函数的定义通常放在头文件中。像常规的头文件和源文件分离编译的情况在模板情况下行不通。
如下模板类Blob 在blob_test.h文件中
#include string
template typename T,typename U
class Blob {
public:Blob(T a,U b);
public:T getA();U getB();
private:T _a;U _b;
};如果将Blob类中的构造函数及getA()和getB()函数定义在文件blob_test.cpp中如下
#include blob_test.h
template typename T,typename U
BlobT,U::Blob(T v,U v1):_a(v),_b(v1) {}template typename T,typename U
T BlobT,U::getA() {return _a;
}template typename T,typename U
U BlobT,U::getB() {return _b;
}
那么在main.cpp文件中
#include blob_test.h
#include iostream
int main() {//Blobint,std::string是实例化的类型Blobint,std::string b(18,123);std::coutA:b.getA()std::endl;std::coutB:b.getB()std::endl;
}编译blob_test.h,blob_test.cpp,main.cpp将会报错因为在main.cpp中只包含了blob_test.h只有声明没有定义编译器无法实例化该模板。
所以对模板一般将声明与定义都写在头文件即声明也包含定义。那么blob_test.h修改如下:
#include string
template typename T,typename U
class Blob {
public:Blob(T a,U b);
public:T getA();U getB();
private:T _a;U _b;
};//BlobT,U就表示一个类
template typename T,typename U
T BlobT,U::getA() {return _a;
}template typename T,typename U
U BlobT,U::getB() {return _b;
}template typename T,typename U
BlobT,U::Blob(T v,U v1):_a(v),_b(v1) {}模板的设计者应该提供一个头文件包含模板定义及在类模板或成员定义中用到的所有名字的声明。模板用户必须包含模板的头文件以及用来实例化模板的任何类型的头问题。 模板的定义与声明可以看看这篇文章。
函数模板
template typename T
int compare(const T v1,const T v2) {if (v1 v2) return -1;if (v2 v1) return 1;return 0;
}std::coutcompare(1,0)std::endl;模板定义以关键字template开始后跟一个模板参数列表这是一个逗号分隔的一个或多个模板参数的列表。
函数模板不必显示写出类型实参编译器可以根据实参推导。比如compare(1,0)在编译期间就被编译器实例化为一个int的compare版本。
一般来说我们可以将类型参数看作类型说明符就像内置类型或类型说明符一样使用。
特别是类型参数可以用来指定返回类型或参数的参数类型以及在函数体内用于变量声明或类型转换。
template typename T
T foo(T *p) {//tmp的类型将是指针p指向的类型T tmp *p;//...return tmp;
}非类型模板参数
templateunsigned N,unsigned M
int compare(const char(p1)[N],const char(p2)[M]);类模板
如下是一个模板类Bolb的声明。
template typename T class Blob {
public:T back();T operator[](size_type i);
private:std::shared_ptrstd::vectorT _data;static size_t _v;
};BlobT就当一个正常的类使用**template typename T BlobT 就是它的完整形式。
类的成员函数定义
当我们定义一个成员函数时应如下定义
template typename T
ret-type BlobT::member-name(parm-list)Blob成员函数定义如下
template typename T
void BlobT::check(size_t i,const std::string msg) const {....;
}template typename T
T BlobT::back() {....
}构造函数的定义
template typename T
BlobT::Blob():data(...) {...
}类的静态成员的定义
template typename T
size_t FooT::_v 0;类模板的实例化
与函数模板不同之处是编译器不能为类模板推断模板参数类型实例化时必须指定类型如BlobintBlobstd::string。
使用模板类型中的类型成员
当编译器遇到这样的语句 T::size_type *p; 时它需要指定我们是正在定义一个名为p的变量还是将一个名为size_type的static数据成员与名为p的变量相乘。默认情况下C语言假定通过作用域运算符访问的名字不是类型。
因此如果我们希望使用一个模板类型参数的类型成员就必须显示告诉编译器该名字是一个类型。我们通过使用关键字typename来实现这一点:
template typename T
typename T::value_type top(const T c) {if (!c.empty()) {return c.back();} else {return typename T::value_type();}}默认模板参数
template typename T, typename F lessT
int compare(const T v1,const T v2, F f F()) {...
}template class T int
class Numbers {....
};指定显示模板实参(函数模板显示实参)
我们可以定义表示返回类型的第三个模板参数从而允许用户控制返回类型
template typename T1,typename T2,typename T3
T1 sum(T2,T3);T1指定返回值的类型。
实例化时返回值的类型被显示指定
// T1是显示指定T2和T3是从函数实参类型推断而来
auto val3 sumlong long(i,lng);引用折叠和右值引用参数
如下一个模板函数f(T)注意参数类型的推导。
template typename T void f(T );
f3(42);//42是一个右值那么模板参数T被推导为int
int i 118;
f3(i);//i是个左值那么模板参数T被推导为int如下例子函数f将改变量i的值因为类型被推导为int。
#include iostream
template typename T void f(T v) {v 18;
}int main() {int i 0;f(i);std::couti:istd::endl;
}当我们将一个左值传递给函数的右值引用参数且此右值引用指向模板类型参数(如T)时编译器推断模板类型参数为实参的左值引用类型。
因此当我们调用f3(i)时编译器推断T的类型为int而非int。
那么对f3(i)编译器实际推导为
void f3int(int );T的类型为int触发了C中的引用折叠。 引用折叠的规则如下
X X 和X 都折叠成类型X。类型X 折叠成X。
那么void f3T(T )的模板参数可以称为万能引用因为实参既可以传入左值也可以传入右值: f(i); f(18);
可变参数模板
可以传入多个实参像printf一样。
template typename T,typename ...Args
void foo(const T t,const Args ...rest);对参数包的扩展
template typename... Args
ostream errorMsg(ostream os,const Args... rest) {return print(os,debug_rep(rest)...);
}对参数包rest进行了扩展将对每个参数都调用debug_rep。
对参数包的转发
//...Args表示模板参数的类型
//Args... 表示以Args声明了形参args
templatetypename ...Args
void fun(Args... args) {work(std::forwardArgs(args)...);
}可变参数模板的示例
#include iostream
void work(int a,int b) {std::cout enter work(int a,int b)std::endl;std::couta:a,b:bstd::endl;std::cout exit work(int a,int b)std::endl;
}void work(int a,int b) {std::cout enter work(int a,int b)std::endl;a 1;b 2;std::cout exit work(int a,int b)std::endl;
}void work(const inta,const int b) {std::cout enter work(const inta,const int b)std::endl;std::cout exit work(const inta,const int b)std::endl;
}template typename ...Args
void foo(Args ...v) {work(std::forwardArgs(v)...);
}int main() {//模板参数的实参类型//类型被推断为int//此时会报错因为选择const int和int,int都可以foo(18,118);int a16,b116;/*模板参数会被推断为int,此时会有引用折叠,但是通过forward*将类型保留转发到了work。*此时 int,const int都适合,但是int更加合适会选择int。*/foo(a,b);std::couta:a,b:bstd::endl;
}模板参数传入可调用对象
#include iostreamtemplatetypename Fun,typename ...Args
void Test(Fun _fun,Args... args) {_fun(std::forwardArgs(args)...);
}//函数对象
struct SWork {void operator()(int a,int b) {std::coutSWork,a:a,b:bstd::endl;}
};//函数
void work(int a,int b) {std::coutwork,a:a,b:bstd::endl;
}int main() {Test(work,18,18);Test(SWork(),19,19);
}