C++11新特性之泛型编程与模板

  • 模板
    • 泛型编程
    • 函数模板
      • 普通函数模板
      • 成员函数模板
      • 函数模板重载
      • 模板函数的特化
    • 类模板
      • 类模板中的成员函数模板
      • 类模板的特化与偏特化
      • 类模板成员特化

模板

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 形参名,......> 返回类型 函数名(参数列表)
{函数体
}

其中templateclass是关键字,当然class可以使用typename关键字代替,两者之间一点区别都没有,这个是真的。
<>括号中的参数叫模板形参,模板形参和函数形参很相像,模板形参不能为空。

普通函数模板

template<typename T>
int compare(const T& left, const T& right) {if (left < right) {return -1; }if (right < left) {return 1; }return 0;
}template<class T=double>
void processValue( T& value )
{std::cout << value << std::endl;
}int main()
{int a=0;processValue(a);std::cout<< compare<int>(3,5) << std::endl;std::cout<< compare(3,5) << std::endl;return 0;
}输出结果为:
0
-1
-1

由上面的例子我们可以看出,除了直接为函数模板指定类型参数之外,我们还可以让编译器从传递给函数的实参推断类型参数,这一功能被称为模板实参推断,形参T可以自动推到出类型,当我们传入的是int类型时,T此时会被替换成int。函数模板支持默认的形参类型,如上面的template。

成员函数模板

不仅普通函数可以定义为模板,类的成员函数也可以定义为模板。

class Printer {
public:template<typename T>void print(const T& t) {cout << t <<endl;}
};int main()
{Printer p;p.print<const 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的工作机制。

函数模板和模板函数是什么?
函数模板的重点是模板。表示的是一个模板,专门用来生产函数。
模板函数的重点是函数。表示的是由一个模板生成而来的函数。

当返回值类型也是参数时
当一个模板函数的返回值类型需要用另外一个模板参数表示时,你无法利用实参推断获取全部的类型参数,这时有两种解决办法:

  • 返回值类型与参数类型完全无关,那么就需要显示的指定返回值类型,其他的类型交给实参推断。
    注意:此行为与函数的默认实参相同,我们必须从左向右逐一指定。
template<typename T1, typename T2, typename T3>
T1 sum(T2 v2, T3 v3) {return static_cast<T1>(v2 + v3);
}auto ret = sum<long>(1L, 23); //指定T1, T2和T3交由编译器来推断template<typename T1, typename T2, typename T3>
T3 sum_alternative(T1 v1, T2 v2) {return static_cast<T1>(v1 + v2);
}
auto ret = sum_alternative<long>(1L, 23); //error,只能从左向右逐一指定
auto ret = sum_alternative<long,int,long>(1L,23); //ok, 谁叫你把最后一个T3作为返回类型的呢?int main()
{int a  = 3;auto ret = sum(1.3443, 23); //指定T1, T2和T3交由编译器来推断,编译错误,必须指定返回类型T1auto ret = sum<double>(1.3443, 23); //编译通过std::cout<< ret << std::endl; //结果为24.3443auto ret1 = sum_alternative<double>(1.3443, 23); //error,只能从左向右逐一指定std::cout<< ret1 << std::endl;auto ret2 = sum_alternative<double,int,double>(1.3443,23); //ok, 谁叫你把最后一个T3作为返回类型的呢?std::cout<< ret2 << std::endl; //结果24.3443return 0;
}
  • 返回值类型可以从参数类型中获得,那么把函数写成尾置返回类型的形式,就可以愉快的使用实参推断了。
template<typename T>
auto sum(T beg, T end) -> decltype(*beg) {decltype(*beg) ret = *beg;for (T it = beg+1; it != end; it++) {ret  = ret + *it;}return ret;
}int main()
{std::vector<int> 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没有任何限制的)

template<typename T>
void func(T& t) { //通用模板函数cout << "In generic version template " << t << endl;
}template<typename 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 <sstream>template<typename 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); //errorPrinter<int> p(3); //okstd::string str = p.to_string();std::cout << str << std::endl; //结果为3return 0;
}

与函数模板不同,类模板不能推断实例化。所以你只能显示指定类型参数使用Printer p(3),而不能让编译器自行推断。

类模板的成员函数既可以定义在内部,也可以定义在外部。定义在内部的被隐式声明为inline,定义在外部的类名之前必须加上template的相关声明。

类模板中的成员函数模板

我们还可以把类模板和函数模板结合起来,定义一个含有成员函数模板的类模板。

template<typename T>
class Printer {
public:explicit Printer(const T& param):t(param){}//成员函数模板template<typename U>void add_and_print(const U& u);
private:T t;
};//注意这里要有两层template的说明
template<typename T>
template<typename U>
void Printer<T>::add_and_print(const U& u) {cout << t + u << endl;
}Printer<int> p(42);
p.add_and_print(1.1); //自动推断U为double,打印出43.1

类模板成员函数实例化

为了节省资源,类模板实例化时并不是每个成员函数都实例化了,而是使用到了哪个成员函数,那个成员函数才实例化。

template<typename T>
class Printer {
public:explicit Printer(const T& param):t(param){}void print() {cout << t << endl;}private:T t;};class empty{};empty e;
Printer<empty> p(e); //ok

虽然成员函数print无法通过编译,但是因为没有使用到,也就没有实例化print,所以没有触发编译错误。

类模板的特化与偏特化

就像函数模板重载那样,你可以通过特化(偏特化)类模板来为特定的类型指定你想要的行为。类模板的特化(偏特化)只需要模板名称相同并且特化列表<>中的参数个数与原始模板对应上即可,模板参数列表不必与原始模板相同模板名称相同。一个类模板可以有多个特化,与函数模板相同,编译器会自动实例化那个最特殊的版本。

#include <typeinfo>template<typename T> //基本模板
class S {
public:void info() {   printf("In base template\n"); }
};template<> //特化
class S<int> { 
public:void info() {printf("In int specialization\n");}
};template<typename T> //偏特化
class S<T*> {
public:void info() {printf("In pointer specialization\n");}
};template<typename T, typename U> //另外一个偏特化
class S<T(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;
}S<float> s1;
s1.info();     //调用base模板                
S<int> s2;
s2.info();     //调用int特化版本
S<float*> s3;
s3.info();     //调用T*特化版本 
S<decltype(func)> s4;
s4.info();     //调用函数特化版本

提供了所有类型实参的特化是完全特化,只提供了部分类型实参或者T的类型受限(例如:T)的特化被认为是不完整的,所以也被称为偏特化。完全特化的结果是一个实际的class,而偏特化的结果是另外一个同名的模板。*

类模板成员特化

除了可以特化类模板之外,还可以对类模板中的成员函数和普通静态成员变量进行特化。

template<typename T>  
class S {
public:void info() {printf("In base template\n");}static int code;
};template<typename T>
int S<T>::code = 10;template<>
int S<int>::code = 100;    //普通静态成员变量的int特化template<>
void S<int>::info() {    //成员函数的int特化printf("In int specialization\n");
} S<float> s1;
s1.info();    //普通版本
printf("Code is: %d\n", s1.code);    //code = 10S<int> 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*> ……//全是模板类

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

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

相关文章

WordPress更改“固定链接”后 页面404原因及解决方法(Nginx版)

网上盛传的方法是&#xff1a; 在 /etc/nginx/nginx.conf文件的 loction / {} 中添加 if (-f $request_filename/index.html){rewrite (.*) $1/index.html break; }if (-f $request_filename/index.php){rewrite (.*) $1/index.php; }if (!-f $request_filename){rewrite (.*…

C++类型萃取之type_traits和type_info

类型萃取类型判断typeiddecltype和declvalenable_if 类型萃取 通过type_traits可以实现在编译期计算、查询、判断、转换和选择&#xff0c;增强了泛型编程的能力&#xff0c;也增强了我们程序的弹性&#xff0c;让我们能够在编译期就能够优化改进甚至排错&#xff0c;进一步提…

使用Phpstorm实现远程开发

Phpstorm除了能直接打开本地文件之外&#xff0c;还可以连接FTP&#xff0c;除了完成正常的数据传递任务之外&#xff0c;还可以进行本地文件与服务端文件的异同比较&#xff0c;同一文件自动匹配目录上传&#xff0c;下载&#xff0c;这些功能是平常IDE&#xff0c;FTP软件中少…

什么是递归函数?

文章目录递归函数递归例题特点效率优点递归函数 递归 递归就是一个函数在它的函数体内调用它自身。执行递归函数将反复调用其自身&#xff0c;每调用一次就进入新的一层。递归函数必须有结束条件。 当函数在一直递推&#xff0c;直到遇到墙后返回&#xff0c;这个墙就是结束条…

apache ab压力测试报错

今天用apache 自带的ab工具测试&#xff0c;当并发量达到1000多的时候报错如下&#xff1a; [rootaa~]# This is ApacheBench, Version 2.3 <Revision:655654> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Sof…

ngOnInit与constructor的区别

前世今生 Angular会管理一个组件的生命周期,包括组件的创建、渲染、子组件创建与渲染、当数据绑定属性变化时的校验、DOM移除之前毁销。 Angular提供组件生命周期钩子便于我们在某些关键点发生时添加操作。 组件生命周期钩子 指令和组件实例有个生命周期用于创建、更新和销…

Nginx配置性能优化

大多数的Nginx安装指南告诉你如下基础知识——通过apt-get安装&#xff0c;修改这里或那里的几行配置&#xff0c;好了&#xff0c;你已经有了一个Web服务器了。而且&#xff0c;在大多数情况下&#xff0c;一个常规安装的nginx对你的网站来说已经能很好地工作了。然而&#xf…

Angular的@Output与@Input理解

@Output与@Input理解 Output和Input是两个装饰器,是Angular2专门用来实现跨组件通讯,双向绑定等操作所用的。 @Input Component本身是一种支持 nest 的结构,Child和Parent之间,如果Parent需要把数据传输给child并在child自己的页面中显示,则需要在Child的对应 directiv…

腾讯云CDN配置

第一步&#xff1a;先去领取腾讯云CDN免费包23333333 以下为正式步骤&#xff1a; 在这里体现大家&#xff0c;域名一定要备案&#xff0c;另外要明白域名如何解析 前边问题不大&#xff0c;一切跟着腾讯云的套路来即可&#xff0c;需要注意的是网上后优化的配置大家可以自行…

Promise.all的深入理解

异步之Promise Promise.all Promise.all接收的promise数组是按顺序执行的还是一起执行的&#xff0c;也就是说返回的结果是顺序固定的吗&#xff1f; 目前有两种答案&#xff1a; 应该是同步执行的&#xff0c;但是这样就有效率问题了&#xff0c;如果想改成异步执行怎么办…

wordpress后台无法登录问题

之前给自己的WordPress加了个标签云&#xff0c;今天登录的时候突然发现网站后台进不去了&#xff0c;无奈各种找材料&#xff0c;这算是皇天不负有心人&#xff0c;总算是给我找到了&#xff0c;现在做一下记录 登录不上的原因在于&#xff1a;wp-admin和wp-admin/是不同的&a…

深入理解Angular订阅者模式

深入理解Angular订阅者模式 如果正在读此篇文章的你学过java,c++等面向对象语言,知道两个模式观察者模式和订阅者模式,分别为:Observer pattern,Pub-sub pattern(Subscriber) 接下来我们结合Angular来说明这两个模式。 Observer pattern This is a pattern of developme…

Ubuntu中安装python3

通过命令行安装Python3.*&#xff0c;只需要在终端中通过命令行安装即可&#xff1a; sudo apt-get install python3 Ubuntu的底层大多数采用的是Python2.*&#xff0c;Python3和Python2是互相不兼容的&#xff0c;完全没法通用的&#xff08;也不知道他们怎么想的o(TヘTo)&a…

Angular深入理解之指令

Angular深入理解之指令 指令有什么功能 Attribute directives 属性指令Structural directives 结构指令自定义属性指令自定义结构指令Angular深入理解之指令 对于初学Angular的同学来说,指令无疑是最痛苦的,那么我们怎么使用自定义的指令呢?指令到底怎么实现呢?为什么要写…

windows下Apache虚拟主机配置

找到host文件&#xff1a;C:\Windows\System32\drivers\etc\hosts 在hosts这么增加&#xff1a; 127.0.0.1 666.666.com 127.0.0.1 777.777.com 修改httpd.conf文件&#xff1a; 打开文件&#xff1a;xxx\xampp\apache\conf\httpd.conf 找到#LoadModule vhost_…

Angular深入理解基本组成

Angular深入理解基本组成 在讲指令时,我们先来了解一下Angular的基本概念和结构。 Module 模块 Angular 是模块化的.Modules 导出 classes, function, values , 以便在其他模块导入使用.angular应用由模块组成,每个模块都做此模块相关的事情组件、方法、类、服务等,他们都…

1607: 字符棱形

1607: 字符棱形 根据读入的字符和边长&#xff0c;勾画字符棱形。 Input 输入数据含有不超过50组的数据&#xff0c;每组数据包括一个可见字符c和一个整数n&#xff08;1≤n≤30&#xff09;。 Output 输出以c为填充字符&#xff0c;边长为n的棱形&#xff0c;勾画每个棱形…

Angular深入理解管道Pipe

Angular深入理解管道 纯管道与非纯管道区别的本质 Pure FunctionImpure Function内置Pipe pipe使用自定义Pipe 管道性能优化Angular深入理解管道 管道的链接 有学过linux shell的同学,应该知道管道,在shell中的管道是IPC,linux的进程间通讯有pipe,FIFO,signal。这里只是简单…

1959: 图案打印

1959: 图案打印 Description 一年一度的植树节就要到了&#xff0c;计算机学院学生准备在学院教学楼门前的空地上种植树木。为使树木排列得更加美观&#xff0c;大家决定把树木排列成菱形。现在告诉你我们所拥有的树木能排列成边长为N的菱形&#xff0c;请你编程输出树木所排…

JS事件的捕获和冒泡阶段

JS事件的捕获和冒泡阶段 这里介绍两个事件模型&#xff1a;IE事件模型与DOM事件模型 IE内核浏览器的事件模型是冒泡型事件&#xff08;没有捕获事件过程&#xff09;&#xff0c;事件句柄的触发顺序是从ChildNode到ParentNode。 <div id"ancestor"> <butt…