【C++】模板进阶

目录

模板进阶

非类型模板参数

非类型模板参数缺省值

模板的特化

概念

函数模板的特化

类模板的特化

全特化

偏特化

模板分离编译

什么是分离编译

模板总结

优点

缺点

模板进阶

如果没有了解模板的兄弟,那么可以先去了解或者学习一下模板的初阶:【C++】模板初阶-CSDN博客

通过模板,我们可以实现出泛型编程,模板分别为函数模板和类模板,下面我们就说一下模板进阶的一些东西

非类型模板参数

模板参数分别为,类型形参和非类型形参

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

举个例子,让我们去了解一下:

我们写个静态栈的结构:

cpp 复制代码
#define N 10
template<class T>//类型模板参数
class Stack
{
private:
    T _a[N];
    size_t _top;
};
int main()
{
    Stack<int> st1;//大小为10
    Stack<int> st2;
    
}

我们想要改栈的大小,就改宏就可以了,但是我们有两个栈呢?一个栈的大小想要10,一个想要1000的大小,这样就不能满足多个栈的需求了,除非再定义一个类模板,但是这样代价太大,再C++模板当中就有一个非类型模板参数概念:

cpp 复制代码
template<class T,size_t N>
    //T是类型模板参数,N是非类型模板参数,N是一个常量
class Stack
{
private:
    T _a[N];
    size_t _top;
};
int main()
{
    Stack<int,100> st1;//100
    Stack<int,20000> st2;//20000
}

我们这样子就可以通过传参完成每个栈想要的大小需求了,再template<class T,size_t N>当中,T是类型模板参数,这里的N是非类型模板参数,这里的N是一个常量

我们可以这样子传参吗?

cpp 复制代码
int main()
{
    static int n;
    cin>>n;
    Stack<int,n> st;//error,非类型模板参数不能是变量
    return 0;
}

这样是错误的,非类型模板参数不能是变量

非类型模板参数缺省值

模板参数都可以给缺省值,模板参数给缺省值和函数参数给缺省值是完全类似的,可以全缺省,也可以半缺省(必须从右往左连续缺省)

cpp 复制代码
//模板参数都可以给缺省值
//模板参数给缺省值和函数参数给缺省值是完全类似的
//可以全缺省
//也可以半缺省 -- 必须从右往左连续缺省
template<class T,size_t N = 10>
class Array
{
private:
    T _a[N];
}
int main()
{
    Array<int> a1;
    Array<int,20> a2;
    return 0;
}

需要注意的是,如果全都是缺省值时不能这样创建对象:

cpp 复制代码
Array a1;

全部都是缺省值,我们可以不传参数,但是我们知道Array是个模板,模板也是有类型的,我们需要这样:

cpp 复制代码
Array<> a1;

注意:

1、浮点数、类对象以及字符串是不支持作为非类型模板参数的

cpp 复制代码
template<class T,string s1>
template<class T,double s1>

2、非类型的模板参数必须在编译期就能确认结果

模板的特化

概念

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,比如:

cpp 复制代码
template<class T>
bool IsEqual(const T& left,const T& right)
{  
    return left==right; 
}
int main()
{
    cout<<IsEqual(1,2)<<endl;
    
    char p1[] = "hello";
    char p2[] = "hello";
    cout<<IsEqual(p1,p2)<<endl;//数组名是指针常量
    
    return 0;
}

这个模板是用来比较整形的,但是我们用来比较字符串了话,会出现问题

我们想一想可能可以这样解决:

cpp 复制代码
template<class T>
bool IsEqual(const T& left,const T& right)
{
    if(T == const char*)
    {
    	return strcmp(left,right)==0;    
        //可是语法不支持
    }
    else
    {
    	return left==right; 
    }
}

这样貌似也可以实现字符串的比较,但是这个在语法上是不支持的,所以不能这样

模板的特化,针对某些类型进行特殊化处理,我们可以这样写:

cpp 复制代码
bool IsEqual(const char*& left,const char*& right)
{
    return strcmp(left,right)==0;
}
int main()
{
    cout<<IsEqual(1,2)<<endl;
    
    char p1[] = "hello";
    char p2[] = "hello";
    cout<<IsEqual(p1,p2)<<endl;//数组名是指针常量
    
    return 0;
}

我们调试过后,发现这里不会进这个函数,因为数组名是指针常量,则这里的const修饰的是*left,是left指向的内容不能修改,而不是left不能修改,这里属于权限放大

需要这样改,这样就可以进去了:

cpp 复制代码
//模板的特化,针对某些类型进行特殊化处理
bool IsEqual(const char*& const left,const char*& const right)
{
    return strcmp(left,right)==0;
}

也可以这样改,将引用去掉

cpp 复制代码
bool IsEqual(const char* left,const char* right)
{
    return strcmp(left,right)==0;
}

函数模板的特化

函数模板的特化步骤:

1、必须要写有一个基础的函数模板

2、关键字template后面接一对空的尖括号<>

3、函数名后跟一对尖括号,尖括号中指定需要特化的类型

4、函数形参表:必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误

cpp 复制代码
template<class T>
void Swap(T& a,T& b)
{
    //vector代价太大
    T tmp = a;
    a = b;
    b = tmp;
}
int main()
{
    int x = 1;
    int y = 2;
    Swap(x,y);
    vector<int> v1 = {1,2,3,4};
    vector<int> v2 = {10,20,30,40};
    Swap(v1,v2);
    return 0;
}

当我们交换的类型为vector时,此时用模板函数进行交换代价太大了,一次拷贝构造+两次赋值重载,所以我们可以这样写:

cpp 复制代码
//函数模板的特化
template<>
void Swap<vector<int>>(vector<int>& a,vector<int>& b)
{
    a.swap(b);
}

类模板的特化

全特化

全特化即是将模板参数列表中所有的参数都确定化

cpp 复制代码
template<class T1,class T2>
class Data
{
public:
	Data() { cout << "Data<T1,T2>"<<endl; }
private:
	T1 _d1;
	T2 _d2;
};
//全特化
template<>
class Data<double, double>
{
public:
	Data() { cout << "Data<double,double>" << endl; }
private:
	double _d1;
	double _d2;
};

int main()
{
    Data<int,int> d1;
    Data<double,double> d2;
    return 0;
}

偏特化

偏特化:任何针对模板参数进一步进行条件限制设计的特化版本,比如:模板类:

偏特化有两种表现方式:

(1)部分特化,将模板参数类表中的一部分参数特化

cpp 复制代码
template<class T1,class T2>
class Data
{
public:
	Data() { cout << "Data<T1,T2>"<<endl; }
private:
	T1 _d1;
	T2 _d2;
};
//偏特化或者半特化
template<class T1>
class Data<T1,char>
{
public:
	Data() { cout << "Data<T1,double>" << endl; }
};

int main()
{
    Data<double,char> d2;
    Data<int,char> d1;
    
    return 0;
}

(2)参数更进一步的限制,偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本

cpp 复制代码
template<class T1,class T2>
class Data
{
public:
	Data() { cout << "Data<T1,T2>"<<endl; }
private:
	T1 _d1;
	T2 _d2;
};
//偏特化或者半特化:不一定是特化部分参数,有可能是对参数的限制
template<class T1,class T2>
class Data<T1*,T2*>
{
public:
	Data() { cout << "Data<T1*,T2*>" << endl; }
};

template<class T1,class T2>
class Data<T1&,T2&>
{
public:
	Data() { cout << "Data<T1&,T2&>" << endl; }
};
int main()
{
    Data<int*,char*> d5;
    Data<int*,int*> d6;
    
    Data<int&,char&> d7;
    Data<int&,int&> d8;
    return 0;
}

模板总结

优点

模板复用了代码,节省资源,更快的迭代开发,因此C++的标准库(STL)因此而产生

增强了代码的灵活性

缺点

模板会导致代码膨胀问题,也会导致编译时间变长

出现类模板编译错误,错误信息非常乱,不容易定位错误

相关推荐
Mr数据杨2 小时前
【Codex】用学生入学评估模块建立新生能力画像
android·java·javascript·django·codex·项目开发
2601_962056232 小时前
Spring Boot 入门 与 无法解析符号 springframework 的解决
java·spring boot·后端
luj_17682 小时前
大律师考核应重能力与科技素养
c语言·开发语言·c++·经验分享·算法
Thomas21433 小时前
Java scala 数组 链表
java·链表·scala
刃神太酷啦3 小时前
Linux 系统 MySQL 完整安装配置教程:从卸载 MariaDB 到优化 my.cnf----《Hello MySQL!》(1)
android·linux·c语言·c++·mysql·leetcode·mariadb
亚历克斯神3 小时前
低代码与 AI 的融合趋势——从可视化搭建到自然语言生成应用
java·spring·微服务
学长毕业设计3 小时前
基于SpringBoot的瑜伽馆网站的设计与实现(源码+文档+讲解视频)
java·spring boot·后端
一嘴一个橘子3 小时前
nginx 常用命令(在 nginx 目录下执行)
java
leonkay3 小时前
C# 特性(Attribute)——【1】基础讲解
开发语言·青少年编程·面试·c#·.net·个人开发
harmful_sheep3 小时前
java group by常见用法
java·开发语言·python