内容大纲:
1.C++模板简介
1.1C++模板概观
1.2C++函数模板
1.3C++类模板
1.4C++操作符重载
2.泛型编程
2.1概述
2.2关联特性(Traits)
2.3迭代器(iterators)
1.C++模板简介
1.1C++模板概观
模板是C++一种特性,允许函数或者类(对象)通过泛型的形式表现或运行。
int Max(int a, int b)
{
return (a>b) ? a : b;
}
long Max(long a, long b)
{
return (a>b) ? a : b;
}
char Max( char a, char b)
{
return (a>b)? a : b;
}
倘若没有模板,那么虽然功能相同,但返回类型和参数类型不同的函数就得重写,这无疑增加了无意义的工作。
简单地来说,模板就像一个模具,根据不同的需求可以做出不同材质的模型。
如果使用了模板,可以省去一堆冗余的代码,上述的三段代码可以缩减成一下的表达方式:
template
T Max( T a, T b)
{
return (a>b)?a:b;
}
使用方式也非常简单:
int i = 1, j = 2;
Max(i, j); //编译器会根据实参类型来推断T是什么类型,这个过程叫隐式实例化
C++主要有两种主要的模板:
类模板 和 函数模板
实例化也有两种类型:
显式:在代码中明确指出针对哪种类型进行实例化
隐式:在首次使用时根据具体情况使用一种合适的类型进行实例化
1.2C++函数模板
函数模板是参数化的一族函数。
简而言之,就是如果不考虑返回类型和输入参数类型,那么这些函数的功能是一样的。
函数模板定义举例:
template //模板参数由关键字typename引入
T Max( T a, T b) //参数型别未定,以模板参数T表示
{
return (a>b)?a:b;
}
需要注意以下两点:
1.可以用class替代typename来定义类别参数,但struct不可以。
2.从语法上来讲class 和 typename没有区别,但是用class会导致误解,譬如会以为只有类才能作为型别参数,所以,尽量使用typename。
函数模板的使用:
int i = 1, j = 2;
float k = 1.0, l = 2.0;
Max(i, j); //T 为int
Max(k,l); //T为float
Max(i,l); //不可以,无法确定T为哪种类型
模板实例化:
用具体型别替代模板参数T的过程叫实例化,从而产生一个模板实例。
综上,得出一个结论模板被编译了两次
1.一次是实例化之前,检查模板代码本身是否有语法错误;
2.实例化期间,检查对模板代码的调用是否合法。
1.2C++类模板
与函数模板类似,类也是可以做成模板,但是类模板比函数模板相比有更多的特性,所以使用起来更加灵活。下面举一个类模板的例子:
const std::size_t DefaultStackSize = 1024;
tmeplate
Class Stack
{
public:
void Push(cosnt T &element);
int Pop(T &element);
int Top(T &element) const ;
private:
std::vector m_Member;
std::size_t m_nMaxSize = n;
};
值得注意的是,类模板的声明时,除了Copy constructor之外,如果在类模板中需要使用到这个类本身,比如定义operator=,那么应该使用其完整的定义(Stack
tmeplate
Class Stack
{
public:
...
Stack(Stack const&);
Stack& operator= (Stack const&);
...
};
如果在类外要定义一个类模板的成员函数,则要指明其是一个模板函数,例如Push函数的定义应当如下:
template
void Stack::Push(const T &element)
{
...
}
类模板特化(specializations)
允许对一个类的某些模板形参类型做特化
特化的作用好处在于:
1.对于某些特俗的型别,可能可以做些特别的优化或提供不同的实现
2.避免在实例化类的时候引起一些可能产生的诡异行为
3.特化一个类模板的时候也意味着需要特化所有其他参数化的成员参数
如果要特化一个类,方法如下:
template<> //声明一个带template<>的类,即空参数列表
class Stack //在类名称后面紧跟的尖括号中显式指明类型
{
...
};
特化后的具体实现是可以和主模板的实现不一样,类似函数重载。
偏特化(Partial specialization)
类模板也可以被偏特化,比如主模板定义为:
template
class MyCalss
{
...
};
则由此可能产生以下几种主模板的偏特化:
1.将模板参数偏特化为同样类型:
template
class MyClass
{
...
};
2.也可以将第二个将第二个模板参数特化为int类型,不是泛化的T:
template
class MyCalss
{
...;
};
3.还可以将类型偏特化为指针:
template
class MyClass
{
...
};
那么知道定义后,我们该如何使用呢?请看下表:
但使用时需要多加小心,防止出现二义性出现。
我们知道函数有默认参数,同样,类模板与其也有异曲同工之处。例如:
template >
//使用std::vector<>作为默认实参
class Stack
{
private:
TContainer m_Container;
...
};
当然,如果你传入一个其他类型的类型时,则这是将以你输入的那个类型为T2的类型。
总而言之:
·模板类的性质是有一个或多个类型未被指定的模板。
·对于类模板而言,只有被调用到的成员函数才会被实例化
·类模板可以用特性的型别特化
·支持偏特化
·也允许有默认值
1.4运算符重载
对于运算符重载,由于前面两门课已经有所探究,并且侯老师讲解得也足够详细,故这里不再继续重述。
2.泛型编程
2.1概述
泛型编程是一种方法,这种方式将类型以一种to-be-specified-later的方式给出。
2.2关联特性
昨天学习C++ primer的第十章,遇到这么一个问题,程序如下:
#include
#include
#include
#include
#include
using namespace std;
bool check_size(const string &s1, const string &s2)
{
return s1.size()>s2.size();
}
int main ()
{
using std::placeholders::_1;
vector s = {"the","quick","red","fox","jumps","ovet","the","red","slow","red","turtle"};
string a = "red";
auto p1 = bind(check_size,_1,"red");
vector::iterator p = find_if(s.cbegin(),s.cend(),p1);
cout<
发生报错
[Error] conversion from '__gnu_cxx::__normal_iterator*, std::vector > >' to non-scalar type 'std::vector >::iterator {aka __gnu_cxx::__normal_iterator*, std::vector > >}' requested```
后来在群里请教了别人,才知道原来我在find_if里使用了cbegin(),则find_if()传出的迭代器也是不可修改指向的值的,所以应该是const_iterator。
所以改成
```C++
vector::const_iterator p = find_if(s.cbegin(),s.cend(),p1);
就可以了。
附加内容:
一、输出容器元素方法:
1.利用普通for循环输出
vector v = {1,2,3,4};
for(vector::iterator it = v.begin( ); it!=v.end(); it++)
{
cout<<*it;
}
2.利用范围for循环输出
//支持C++11的编译器
vector v = {1,2,3,4};
for(auto it:v)
{
cout<
可以看到,利用范围for来遍历元素是非常方便的。
3.利用标准库for_each算法和lambda表达式(方便快捷,个人比较喜欢)
vector v = {1,2,3,4};
for_each( v.cbegin(), v.cend(), [](int i) { cout << i << " " ; });
4.利用流迭代器和标准库copy算法
ostream_iterator out_iter(cout, " ");
vector v = {1,2,3,4};
copy( vec.cbegin( ), vec.cend( ), out_iter);
二、关于copy算法
重要的一点是传递给copy的目的序列至少要包含与输入序列一样多的元素
即不能复制元素到一个空或者元素数量少于母板的数目。但有一点要注意,利用插入迭代器时,可以使用空的容器。
list lst = {1, 2, 3, 4 };
list v1, v2;
copy(lst.cbegin( ), lst.cend( ), front_inserter(lst2) );
由于有迭代器作为桥梁,因为copy函数是先将值传递给迭代器,再由迭代器来操作容器,这样,容器即便是空的,经过插入迭代器处理后可以产生容纳该被复制元素的空间,所以可以使用空容器。