我们都知道在c++与c语言的区别中,最大的不同就是c++訲多了对象和模板,前面我们介绍了面向对象的编程,现在我们来认识一下c++中模板的知识。
模板八函数或类要处理的数据类型参数化,即表现为数据的多态性。在面向对象技术中,模板是另一种代码重用机制。
提示:以下是本篇文章正文内容,下面案例可供参考
在强类型程序设计语言中,参与运算的所有对象的类型在编译时即可确定下来,并且编译程序将进行严格的类型检查,这样就可以在程序未运行之前就检查出类型不兼容的错误。
但是由于这种强类型语言在提高程序的可靠性的同时又带来了一些负作用。
例如我们在使用函数求某一个计算类容的时候,会出现参数类型不一样,他们的算法相同,但是参数不同,所以程序员就必须写两个程序。这就不利于程序的扩展和维护。
如何解决强类型的严格性与灵活性的冲突?以往有三种方法。
第一种:利用宏函数,使用宏函数虽然确实会简便问题,但是我们知道引入宏函数后可能会导致程序出现以些意想不到的问题。所以在c++中一般不会使用宏函数。
第二种:为各种类型重载这一函数,但是重载函数又显得麻烦。增加工作量。
第三种:放松类型检查,在编译期间忽略这些类型的匹配问题。而在运行期间进行类型匹配检查,但是在程序运行的时候可能会出现类型不兼容的错误。
综和上面的方法,我们发现无论何种方法,都会有漏洞,那么最理想的方法是什么?最理想的方法是直接将数据类型作为类的参数,就像函数可以将数据作为参数一样。这种机制叫做“类属”(genericity),强类型程序设计语言(如Ada,Eiffel等语言)通常都采用这种方式
模板是一种参数化多态性的工具,可以为逻辑功能相同而类型不同的程序提供代码共享的机制。
由于c+程序结构的主要构件是类和函数,所以在c++中,模板被分为函数模板和类模板。模板并非一个实实在在的函数或类,仅仅是函数或类的描述,模板运算对象的类型不是实际的数据类型,而是一种参数化的类型(又称为类属类型)。带类属参数的函数叫做函数模板,带类属参数的类叫做类模板。
通常,设计的算法是可以处理多种数据类型的,c++提供的函数模板可以一次定义出具有共性(除类型参数外,其余全相同)的一组函数,同时也可以处理多种不同类型数据的函数。从而大大增强了函数设计的通用性。
函数模板定义的格式:
template<模板参数表>
<返回值类型><函数名>(<参数表>)
{
<函数体>
}
其中,关键字template是定义模板的关键字,<模板参数列表>中包含一个或多个用逗号分开的模板参数项,每一项又保留字class或者typename开始,后跟用户命名的标识符,此标识符为模板参数,表示数据类型。函数模板中可以利用这些模板参数定义函数返回值类型,参数类型和函数体中的变量类型。它同基本数据类型一样,可以在函数中的任何地方使用。
#include
#include
using namespace std;
template <typename T> //函数模板;
T Max(T a, T b)
{
return a > b ? a : b;
}
int main()
{
int a, b;
cout << "Input two integers to a&b:" << endl;
cin >> a >> b;
cout << "max(" << a << "," << b << ")=" << Max(a, b) << endl; //有些编译软件不能使用max模板函数,是由于系统自带max函数;
char c, d;
cout << "Input two chars to c&d:" << endl;
cin >> c >> d;
cout << "max(" << "\'" << c << "\'" << "," << "\'" << d << "\'" << ")=";
cout<< Max(c, d) << endl;
float x, y;
cin >> x >> y;
cout << "Input two float x&y:" <<endl;
cout << "max(" << x << "," << y << ")=" << Max(x, y) << endl;
cout << "Input two string to p&h:" << endl;
string p, h;
cin >> p >> h;
cout << "max(" << "\"" << p << "\"" << "," <<"\"" << h <<"\"" << ")=";
cout << Max(p, h) << endl;
return 0;
}
在程序中,我们可以发现在使用相同的函数时,不再需要多次定义数据类型,编译器会根据语句中的实际数据类型而产生相应的函数。
这就极大的减轻了程序员的工作负担。
下面我们来看看他的图示:
函数模板的声明和定义必须是全局作用域,而且模板不能被声明成类的成员函数。
函数模板有多种重载方式,可以定义同名的函数模板,提供不同的参数和实现;也可以用其他非模板函数重载。
重载函数模板和重载函数类似,重载函数模板便于定义类属参数,或者函数参数的类型。个数不同所进行的操作。
#include
using namespace std;
template <typename T>
T Max(T a, T b)
{
return a > b ? a : b;
}
//重载;
template<typename T>
T Max(T a[], T n)
{
T temp;
temp = a[0];
for (int i = 0; i < n; i++)
{
if (temp < a[i])
temp = a[i];
}
return temp;
}
int main()
{
int a, b;
cout << "Input two integers to a&b" << endl;
cin >> a >> b;
cout << "Max(" << a << "," << b << ")=" << Max(a, b) << endl;
int i, aa[10] = { 3,7,9,11,0,6,7,5,4,2 };
cout << "The original array:" << endl;
for (i = 0; i < 10; i++)
{
cout << aa[i] << ",";
}
cout << endl;
cout << "Max of 10 integers is" << Max(aa, 10) << endl;
return 0;
}
函数模板实例化时,实际参数类型将替换模板参数,虽然这种参数替换具有类型检查功能,但是却没有普通传值参数的类型转换机制。
也就是说,函数模板没办法预知隐式的类型转换。这个时候我们就可以用非模板函数重载一个同名的函数模板;
#include
using namespace std;
template <typename T>
T Max(T a,T b)
{
return a > b ? a : b;
}
int Max(int a, float b) //用普通函数重载函数模板;
{
return a > b ? a : b;
}
int main()
{
char a = '4', b = '5';
float c = 5.7;
int d = 10;
cout<<Max(a, b) << endl;
cout << Max(d, c) << endl;
return 0;
}
#include
#include
using namespace std;
template <typename T>
T Max(T a, T b)
{
return a > b ? a : b;
}
int Max(int a, float b)
{
return a > b ? a : b;
}
char* Max(char* a,char* b)
{
return strcmp(a, b) > 0 ? a : b;
}
int main()
{
char a = '4', b = '5';
int c = 5;
cout << "Max(" << "\'" << a << "\'" << "," << "\'" <<b << "\'" << ")=";
cout << Max(a, b) << endl;
cout << "Max(" << "\'" << a << "\'" << "," << "\'" << c << "\'" << ")=" << Max(a, c) << endl;
const char* p, * h;
p = "qaz";
h = "wsx";
cout << "Max(" << "\"" << p << "\"" << "," << "\"" << h << "\"" << ")=" << Max(p,h) << endl;
}
同函数模板一样,使用类模板可以为类定义一种模式,使得类中的某些数据成员。某些成员函数的参数以及某些成员函数的返回值能取任意类型。类模板是对一批仅有成员数据类型不同类的抽象,程序员只要为这一批类所组成的整个类家族创建一个类模板,然后给出一套程序代码,就可以用来生成多种具体的类,即模板类。
类是对一组对象的公共性质的抽象,而类模板则是对不同类的公共性质的抽象,英雌类模板属于是更高层次的抽象。
格式:
template<模板参数表>
class <类模板名>
{
<类成员声明>
}
其中,<模板参数表>中包含一个或多个用逗号隔开的类型,参数项可以包含数据类型,也可以包含类属类型,如果是类属类型,则必须加class或者typename做前缀。
类模板中的成员函数的定义:
template<模板参数表>
返回值类型<类模板名><类型列表>::<函数名>(参数表)
{
函数体
}
<类型名表>既是类模板中定义中的类型形式参数表中的参数名。
类模板中的成员函数放在类模板的外部的时候,也可以在前面加inline表明内连函数函数。
与函数模板不同的是,函数模板的实例化是由编译系统在处理函数调用时自动完成,而类模板的实例化必须由程序员在程序中显示的指定。
#include
#include
using namespace std;
const int size = 10;
template <typename AType>
class atype
{
public:
atype()
{
int i;
for (i = 0; i < size; i++)
{
array[i] = i;
}
}
AType& operator[](int n);
private:
AType array[size];
};
template <typename AType>
AType& atype<AType>::operator[](int n)
{
if (n<0 || n>size)
{
cout << "下标" << n << "超出范围!" << endl;
exit(1);
}
return array[n];
}
int main()
{
const int size = 10;
atype<int>intob;
atype<double>doubleob;
int i;
cout << "Integer 数组:";
for (i = 0; i < size; i++)
intob[i] = i;
for (i = 0; i < size; i++)
{
cout << intob[i] << " ";
}
cout << endl;
cout << "Double 数组:";
for (i = 0; i < size; i++)
{
doubleob[i] = (double)i / 2;
}
for (i = 0; i < size; i++)
{
cout << doubleob[i] << " ";
}
cout << endl;
intob[12] = 100; //下标越界;
return 0;
}
类模板的实例化创建的结果是一种类型,而类的实例化创建的结果则是一个对象。
在类模板的《模板参数表中》,必须至少有一个类参数,当然也可以有多个类参数。还可以有非类参数的参数,这样的参数一般称之为函数类型参数,也可以称之为无类型模板参数。
函数参数类型只限于整型,指针和引用,其他的类型(例如浮点型float)则不能使用。传递给函数参数类型的实参要么是整型常量,要么由指向全局函数或对象的指针或引用组成。由于函数参数类型的值不能改变,所以函数参数类型参数本身被看作常量,因此,函数类型参数可以用来设定数组大小。
#include
#include
using namespace std;
template <typename AType,int size> //size作为函数参数类型参数
class atype //模板类;
{
public:
atype()
{
int i;
for (i = 0; i < size; i++)
array[i] = i;
}
AType& operator[](int n); //"[]"运算符重载;至少含有一个参数;
private:
AType array[size];
};
template <typename AType,int size>
AType& atype<AType, size>::operator[](int n)
{
//下标越界检查;
if (n<0 || n>size)
{
cout << "下标" <<n<<"超出范围" << endl;
exit(1);
}
return array[n];
}
int main()
{
//10个元素的integer数组类,intob为该类的一个对象;
atype<int, 10>intob;
//10个元素的double数组类,doublob为该类的一个对象
atype<double, 10>doublob;
int i;
cout << "integer[]数组类: ";
for (i = 0; i < 10; i++)
{
intob[i] = i;
cout << intob[i] << " ";
}
cout << endl;
cout << "doublob[]数组类: ";
for (i = 0; i < 10; i++)
{
doublob[i] = (double)i / 2;
cout << doublob[i] << " ";
}
cout << endl;
intob[12] = 100;
return 0;
}
类模板可以包含于通用类型相关的默认参数。当类模板被实例化时,如果没有指定其他的数据类型,则使用默认数据类型。
#include
#include
using namespace std;
template<typename AType=int,int size=10>
class atype //使用默认参数的模板类;
{
public:
atype()
{
int i;
for (i = 0; i < size; i++)
{
array[i] = i;
}
}
AType& operator[](int n);
private:
AType array[size];
};
template<typename AType,int size>
AType& atype<AType, size>::operator[](int n)
{
if (n<0 || n>size)
{
cout << "下标" << n << "越界" << endl;
exit(1);
}
return array[n];
}
int main()
{
//12个元素的integer数组类,inton为模板类的一个对象;
atype<int,12>intob;
//double数组类,默认10个元素长度,
atype<double>doubleob;
//数组类,默认int型,默认10个元素长度;
atype<>defaultob;
int i;
cout << "Integer 数组类:";
for (i = 0; i < 12; i++)
{
intob[i] = i;
cout << intob[i] << " ";
}
cout << endl;
cout << "double 数组类:";
for (i = 0; i < 10; i++)
{
doubleob[i] = (double)i / 2;
cout << doubleob[i] << " ";
}
cout << endl;
cout << "默认参数类型:";
for (i = 0; i < 10; i++)
{
cout << defaultob[i] << " ";
}
cout << endl;
return 0;
}
c++中包含一个有许多组件的标准库。标准模板库(Standard Template Library,STL)是标准c++标准库的一部分。
“容器”是数据结构,是包含对象的对象。例如,数组,队列,堆栈,树,图等数据结构中的每一个节点都是一个对象。这些结构按照某种特定的逻辑关系组合在一起,就成为了一个新的对象。
在前面介绍c++基础知识的时候,就已经介绍了STL模板库,但是很多人并不知道他的原理,而要想弄懂这个问题就要知道容器的接口是什么。
我们所使用的STL模板斗都是将一些代码封装为一个容器,从而使用它,正是由于这样才使得我们的工作减轻很多负担。要想正确的使用模板,了解他们所对应的函数原型显得很重要。下面我们以vector模板为例,介绍他的原函数:
顺序容器将一组具有相同类型的元素一样额的线性形式组织起来。顺序容器分为vector,list,deque三种类型。他们在某些方面很相似。前面已经介绍过了,就不过多介绍。
关联容器具有根据一组索引来快速提取元素的能力,其中元素可以通过键值(key)来访问。四种关联容器可以分为两种:set和map
(1).set是一种集合,其中可以包含0个或多个不重复的以及不排序的元素,这些元素被称为键值。
例如,set集合s
{-2,34,56}
中包含三个键值,不能通过下标来访问集合;
(2).map是一种映像,其中包含0个或多个不排序的元素对,一个元素是不重复的键值,另一个是与键相关联的值。
例如,map集合m
{(first,4),(second,-99),(third,50)}
中包含3对元素。每对元素都由一个键值和相关联的值构成。
map和multimap容器的元素是按照关键字顺序排列的,因此提供按关键字快速查找元素。成员函数find(),count(),lower_bound(),upper_bound()基于元素键值的查找和计数。
#include
#include
#include
using namespace std;
int main()
{
int i;
vector<int>nums; //整型向量,长度为0;
nums.insert(nums.begin(), -99); //在第一处插入数据-99
nums.insert(nums.begin(), 50);
nums.insert(nums.end(), 4);
for (i = 0; i < nums.size(); i++)
{
if (nums[i])
cout << nums[i] << " ";
}
cout << endl;
nums.erase(nums.begin()); //删除向量中第一处的值
nums.erase(nums.begin());
for (i = 0; i < nums.size(); i++)
{
if(nums[i])
cout << nums[i] << " ";
}
cout << endl;
return 0;
}
对于迭代器的理解,可以理解为面向对象版本的指针,但是和指针又有一些不同。通俗点来说就是指针是迭代器,但迭代器又不仅仅是指针,指针可以指向内存中的一个地址,然后通过这个地址访问相应的内存单元。而迭代器更抽象,它可以指向容器中的一个位置,然后就可以直接访问这个位置的元素。
STL迭代器主要包括5中基本类别:输入(input)迭代器,输出(output)迭代器,前向(forward)迭代器,双向(bidirectional)迭代器和随机访问(random access)迭代器。
迭代器在头文件iterator中声明。因为不同类型的容器支持不同的迭代器,所以不必显式指定包含iterator文件也可以使用迭代器。
vector和deque容器支持随机访问。list,map,set,multiset和multimap支持双向访问;
可以定义各种容器的迭代器对象(iterator类型对象)。迭代器对象通常被称为迭代子或迭代算子。
eg:
#include
#include
#include //迭代器头文件可以省略;
using namespace std;
int main()
{
list<int>nums; //整型双向链表
for (int i = 0; i < 10; i++)
{
nums.insert(nums.end(), i * 10);
}
list<int>::const_iterator p1; //p1是双向链表的迭代子;
cout << "整型双向链表的正向输出:" << endl;
for (p1 = nums.begin(); p1 != nums.end(); p1++) //这里p1不再是用循环中常用的小于来判断循环结束条件;
{
cout << *p1 << " ";
}
cout << endl;
list<int>::reverse_iterator p2;
p2 = nums.rbegin(); //反向迭代指向最后一个元素;
cout << "逆向输出双向链表中所有元素:" << endl;
while (p2 != nums.rend())
{
cout << *p2 << " ";
p2++;
}
cout << endl;
return 0;
}
上面的程序中在使用迭代器的时候,我们发现迭代器的定义有点不一样,那么他们的差距在哪里?
预定义迭代器 | |
---|---|
预定义迭代器 | ++操作方向 |
iterator | 向前 |
const_iterator | 向前 |
reverse_iterator | 向后 |
const_ireverse_terator | 向后 |
c++中的STL中包含大约70种标准算法。这些算法是用于对容器的数据施加特定操作的函数模板。
STL中几乎所有的算法的头文件都是#include
从对容器的访问性质来说,算法分为只读(及不允许改写元素)和改写(即可以修改元素)两种。从功能上来说可以分为查找,比较,计算,排序,置值,合并,集合,管理等。
如同STL容器是常用数据结构的类模板一样,STL算法是用于对容器的数据施加特定操作的函数模板。
例如STL中的排序算法:
第一种形式:
template<typename RandomAcessIterator>
void sort(RandomAcessIterator first,RandomAcessIterator last);
按照升序排列;
第二种形式:
template<RandomAcessIterator,class Compare>
void sort(RandomAcessIterayor first,RandomAcessIterator last,Compare pr)
按照pr函数的排序方式来排序。
//reverse和sort的应用
#include
#include
#include
using namespace std;
bool inoder(int, int);
int main()
{
vector<int>nums; //整型向量,长度为;
for (int i = 0; i < 10; i++)
{
nums.insert(nums.end(), i * 10);
}
cout << "向量的初始顺序:";
vector<int>::const_iterator p1;
for (p1 = nums.begin(); p1 != nums.end(); p1++)
{
cout << *p1 << " ";
}
cout << endl;
//函数逆置;
reverse(nums.begin(), nums.end());
cout << "逆置排列:";
for (int i = 0; i < 10; i++)
{
cout << nums[i] << " ";
}
cout << endl;
//调用第一种排序方式排序;
sort(nums.begin(), nums.end());
cout << "使用第一种方式排序后的结果为:";
for (int i = 0; i < 10; i++)
{
cout << nums[i] << " ";
}
cout << endl;
//调用第二种方式排序
sort(nums.begin(), nums.end(), inoder);
vector<int>::const_iterator p2;
cout << "第二种排序方式的输出结果:";
for (p2 = nums.begin(); p2 != nums.end(); p2++)
{
cout << *p2 << " ";
}
cout << endl;
}
bool inoder(int a, int b)
{
return a > b;
}