C++模版用法和注意事项

模版与泛型编程

面向对象的多态与组合并不能完全满足实际编程中对于代码复用的全部要求,泛型编程应运而生,而且享有和面向对象等同的地位。面向对象将操作绑定到数据,泛型则是将操作应用于不同数据结构和类型。C++中泛型编程的体现就是模版。模板的技术核心体现在编译期的动态机制,模板实例化则是在编译的过程中,编译器通过“类型推导”进行实例化。而运行的时候,系统不知道模板的概念。与之相比,面向对象就是在运行时调用构造函数进行类的对象的实例化。

模版的应用:当一个类或函数的概念适用于不同类或者不同基本数据类型时,应该用模版来实现。C++提倡少用宏,鼓励使用模板。模板是C++语言内置,而且模板操作的类型在编译时是已知,是类型安全的。而宏的本质则是纯粹的文本替换,编译器不会验证宏参数是否为兼容类型,会在不进行任何特殊类型检查的情况下扩展宏。

模版的特化与偏特化

首先看一下这篇文章;IBM 编译器中国开发团队-究竟什么是特化?模版的特化,某种程度上有点儿像函数的重载。

对于函数的重载,编译器根据传递给函数的实参类型来决定调用哪个函数,这就是重载解析。在调用前,编译器有一个候选函数调用列表,每个调用函数都有各自的参数,编译器根据参数最匹配原则选择相应的函数 。

对于模版的特化,同样是对特定类型进行特殊的操作,编译器来选择最佳匹配。全特化限定模板实现的具体类型,偏特化只限定模版实现类型中一部分。一般,类模版ji既可以全特华又可以偏特化,而函数模版则只有全特化,函数模版偏特也没有必要,函数重载即可实现。

#include <iostream>
using namespace std;

template<class T1, class T2> class Test {
public:
	Test(T1 i, T2 j) :
			a(i), b(j) {
		cout << "模板类" << endl;
	}
private:
	T1 a;
	T2 b;
};

template<> class Test<int, char> {
public:
	Test(int i, char j) :
			a(i), b(j) {
		cout << "全特化" << endl;
	}
private:
	int a;
	char b;
};

template<class T2> class Test<char, T2> {
public:
	Test(char i, T2 j) :
			a(i), b(j) {
		cout << "偏特化" << endl;
	}
private:
	char a;
	T2 b;
};

int main() {
	Test<long, char> test1(1,‘a‘);		//输出:模板类
	Test<float, char> test2(1,‘a‘);		//输出:模版类
	Test<int, char> test3(1,‘a‘);		//输出:全特化
	Test<char, double> test4(‘a‘,1.0);	//输出:偏特化
	Test<char, long> test5(‘a‘,1);		//输出:偏特化
}

模版的参数与类型

在决定模板参数类型前,编译器执行下列隐式类型转换:左值变换、修饰字转换、派生类到基类的转换。实际使用中,参数可以显示也可以隐式:

  • 显式类型参数:对于模板函数,在函数名后添加 < {类型参数表} >。对于模板类,在类后添加 < {类型参数表} >;
  • 隐式类型参数:对于模板函数,如果类型参数可以推导,那么可以省略类型参数表,(不过一般还是不要省略为好);

函数模版举例:

template<class T> T min(T x, T y) {
	return (x < y) ? x : y;
}
/* 对特定的类型全特化 */
template<> long min<long>(long x, long y) {
	return (x < y) ? y : x;
}

int main() {
	int n1 = 1, n2 = 2;
	char a = ‘a‘, b = ‘b‘;
	long n3 = 1, n4 = 2;
	std::cout << min<int>(n1, n2) << "\n";	//输出:1,显示类型参数,模板的特化
	std::cout << min(a, b) << "\n";   		//输出:a,隐式类型参数,编译器可以自动类型推导
	std::cout << min(n3, n4) << "\n";   		  //输出:2,隐式类型参数,函数模板的全特化
	return 0;
} //这个简单的模板在特化时基本只包含类型的查找与替换,作用类似于“类型安全的宏”。

模版的声明定义和使用

前面说过,模版的动态机制体现在编译器,模版实例化也是在编译器进行类型的确定。所以,只有将模板类.cpp文件同调用程序.cpp文件一起作为一个编译单元编译运行,才能真正确定类的真正类型。具体可以有以下两种做法:

  • 将C++模板类的声明和定义都放在一个文件(推荐这种做法),如 .h 或 .cpp 文件中,使用的时候加入 #include "模板类文件名.h(或.cpp)"  即可;
  • 将C++模板类的声明和定义分别放在 .h 和 .cpp 文件中,且在 .cpp 文件中包含 #include ".h",使用时和一般的类不同,一般的类是#include"类.h",但是模板类是 "模板类.cpp" ,因为要把它们作为一个编译单元编译,编译器决定。而普通类编译后由连接器连接就行。

类模版举例,作容器:

#include <iostream>
const int DefaultSize = 10;

/*一个简单的Array容器 */
template<class T> class Array {
public:
	Array(int size = DefaultSize);
	Array(const Array &rhs);
	~Array() {delete[] pType;}

	Array& operator =(const Array&);

	T& operator[](int offset) {
		return pType[offset];
	} // 运算符重载[]

	const T& operator[](int offset) const {
		return pType[offset];
	} // 运算符重载[], const版本

	int getSize() const {
		return size;
	}

private:
	T *pType;
	int size;
};

template<class T> Array<T>::Array(int size):size(size) {
	pType = new T(size);
}

/* 用此种类型对象做参数再构造一个此种类型的对象 */
template<class T> Array<T>::Array(const Array &rhs) {
	size = rhs.getSize();
	pType = new T[size];
	for (int i = 0; i < size; i++) {
		pType[i] = rhs[i];
	}
}

/* 重载 = ,形参和返回值都是引用 */
template<class T> Array<T>& Array<T>::operator =(const Array &rhs) {
	if (this == &rhs) {
		return *this;
	}
	delete[] pType;
	size = rhs.getSize();
	pType = new T(size);
	return *this;
}

class Animal {
public:
	Animal(int weight) :weight(weight) {}
	Animal():weight(0){}
	~Animal() {}

	/* 函数后面有const,表示只读,内部不能修改成员变量的值;*/
	int getWeight() const {
		return weight; // 这里如果是weight++,就报错了
	}
	void display() const {
		std::cout << weight;
	}
private:
	int weight;
};

int main() {
	Array<int> integers;
	Array<Animal> animals;
	Animal *pAnimal;
	for (int i = 0; i < integers.getSize(); i++) {
		integers[i] = i;
		pAnimal = new Animal(i * 2);
		animals[i] = *pAnimal;
	}
	for (int i = 0; i < integers.getSize(); i++) {
		std::cout << "the array[" << i << "]:\t";
		std::cout << integers[i] << "\t\t";
		std::cout << "animals[" << i << "]:\t";
		animals[i].display();
		std::cout << std::endl;
	}
	return 0;
}

输出:

the array[0]:	0		animals[0]:	0
the array[1]:	1		animals[1]:	2
the array[2]:	2		animals[2]:	4
the array[3]:	3		animals[3]:	6
the array[4]:	4		animals[4]:	8
the array[5]:	5		animals[5]:	10
the array[6]:	6		animals[6]:	12
the array[7]:	7		animals[7]:	14
the array[8]:	8		animals[8]:	16
the array[9]:	9		animals[9]:	18

函数对象Function Object

标准库经常用到函数对象,也叫仿函数(Functor) 。函数对象就是一个重载了”()”运算符的struct或class,利用对象支持operator()的特性,来达到模拟函数调用效果的技术。函数对象有两大优势:

  • 函数对象可以包含状态;
  • 函数对象属于类型可用作模板参数;

函数对象举例:

#include<iostream>

class Add {
public:
	int operator()(int a, int b) {
		return a + b;
	}
};

int main() {
	Add add;
	std::cout << add(2, 3) << "\n"; // 输出5
}

经过近阶段对C++的了解,越来越理解:在C++语言里,存在这一种很强的趋势,就是如果你不明白C++语言的细节,你就无法做好任何事情。 – Larry Wall, developer of the Perl

C++模版用法和注意事项

时间: 2024-10-21 08:04:00

C++模版用法和注意事项的相关文章

sql优化(oracle)- 第二部分 常用sql用法和注意事项

第二部分 常用sql用法和注意事项               1. exists 和 in                             2. union 和 union all                       3. with as  4. order by  5. group by  6. where 和 having  7. case when 和 decode 1.exits和in用法1)说明: 1. exists先对外表做循环,每次循环对内表查询:in将内表和外表

MySQL 子查询用法和注意事项

有时候一条sql语句解决不了什么问题,需要嵌套sql语句来实现目标,但是会影响执行的效率,这里简单说说其用法和注意事项. MySQL子查询是嵌套在另一个查询(如SELECT,INSERT,UPDATE或DELETE)中的查询. 1,MySQL子查询在WHERE子句中 =,>,< WHERE子句中使用IN或NOT IN运算符 SELECT customerNumber, checkNumber, amountFROMpaymentsWHEREamount > (SELECT AVG(amo

transform的用法和注意事项

1.作用: 1)transform可以控制平移.比例缩放和旋转. 2)transform中的方法主要分为两种:带make和不带make的方法. 3)带make的方法主要是基于控件最初的状态进行改变,所以通常只能改变一次. 4)不带make的方法需要传入一个transform,然后控件按照传入进来的transform进行改变,通常传入都是控件自身的transform. 5)transform和frame一定不能混合使用,否则会发生不可预料的问题. 2.方法的使用: 1)带make的平移方法,()中

分享一个前辈写的table的一些用法和注意事项

表格(table)是一直以来长期被大家使用的标签,直到现在还是在用,不过呢因为现在的网站重构,建议大家不要表格(table)来布局而很多朋友误以为用表格(table)就是所谓的不标准了,其实不然,表格(table)是一个重要元素. 前不久为了寻找表格(table)所包含的主要标签,一直在寻找着,找到当然就是跟大家一起来分享一下. 表格标签主要包含的标签有table.caption.th.tr.td.thead.tfoot.tbody.col.colgroup,针对每个的介绍如下: <table>

Android中日期函数Calendar的一些用法和注意事项

1.月份获取时加1 Canlendar.MONTH + 1 因为使用的是罗马历,Calendar.MONTH返回的数值不是一年中月份的值,而是当前月份距离第一个月份的差值 如:当前月份为9月份,距离1月份,差值是8,所以Canlendar.MONTH返回的是8而不是9 2.获取星期几时减1 Calendar.DAY_OF_WEEK - 1 取得当前日期是一周中第几天时就要考虑一周的第一天是星期几的问题. 如果第一天是星期日,那2代表的就是星期一:如果第一天是星期一,那2代表的就是星期二 默认一周

substring()的用法和注意事项

作者原创:转载请注明出处 substring()方法的作用为截取字符串,其有两种用法: 分别如下: substring(int beginIndex);这个的作用为截取从beginindex位置处的元素开始,默认截取至剩余所有. substring(int beginIndex, int endIndex);这个的作用为截取从beginIndex开始,截取至endIndex-1位置间的元素. 该方法不仅在java代码中可用,在JavaScript中同样可用, var vodArray=[]; v

Mac神器Iterm2的Shell Integration的用法和注意事项

在iterm2 v3.0版本中有了个新的feature——Shell Integration,其中比较重要的功能就是可以取代传统的“rz”.“sz”(即:向服务器上传.下载文件) 具体的用法可以参见官网所示: https://iterm2.com/documentation-shell-integration.html 下面列出一些特别要注意的四项内容(这四项内容是我踩过的坑) 注意项一: curl -L https://iterm2.com/misc/install_shell_integra

boost::asio::streambuf 基本用法和注意事项

streamsize  sgetn(char_type *store,streamsize n)    返回缓冲区下n个字符并存储到store中,并将缓冲区位置后移n个字节 代码说明:本来是想不断的通过sgetn函数获取到streambuf的内容,由于没有完全理解sgetn获取流的方式,导致了问题的产生 int Teststreambuf() { boost::asio::streambuf request; std::ostream request_stream(&request); requ

linux signal 用法和注意事项

http://blog.chinaunix.net/uid-9354-id-2425031.html 所以希望能用相同方式处理信号的多次出现,最好用sigaction.信号只出现并处理一次,可以用signal.   signal函数每次设置具体的信号处理函数(非SIG_IGN)只能生效一次,每次在进程响应处理信号时,随即将信号处理函数恢复为默认处理方式.所以如果想多次相同方式处理某个信号,通常的做法是,在响应函数开始,再次调用signal设置,如下图: int sig_int(); //My s