泛型程序设计(generic programming)是一种算法在实现时不指定具体要操作的数据的类型的程序设计方法。所谓“泛型”指的是算法只要实现一遍,就能适用于多种数据类型。泛型程序设计方法的优势在于能够减少重复代码的编写。最成功的应用就是 C++ 的标准模板库(STL)。在 C++ 中,模板分为函数模板和类模板两种。在编写函数和类时可考虑是否可以使用模板使用。
在C++中,数据的类型也可以通过参数来传递,在函数定义时可以不指明具体的数据类型,当发生函数调用时,编译器可以根据传入的实参自动推断数据类型,这就是类型的参数化。值(Value)和类型(Type)是数据的两个主要特征,它们在C++中都可以被参数化。
函数模板,实际上是建立一个通用函数,它所用到的数据的类型(包括返回值类型、形参类型、局部变量类型)可以不具体指定,而是用一个虚拟的类型来代替(实际上是用一个标识符来占位),等发生函数调用时再根据传入的实参来逆推出真正的类型。这个通用函数就称为函数模板(Function Template)。
在函数模板中,数据的值和类型都被参数化,发生函数调用时编译器会根据传入的实参来推演形参的值和类型。换个角度说,函数模板除了支持值的参数化,还支持类型的参数化。
一但定义了函数模板,就可以将类型参数用于函数定义和函数声明了。即原来使用 int、float、char 等内置类型的地方,都可以用类型参数来代替。
1、函数模板
1.1语法
1 template <typename 类型参数1 , typename 类型参数2 , ...> 返回值类型 函数名(形参列表) 2 { 3 //在函数体中可以使用类型参数 4 } 5 6 template <typename/class T> T add(T a, T b) 7 { 8 ... 9 }
语法和示例如上所示:类型参数可以有多个,它们之间以逗号,
分隔。类型参数列表以< >
包围,形式参数列表以( )
包围。
函数模板也可以提前声明,不过声明时需要带上模板头,并且模板头和函数定义(声明)是一个不可分割的整体,它们可以换行,但中间不能有分号。
1 template <typename/class T> T add(T a,T b); //模板声明 2 template <typename/class T> 3 T add(T a,T b) 4 { 5 ... 6 }
typename
关键字也可以使用class
关键字替代,它们没有任何区别。C++ 早期对模板的支持并不严谨,没有引入新的关键字,而是用class来指明类型参数,但是class关键字本来已经用在类的定义中了,这样做显得不太友好,所以后来 C++又引入了一个新的关键字typename,专门用来定义类型参数。不过至今仍然有很多代码在使用 class关键字,包括 C++标准库、一些开源程序等。
2、类模板
C++除了支持函数模板,还支持类模板(Class Template)。函数模板中定义的类型参数可以用在函数声明和函数定义中,类模板中定义的类型参数可以用在类声明和类实现中。类模板的目的同样是将数据的类型参数化。
2.1 语法
1 template<typename 类型参数1 , typename 类型参数2 , …> class 类名 2 { 3 //TODO: 4 } 5 6 template<typename T1, typename T2> //这里不能有分号 7 class Point{ 8 public: 9 Point(T1 x, T2 y): m_x(x), m_y(y){ } 10 public: 11 ... 12 private: 13 T1 m_x; //x坐标 14 T2 m_y; //y坐标 15 };
类模板和函数模板都是以template开头(当然也可以使用class,目前来讲它们没有任何区别),后跟类型参数;类型参数不能为空,多个类型参数用逗号隔开。一但声明了类模板,就可以将类型参数用于类的成员函数和成员变量。
以上仅仅是类的声明,还需要在类外定义成员函数。在类外定义成员函数时仍然需要带上模板头。格式为:
1 template<typename 类型参数1 , typename 类型参数2 , …> 2 返回值类型 类名<类型参数1 , 类型参数2, ...>::函数名(形参列表) 3 { 4 //TODO: 5 } 6 7 template <typename/class T1, typename/class T2> 8 T1 Point<T1,T2>::SetX(T1 x) 9 { 10 ... 11 }
除了template关键字后面要指明类型参数,类名后面也要带上类型参数,只是不加typename关键字。此外要注意的是在类外定义成员函数时,template 后面的类型参数要和类声明时的一致。
2.2 使用类模板创建对象
使用类模板创建对象时,需要指明具体的数据类型。如下:
1 Point<int, float> p2(10, 15.5); //对象变量 2 Point<char*, char*> *p = new Point<char*, char*>("东经180度", "北纬210度"); //对象指针
与函数模板不同的是,类模板在实例化时必须显式地指明数据类型,编译器不能根据给定的数据推演出数据类型。当使用对象指针实例化时,赋值号两边都要指明具体的数据类型,且要保持一致。
3、编程语言分类
根据“在定义变量时是否需要显式地指明数据类型”可以分为强类型语言和弱类型语言。强类型和弱类型在这里是站在变量定义和类型转换的角度讲的,也可以站在编译和运行的角度看。
强类型语言在定义变量时需要显式地指明数据类型,并且一旦为变量指明了某种数据类型,该变量以后就不能赋予其他类型的数据,除非经过强制类型转换或隐式类型转换。典型的强类型语言有 C/C++、Java、C#等。
弱类型语言在定义变量时不需要显式地指明数据类型,编译器(解释器)会根据赋给变量的数据自动推导出类型,并且可以赋给变量不同类型的数据。典型的弱类型语言有JavaScript、Python、PHP、Ruby、Shell、Perl 等。
不管是强类型语言还是弱类型语言,在编译器(解释器)内部都有一个类型系统来维护变量的各种信息。弱类型语言往往是一边执行一边编译,这样可以根据上下文(可以理解为当前的执行环境)推导出很多有用信息,让编译更加高效。将这种一边执行一边编译的语言称为解释型语言,而将传统的先编译后执行的语言称为编译型语言。强类型语言较为严谨,在编译时就能发现很多错误,适合开发大型的、系统级的、工业级的项目;而弱类型语言较为灵活,编码效率高,部署容易,学习成本低,在Web开发中大显身手。另外强类型语言的IDE一般都比较强大,代码感知能力好,提示信息丰富;而弱类型语言一般都是在编辑器中直接书写代码。
C++模板也是被迫推出的,最直接的动力来源于对数据结构的封装。数据结构关注的是数据的存储,以及存储后如何进行增加、删除、修改和查询操作,它是一门基础性的学科,在实际开发中有着非常广泛的应用。