自学内容网 自学内容网

【C++】详细介绍模版进阶,细节满满

目录

一、非类型模版参数:

1、介绍:

2、使用:

3、注意:

4、应用

二、模版特化

(一)、概念

(二)、函数模版特化

1、步骤:

2、举例:

3、不建议使用函数模版特化

(三)、类模版特化

1.全特化:

2、偏特化

2.1、部分特化

2.2、参数更进一步的限制

2.3、注意:

2.4、普通指针变量传递给const指针变量的引用(权限缩小)

三、模版的分离编译

1、什么是分离编译?

2、模版的分离编译

四、模版的优缺点

1.优点:

2、缺点:


一、非类型模版参数:

1、介绍:

模板参数分类类型形参与非类型形参。
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

2、使用:

#define N 20
template<class T>
struct stack
{

private:
T _arr[N];
T _top;
};

int main()
{
stack<int> s1;
    stack<int> s2;
return 0;
}

如上述代码,模拟实现一个数组栈时,栈s1想要10个空间,栈s2想要20个空间,此时的结构就无法实现。

此时我们就可以使用非类型模版参数,即增加一个非类型模版参数:

template<class T,size_t N>
struct stack
{

private:
T _arr[N];
T _top;
};

int main()
{
stack<int,10> s1;
stack<int,20> s2;
return 0;
}

此时,我们需要多少变量,就传多少值。

3、注意:

(1)、非类型模版参数只能传整形常量,浮点数、类对象以及字符串等等是不允许作为非类型模板参数的。
(2)、非类型的模板参数必须在编译期就能确认结果。
(3)、模版是按需实例化,只实例化调用过的函数,这就导致即使某些模版函数里面有一些错误,但如果没有调用该函数的话,也不会检查出来。
如上,N为常量,即使fun函数里面对N进行++,但没有对象调用fun函数,编译器就不会报错。

4、应用

应用一:位图

应用二:array容器

二、模版特化

(一)、概念

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理。
例如用仿函数进行大小比较时,如果传入的是指针,我们预期是想比较指针所指向内容的大小,但它会直接比较指针(地址)的大小,这是就需要对这一类型进行特化。

(二)、函数模版特化

1、步骤:

1、必须要先有一个基础的函数模板(正常模版)
2、关键字template后面接一对空的尖括号<>
3、函数名后跟一对尖括号,尖括号中指定需要特化的类型
4、函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误

2、举例:

template<class T>
class test
{
public:
test(T a1):a(a1)
{}
bool operator<(test a2)
{
return this->a < a2.a;
}
private:
int a;
};
//函数特化
template<class T>
bool myLess(T s1, T s2)
{
cout << "程序走了正常函数模版" << endl;
return s1 < s2;
}

template<>
bool myLess<test<int>*>(test<int>* s1, test<int>* s2)
{
cout << "程序走了特化" << endl;
return *s1 < *s2;
}


int main()
{
test<int> a1(10);
test<int> a2(20);
//正常情况
cout << "正常情况:" << myLess(10, 20) << endl;
//特殊情况
test<int>* pa1 = &a1;
test<int>* pa2 = &a2;
cout << "特殊情况:" << myLess(pa1, pa2);

return 0;
}

如上,当我们实现一个函数的功能是比较大小时,如果此时我们实参传的是值的指针,那么函数就会读指针(即地址)进行比较,这样的结果是不确定的,因为申请的空间不确定谁大谁小,同时也不符合我们的预期。此时我们就需要对该指针类型进行特化,如上的test<int>*,根据特化步骤单独写一个特化模版函数,当传入的参数是test<int>*类型时,程序就会调用特化后的函数。

(通俗来讲就是,对某一个类型单独进行处理,当传入的实参类型与特化类型匹配的话就会进入特化后的模版函数)。

3、不建议使用函数模版特化

不建议使用函数模版特化,因为当遇见参数列表有引用或const修饰时的情况,处理起来会变得很麻烦。

上述情况用编译器优先匹配的原则也可以解决,有现成用现成。

bool myLess(test<int>* s1, test<int>* s2)
{
cout << "编译器优先匹配" << endl;
return s1 < s2;
}

(三)、类模版特化

1.全特化:

全特化即是将模板参数列表中所有的参数都确定化。
//类模板特化
//正常模版
template<class T1,class T2>
class myTest
{
private:
T1 _a1;
T2 _a2;
public:
void fun()
{
cout << "调用正常模版" << endl;
}
};

//全特化指针类型
template<>
class myTest<int*,int*>
{
private:
int* _a1;
int* _a2;
public:
void fun()
{
cout << "调用全特化int指针模版" << endl;
}
};


int main()
{
//正常模版
myTest<int,int> aa;
aa.fun();
//全特化int指针
myTest<int*, int*> bb;
bb.fun();
return 0;
}

注意全特化模版的写法,template<>里面没有数据,将所有模版替换为具体类型(int*为例),这样当,类实例化时传递的参数与之匹配时,就会使用全特化模版。

2、偏特化

2.1、部分特化
将模板参数类表中的一部分参数特化
//偏特化
//正常模版
template<class T1,class T2>
class myTest
{
private:
T1 _a1;
T2 _a2;
public:
void fun()
{
cout << "调用正常模版" << endl;
}
};

//部分参数特化
template<class T1>
class myTest<T1,int*>
{
private:
T1 _a1;
int* _a2;
public:
void fun()
{
cout << "调用部分参数模版" << endl;
}
};
int main()
{
myTest<char, int*> s1;
s1.fun();
return 0;
}

类似的,这里我们特化了第二个参数为具体的int*,那么当传模版参数中只要第二个模版参数的类型是int型,那么就会实例化部分参数特化的类。要注意写法。

2.2、参数更进一步的限制
偏特化并不仅仅是指特化部分参数,还可以是针对模板参数更进一步的 条件限制 所设计出来的一个特化版本。如限制类型是指针或者引用等。
//偏特化
//正常模版
template<class T1,class T2>
class myTest
{
private:
T1 _a1;
T2 _a2;
public:
void fun()
{
cout << "调用正常模版" << endl;
}
};

部分参数特化
//template<class T1>
//class myTest<T1,int*>
//{
//private:
//T1 _a1;
//int* _a2;
//public:
//void fun()
//{
//cout << "调用部分参数模版" << endl;
//}
//};

//进一步限制类型
template<class T1,class T2>
class myTest<T1*, T2*>
{
private:
T1* _a1;
T2* _a2;
public:
void fun()
{
cout << "调用进一步限制成指针的模版" << endl;
}
};

int main()
{
myTest<char, int> s1;
s1.fun();
myTest<char*, int*> s2;
s2.fun();
myTest<double*, char*> s3;
s3.fun();
return 0;
}
上述写法将模版参数全部限制成指针,不管什么类型的指针,只要两个模版参数都是原生指针,那么就会实例化进一步限制的特化模版类。
2.3、注意:

(1)、全特化和进一步限制特化的区别,通俗来讲可以理解为,全特化只针对一种类型进行特化,而后者则是针对某一类类型,如指针类,引用类.

(2)、同时存在全特化和偏特化,则会优先匹配全特化,后匹配偏特化,因为全特化的类型更加明确,类似于编译器优先匹配原则。如果都匹配不上的就会匹配正常模版。

2.4、普通指针变量传递给const指针变量的引用(权限缩小)

我们知道,普通指针变量是可以传递给const修饰的指针变量的,这样所指向的值是不能改变的,这属于权限缩小问题。同时普通变量也可以传递给引用。

那么当把普通指针变量传递给const指针变量的引用该怎么书写呐?

错误写法:

因为当把int* 传递给const int* 的引用时,中间会产生一个临时变量而临时变量具有常属性,所以我们应该使用const指针变量的const引用进行接收:

正确写法:

三、模版的分离编译

1、什么是分离编译?

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链 接起来形成单一的可执行文件的过程称为分离编译模式。
例如将函数的声明放进 .h 文件,函数的定义放 .cpp 文件,程序翻译的前三个过程.h 和 .cpp是分别进行,最后链接阶段在链接起来。

2、模版的分离编译

函数模版和类模版都不支持声明和定义分离,所以最好的办法就是将声明和定义放在同一个源文件。

四、模版的优缺点

1.优点:

1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
2. 增强了代码的灵活性

2、缺点:

1. 模板会导致代码膨胀问题,也会导致编译时间变长
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误


原文地址:https://blog.csdn.net/hffh123/article/details/143700591

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!