C++模板与泛型编程

To-Do:

  1. typename与class的异同

  2. 类模板和模板类
    以及函数模板和模板函数

  3. 非类型模板参数的完善
    P580 与 https://blog.csdn.net/lanchunhui/article/details/49634077

  4. inline & constexpr 函数模板
    与类型无关的代码
    P581-583 的所有内容

  5. 在模板作用域中引用模板类型
    P585

  6. 类模板与友元开始
    P588-591

  7. 模板参数
    P592-597

  8. 控制实例化
    P597-600

  9. 从这里往后的基本都没有

    到时候回顾拓展的, 如果熟悉就直接跳过吧

    不过基本是需要遍历, 即便是JB老师讲过的内容, 其中也有很多与C++ Primer中不相符的地方需要进行补充


泛型编程概述:

学习模板库首先应该理解的是泛型编程的概念

其可以总结下面的一句话:

它是把数据类型作为一种参数传递进来

泛型编程让你编写完全一般化并可重复使用的算法,其效率与针对某特定数据类型而设计的算法相同

泛型编程的代表作品STL, 它包含很多计算机基本算法和数据结构,而且将算法与数据结构完全分离,其中算法是泛型的,不与任何特定数据结构或对象类型系在一起

C++模板技术:

通常有两种形式:函数模板和类模板;

函数模板
将函数参数类型提取出来作为“类型参数”,通过创建通用的函数模板,避免函数体的重复定义

类模板
将类中所处理的数据类型提取出来作为“类型参数”,创建通用的类模板

函数模板的定义:

//函数模板:
template <typename T>
T max(T a, T b){
	return a<b?b:a;
}

template <typename T1, typename T2, typename T3>
T3 min(T1 a, T2 b)
{
	....
}

几个关键字:

  1. template:
    表明开始进行一次模板定义

  2. <>中括号中的内容
    称之为模板参数列表
    而其中的内容为逗号分隔的一个或多个模板参数

  3. typename:
    用于申明泛指类型
    其中后头的T为模板标志, 相当于形参, 支持用户自定义

    注意每个模板参数前头都要有一个typename, 和函数的形参声明类似

    这里的typename可以替换为class
    二者具体的区别将在下头说明

注意:

每次进行定义或声明一个函数模板时, 都要有个头:

template <typename T1, ...>

由于template关键字没有明显的作用范围, 所以, 其只是作用下一条语句

非类型模板参数:

模板参数列表中还可以是非类型模板参数:

template <typename T, int x, unsigned y>
T max(T a, T b){
	return a<b?b:a;
}

即其中可以加入非typename或class关键字的特定数据类型

而后, 模板实例化时需要提供足够多的模板参数 (与函数的参数相似):

template <typename T, int x, unsigned y>
class MyClass{
public:
	int xx=x;
	unsigned yy=y;

};
//实例化时:
class MyClass<int> obj1;	//报错, 需要提供3个参数

但是, 非类型模板参数是有类型限制的

浮点数和类对象(class-type)不允许作为非类型模板参数

如:

template <typename T, int x, double y>	//报错
T max(T a, T b){
	return a<b?b:a;
}

函数模板的实例化:

直接当成普通的函数进行调用即可

//其中max为上头的函数模板:
cout<< max(3,6) <<endl;
char c1=‘a’;
char c2=‘b’;
cout<< max(c1,c2) <<endl;

编译器由此生成的函数实例:

//编译器推断出所有的T都是int类型
int  max<int>(int  a, int  b){
	return a<b?b:a;
}
//编译器推断出所有的T都是char类型
char  max<char>(char   a, char   b){
	return a<b?b:a;
}

函数生成的二义性:

当编译器无法推导出合适类型时, 报错(二义性)

max(4, 4.2); 	//二义性报错

解决方法就是显式制定编译器生成什么类型的函数:

函数生成的后置类型声明:

max <int> (4,7);   
max <double> (4, 4.2);  

显示的指定模板参数的具体类型, 解决上头的二义性问题

注意这是一个良好的编程习惯, 方式ERROR且提高可读性, 需严格遵守

自动类型推导:

在定义模板时使用auto & decltype

但是这里的使用方式上与传统用法有一点区别

//auto的使用:
template <typename _Tx,typename _Ty>
void Multiply(_Tx x, _Ty y){
    auto v = x*y;
    std::cout << v;
}
//decltype的使用:
template <typename _Tx, typename _Ty>
auto multiply(_Tx x, _Ty y)->decltype(x*y){
    return x*y;
}
  • auto的使用和之前相同

  • decltype的使用需要用auto进行占位

    按理来说, 原本的代码应该是这个样子的:

    decltype(x*y) multiply(_Tx x, _Ty y)
    

    但是在编译时, x & y的静态类型不确定, 类型推断报错

    所以C++11引入了全新的语法 返回值类型后置语法, 用来解决这个问题 (所以是template模板特供):

    template <typename T, typename U>
    auto add(T t, U u) -> decltype(t + u) {
        return t + u;
    }
    

    使用一个auto占位, 而后用->指向真正的返回值类型
    并且这个返回值类型并不一定是decltype推出的:

    template <typename T1, typename T2>
    auto  sum(T1  a, T2 b)  -> T1	//这样也可以
    {
    	return a+b;
    }
    

类模板的定义:

注意类模板和模板类是有区别的, 这个等到之后在进行深究

template <typename T>
class Exam
{
public:
	void setValue (T const& value);
	T getValue() const;
private:
	T elems;   
};   

几个关键字:

都和函数模板一样, 这里就不做重复的表述了

这里同样也可以将typename换成class

//来个大号的定义, 可以看的更清楚一点
template <typename T>
class MyVector{
public:
	MyVector(int maxSize=1);
	MyVector(const MyVector<T>& a);
	MyVector(MyVector<T>&& a);
	MyVector& operator=(const MyVector<T>& a);
	MyVector& operator=(MyVector<T>&& a);
	~MyVector();
	int getSize()const {return size;}
	T getValue(int index)const;
	void setValue(int index,T value);
	T& operator[](int index);
	const T& operator[](int index)const;
	void pushData(T data);
	void removeData();
	void removeData(int index);
};

类模板成员的定义:

  1. 数据成员的定义与普通类相同

  2. 成员函数的定义:

    • 当成员函数定义在类内部时, 和普通类相同

    • 当其定义在外部时, 每一个成员函数都需要在原有的定义上加上一个模板标志:

      //需要添加额外的模板标志
      template <typename T>
      //而后就是正常的定义
      //注意这里Exam<T> 和常规的定义不相同
      void Exam<T> ::setValue(const T& value)
      {
      	elems = vlaue;
      }
      //由于每一个成员函数的类外定义都需要Exam<T>
      //所以每一个都要来一句template <typename T>
      template <typename T>
      //注意ClassName后也要加一个<T>
      T Exam<T> ::getValue() const
      {
      	return elems;
      }
      

注意, 类模板的成员都需要定义在同一个文件中( 通常是头文件)

如果将成员函数定义在.cpp文件中, 由于每个.cpp文件是分开独立编译的, 使得编译器便无法确定要根据什么类型来产生相应的类,也就造成了错误

允许不带模板参数列表的情况:

注意:

MyVector(const MyVector<T>& a);
MyVector(MyVector<T>&& a);

虽然之前说了, 使用类模板时必须提供其模板参数列表, 但是上头的这几个函数形参中有MyVector对象, 其后头跟的<T>可有可无

因为在类模板中, 这个硬性要求被简化了:

MyVector(const MyVector& a);
MyVector(MyVector&& a);
//以上代码等价为上头的代码

当在类模板的作用域中使用不带模板参数列表的类模板时, 默认为使用定义中的参数

但是, 显然加上<T>更能凸显出程序的意图:
明确需要的是MyVector<T>的形参, 提高了程序的可读性

并且, 在需要特定类型时, 就必须要指明:

MyVector(const MyVector<double> &a){
	this->size=a.size;
	return ;
}

类模板的实例化:

int main(){
	Exam<int> e1;
	e1.setValue(5);
	cout<<e1.getValue()<<endl;
	Exam<double> e2;
	e2.setValue(5.5);
	cout<<e2.getValue()<<endl;
	return 0;
}

类模板和函数模板不同, 需要显式指明模板参数的类型:

类模板名<真实类型参数表> 对象名(构造函数实际参数表);

注意:

类模板成员函数只有在被使用到时才会实例化

指明:

MyVector(const MyVector<double> &a){
	this->size=a.size;
	return ;
}

类模板的实例化:

int main(){
	Exam<int> e1;
	e1.setValue(5);
	cout<<e1.getValue()<<endl;
	Exam<double> e2;
	e2.setValue(5.5);
	cout<<e2.getValue()<<endl;
	return 0;
}

类模板和函数模板不同, 需要显式指明模板参数的类型:

类模板名<真实类型参数表> 对象名(构造函数实际参数表);

注意:

类模板成员函数只有在被使用到时才会实例化

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值