剖析C++模板(上)

原创 2001年06月22日 16:12:00

无类型的模板参数

       这里有一个用来产生随机数的类,它可以接受一个的数字,然后通过重载()符号,来产生一个符合要求的随机数。具体代码如下:

//: C03:Urand.h<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

// 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)——idid可以是这个类T中的一个对象,通常情况下,你可以直接对id进行操作,但你不可以用idcreat一个其他的对象。然而在这里,在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);

} ///:~

深入C++ Builder之编写自己的元件-深入分析VCL继承、消息机制

这篇文章提及内容可能大家已经在很多地方看到过了,作者也是如此,只不过还看了很多VCL源代码,加上自己实际编写元件的经验,拼凑了这么一篇文章。所以所有言论都是个人观点、经验的描述,仅供参考。 你可转载...
  • zb872676223
  • zb872676223
  • 2014年10月27日 23:38
  • 657

【C++】类模板(template)作用对比举例

一、类模板 类模板是后期C++加入的一种可以大大提高编程效率的方法 关键字template 用法 template class T {      //.... }   二、举个栗子 我们要写一个比较...
  • qq_31828515
  • qq_31828515
  • 2016年07月07日 15:44
  • 2203

C++ 模板详解(二)

C++模板     四、类模板的默认模板类型形参   1、可以为类模板的类型形参提供默认值,但不能为函数模板的类型形参提供默认值。函数模板和类模板都可以为模板的非类型形参提供默认值。  ...
  • imxiangzi
  • imxiangzi
  • 2015年07月13日 11:34
  • 2822

c++重载与模板

一、 模板是泛型编程的重要支持,函数和类模板在STL中运用的非常广泛,首先谈谈c中的函数重载和c++中的函数模板的区别: 1、首先,函数重载是函数名相同,但参数个数和类别不同,如果增加参数个数和参数型...
  • monkey_D_feilong
  • monkey_D_feilong
  • 2016年07月12日 19:48
  • 1002

c++编译器模板机制剖析

编译器编译原理   gcc 基本概念 什么是 gcc,gcc(Gun Compiler Collection 缩写),最初是是作为 c 语言的编译器(Gun C Co...
  • liulongling
  • liulongling
  • 2016年03月23日 18:48
  • 875

C++模板类的使用和继承

定义一个类模板,注意两点: 1,类的定义前面使用关键词:template 2,函数的实现部分,在每个函数名的上一行也要加关键词template , 并且在函数名后面添加,例如 template...
  • taigw
  • taigw
  • 2014年01月14日 10:59
  • 2797

C++函数模板的使用

函数模板: 函数模板是函数的蓝图或处方,编译器使用它生成函数系列的新成员。新函数在第一次使用时创建。从函数模板中生成的函数称为该模板的一个实例或模板的实例化。函数模板的开头是关键字template,表...
  • u010142437
  • u010142437
  • 2014年06月17日 15:40
  • 1385

c++模板链表实现

简介:主要是利用模板实现链表的操作。模板的使用,使得程序的开发量大大地减少。 可以先定义一个链表LinkList,之后可以定义自己的类了(例如:Student类),使用时就可以这样调用了 LinkLi...
  • invisible_sky
  • invisible_sky
  • 2016年03月24日 17:48
  • 867

C++ Template 基础篇(一):函数模板

C++ Template 基础篇(一):函数模板Template所代表的泛型编程是C++语言中的重要的组成部分,我将通过几篇blog对这半年以来的学习做一个系统的总结,本文是基础篇的第一部分。C Te...
  • lezardfu
  • lezardfu
  • 2017年02月24日 19:40
  • 1029

C++函数模板详解

原文链接-------------------点击打开链接 经常有碰到函数模块的应用,很多书上也只是略有小讲一下,今天又狂碰到函数模块,无奈特地找来C++编程经典 C++函数模...
  • qq_24489717
  • qq_24489717
  • 2016年01月05日 01:12
  • 879
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:剖析C++模板(上)
举报原因:
原因补充:

(最多只允许输入30个字)