无类型的模板参数
这里有一个用来产生随机数的类,它可以接受一个的数字,然后通过重载()符号,来产生一个符合要求的随机数。具体代码如下:
//: C03:Urand.h
// Unique random number generator
#ifndef URAND_H
#define URAND_H
#include <cstdlib>
#include <ctime>
template<int upperBound>
class Urand
{
int used[upperBound];
bool recycle;
public:
Urand(bool recycle = false);
int operator()(); // The "generator" function
};
template<int upperBound>
Urand<upperBound>::Urand(bool recyc)
: recycle(recyc)
{
memset(used, 0, upperBound * sizeof(int));
srand(time(0)); // Seed random number generator
}
template<int upperBound>
int Urand<upperBound>::operator()()
{
if(!memchr(used, 0, upperBound))
{
if(recycle)
memset(used,0,sizeof(used) * sizeof(int));
else
return -1; // No more spaces left
}
int newval;
while(used[newval = rand() % upperBound])
; // Until unique value is found
used[newval]++; // Set flag
return newval;
}
#endif // URAND_H ///:~
类Urand工作原理是这样的:它保留了一个对所有可以取到的数字在随即空间里的MAP(映射)(随机数的上限通过参数传给模板),并且给使用过的数字打上标记。可控的构造函数允许你在所有使用完所有资源后,重新回收再用。请注意:这个类是从优化速度的角度出发来实现功能的,所以它为所有实体都分配了映射空间。
缺省模板参数
关键字typename的使用
看下面的代码:
//: C03:TypenamedID.cpp
// Using 'typename' to say it's a type,
// and not something other than a type
template<class T> class X
{
// Without typename, you should get an error:
typename T::id i;
public:
void f() { i.g(); }
};
class Y
{
public:
class id
{
public:
void g() {}
};
};
int main()
{
Y y;
X<Y> xy;
xy.f();
} ///:~
从模板中的定义可以看出:我们假设了类T中含有某个嵌套在类内部的也可以用来声明变量的类型(type)——id。id可以是这个类T中的一个对象,通常情况下,你可以直接对id进行操作,但你不可以用id再creat一个其他的对象。然而在这里,在typename的帮助下,我们却做到了这一点。程序中Id被当作一个确确实实存在的类型来处理了,但如果我们丢掉了typename关键字,编译器就无法知道id 究竟是个什么东西了。
(在没有typename的时候)编译器会选择并区分我们在模板中定义的东西究竟是什么类型的,于是乎,它会把id当成其他的东西而不是一个类型(type),换句话说:它总是更乐意把标示看成一个对象(甚至是可变的私有类型),一个枚举或者什么类似的声明。当然,它不会的——也不能——就把它看成是一个(类型)type,它没这么聪明,当我们把id作为一个type使用的时候,编译器会无法理解的。
Typename关键字告诉了编译器把一个特殊的名字解释成一个类型,在下列情况下必须对一个name使用typename关键字:
1. 一个唯一的name(可以作为类型理解),它嵌套在另一个类型中的。
2. 依赖于一个模板参数,就是说:模板参数在某种程度上包含这个name。当模板参数使编译器在指认一个类型时产生了误解。
保险期间,你应该在所有编译器可能错把一个type当成一个变量的地方使用typename。就像上面那个例子中的T::id,因为我们使用了typename,所以编译器就知道了它是一个类型,可以用来声明并创建实例。
给你一个简明的使用指南:如果你的类型在模板参数中是有限制的,那你就必须使用typename.
用typename自定义一个类型
要知道typename关键字不会自动的typedef,
typename Seq::iterator It;
只是声明了一个Seq::iterator类型的变量,如果你想定义一个新类型的话,你必须这样:
typedef typename Seq::iterator It;
使用typename来代替class
详细介绍了typename的使用方法之后,我们现在就可以选择typename来取代class声明,这样可以增加程序的清晰度。
//: C03:UsingTypename.cpp
// Using 'typename' in the template argument list
template<typename T> class X { };
int main()
{
X<int> x;
} ///:~
你当然也会看到许多类似的代码没有使用typename关键字,因为模板概念诞生之后很久了,才有了typename关键字的加入。
函数模板
一个模板类描述的是一个无限的类的集合,你看到的是这些类中最普遍的地方。当然,C++也支持无限集合函数的概念,有是这是很有用的, 这些东西实质上是一样的,除非你就是想声明一个函数而不是一个类。
你可能已经知道了,我们要创建模板函数的原因,就是因为我们发现很多函数看上去是完全一样的,除了他们所处理的类型不同以外。并且,一个函数模板在许多地方都是非常有用的,就像我们在第一个例子中阐述的和我们将看到的第二个例子,它使用了容器(containers)和陈述(iterators)。
字符串转换系统
//: C03:stringConv.h
// Chuck Allison's string converter
#ifndef STRINGCONV_H
#define STRINGCONV_H
#include <string>
#include <sstream>
template<typename T>
T fromString(const std::string& s)
{
std::istringstream is(s);
T t;
is >> t;
return t;
}
template<typename T>
std::string toString(const T& t)
{
std::ostringstream s;
s << t;
return s.str();
}
#endif // STRINGCONV_H ///:~
这里是测试程序,它包括了标准库complex的使用:
//: C03:stringConvTest.cpp
#include "stringConv.h"
#include <iostream>
#include <complex>
using namespace std;
int main()
{
int i = 1234;
cout << "i == \"" << toString(i) << "\"\n";
float x = 567.89;
cout << "x == \"" << toString(x) << "\"\n";
complex<float> c(1.0, 2.0);
cout << "c == \"" << toString(c) << "\"\n";
cout << endl;
i = fromString<int>(string("1234"));
cout << "i == " << i << endl;
x = fromString<float>(string("567.89"));
cout << "x == " << x << endl;
c = fromString< complex<float> >(string("(1.0,2.0)"));
cout << "c == " << c << endl;
} ///:~
输出结果是:
i == "1234"
x == "567.89"
c == "(1,2)"
i == 1234
x == 567.89
c == (1,2)
内存分配系统
在更安全使用malloc()、calloc()和realloc()等内存分配函数的议题中,我们有许多事可以做,接下来的函数模板处理了一个函数getmem(),这个函数即可以分配新的内存空间,或者调整以分配内存空间的大小,它把新空间全部置0,并检查操作是否成功。这样,你只需要告诉它需要多少空间就行了,还减少了程序出错的可能。
//: C03:Getmem.h
// Function template for memory
#ifndef GETMEM_H
#define GETMEM_H
#include "../require.h"
#include <cstdlib>
#include <cstring>
template<class T>
void getmem(T*& oldmem, int elems)
{
typedef int cntr; // Type of element counter
const int csz = sizeof(cntr); // And size
const int tsz = sizeof(T);
if(elems == 0)
{
free(&(((cntr*)oldmem)[-1]));
return;
}
T* p = oldmem;
cntr oldcount = 0;
if(p)
{
// Previously allocated memory
// Old style:
// ((cntr*)p)--; // Back up by one cntr
// New style:
cntr* tmp = reinterpret_cast<cntr*>(p);
p = reinterpret_cast<T*>(--tmp);
oldcount = *(cntr*)p; // Previous # elems
}
T* m = (T*)realloc(p, elems * tsz + csz);
require(m != 0);
*((cntr*)m) = elems; // Keep track of count
const cntr increment = elems - oldcount;
if(increment > 0)
{
// Starting address of data:
long startadr = (long)&(m[oldcount]);
startadr += csz;
// Zero the additional new memory:
memset((void*)startadr, 0, increment * tsz);
}
// Return the address beyond the count:
oldmem = (T*)&(((cntr*)m)[1]);
}
template<class T>
inline void freemem(T * m) { getmem(m, 0); }
#endif // GETMEM_H ///:~
为了能够清空新的内存空间,程序分配了一个计数器来记录有多少个内存块被分配了,typedef cntr就是这个计数器的类型。
有一个指针的引用(oldmem)非常关键,因为在我们分配新内存空间的时候,原来的内存头指针就改变了,这个可以帮我们找回头指针。
如果参数传递的是0,这块内存就被释放掉,这是附加功能freemem()所借用的。
你会发现getmem的操作是相当底层的,这里有许多类型和字节的操作,例如,指针oldmem并没有指向内存的开始空间,它把内存的起始空间让给计数器使用。所以,当我们要free()这块内存,getmem()必须倒退这个指针cntr所占用的字节数,因为oldmem是一个T*,它必须首先被转换成cntr*,然后索引倒退一个位置,最后在该地址执行free():
free(&(((cntr*)oldmem)[-1]));
类似的,如果预先分配过内存,getmem()也必须先拿到目前内存的分配情况,然后再重新计算调用realloc()的方法。如果尺寸增加了,为了清空新的地址空间,我们就必须算出,使用memset的起始地址,最后,oldmem的地址依然是越过计数器的地址空间。
oldmem = (T*)&(((cntr*)m)[1]);
重申:因为oldmem是一个对指针的引用,它将可以改变外界传进来的任何参数
这里有一个测试getmem()的程序:
//: C03:Getmem.cpp
// Test memory function template
#include "Getmem.h"
#include <iostream>
using namespace std;
int main()
{
int* p = 0;
getmem(p, 10);
for(int i = 0; i < 10; i++)
{
cout << p[i] << ' ';
p[i] = i;
}
cout << '\n';
getmem(p, 20);
for(int j = 0; j < 20; j++)
{
cout << p[j] << ' ';
p[j] = j;
}
cout << '\n';
getmem(p, 25);
for(int k = 0; k < 25; k++)
cout << p[k] << ' ';
freemem(p);
cout << '\n';
float* f = 0;
getmem(f, 3);
for(int u = 0; u < 3; u++)
{
cout << f[u] << ' ';
f[u] = u + 3.14159;
}
cout << '\n';
getmem(f, 6);
for(int v = 0; v < 6; v++)
cout << f[v] << ' ';
freemem(f);
} ///:~