模板进阶和array

模板进阶

非类型模板参数

cpp 复制代码
#include<iostream>
////非类型模板参数
//实现一个静态栈
template<class T,size_t N=10>//给一个非类型模板参数可以当成常量使用在类里面
class stack {
	T a[N];//可以直接当常量使用.
	int capacity;
};

模板里面可以直接定义整型,int char但是double,float不行,这种c++20后面才支持,自定义类型不支持,但是指针行

c++20之前支持一下:

  1. 整型(枚举)
  2. 指针类型(函数指针,对象指针)
  3. 引用(对象引用,函数引用)
  4. 成员指针

这里面可以没有T

cpp 复制代码
template<size_t b>
class s {

};

容器里面 的非类型模板参数

array是一个静态数组,一开始就开好了空间。

它也支持迭代器,[],size.等很多接口,但是不支持头插尾插,因为这个已经开好了空间,不能扩容了。

?为什么要设置这个array了,它和int a[];区别是什么呢;

  1. 普通数组在写的时候,越界会抽取检查,读的时候并不会。
  2. array数组不管读和写入都会严格检查,也不是抽取检查
cpp 复制代码
std::array<int, 10> d1;
	int a[10];
	a[11] = 12;//会报越界错误
	std::cout<<a[11];//不会发生错误;
cpp 复制代码
std::array<int, 10> d1;
	int a[10];
	//a[11] = 12;
	//a[15] = 3;不会报错
	//std::cout << a[11];
	std::cout << d1[11];//会报错

3.STL容器里面不能直接用int a[]作为元素,因为STL里面的元素要克构造,可赋值的,这也是array的好处

4.在函数传参的时候,int a[]只能传指针,如果在其他函数输出,还不能使用迭代器,还需要传数组的长度。而array就可以直接传过去,输出使用迭代器更方便。

函数模板的特化

cpp 复制代码
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

	bool operator<(const Date& d)const
	{
		return (_year < d._year) ||
			(_year == d._year && _month < d._month) ||
			(_year == d._year && _month == d._month && _day < d._day);
	}

	bool operator>(const Date& d)const
	{
		return (_year > d._year) ||
			(_year == d._year && _month > d._month) ||
			(_year == d._year && _month == d._month && _day > d._day);
	}

	friend std::ostream& operator<<(std::ostream& _cout, const Date& d);
private:
	int _year;
	int _month;
	int _day;
};
std::ostream& operator<<(std::ostream& _cout, const Date& d)
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}

template<class T>
bool less(T& x, T& y) {
	return x < y;
}



int main(){

Date d1(2025, 1, 1);
	Date d2(2025, 1, 3);
	std::cout << less(d1, d2) << std::endl;
}

对于这种对象,完全没有问题函数的模板,但是如果是对象的指针就会出现问题.

cpp 复制代码
Date* d1 = new Date(2025, 1, 1);
	Date*d2=new Date(2025, 1, 3);
	std::cout << less(d1, d2) << std::endl;

对于这种Date*函数普通模板就会出现问题,它们比较是地址,但new 的地址是随机的。会出现问题

出现随机结果。

这时候我们可以利用函数模板特化,写出一个Date*的函数模板,这样就不会出现错误

cpp 复制代码
template<>
bool less<Date*>(Date* & x, Date* & y) {
	return *x < *y;
}

还有就是我们一般传不改变的新参,都加上const.但是函数模板特化有个特点就是函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇
怪的错误。

即使加上了const,为什么还会报错,因为const在前面修饰的是指针指向的内容,修饰的Date,//而上面的函数模板修饰的引用本身,所以我们应该在*后面加const,修饰本身。

解决:

cpp 复制代码
template<>
//bool less<Date*>(const Date* & x, const Date* & y)
bool less<Date*>( Date* const& x,  Date* const& y) {//即使加上了const,为什么还会报错,因为const在*前面修饰的是指针指向的内容,修饰的Date*
	return *x < *y;//而上面的函数模板修饰的引用本身,所以我们应该在*后面加const,修饰本身。
}

总结:

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

类模板的特化

全特化

就是把模板参数里面的所有参数都确定化。在类名后面加括号。写函数模板特化和类模板特化,前面必须有普通的模板,必须有主模板声明

cpp 复制代码
//类模板
template<class T1,class T2>
class stack {
	void fun() {
		std::cout << "普通模板" << std::endl;
	}

};

template<>
class stack<int,int>{//在类名后面加括号。写函数模板特化和类模板特化,前面必须有普通的模板,必须有主模板声明
	void fun() {
		std::cout << "int模板" << std::endl;
	}

};

不管什么类型都可以确定化,自定义也可以,指针,引用

cpp 复制代码
template<>
/*class stack<int,int>*/
/*class stack<int, double>*/ 
class stack<Date*, Date*> {//在类名后面加括号。写函数模板特化和类模板特化,前面必须有普通的模板,必须有主模板声明
	void fun() {
		std::cout << "int模板" << std::endl;
	}

};

偏特化(半特化)

偏特化不仅仅只是对像缺省参数一样对其中的模板参数进行特化,它还可以限制全部或者个别参数必须为指针,引用,但是指针和引用的类型不限制

cpp 复制代码
//同样偏特化前面也必须有主模板的声明
template<class T>
class stack<T, int> {
	void sun() {
		std::cout << "stack<T, int>半特化" << std::endl;
	}

};

template<class T1,class T2>
class stack<T1*,T2*> {///这就是为什么叫偏特化,可以进一步限制,传的只能是指针,但是什么指针都可以。
	void sun() {
		std::cout << "stack<T1*,T2*>半特化" << std::endl;
	}

};

template<class T1, class T2>
class stack<T1&, T2&> {///这就是为什么叫偏特化,可以进一步限制,传的只能是指针,但是什么指针都可以。
	void sun() {
		std::cout << "stack<T1&,T2&>半特化" << std::endl;
	}

};

template<class T2>
class stack<int, T2&> {///这就是为什么叫偏特化,可以进一步限制,传的只能是指针,但是什么指针都可以。
	void sun() {
		std::cout << "stack<int,T2&>半特化" << std::endl;
	}

};

特点类名后面的模板参数必须和主模板声明一样多。不能只写一个就不叫模板特化

特化类模板来实现排序

cpp 复制代码
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

	bool operator<(const Date& d)const
	{
		return (_year < d._year) ||
			(_year == d._year && _month < d._month) ||
			(_year == d._year && _month == d._month && _day < d._day);
	}

	bool operator>(const Date& d)const
	{
		return (_year > d._year) ||
			(_year == d._year && _month > d._month) ||
			(_year == d._year && _month == d._month && _day > d._day);
	}

	friend std::ostream& operator<<(std::ostream& _cout, const Date& d);
private:
	int _year;
	int _month;
	int _day;
};
std::ostream& operator<<(std::ostream& _cout, const Date& d)
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}

template<class T>
class less {
public:
	bool operator()(const T& x, const T& y) {
		return x < y;
	}

};


//利用类模板的特化
	std::vector<Date*> d1;
	Date* s1 = new Date(2025, 1, 3);
	Date* s2 = new Date(2025, 1, 1);
	Date* s3 = new Date(2025, 1, 2);
	Date* s4 = new Date(2025, 1, 6);
	d1.push_back(s1);
	d1.push_back(s2);
	d1.push_back(s3);
	d1.push_back(s4);
	std::sort(d1.begin(), d1.end(),less<Date*>());//开始我写成这个less(Date*),语法混乱了
	for (auto& e : d1) {
		std::cout << e << " ";
	}
	std::cout << std::endl;

这个还是排的指针,并不能排日期的,所以我们可以特化less类模板,来实现

cpp 复制代码
template<>
class less<Date*>{
public:
	bool operator()(const Date*& x, const Date* & y) {
		return x < y;
	}

};

这里为什么会报错,因为Date*,const修饰的是Date*,要进行类型转换,生成一个临时的变量,引用不能存储临时变量

解决方案有多种

  1. 在*后面加const,因为类模板特化类里面可以不一样
cpp 复制代码
template<>
class less<Date*>{
public:
	bool operator()(const Date* const & x, const Date* const & y) {
		return *x < *y;
	}

};

2.接受一样的类型,不进行转换

cpp 复制代码
template<>
class less<Date*> {
public:
   bool operator()(Date* x,  Date*  y) {
   	return *x < *y;
   }

};

3.使用半特化,指定指针

cpp 复制代码
template<class T>
class less<T*> {
public:
	bool operator()(T* x, T* y) {
		return *x < *y;
	}

};

为什么模板的声明和定义不能分离到两个文件



这里会发生链接错误,add找不到定义,为什么找不到定义了

首先编译器要进行 预处理:

a.h a.cpp a.main

预处理:头文件展开/条件编译什么if都编译为指令/宏替换/去掉注释

a.i main.i

编译:检查语法和生成汇编代码

a.s main.s

汇编:把汇编码转换成二进制机械码。

链接:合成可执行文件,链接其他文件定义的函数和变量等

解决方法:

1.在定义文件中,显示实列化

cpp 复制代码
template<class T>
T add(T& x, T& y) {
	return x + y;
}
template//显示实列化
int add(int& x, int& y);

但是这样很麻烦,如果要传double,又要实列化一个double

2.还是声明和定义都放在一个文件里面头文件.

相关推荐
一匹电信狗3 小时前
【牛客CM11】链表分割
c语言·开发语言·数据结构·c++·算法·leetcode·stl
困鲲鲲3 小时前
ROS2系列 (10) : C++话题通信节点——发布者示例
c++·ros2
..过云雨3 小时前
11.【Linux系统编程】文件系统详解——从磁盘硬件到文件系统
linux·c++·后端·缓存
码住懒羊羊3 小时前
【C++】模板进阶 | 继承
android·java·c++
yong99903 小时前
C++语法—类的声明和定义
开发语言·c++·算法
狂奔的sherry3 小时前
构造/析构/赋值运算理解
开发语言·c++
大佬,救命!!!3 小时前
C++多线程运行整理
开发语言·c++·算法·学习笔记·多线程·新手练习
蜗牛沐雨4 小时前
C++ 输出流(Output Stream)全解析
开发语言·c++
小白讲编程5 小时前
C++ 基础学习总结:从入门到构建核心认知
c++·学习·青少年编程