模板基础知识
概念
C++中的模板是一种通用编程工具,允许你编写与数据类型无关的代码。模板允许你编写函数或类,其中的某些部分可以用类型或值替换,从而实现通用性和灵活性。模板是C++中强大而灵活的特性之一,它们使得编写可重用的代码变得更加容易。
在C++中,有两种主要类型的模板:函数模板和类模板。
函数模板
函数模板允许你编写一个通用的函数,其中的某些类型可以由用户指定。以下是一个简单的函数模板的示例:
template <typename T>
T add(T a, T b) {
return a + b;
}
在这个例子中,<typename T>
表示一个模板参数,T
是一个占位符,表示类型。函数模板 add
可以接受两个相同类型的参数,并返回它们的和。
使用示例:
cout << "add(3, 4) = " << add(3, 4) << endl; // 实例化为 add<int>(3, 4);
cout << "add(3.2, 4.2) = " << add(3.2, 4.2) << endl; // 实例化为 add<double>(3.2, 4.2);
在这个示例中我们只能传入相同数据类型的元素,那我们能不能传入不同数据类型的元素嘞?我们来试一下:
/*************************************************************************
> File Name: test.cpp
> Author:Xiao Yuheng
> Mail:3312638794@qq.com
> Created Time: Thu Nov 9 15:07:26 2023
************************************************************************/
#include <iostream>
using namespace std;
template<typename T, typename U>
T add(T a, U b) {
return a + b;
}
int main() {
cout << "add(3, 4) = " << add(3, 4) << endl;
cout << "add(3.2, 4.2) = " << add(3.2, 4.2) << endl;
cout << "add(3, 4.2)" << add(3, 4.2) << endl;
cout << "add(4.2, 3)" << add(4.2, 3) << endl;
return 0;
}
我们在这里定义两个模板类型,这样是不是就可以接收两种不同类型的数据了,我们来运行一下:
这里我们可以发现
cout << "add(3, 4.2)" << add(3, 4.2) << endl;
cout << "add(4.2, 3)" << add(4.2, 3) << endl;
这两行代码的输出结果竟然是不一样的,这是什么原因嘞,我们来看一下这个返回值的类型,没错当返回值类型T
是double
类型时,结果就是正确的,当返回值类型T
为int
时,返回值会自动保留整数部分。这时返回的结果就不是我们想要的,我们要怎么解决这个问题呢?我们接着往下看:
decltype
的使用
decltype
是C++11引入的关键字,用于获取表达式的类型。它的作用是告诉编译器,根据给定表达式的类型来声明一个变量或定义一个返回类型。
语法格式如下:
decltype(expression)
其中 expression
是一个任意的表达式。
我们来使用一下:
/*************************************************************************
> File Name: test.cpp
> Author:Xiao Yuheng
> Mail:3312638794@qq.com
> Created Time: Thu Nov 9 15:07:26 2023
************************************************************************/
#include <iostream>
using namespace std;
template<typename T, typename U>
decltype(T() + U()) add(T a, U b) {
return a + b;
}
int main() {
cout << "add(3, 4) = " << add(3, 4) << endl;
cout << "add(3.2, 4.2) = " << add(3.2, 4.2) << endl;
cout << "add(3, 4.2)" << add(3, 4.2) << endl;
cout << "add(4.2, 3)" << add(4.2, 3) << endl;
return 0;
}
我们把返回值类型换成decltype(T() + U())
,因为我们无法使用变量a
和变量b
所以我们直接调用这两个类型的构造函数,我们来运行一下看看结果:
没有问题,可是这时又出现一个问题,如果着个函数没有默认构造呢?如下这种情况:
/*************************************************************************
> File Name: test.cpp
> Author:Xiao Yuheng
> Mail:3312638794@qq.com
> Created Time: Thu Nov 9 15:07:26 2023
************************************************************************/
#include <iostream>
using namespace std;
class A {
public:
A() = delete;
A(int x) : x(x) {}
int x;
};
class B {
public:
B() = delete;
B(int x) : x(x) {}
int x;
};
class C {
public:
C() = delete;
C(int x) : x(x) {}
int x;
};
C operator+(const A &a, const B &b) {
return a.x + b.x;
}
C operator+(const B &a, const A &b) {
return a.x + b.x;
}
ostream &operator<<(ostream &out, const C &c) {
out << c.x;
return out;
}
template<typename T, typename U>
decltype(T() + U()) add(T a, U b) {
return a + b;
}
int main() {
cout << "add(3, 4) = " << add(3, 4) << endl;
cout << "add(3.2, 4.2) = " << add(3.2, 4.2) << endl;
cout << "add(3, 4.2)" << add(3, 4.2) << endl;
cout << "add(4.2, 3)" << add(4.2, 3) << endl;
A a(123);
B b(456);
cout << "add(a, b) = " << add(a, b) << endl;
return 0;
}
就比如这段代码,我们自定义了两个类,但是呢?这两个类的默认构造被我删掉了,所以这个模板在这个时候就不成立了,我们怎么才能让他成立呢?如下:
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
我们可以把返回值写在后面,这样我们就可以访问a
对象和b
对象了,前面用auto来占位,这样我们就完美解决了这个问题。
类模板
类的使用
/*************************************************************************
> File Name: test.cpp
> Author:Xiao Yuheng
> Mail:3312638794@qq.com
> Created Time: Thu Nov 9 15:53:48 2023
************************************************************************/
#include <iostream>
using namespace std;
template<typename T>
class PrintAny {
public:
PrintAny(ostream &out) : out(out) {}
PrintAny &Print(T a) {
out << a;
return *this;
}
PrintAny &endl() {
out << std::endl;
return *this;
}
private:
ostream &out;
};
int main() {
PrintAny<int> pint(cout);
PrintAny<double> pdouble(cout);
PrintAny<string> pstring(cout);
pint.Print(3).endl();
pdouble.Print(6.5).endl();
pstring.Print("xiaoyuheng").endl();
return 0;
}
1. 模板类 PrintAny
:
这是一个模板类,接受一个模板类型 T
。类中有一个私有成员 ostream &out
,它是一个对输出流的引用,用于确定数据输出的目标。构造函数 PrintAny(ostream &out)
初始化这个输出流。
类中有两个成员函数:
PrintAny &Print(T a)
:接受一个类型为T
的参数a
,将其输出到out
引用的输出流中,并返回*this
以支持链式调用。PrintAny &endl()
:输出一个换行符到out
引用的输出流中,并返回*this
以支持链式调用。
2. main
函数:
在 main
函数中,我们实例化了三个不同类型的 PrintAny
对象:
PrintAny<int> pint(cout);
:用于打印整数。PrintAny<double> pdouble(cout);
:用于打印双精度浮点数。PrintAny<string> pstring(cout);
:用于打印字符串。
然后,我们使用相应的对象调用 Print
函数来输出数据,并使用 endl
函数输出换行。
3. 使用示例:
cppCopy codepint.Print(3).endl(); // 输出整数 3,并换行
pdouble.Print(6.5).endl(); // 输出双精度浮点数 6.5,并换行
pstring.Print("xiaoyuheng").endl(); // 输出字符串 "xiaoyuheng",并换行
这样的设计使得你可以通过不同的实例来处理不同类型的数据,而不需要为每个类型都编写一个特定的输出函数。通过模板类的设计,可以提高代码的可重用性和通用性。
完美来运行一下,看看运行结果:
成员函数的使用
/*************************************************************************
> File Name: test.cpp
> Author:Xiao Yuheng
> Mail:3312638794@qq.com
> Created Time: Thu Nov 9 15:57:11 2023
************************************************************************/
#include <iostream>
using namespace std;
class PrintAny {
public:
PrintAny(std::ostream &out) : out(out) {}
template<typename T>
PrintAny &Print(T a) {
out << a;
return *this;
}
PrintAny &endl() {
out << std::endl;
return *this;
}
private:
ostream &out;
};
int main() {
PrintAny p(cout);
p.Print(3).endl().Print(4.3).endl().Print("xiaoyuheng").endl();
return 0;
}
1. 类 PrintAny
:
这是一个用于在不同类型上输出信息的类。与之前的版本不同,这里的 PrintAny
类本身不是一个模板类,但它包含了一个函数模板 Print
。
PrintAny(std::ostream &out)
:构造函数,接受一个ostream
引用,用于确定数据输出的目标。template<typename T> PrintAny &Print(T a)
:函数模板,接受一个类型为T
的参数a
,将其输出到out
引用的输出流中,并返回*this
以支持链式调用。PrintAny &endl()
:输出一个换行符到out
引用的输出流中,并返回*this
以支持链式调用。
2. main
函数:
在 main
函数中,我们实例化了一个 PrintAny
对象 p
,并使用它来输出不同类型的数据:
cppCopy code
p.Print(3).endl().Print(4.3).endl().Print("xiaoyuheng").endl();
这一行代码展示了链式调用的方式,通过调用 Print
和 endl
函数,可以在一行中输出整数、浮点数和字符串,并在每次输出后换行。
3. 输出示例:
plaintextCopy code3
4.3
xiaoyuheng
his` 以支持链式调用。
2. main
函数:
在 main
函数中,我们实例化了一个 PrintAny
对象 p
,并使用它来输出不同类型的数据:
cppCopy code
p.Print(3).endl().Print(4.3).endl().Print("xiaoyuheng").endl();
这一行代码展示了链式调用的方式,通过调用 Print
和 endl
函数,可以在一行中输出整数、浮点数和字符串,并在每次输出后换行。
3. 输出示例:
plaintextCopy code3
4.3
xiaoyuheng
这是程序的输出结果,每个数据后面都有一个换行符。