外贸网站制作策划,阿里巴巴网站建设改图片,农夫山泉vi设计案例,黑彩网站自己可以做么模板泛型编程函数模板普通函数模板成员函数模板函数模板重载模板函数的特化 类模板类模板中的成员函数模板类模板的特化与偏特化类模板成员特化 模板
Template所代表的泛型编程是C语言中的重要组成部分。
泛型编程
泛型编程#xff08;Generic Programming#xff09;是…模板泛型编程函数模板普通函数模板成员函数模板函数模板重载模板函数的特化 类模板类模板中的成员函数模板类模板的特化与偏特化类模板成员特化 模板
Template所代表的泛型编程是C语言中的重要组成部分。
泛型编程
泛型编程Generic Programming是一种语言机制通过它可以实现一个标准的容器库。 像类一样泛型也是一种抽象数据类型但是泛型不属于面向对象它是面向对象的补充和发展。 在面向对象编程中当算法与数据类型有关时面向对象在对算法的抽象描述方面存在一些缺陷。
首先我们先来了解什么是泛型编程看下面的例子。
比如对栈的描述
class stack
{push(参数类型) //入栈算法pop(参数类型) //出栈算法}
如果把上面的伪代码看作算法描述没问题因为算法与参数类型无关。但是如果把它写成可编译的源代码 就必须指明是什么类型否则是无法通过编译的。使用重载来解决这个问题即对N种不同的参数类型写N个 push和pop算法这样是很麻烦的代码也无法通用。
若对上面的描述进行改造如下 首先指定一种通用类型T不具体指明是哪一种类型。
class stack参数模板 T
{push(T) //入栈算法pop(T) //出栈算法}
这里的参数模板T相当于一个占位符当我们实例化类stack时T会被具体的数据类型替换掉。 若定义对象S为statc类型在实例化S时若我们将T指定int型则 这时候类S就成为
class S
{push(int) //入栈算法pop(int) //出栈算法
}
这时我可以称class stack参数模板 T是类的类通过它可以生成具体参数类型不同的类。
泛型在C中的应用 泛型在C中的主要实现为模板函数和模板类。
函数模板
把处理不同类型的公共逻辑抽象成函数就得到了函数模板。
函数模板的格式
template class 形参名class 形参名...... 返回类型 函数名(参数列表)
{函数体
}
其中template和class是关键字当然class可以使用typename关键字代替两者之间一点区别都没有这个是真的。 括号中的参数叫模板形参模板形参和函数形参很相像模板形参不能为空。
普通函数模板
templatetypename T
int compare(const T left, const T right) {if (left right) {return -1; }if (right left) {return 1; }return 0;
}templateclass Tdouble
void processValue( T value )
{std::cout value std::endl;
}int main()
{int a0;processValue(a);std::cout compareint(3,5) std::endl;std::cout compare(3,5) std::endl;return 0;
}输出结果为
0
-1
-1
由上面的例子我们可以看出除了直接为函数模板指定类型参数之外我们还可以让编译器从传递给函数的实参推断类型参数这一功能被称为模板实参推断形参T可以自动推到出类型当我们传入的是int类型时T此时会被替换成int。函数模板支持默认的形参类型如上面的template。
成员函数模板
不仅普通函数可以定义为模板类的成员函数也可以定义为模板。
class Printer {
public:templatetypename Tvoid print(const T t) {cout t endl;}
};int main()
{Printer p;p.printconst char*(abc);p.print(1);return 0;
}输出结果
abc
1
使用的方式和普通函数模板没有什么两样。
总结 1) 函数模板并不是真正的函数它只是C编译生成具体函数的一个模子。 2) 函数模板本身并不生成函数实际生成的函数是替换函数模板的那个函数。这种替换是编译期就绑定的。 3) 函数模板不是只编译一份满足多重需要而是为每一种替换它的函数编译一份。 4) 函数模板不允许自动类型转换。 5) 函数模板不可以设置默认模板实参。比如template 不可以。 6) 函数模板的模板形参不能为空。
补充
为什么成员函数模板不能是虚函数(virtual)
这是因为c compiler在parse一个类的时候就要确定vtable的大小如果允许一个虚函数是模板函数那么compiler就需要在parse这个类之前扫描所有的代码找出这个模板成员函数的调用实例化然后才能确定vtable的大小而显然这是不可行的除非改变当前compiler的工作机制。
函数模板和模板函数是什么 函数模板的重点是模板。表示的是一个模板专门用来生产函数。 模板函数的重点是函数。表示的是由一个模板生成而来的函数。
当返回值类型也是参数时 当一个模板函数的返回值类型需要用另外一个模板参数表示时你无法利用实参推断获取全部的类型参数这时有两种解决办法
返回值类型与参数类型完全无关那么就需要显示的指定返回值类型其他的类型交给实参推断。 注意此行为与函数的默认实参相同我们必须从左向右逐一指定。
templatetypename T1, typename T2, typename T3
T1 sum(T2 v2, T3 v3) {return static_castT1(v2 v3);
}auto ret sumlong(1L, 23); //指定T1, T2和T3交由编译器来推断templatetypename T1, typename T2, typename T3
T3 sum_alternative(T1 v1, T2 v2) {return static_castT1(v1 v2);
}
auto ret sum_alternativelong(1L, 23); //error只能从左向右逐一指定
auto ret sum_alternativelong,int,long(1L,23); //ok, 谁叫你把最后一个T3作为返回类型的呢int main()
{int a 3;auto ret sum(1.3443, 23); //指定T1, T2和T3交由编译器来推断编译错误必须指定返回类型T1auto ret sumdouble(1.3443, 23); //编译通过std::cout ret std::endl; //结果为24.3443auto ret1 sum_alternativedouble(1.3443, 23); //error只能从左向右逐一指定std::cout ret1 std::endl;auto ret2 sum_alternativedouble,int,double(1.3443,23); //ok, 谁叫你把最后一个T3作为返回类型的呢std::cout ret2 std::endl; //结果24.3443return 0;
}
返回值类型可以从参数类型中获得那么把函数写成尾置返回类型的形式就可以愉快的使用实参推断了。
templatetypename T
auto sum(T beg, T end) - decltype(*beg) {decltype(*beg) ret *beg;for (T it beg1; it ! end; it) {ret ret *it;}return ret;
}int main()
{std::vectorint v {1, 2, 3, 4};auto s sum(v.begin(), v.end()); //s 10std::cout s std::endl; //结果为10return 0;
}
函数模板重载
函数模板之间函数模板与普通函数之间可以重载。编译器会根据调用时提供的函数参数调用能够处理这一类型的最特殊的版本。在特殊性上一般按照如下顺序考虑 1. 普通函数 2. 特殊模板限定了T的形式的指针、引用、容器等 3. 普通模板对T没有任何限制的
templatetypename T
void func(T t) { //通用模板函数cout In generic version template t endl;
}templatetypename T
void func(T* t) { //指针版本cout In pointer version template *t endl;
}void func(string* s) { //普通函数cout In normal function *s endl;
}int i 10;
func(i); //调用通用版本其他函数或者无法实例化或者不匹配
func(i); //调用指针版本通用版本虽然也可以用但是编译器选择最特殊的版本
string s abc;
func(s); //调用普通函数通用版本和特殊版本虽然也都可以用但是编译器选择最特化的版本
func(s); //调用指针版本通过告诉编译器我们需要用template而不是普通函数模板函数的特化
有时通用的函数模板不能解决个别类型的问题我们必须对此进行定制这就是函数模板的特化。函数模板的特化必须把所有的模版参数全部指定。
template
void func(int i) {cout In special version for int i endl;
}int main()
{int i 10;func(i); //调用特化版本return 0;
}类模板
类模板也是公共逻辑的抽象通常用来作为容器例如vector或者行为的封装。
类模板的格式 template
#include iostream
#include vector
#include string
#include sstreamtemplatetypename T
class Printer {
public:explicit Printer(const T param):t(param){}//右值引用string to_string(){std::stringstream ss;ss t;return std::move(string(ss.str()));} void print() {cout t endl;}
private:T t;
};int main()
{Printer p(1); //errorPrinterint p(3); //okstd::string str p.to_string();std::cout str std::endl; //结果为3return 0;
}与函数模板不同类模板不能推断实例化。所以你只能显示指定类型参数使用Printer p(3)而不能让编译器自行推断。 类模板的成员函数既可以定义在内部也可以定义在外部。定义在内部的被隐式声明为inline定义在外部的类名之前必须加上template的相关声明。 类模板中的成员函数模板
我们还可以把类模板和函数模板结合起来定义一个含有成员函数模板的类模板。
templatetypename T
class Printer {
public:explicit Printer(const T param):t(param){}//成员函数模板templatetypename Uvoid add_and_print(const U u);
private:T t;
};//注意这里要有两层template的说明
templatetypename T
templatetypename U
void PrinterT::add_and_print(const U u) {cout t u endl;
}Printerint p(42);
p.add_and_print(1.1); //自动推断U为double打印出43.1类模板成员函数实例化
为了节省资源类模板实例化时并不是每个成员函数都实例化了而是使用到了哪个成员函数那个成员函数才实例化。
templatetypename T
class Printer {
public:explicit Printer(const T param):t(param){}void print() {cout t endl;}private:T t;};class empty{};empty e;
Printerempty p(e); //ok虽然成员函数print无法通过编译但是因为没有使用到也就没有实例化print所以没有触发编译错误。
类模板的特化与偏特化
就像函数模板重载那样你可以通过特化偏特化类模板来为特定的类型指定你想要的行为。类模板的特化偏特化只需要模板名称相同并且特化列表中的参数个数与原始模板对应上即可模板参数列表不必与原始模板相同模板名称相同。一个类模板可以有多个特化与函数模板相同编译器会自动实例化那个最特殊的版本。
#include typeinfotemplatetypename T //基本模板
class S {
public:void info() { printf(In base template\n); }
};template //特化
class Sint {
public:void info() {printf(In int specialization\n);}
};templatetypename T //偏特化
class ST* {
public:void info() {printf(In pointer specialization\n);}
};templatetypename T, typename U //另外一个偏特化
class ST(U) {
public:void info() {std::cout typeid(T).name() std::endl; std::cout typeid(U).name() std::endl;printf(In function specialization\n);}
};int func(int i) {return 2 * i;
}Sfloat s1;
s1.info(); //调用base模板
Sint s2;
s2.info(); //调用int特化版本
Sfloat* s3;
s3.info(); //调用T*特化版本
Sdecltype(func) s4;
s4.info(); //调用函数特化版本提供了所有类型实参的特化是完全特化只提供了部分类型实参或者T的类型受限例如T的特化被认为是不完整的所以也被称为偏特化。完全特化的结果是一个实际的class而偏特化的结果是另外一个同名的模板。*
类模板成员特化
除了可以特化类模板之外还可以对类模板中的成员函数和普通静态成员变量进行特化。
templatetypename T
class S {
public:void info() {printf(In base template\n);}static int code;
};templatetypename T
int ST::code 10;template
int Sint::code 100; //普通静态成员变量的int特化template
void Sint::info() { //成员函数的int特化printf(In int specialization\n);
} Sfloat s1;
s1.info(); //普通版本
printf(Code is: %d\n, s1.code); //code 10Sint s2;
s2.info(); //int特化版本
printf(Code is: %d\n, s2.code); //code 100*补充*
类模板的重点是模板。表示的是一个模板专门用于产生类的模子
例如
template typename T class Vector { … };
模板类的重点是类。表示的是由一个模板生成而来的类 例如
Vector int 、Vector char 、Vector Vector int 、Vector Shape* ……//全是模板类