C++模板是C++语言中实现泛型编程的核心机制,它允许程序员定义通用的代码框架,这些框架在编译时可以根据提供的具体类型参数生成相应的特定类型实例。
泛型编程的特点代码复用和安全性!
模板主要分为两大类:函数模板 和类模板。
函数模板
基本语法:
template <typename T, typename U, ...>
函数声明或定义
语法解释:
template ------声明创建模板
typename ------表明其后面的 符号为一种数据类型,可以用class代替。
T ------这个是通用的数据类型,名称可以替换,通常为大写字母。
函数模板的注意事项:
1.模板类型推导(当函数模板被调用时,编译器会根据传递给模板函数的实际参数自动推导出相应的模板参数类型)时同一个通用类型参数要一致。
2.函数模板本身并不生成任何代码。只有当模板被实例化,即编译器根据实际使用的参数类型确定出模板参数 T 的具体值时,才会生成对应的具体函数版本。
普通函数和函数模板的区别:
a.普通函数调用时可以发生自动类型转换(隐式类型推导)
#include<iostream>
int main()
{
int a{ 33 };
char b = 'a';
std::cout << a+b<< std::endl;
return 0;
}
//输出结果是
//130
在a+b时发生了隐式类型转化,'a'转化为ASCN码值结果是33+97所以结果是130
b.函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
#include<iostream>
template<class T>
T add(T a, T b)
{
return a + b;
}
int main()
{
int a{ 33 };
char b = 'a';
std::cout <<add(a,b)<< std::endl;
return 0;
}
//结果出错!
这个会有错误,没有发生隐式转化
c.如果利用显式指定类型的方式,可以发生隐式类型转换
#include<iostream>
template<class T>
T add(T a, T b)
{
return a + b;
}
int main()
{
int a{ 33 };
char b = 'a';
std::cout <<add<int>(a,b)<< std::endl;
return 0;
}
//结果是
//130
如果将这个代码修改这个,可以正确输出130
函数模版的局限性:
对于函数模板我们需要具体化模板
对于具体化的模板会被优先调用
#include<iostream>
template<class T>
void compare(T a,T b)
{
if (a==b)
{
std::cout << "same" << std::endl;
}
else
{
std::cout << "Notsame" << std::endl;
}
}
class first
{
public:
first(int a) :number(a) {};
int number;
};
int main()
{
first s(88),ss(888);
compare(s, ss);
return 0;
}
//这个会产生错误
如果具体化模版可以解决这个问题
#include<iostream>
template<class T>
void compare(T& a, T& b)
{
if (a == b)
{
std::cout << "same" << std::endl;
}
else
{
std::cout << "Notsame" << std::endl;
}
}
class first
{
public:
first(int a) :number(a) {};
int number;
};
//具体化模板的语法
template<>
void compare(first& a, first& b)
{
if (a.number == b.number)
{
std::cout << "same" << std::endl;
}
else
{
std::cout << "Notsame" << std::endl;
}
}
int main()
{
first s(88),ss(888);
compare(s, ss);
return 0;
}
//结果是
//Notsame
普通函数和函数模版的调用:
当普通函数与函数模板都能匹配调用时,编译器通常优先选择普通函数。这是因为普通函数更具体,而函数模板需要经过类型推导和实例化的过程。
#include<iostream>
void put(int a)
{
std::cout << "普通函数的调用" << std::endl;
}
template<class T>
void put(T a)
{
std::cout << "模板函数的调用" << std::endl;
}
int main()
{
int a{ 9 };
put(a);
return 0;
}
//结果是
//普通函数的调用
若要强制调用函数模板,可以显式指定模板参数或者空模板参数列表
#include<iostream>
void put(int a)
{
std::cout << "普通函数的调用" << std::endl;
}
template<class T>
void put(T a)
{
std::cout << "模板函数的调用" << std::endl;
}
int main()
{
int a{ 9 };
put<int>(a); //put<>(a);
return 0;
}
//结果是
//模板函数的调用
函数模板也可以进行重载
#include<iostream>
void put(int a)
{
std::cout << "普通函数的调用" << std::endl;
}
template<class T>
void put(T a)
{
std::cout << "模板函数的调用" << std::endl;
}
template<class T>
void put(T a,T b)
{
std::cout << "重载模板函数的调用" << std::endl;
}
int main()
{
int a{ 9 };
int b{ 9 };
put(a,b);
return 0;
}
//结果是
//重载模板函数的调用
如果普通函数与模板函数之间模板函数有更好的选择,那会调用模板函数
#include<iostream>
void put(int a)
{
std::cout << "普通函数的调用" << std::endl;
}
template<class T>
void put(T a)
{
std::cout << "模板函数的调用" << std::endl;
}
int main()
{
char a = 'a';
put(a);
return 0;
}
//结果是
//模板函数的调用
类模板
基本语法:
类模板的语法与函数模板一致
template <typename T, typename U, ...>
函数声明或定义
语法解释:
template ------声明创建模板
typename ------表明其后面的 符号为一种数据类型,可以用class代替。
T ------这个是通用的数据类型,名称可以替换,通常为大写字母。
类模版和函数模版的区别:
1.类模板没有自动类型推导的使用方式(在C++17后有自动推导的使用方式)
正确的举例:
#include<iostream>
#include<string>
template<class T,class Y>
class first
{
public:
first(T a,Y b):A(a),B(b){}
T A;
Y B;
};
int main()
{
first <int, std::string>secend(44, "haha");
std::cout << secend.A << " " << secend.B;
return 0;
}
//输出的结果
44 haha
在C++17之前如果不进行显示类型指示,那会发生错误
举例子:
#include<iostream>
#include<string>
template<class T,class Y>
class first
{
public:
first(T a,Y b):A(a),B(b){}
T A;
Y B;
};
int main()
{
first secend(44, "haha");
std::cout << secend.A << " " << secend.B;
return 0;
}
2.类模板在模板参数列表中可以有默认参数
#include<iostream>
#include<string>
template<class T,class Y=int>
class first
{
public:
first(T a,Y b):A(a),B(b){}
T A;
Y B;
};
int main()
{
first <int,std::string>secend(44, "haha");
std::cout << secend.A << " " << secend.B;
return 0;
}
类模板的默认值与函数参数的默认值的很相似,如果进行了显示类型指示那就会覆盖默认值,如果一个参数被设立了默认值,那这个参数右边所有的参数有都需设立为默认。
类模版成员函数的创建时间
1.普通类中的成员函数一开始就可以创建
2.类模板中的成员函数在调用时才创建
类模板中的成员函数并不是一开始就创建的,在调用时才去创建
类模版作为函数参数
1.指定传入的类型------直接显式对象的数据类型
cpp
#include <iostream>
template <class T>
class MyClass
{
public:
T value;
MyClass(T val) : value(val) {}
};
void printValue(MyClass<int> &obj)
{
std::cout << "Value: " << obj.value << std::endl;
}
int main()
{
MyClass myIntObject(42);
printValue(myIntObject);
return 0;
}
//输出结果是
Value: 42
2.参数模板化------将对象中的参数变为模板参数进行传递
cpp
#include <iostream>
template <class T>
class MyClass
{
public:
T value;
MyClass(T val) : value(val) {}
};
template <class T>
void printValue(MyClass<T> obj)
{
std::cout << "Value: " << obj.value << std::endl;
}
int main()
{
MyClass<int> myIntObject(42);
MyClass<double> myDoubleObject(3.14);
printValue(myIntObject); // 自动推导为 MyClass<int>
printValue(myDoubleObject); // 自动推导为 MyClass<double>
return 0;
}
3.整个类模板化------将这个对象类型,模板化进行传递
cpp
#include <iostream>
template <class T>
class MyClass
{
public:
T value;
MyClass(T val) : value(val) {}
};
template <class T>
void printValue(T obj)
{
std::cout << "Value: " << obj.value << std::endl;
}
int main()
{
MyClass<int> myIntObject(42);
MyClass<double> myDoubleObject(3.14);
printValue(myIntObject); // 自动推导为 MyClass<int>
printValue(myDoubleObject); // 自动推导为 MyClass<double>
return 0;
}
//输出是
Value: 42
Value: 3.14
类模板成员在类外实现
#include<iostream>
#include<string>
template<class T,class Y>
class first
{
public:
first(T a, Y b);
void show();
T A;
Y B;
};
//类模板成员函数类外实现
template<class T,class Y>
first<T,Y>::first(T a,Y b)
{
A = a;
B = b;
}
//成员函数类外实现
template<class T,class Y>
void first<T,Y>::show()
{
std::cout <<A<<" " << B;
}
int main()
{
first <int,std::string>secend(44, "haha");
secend.show();
return 0;
}
//输出的结果是:
44 haha
类模板中成员函数类外实现时,需要加上模板参数列表
类模板与继承
当类模板碰到继承时,需要注意一下几点:
当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
如果不想指定,编译器无法给子类分配内存
cpp
#include<iostream>
template<class T>
class first
{
public:
T a;
};
class secend :public first
{
public:
};
int main()
{
class secend AA(33);
}
//会出现错误
如果想灵活指定出父类中T的类型,子类也需要变为类模板
cpp
#include<iostream>
template<class T>
class first
{
public:
T a;
};
class secend :public first<int>
{
public:
};
int main()
{
class secend AA(33);
}
//不会出错
将子类也定义为模板
cpp
#include<iostream>
template<class T>
class first
{
public:
T a;
};
template<class T,class Y>
class secend :public first<Y>
{
public:
T aa;
Y b;
secend(T aaval, Y bval, Y firstaval): first<Y>(firstaval), aa(aaval), b(bval) {}
};
int main()
{
class secend <int ,int>AA(33,44,55);
}
类模板与友元
掌握类模板配合友元函数的类内和类外实现
全局函数类内实现,直接在类内声明友元即可、
全局函数类外实现,需要提前让编译器知道全局函数的存在
cpp
#include <iostream>
template <typename T>
class MyClass {
public:
MyClass(T value) : data(value) {}
// 声明友元函数
friend void printData(const MyClass<T>& obj);
private:
T data;
};
// 全局函数类内实现(直接在类定义中定义)
template <typename T>
void printData(const MyClass<T>& obj) {
std::cout << "Data: " << obj.data << std::endl;
}
int main() {
MyClass<int> myObject(42);
printData(myObject); // 输出:Data: 42
return 0;
}
cpp
#include <iostream>
// 前置声明模板类
template <typename T> class MyClass;
// 前置声明全局友元函数
template <typename T>
void printData(const MyClass<T>& obj);
template <typename T>
class MyClass {
public:
MyClass(T value) : data(value) {}
// 声明友元函数
friend void printData<>(const MyClass<T>&);
private:
T data;
};
// 全局函数类外实现
template <typename T>
void printData(const MyClass<T>& obj) {
std::cout << "Data: " << obj.data << std::endl;
}
int main() {
MyClass<int> myObject(42);
printData(myObject); // 输出:Data: 42
return 0;
}
类模板份文件书写
在C++中,类模板通常涉及多个文件的组织:头文件(.h 或 .hpp)用于声明模板类,源文件(.cpp)用于实现模板类的方法。但是,由于类模板的特殊性,其声明和实现通常合并到同一个头文件中。
如果将模板的声明放在头文件中,而实现放在源文件中,当其他源文件通过包含头文件使用模板时,编译器无法看到模板的实现,导致无法完成模板实例化。这与普通类不同,普通类的成员函数实现可以分离在源文件中,因为编译器只需要知道类的接口(声明)即可编译依赖该类的代码。