1、模板
1.1、模板的概念
模板就是生成一个通用的函数,这个函数可以接受任意数据类型的参数,可以返回任意类型的值。
模板是泛型编程的基础。所谓泛型编程就是编写与类型无关的逻辑代码,是一种强大的复用方式。
1.2、模板的分类
1.2.1、模板函数
- 1、书写格式
template<class 形参1 , class 形参2,...>
返回类型 函数名(形参表)
{
// ;
}
template<typename 形参1 , typename 形参2,...>
返回类型 函数名(形参表)
{
// ;
}
模板形参定义class和typename是完全等价的,没有区别,所以你可以任意使用,但是不要两个混搭,让人看着不舒服。
- 2、代码示例
eg:
void Swap(int *x1,int *x2)
{
int tmp = *x1;
*x1 = *x2;
*x2 = tmp;
}
void Swap(int **x1, int **x2)
{
int *tmp = *x1;
*x1 = *x2;
*x2 = tmp;
}
int main()
{
int a = 1;
int b = 2;
Swap(&a, &b);
int *p1 = &a;
int *p2 = &b;
Swap(&p1, &p2);
return 0;
}
根据上面例子看出:当你想进行两个整型交换时,是不是要写一个交换函数,当你想交换两个指针,又要写一个交换函数,那么你想交换两个字符串是不是又要写,为了解决这种麻烦,c++有了模板的这个概念,
template<class T>
void Swap(T *x1, T* x2)
{
T tmp = *x1;
*x1 = *x2;
*x2 = tmp;
}
这就是模板,它不关注你具体需要的类型,它会根据你的需求确定不同的函数类型执行不同的代码(这叫做模板函数实例化)。并且在c++库中交换函数swap也是用模板实现的。
重要的一点:编译时不会编译模板函数大括号里面的内容,那是因为模板函数还没有实例化,因为不知道函数形参类型,所以根本不能在堆栈上开辟空间。
//实例化调用
void Test()
{
int a = 1;
int b = 2;
Swap(&a,&b);//这叫做隐式实例化调用,需要系统自己推演实例化类型。
Swap<int>(&a,&b);//这叫做显示实例化,不需要推演就可以直接调用。
}
- 3、调用规则
- 3.1:没有显示实例化时,系统会调用默认模板函数
- 3.2:当既有模板函数又有原函数时时,会直接调用原函数,不会调用模板函数
- 3.3:在重载函数模板中,显示实例化后会调用最匹配的的模板函数,不再调用默认的模板函数,如果没有找到最匹配的会再调用默认模板函数
- 3.4:模板参数支持设置缺省值,如果全缺省时,就不再需要显示实例化了。
- 3.5:缺省规则不遵循从右到左缺省原则
- 3.6:如果模板设置了默认缺省值并且显示实例化了,系统会使用显示实例化的参数而不是默认参数。
eg:
void show(int a, int b)
{
cout << "show()的普通函数" << endl;
}
template <class T>
void show(T a,T b)
{
cout << "默认模板函数" << endl;
}
template <class T1, class T2>
void show(T1 a,T2 b)
{
cout << "不同类型的模板函数" << endl;
}
template <class T>
void show(T a,T b)
{
cout << "默认模板函数" << endl;
}
template <class T1, class T2>
void show(T1 a,T2 b)
{
cout << "不同类型的模板函数" << endl;
}
1.2.2、模板类
- 1、书写格式
template<class 形参名1, class 形参名2, ...,class 形参名n>
class 类名
{
//类定义;
};
- 2、代码示例
template<class T>
class A
{
public:
A()
{}
protected:
T _a;
};
int main()
{
A<int>a;//必须传类的类型,否则在实例化时不能给对象a开辟空间大小
return 0;
}
2、非类型模板参数
2.1、定义
模板参数不仅可以定义为类型还可以定义为固定类型的常量值。
2.2、例子
模板类与模板函数都可以用非类型形参。
template<class T,int n>
void sum(T a,int n)
{
T i = a;
T sum = 0;
for (i=a; i <= n; ++i)
{
sum += i;
}
cout << sum << endl;
}
template<class T,int MAXSIZE>
class SeqList
{
public :
SeqList();
private :
T _array [MASIZE];
int _size ;
};
void Test()
{
sum<int, 3>(0, 3);
SeqList<int,10>S;
}
2.3、使用规则
规则
- 1:可转化为整型的类型都可以作为非类型模板参数,比如:int、char、short、long、unsigned、bool,指针和引用也可以作为非类型模板参数
- 2:浮点数不能作为非类型模板形参,比如:float、double
- 3:类,字符串不可以作为非类型模板参数
- 4:实参必须是编译时常量表达式,不能使用非const的局部变量,局部对象地址和动态对象
- 5:非const的全局指针,全局对象,全局变量都不是常量表达式。
- 6:由于形参的已经做了限定,字符串,浮点型即使是常量表达式也不可以作为非类型实参
template<class T,int MAXSIZE>
class List{
private:
T array[MAXSIZE];
public:
void show()
{
//;
}
};
const int num1 = 9; ;//全局变量
static int num2= 9; ;//全局变量
void Test()
{
const int num3 = 9; ;//局部变量
List<int,num1> list; //正确
List<int,num2> list; //错误
List<int,num3> list; //正确
}
//再看一个关于指针和字符串比较特别的例子
template<char const* name>
class pointer
{
};
char a[] = "aa";;//全局变量
char *b = "aa";//全局变量
char *const c = "aa";//全局变量,顶层指针,指针常量
void Test()
{
char d[] = "aa";//局部变量
pointer<"aa"> p1;//错误
pointer<a> p2;//正确
pointer<b> p4;//错误,error C2975:“pointer”的模板参数无效,应为编译时常量表达式
pointer<c> p5;//错误,error C2970: “c”: 涉及带有内部链接的对象的表达式不能用作非类型参数
pointer<d> p3;//错误,局部变量不能用作非类型参数
}