templates

啥是模版?
把类型或是值当成参数的类或是函数。

template<typename T>
class A{
public:
    explicit A(int s);
    ~A(){ delete [] e; }
    T& operator[](int i);
    const T& operator[](int i) const;
    int size() constreturn sz; }

private:
    T* e;
    int sz;
};

上面的typename可以换成class
template< typename T> 可以读成 for all T,或是for all types T

在实现的前面加一个template< typename T> 。 eg:

template< typename T> 
A< T >::A(int s) {
    ...
}

在此,新标准有一个需要注意的地方:

vector<list<int>> v;

新标准之后, >> 中间不需要空格了,算是修复了以前的一个小瑕疵

模版 是一种编译期机制。所以并不花费运行期的资源。
模版除了用类型作为参数,也可以用值作为参数。

template<typename T, int N>
strcut buf{
    using value_type = T;
    constexpr int size() { return N; }
    T[N];
};

其中using在这里的表示,为类型T取一个别名。除此之外还可以给namespace取别名。
constexpr是编译期的玩意。
用数值作为模版参数 ,在很多场合都是非常有用的。只是这个参数一定要是常量表达式。

模版能用来干嘛?
先来看看模版相对其他技术的优势:
可以传递类型、延时类型检查、常量参数(可以在编译期进行计算)。
总的来说,模版提供了一到强大的机制( 编译期计算和类型操作)可以使代码更加紧凑高效

模版最常用的是支持泛型编程(generic programming)
gp着重于设计、实现、使用泛型算法。
模版提供了编译期参数多态性。

如果一个类型被称为常规类型,必须和int、string、vector等一样,需要满足以下几点:
有默认的构造函数
可以拷贝(通过拷贝构造和赋值构造)
可以比较(==、!=)
没有过高编程技巧带来的技术问题
string就是一个基本类型。

模版的另一个使用就是函数对象,有时也称为仿函数(functor)
什么是函数对象?
如果一个类的使用看起来像一个函数,那么这个类可以称为函数对象。
如何使一个类的使用看起来像函数?
实现operator()

仿函数比较常用的是用在算法中的参数

相比函数指针,仿函数还是一个函数对象,可以将部分值保存在自己的对象中,这样就不需要函数指针的全局变量什么的,算是向优雅迈进了一小步。
再者,仿函数可以将operator()做成inline内联函数,这样效率比函数指针高一些。

仿函数是泛型算法的一个关键点,函数对象也经常被称为policy objects。

// =======================================================================================
// functor

template <typename T>
class Less_than {
public:
  Less_than ( const T& v ) : val ( v ) {
  }

  bool operator() ( const T& x ) const {
    return x < val;
  }

private:
  const T val;
};

template <typename C, typename P>
int count ( const C& c, P pred ) {
  int cnt = 0;
  for ( const auto & x : c )
    if ( pred ( x ) )
      ++cnt;

  return cnt;
}

void ff ( const std::vector<int>& vec,
         const std::list<std::string>& lst,
         int x,
         const std::string& s ) {
  std::cout << count ( vec, Less_than<int>{x} ) << std::endl;
  std::cout << count ( lst, Less_than<std::string>{s} ) << std::endl;
}

void gg ( const std::vector<int>& vec,
         const std::list<std::string>& lst,
         int x,
         const std::string& s ) {
  std::cout << count ( vec, [&]( int a ) { return a < x; } ) << std::endl;
  std::cout << count ( lst, [&]( const std::string& a ) { return a < s; } ) << std::endl;
}

void test_func () {
  std::cout << std::endl << std::endl << "-------------------------- functor test------------------------" << std::endl;

  // 将仿函数作为一个对象使用
  Less_than<int> lti { 234 };
  Less_than<std::string> lts { "abc" };

  std::cout << "123 < 234 : " << (lti(123) ? "true" : "false") << std::endl;
  std::cout << "bb < abc : " << (lts("bb") ? "true" : "false") << std::endl;

  // 将仿函数用在算法中
  // ff()将仿函数用在算法中,不管是当成一个对象使用,还是用在算法中,
  // 都是先创建一个函数对象(仿函数)的实例,再调用operator()来实现逻辑
  std::vector<int> vec { 1, 2, 3, 4, 5 };
  std::list<std::string> ls { "abcdef", "cd" };
  ff ( vec, ls, 4, "c" );

  // 好了,新标准看到了函数指针的局限,就告诉我们还有仿函数,
  // 在某些情况下,新标准还告诉我们有个东西叫lambda
  // g() 中[&](int a) { return a < x; }这个就叫lambda表达式
  gg ( vec, ls, 2, "b" );

  // lambda中的[&]:
  // []被称为捕获列表,指出了lambda里面如何引用外面的变量
  // [] 表示不捕获
  // [&] 表示以引用的方式捕获外面的值 [=]表示以复制的方式捕获外面的值
  // [&x] 表示x变量以引用的方式捕获  [=x] 表示以复制的方式捕获
  // lambda 比较方便简洁 缺点是模糊
  // 对于琐碎的操作,使用lambda可以,对于不琐碎的操作,最好不好用lambda
}

// =======================================================================================

下面看一下模版中的可变参数


// =======================================================================================
// variadic templates

// 可变参数的个数不确定,类型也可以不一样,把这些参数称为参数包
// 主要的问题主要集中在如何拆包,下面演示递归拆包(其他的拆包方式(逗号拆包)可以google)


// 由于所有的测试例子都写在一个文件里,为了解决命名问题,使用了namespace
namespace var {

namespace {

// g() 主要是用来处理每一个具体的元素的
template <typename T>
void g ( const T& v ) {
  std::cout << "one of the variadic templates: " << v << std::endl;
}

// 递归的终结 这个不能少,少了就没有出来的了
// 这个是用空来终结,也可以用最后一个元素来终结
void f () {
}

// 逗号拆包
template <typename T>
void print ( const T& t ) {
  std::cout << "one of param list " << t << std::endl;
}

};

// 递归拆包
// typename... 中的省略号表示这是一个可变参数
// tail... 参数表示剩下的可变参数
template<typename T, typename... Tail>
void f ( T head, Tail... tail ) {
  g ( head );
  f ( tail... );
}

// 逗号拆包
// 原理一:a = (b = c, d); 根据逗号原理,先将c丢给b,然后将d丢给a;
// 原理二:初始化参数列表
// 利用新标准的新特性(初始化参数列表),来初始化一个变长的数组
// 最后数组里的全是0,而初始化会被展开成print(arg0) print(arg1)...等
template <typename... Tail>
void t ( Tail... tail ) {
  int arr[] = { ( print ( tail ), 0 )... };
}

};

void test_var () {
  std::cout << std::endl << std::endl << "-------------------------- variadic templates test------------------------" << std::endl;

  var::f (1, 2, 3, "12", "abc", 12, 'c', 12.33);

  var::t ( "abd", "d", 'c', 2993.12, 12, 32, 0 );
}

// =======================================================================================

上面的例子中只解释了递归拆包和逗号拆包的方式,其中需要注意的是递归的结束,这个是不能省的,其次在递归中,是将头一个拿出来处理,剩下的继续走递归;而逗号拆包是利用初始化列表和逗号原理来实现。

若加上新标准(c++11的初始化列表、c++14的lambda),进一步的还可以扩展很多,详情可以google

由于可变参数的高灵活性,在标准库中大量使用了这种方式。

别名
在模版中的别名之前也略提及,用法是
using AA = T; 在标准库中也是大量用到。

模版编译模型
编译期做的事

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值