20170305STL02_模板实例化

作业讲解/模板深入:

1:模板对我们之后学习STL有很大的帮助,iostream里面的一些东西也是使用了泛型,对泛型不是很了解,学的STL就相当于没有学,很多人学了STL却不敢用也是因为这些东西已经给他们造成了伤害。
2:泛型的出现是生成代码的代码,为我们减少代码量。昨天通过函数模板写的模板函数,在实例化的时候生成了很多代码。
3: 函数模板:template<typename T>下面如果接的是一个函数,那么这一行就称之为函数模板。尖括号里面的T称之为 模板参数。包括函数模板和下面接的函数一起总称为 模板函数。模板函数会在编译的时候实例化(生成代码的过程,实例化过程),根据模板函数生成对应的函数,函数里面传递的参数将通过模板参数来确定, 实例化是在编译时进行的,而非运行时。他与宏的区别在于,宏没有类型检测,而函数模板是有类型检测的。模板在实例化的时候是有两个步骤的:
    1:检查语法错误(模板函数层面的检测(比如,缺少分号……)),如果这一步正确,那么就可以通过。不正确,就不会进入第二步。
    2:检查调用错误(参数类型无法匹配成功,非法的运算(类比较大小等)……)
4:模板函数编译的步骤细节:通过第一步的语法检测后就会生成对应的函数,第二步会检查生成的各个函数的语法问题。
5:模板函数导致的问题:如果将模板函数的实现放在cpp里面,就会导致错误的出现:模板并不是一个单纯的函数,他是一个用来生成函数的模板,他并不能凭空地生成,他必须按照你调用的结果来生成,如果将函数的实现放在cpp里面,main函数调用的时候,.h库文件的声明确实发生了对应的改变(模板参数改为了对应main函数需要调用的),而他的实现却没有生成,所以找不到,导致链接的时候出错。总之就是声明有了,调用的时候找不到对应实现的方法。
6:模板函数问题的解决方法:如果我们非要将声明和实现分开,分别下载.h和.cpp里面,我们只能通过伪分离来实现:
    1:将.cpp里面的include"Demo"删掉。
    2:在Demo.h里面声明下面写上#include"Demo.cpp"。因为#include指令就是将另外文件的内容直接展开到这个地方。
    写模板的时候也没有必要做伪分离。例如vector类就是直接写在.h里面的。分开的话容易弄混淆,也可以把.cpp后最改为.hpp或者.tcc等来区分。
//演示代码:
//.h文件内容:
#ifndef DEMO_H_
#define DEMO_H_

template<typename T>
T Max(T lhs, T rhs);
#include "Demo.cpp"

#endif//!DEMO_H_
//.cpp内容:
template<typename T>
T Max(T lhs, T rhs)
{
return lhs > rhs ? lhs : rhs;
}

注意:.h是有隐含属性的,他是申明实现在一起的函数,所有函数默认是inline的。这可能导致代码严重膨胀。
7:题外话:因为模板的纯在,C++编译器是世界是最难写的编译器。而C++编译器基本只有大型公司(微软,google等)才有。
8:模板函数是可以重载的,参数不同,参数数量不同,参数和数量都不同,都可以构成重载。模板参数列表(模板里面的各个参数类型)可以不再函数返回值和函数体里面出现,但是必须在函数参数列表中使用一次,如果未使用所有的模板参数,则会报错:未能为T推导,模板参数。只有使用过一次或者调用的时候自己指定,这个T才能够被推导,否则无法被推导,就会出错。模板函数还可以和普通的函数构成重载。
    1:模板参数不同,可以导致参数类型不同的重载。
//演示代码:
#include <iostream>

/*template<typename T>
const T Max(T lhs, T rhs)
{
	return lhs > rhs ? lhs : rhs;
}*/
template<typename T1,typename T2>
const T1 Max(T1 lhs, T2 rhs)
{
	
}
template<typename T>
const T Max(T lhs, T rhs, T other)
{
	return lhs > rhs ? lhs : rhs;
}
//参数列表必须在传参的时候使用一次或者是用函数的时候手动指定类型。
template<typename T>
const T Max(int lhs, int rhs)
{
	return lhs > rhs ? lhs : rhs;
}//调用示例:int a = Max<int>(11,22);
template<typename T, int num = 10>
//可能导致二义性,第二个不能为指针,因为这个只能为常量值。const的也可以
const T Max(T lhs, T rhs)
{

}
/*
//调用示例:
int num = 10;
Max<int,num>(1,2);//第二个参数必须是确定值,不可以是变化的,因为我们进行实例化的时候必须知道这个值。
*/
const int Max(int lhs, int rhs)//模板函数可以和普通函数构成重载
{

}
int main()
{

	return 0;
}

模板函数特化:

1:函数模板特化和重载的区别:特化是指,当你是用的参数是这个的时候就不用再自动生成合适的代码了,如果整个工程没有使用到这个特化,那么这个特化也是不会生成的。而重载是直接调用另外的已有的函数,不存在生不生成,他一直都在。
2:他们的调用规则是:如果存在重载的非模板函数的时候,他会优先调用我们的非模板函数,条件不吻合的时候才会调用特化的函数,再不行才会看模板函数。
#include <iostream>

template<typename T>
T Max(T lhs, T rhs)
{
	return lhs > rhs ? lhs : rhs;
}
//函数模板的特化。
template<>//特化,特殊化,如果所有函数没使用到这个特化,他也是不会生成的。
const char* Max(const char *lhs, const char *rhs)//参数必须和上面一样,是不可变的才可以。
{
	return strcmp(lhs, rhs) > 0 ? lhs : rhs;//一样返回0,lhs大返回正数,否则返回负数
}
//下面这种只能称之为重载,和特化性质不一样。条件吻合他会优先调用非模板函数,如果不行再看特化函数,最后才考虑模板函数。
const char* Max(const char* lhs, const char* rhs)
{
	return strcmp(lhs, rhs) > 0 ? lhs : rhs;//一样返回0,lhs大返回正数,否则返回负数
}

int main()
{
//	Max(1, 2);
	Max("a", "b");//字符串,比较的是指针,比较出来的结果并不是我们需要的。
	//我们比较字符串应该是用compane来比较。
	return 0;
}

类模板和适配器:

1:在类的前面加上一个类模板就可以让这个类变成模板类,他和函数模板是一样的。类模板和函数模板大致相同,但是还是有区别:
    1:类模板是无法进行推导的。因为类里面使用模板,可以做的事情就变得太多,没人能够推导出里面的模板是什么样的。
    2:类里面如果没有使用模板参数T是没有问题的。因为它本身就无法推导。我们使用模板类的时候就必须自己指定类型,无法自动推导。
2:模板类之间是可以继承的,继承的派生类必须指定模板类型,他不在是一个简简单单的继承。模板类的继承在STL里面用的非常多。
3:stack在STL里面称为适配器,STL里面的类接口都是一样的。
#include <iostream>
#include <vector>

template<typename T>//类模板是无吖推导的,
class Demo1//类里面如果没有使用推到类型T是没有问题的。
{
public:
	Demo1(int num) :num_(num)
	{

	}
private:
	int num_;
};
class other:Demo1<int>//这里必须指定类型。否则不知道如何实例化,无法继承。
{//此时,other并不是模板类。一般用来做一些固定的适配器。
public:
protected:
private:
};
//这个模板类和vector就没有多大的关系了,而且还可以使用vector里面所有的方法。
template<typename T,typename CON = std::vector<T>>//相当于一个适配器。
class stack//和vector只是关联关系。
{
public:
	void Push(T value)
	{
		data_.push_back(value);
	}
private:
	CON data_;
};
int main()
{
	stack<int> demo;
	stack<int, std::string> demo;//可以定位其他的,相当灵活,STL里面的接口很多都是一样的,因此可以适应。
	demo.Push(12);
	return 0;
}

特化:

1:作业注意点:
    1:习惯用c开头的变量来表示C++里面的容器。C++11里面的容器有一些特性,他们里面大致的结构是一样的,比如,at、begin、end、size等方法每个容器都是有的。
    2:通过我们已知的这些方法名可以统一接口,因为里面使用了泛型,他也是一种多态。
    3:多态分为两种:动态多态和静态多态。多态能够有一个统一的接口,让我们实现很多的操作
        1:动态多态:是运行期间我们通过抽象的方式来达到,我们可以用 父类指针来操作子类的对象,有必须实现的接口:纯虚函数(强制实现的)等。
        2:静态多态:编译期间所完成的多态, 它使用的泛型,它里面规定有了统一了接口,并且告诉你你要是用这个的话必须要实现哪些接口,是非强制性的。
    注意:动态多态更难调试,但是其运行速度更快,静态多态与之相反。
2:使用了模板的类,当使用不同的类型的时候,会生成不同的类,是不可以支持类与类成员之间转换的(除非自己写隐式转换方法)。
3:需要注意的是, 模板类访问自己本身的变量也要注意是不是自己的变量,生成的不同模板类型的类不可以互相访问变量。但是在写程序的时候是写在一个类里面的,所以非常值得注意的是,模板类里面的操作一定要想清楚是不是操作的本类的对象。
template<typename T>
class Demo
{
public:
	Demo(const T other) :data_(other)
	{
		
	}
	void Assign(Demo other)
	{
		data_ = other.data_;//此时不适合不同类型的类之间,因为other.data_可能不是一个类,写其他方法也是一样的,要考虑不是一个类的时候。
	}
private:
	T data_;
};

int main()
{

	Demo<int> i(10);
	Demo<double> d(10.2);
	i.Assign(d);//error C2664: “void Demo<int>::Assign(Demo<int>)”: 无法将参数 1 从“Demo<double>”转换为“Demo<int>”

	return 0;
}

    这种问题可以使用模板解决,直接将Assign方法的Demo other改成X other就好了,在Assign方法前加上template<typename X>,data_为私有的,还需写一个GetData方法或者将data_设置成公开的。使用模板的类,里面的数据访问一定要注意,应为可能就不是一个类。
	template<typename X>
	void Assign(X other)
	{
		data_ = other.GetData();
	}
	const T& GetData() const//相当于统一了接口,每个生成的类都有这个接口
	{
		return data_;
	}

4:像上面这样的类里面使用模板的方法称之为 成员模板,他可以在任何类里面出现。如果只是这里是用模板,而如果类的前面没使用,那么在cpp文件写实现的时候,这个类并不是模板类,所以前面不用加上<T>。如果模板类里面使用了成员模板,在实现的时候需要写两个template,此时,是有先后顺序的,必须将类模板写在前面,然后写成员模板。
template<typename T>//在类外面实现。
template<typename X>//这两个typename是不可以重名的
void Demo<T>::Assign(X other)
{
	data_ = other.GetData();
}

5:如果模版类里面有的方法而对应特化的类里面没有写这个方法,那么特化的类就不能调用这个方法,他既不是继承,又没有这些方法,特化类和本身模板类生成的不同的类,所以不能互相调用方法。模板类在编译的时候生成,如果有特化他就会调用特化的,源模板类不会再生成,特化的就是自己写的类,全新的类。这样的特化称之为全特化(对整个类进行的特化)。
    注意:特化一般用在对一些模板类的特殊的类型。比如对一个模板类的指针类型进行特化。
6:如果模板类的里面有很多方法,在进行特化的时候,如果全特化就会特别麻烦,可以使用偏特化,偏特化就是对模板类里面的某些方法的某些模板类型的特化。
template<typename T>
class Array
{
public:
	Array()
	{
		std::cout << "Array()<T>" << std::endl;
	}
	~Array()
	{
		std::cout << "~Array()<T>" << std::endl;
	}
private:
	T data_;
};
template<>//这就是偏特化

Array<int*>::Array()//相当于只对Array类的构造函数参数为int*的时候的特化
{
	data_ = new int;
}
template<>
Array<int*>::~Array()//对析构函数的偏特化
{
	delete data_;
}

注意:偏特化并不是对整个类进行特化,他只是对某些方法进行特化(其余的代码编译器还是会帮我们生成,只是特化的这几个方法不会自动生成)。全特化是特化的整个类
7:模板类继承的时候必须要指定父类的模板类型。我们一般不会那一个普通类继承某个模板类,一般是一个模板类继承另一个模板类,模板类和模板类之间的继承。
8:模板类可以拿来做单例模式,我们把我们的模板类做成一个包装器,我们模板可以装任何一个类,把一个模板类做成一个单例模式,那么其他的也都是单例的了。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值