C++模板机制笔记

《C & c++ CODE CAPSULES》读书笔记

引子:

利用函数重载为可能需要的每个数据类型提供一个不同版本的交换函数swap():

#include <iostream>
using namespace std;

void swap(int& x, int& y){
	int tmp = x;
	x = y;
	y = tmp;
}

void swap(float& x, float& y){
	float tmp = x;
	x = y;
	y = tmp;
}

void swap(char*& x, char*& y){
	char* tmp = x;
	x = y;
	y = tmp;
}

int main(){
	int a = 1, b = 2;
	float c = 1.0, d = 2.4;
	char *s = "hello", *p = "world";

	swap(a, b);
	cout << "a == " << a << ",b == " << b << endl;
	swap(c, d);
	cout << "c == " << c << ",d == " << d << endl;
	swap(s, p);
	cout << "s == " << s << ",p == " << p << endl;

	return 0;
}

我们可以总结两条:

1.当需要处理一个新的类型时,需要对一个交换函数swap()建立新的重载并且重建软件。

2.每个重载的逻辑是相同的,只是数据类型不同。


一、泛型编程

模板是类型抽象:可以将模板工具看作是将类型作为参数的编译期机制。


二、函数模板

1.函数模板是一个“形式语言机制”,这个机制为一个从实际函数调用的上下文中引用适当的数据类型的函数自动生成代码。交换函数swap()的模板如下:

#include <iostream>
using std::cout;
using std::endl;

template<class T>
void swap(T& a, T& b){
	T tmp = a;
	a = b;
	b = tmp;
}

int main(){
	int a = 1, b = 2;
	float c = 1.0, d = 2.4;
	char *s = "hello", *p = "world";

	swap(a, b);
	cout << "a == " << a << ",b == " << b << endl;
	swap(c, d);
	cout << "c == " << c << ",d == " << d << endl;
	swap(s, p);
	cout << "s == " << s << ",p == " << p << endl;

	return 0;
}

因为交换函数swap()模板在标准库中已经定义了,所以将using namespace std;换成using std::cout;和using std::endl。

2.好习惯:在一个专门的包含文件中包含所有的模板代码。

3.要定义一个具有参数在函数参数列表中但是不使用的函数模板,这是能够做到的,例如:

template<class To,class From>
To convert(const From& f){
	return To(f);
}

int main(){
	string s = convert<string>("foo");
	cout << "s == " << s << endl;
	string t = convert<string, char*>("bar");
	cout << "t == " << t << endl;

	return 0;
}

convert的第一次调用从实参“foo”推断出第二个模板参数From是char* 型,第二次是显示地限定这两个类型。但是,convert的调用中没有办法推断参数To的类型,在这种情况下需要显示地限定函数调用来说明需要哪个实例。


三、类模板

C++允许类模板,其中类使用的类型可以作为模板参数出现。

1.一个类模板中定义的成员函数成为有效的函数模板。注意:在任何需要类名出现的地方都要写上<T>。例如下面的程序分别使用整型和字符串类型集合Set<int>和Set<string>:

set.h

#ifndef SET_H
#define SET_H
#include <iostream>
#include <algorithm>
#include <stddef.h>

template<class T>
class Set{
public:
	Set();
	bool contains(const T&)const;
	void insert(const T&);
	void remove(const T&);
	void print(std::ostream&)const;
private:
	enum{LIMIT=64};
	T elems[LIMIT];
	size_t nelems;
};

template<class T>
Set<T>::Set(){
	nelems = 0;
}

template<class T>
bool Set<T>::contains(const T& x)const{
    const T* eof = elems + nelems;
	return std::find(elems, eof, x) != eof;
}

template<class T>
void Set<T>::insert(const T& x){
	if (nelems < LIMIT&&!contains(x)){
		elems[nelems++] = x;
	}
}

template<class T>
void Set<T>::remove(const T& x){
    T* eof = elems + nelems;
	if (std::remove(elems, eof, x) != eof){
		nelems--;
	}
}

template<class T>
void Set<T>::print(std::ostream& os)const{
	os << '{';
	for (int i = 0; i < nelems; i++){
		if (i>0)
			os << ',';
		os << elems[i];
	}
	os << '}';
}

//全局函数
template<class T>
std::ostream& operator<<(std::ostream& os, const Set<T>& s){
	s.print(os);
	return os;
}
#endif

test.cpp

#include "set.h"
#include <iostream>
#include <string>
using namespace std;

int main(){
	Set<int> s;
	s.insert(1);
	s.insert(12);
	s.insert(200);
	cout << s << endl;
	s.remove(12);
	cout << s << endl;
	cout << "s " << (s.contains(12) ? "does" : "does not") << "contains 12" << endl;
	cout << endl;

	Set<string> t;
	t.insert("lewa");
	t.insert("james");
	t.insert("hello");
	cout << t << endl;
	t.remove("lewa");
	cout << t << endl;
	cout << "t " << (t.contains("james") ? "does" : "does not") << "contains james" << endl;

	return 0;
}

输出结果:


2.可以定义一个其本身是一个独立模板的成员函数。例如:

#include <iostream>
using namespace std;

template<class T>
class A{
public:
	A(){
		cout << "default ctor\n";
	}
	template<class B>
	A(const B& b){
		cout << "converting from " << b << '\n';
	}
	template<class C>
	void f(C c){
		cout << c << endl;
	}
};

int main(){
	A<int> a;
	a.f("hello");
	a.f(1.2);

	A<float> a1("world");
	a1.f('c');

	return 0;
}

输出结果:


在上面的测试程序中,声明a时调用默认构造函数A<int>::A()。调用a.f("hello")实例化void A<int>::f(char*),接着调用实例化void A<int>::f(float)。定义a1时要求构造函数A<float>::A(const char*&),最后一个函数调用为void A<float>::f(char)创建代码。

另外,这里是在类里定义A的成员的,意味着函数体出现在自己的类模板定义中。如果要在类外定义f,则需要写成:

template<class T> template<class C>
void A<T>::f(C c){...}

四、模板参数

1.除了数据成员和成员函数以外,类也能包含被定义为嵌套类或者typedef的类型,例如:

#include <iostream>
using namespace std;
class Foo{
public:
	typedef int U;
};

class Bar{
public:
	typedef char* U;
};

template<class T>
class Baz{
	typename T::U x;
public:
	Baz(const typename T::U& t) :x(t){}
	void f(){
		cout << x << endl;
	}
};

int main(){
	Baz<Foo> b1(1);
	b1.f();
	Baz<Bar> b2("hello");
	b2.f();

	return 0;
}

输出结果:


编译器很容易对诸如Baz<T>::x之类的声明产生混淆,所以无论何时当采用类似T::U的用法时,要使用关键字typename或者class告诉编译器T::U是一个类型,T是一个模板类型参数,而U也是一个类型。

2.模板也可以有无类型参数,最常见的是作为容器维数使用的整数值。例如下面的程序中定义了一个用户可以选择或指定容器大小的Set模板:

set2.h

#ifndef SET_H
#define SET_H
#include <iostream>
#include <algorithm>
#include <stddef.h>

template<class T,size_t LIMIT>
class Set{
public:
	Set();
	bool contains(const T&)const;
	void insert(const T&);
	void remove(const T&);
	void print(std::ostream&)const;
private:
	T elems[LIMIT];
	size_t nelems;
};

template<class T, size_t LIMIT>
Set<T,LIMIT>::Set(){
	nelems = 0;
}

template<class T, size_t LIMIT>
bool Set<T,LIMIT>::contains(const T& x)const{
	const T* eof = elems + nelems;
	return std::find(elems, eof, x) != eof;
}

template<class T, size_t LIMIT>
void Set<T,LIMIT>::insert(const T& x){
	if (nelems < LIMIT&&!contains(x)){
		elems[nelems++] = x;
	}
}

template<class T, size_t LIMIT>
void Set<T,LIMIT>::remove(const T& x){
	T* eof = elems + nelems;
	if (std::remove(elems, eof, x) != eof){
		nelems--;
	}
}

template<class T, size_t LIMIT>
void Set<T,LIMIT>::print(std::ostream& os)const{
	os << '{';
	for (int i = 0; i < nelems; i++){
		if (i>0)
			os << ',';
		os << elems[i];
	}
	os << '}';
}

//全局函数
template<class T, size_t LIMIT>
std::ostream& operator<<(std::ostream& os, const Set<T,LIMIT>& s){
	s.print(os);
	return os;
}
#endif

test2.cpp

#include "set2.h"
#include <iostream>
#include <string>
using namespace std;

int main(){
	Set<int,10> s;
	s.insert(1);
	s.insert(12);
	s.insert(200);
	cout << s << endl;
	s.remove(12);
	cout << s << endl;
	cout << "s " << (s.contains(12) ? "does" : "does not") << "contains 12" << endl;
	cout << endl;

	Set<string,12> t;
	t.insert("lewa");
	t.insert("james");
	t.insert("hello");
	cout << t << endl;
	t.remove("lewa");
	cout << t << endl;
	cout << "t " << (t.contains("james") ? "does" : "does not") << "contains james" << endl;

	return 0;
}

输出结果:


无类型模板实参必须是编译期常数表达式,可以是整型值包括枚举常量,也可以是一个指针或者一个引用,但不允许是浮点值。也可以使用默认模板参数,例如:

template<class T,size_t LIMIT=10>
class Set{};
//声明
Set<int> s;
//将会把LIMIT设定为10

另外,默认模板参数只能作为尾部参数出现在模板声明中!


五、模板特化

下面的代码是用于比较两个对象的函数模板:

template<class T>
int comp(const T& a,const T& b){
    return (a<b)?-1:(a==b)?0:1;
}

现在有一个问题,如果用char*型实例化comp这个函数将不会正确进行。为了让comp具有C风格字符串的特点,可以提供显式特化:

template<>
int comp<const char*>(const char*& a,const char*& b){
    return strlen(a,b);
}

1.只要这个特化在调用具有C风格字符串参数的comp之前声明了,它就会重写来自原始模板的默认实例。

2.可以用不同的方法来特化模板,但是为了统一风格建议使用显式特化,例如:

spec.cpp

#include <iostream>
#include <string.h>
#include <string>

using namespace std;

template<class T>
size_t bytes(T& t){
	cout << "using primary template ";
	return sizeof t;
}

size_t bytes(char*& s){
	cout << "using char* overload ";
	return strlen(s) + 1;
}

template<>
size_t bytes<wchar_t*>(wchar_t*& w){
	cout << "using wchar_t* specialization ";
	return 2 * (wcslen(w) + 1);
}

template<>
size_t bytes<float>(float& x){
	cout << "using float specialization ";
	return sizeof x;
}

template<>
size_t bytes<string>(string& s){
	cout << "using string specialization";
	return sizeof s;
}

int main(){
	int i = 4;
	cout << "bytes in i: " << bytes(i) << endl;
	char* s = "hello";
	cout << "bytes in s: " << bytes(s) << endl;
	wchar_t* w = L"goodbye";
	cout << "bytes in w: " << bytes(w) << endl;
	string t = "world";
	cout << "bytes in t: " << bytes(t) << endl;
	float x = 3.5;
	cout << "bytes in x: " << bytes(x) << endl;
	double y = 2;
	cout << "bytes in y: " << bytes(y) << endl;
	return 0;
}

输出结果:


3.类模板特化举例:

spec2.cpp

#include <iostream>
using namespace std;
template<class T>
class A{
public:
	A(){
		cout << "primary\n";
	}
};

template<>
class A<char>{
public:
	A(){
		cout << "char specialization\n";
	}
};

template<>
class A<float>{
public:
	A(){
		cout << "float specialization\n";
	}
};

int main(){
	A<int> a1;
	A<char> a2;
	A<float> a3;

	return 0;
}

输出结果:


4.只特化部分类模板参数也是可以的。无论何时只要看到类型的角括号后面紧跟类模板名,就是说这里时模板特化!例如:

partical.cpp

#include <iostream>
using namespace std;
template<class T,class U>
class A{
public:
	A(){
		cout << "primary template\n";
	}
};

template<class T,class U>
class A<T*, U>{
public:
	A(){
		cout << "<T*,U>partial specialization\n";
	}
};

template<class T>
class A<T, T>{
public:
	A(){
		cout << "<T,T>partial specialization\n";
	}
};

template<class T,class U>
class A<int,U>{
	A(){
		cout << "<int, U>partial specialization\n";			//这里程序出错,
										//“部分专用化“A<int,U>”中未使用或不能推导出的模板参数”
	}
};

int main(){
	A<char, int> al;
	A<char*, int> a2;
	A<float, float> a3;
	A<int, int> a4;
	A<int, float> a5;

	return 0;
}

上面的问题求解决~~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值