C++语法——模板基础
观前提醒
本人刚学C++不久,超级无敌大萌新。如有错误,请立即指出。
本人全程使用C++20进行编写测试
引入
swap()
是非常常用的函数,使用过程中我们会发现,实参不管是什么类型,只要是相同的类型,它们就能交换。
int main()
{
int a = 1, b = 2;
swap(a, b); // a == 2, b == 1
char c = 'A', d = 'B';
swap(c, d); // c == 'B', d == 'A'
std::string a = " ", b = "\n";
swap(a, b); // a == "\n", b == " "
struct Node { int q, p; }e = { 1,2 }, f = { 3,4 };
swap(e, f); // e == {3,4}, f == {1,2}
return 0;
}
这是如何实现的?下面是一种方法
void swap(int& a, int& b)
{
int t = a;
a = b;
b = t;
}
void swap(char& a, char& b)
{
char t = a;
a = b;
b = t;
}
void swap(std::string& a, std::string& b)
{
std::string t = a;
a = b;
b = t;
}
但是有一个明显的缺陷,如果有100个类型就要重载100个swap函数,那么有什么其他的方法呢?且听我慢慢道来。
auto类型
auto
在C++11中被赋予了新的含义,也就是我们现在用的。
auto
有多种用法。
auto
的主要作用就是自动推导类型。
看以下代码
右侧是一个int
类型,auto
会自动判断出右侧的类型,所以a
是int
类型。
任何一个auto
都需要初始值,否则会因为无法推导其类型而报错。
来看以下代码
int main()
{
auto a = 1;
auto b = std::string(); // OK, std::string b
auto c = 1LL; // OK, long long c
auto d; // 无法推导d的类型(需要初始值),非法
return 0;
}
auto
不得滥用,大部分情况该用什么类型就写什么类型。以下是auto
的使用场景。
1. 自动推导类型,简化一些复杂的类型,比如一些迭代器
int main()
{
std::vector<int> v = { 1,2,3,4,5 };
for (std::vector<int>::iterator it = v.begin(); it != v.end(); it++) {}
for (auto it = v.begin(); it != v.end(); it++) {}
for (int it : v) {}
for (auto it : v) {}
// 这四句话的效果是一样的
return 0;
}
解释一下后两行的for
,这两行的for
和1、2行的for
,本质上是一样的。1、2行的写法已经被淘汰了。要注意,3、4行的for
中,·it
前面写的不是迭代器类型,而是int
。
2. 函数返回类型自动推导(C++14)
auto max(int a, int b)
{
return (a > b ? a : b);
}
int max(int a, int b)
{
return (a > b ? a : b);
}
这两段代码是等价的。编译器自动推导了返回值的类型。
3.简写函数模板(C++20)
具体的函数模板在后面。
auto max(const auto& a, const auto& b)
{
return (a > b ? a : b);
}
形参的两个auto
都可以是任意类型(只不过下面return
的值会报错),这里a
与b
的值取决于实参。
auto f(const auto& a, const auto& b) {};
int main()
{
f(3, std::vector<int>{}); //用{}初始化为空
// OK, 三个auto推导出不同的类型
// 第一个auto推导出void
// 第二个auto推导出int,a类型为const int&
// 第三个auto推导出vector<int>,b的类型为const vector<int>&
return 0;
}
要注意的是,auto
推导出的类型不含引用和cv修饰符(即const和volatile)
函数模板
函数模板的声明与定义
模板分很多,这里主要讲函数模板
上文提到了,使用auto
进行简化函数模板,但依旧会出现问题:不能保证一个形参或几个形参推到后是同一个类型。接下来我们看看标准的函数模板长什么样。
template < 形参列表 > 函数声明
这就是一个模板,一般函数声明那里要换行。
下面就是一个max()
函数的简单实现 (STL里完全不是这么写的)
template<typename T>
T max(T a, T b)
{
return (a > b ? a : b);
}
为了更好理解,我把一些修饰符去掉了。
模板的形参列表中,先写一个typename
,再写一个形参的名字,这里的名字是T
,当然也可以是别的名字比如orz
,但T
比较常用。这里的T
可以被推导,但max
的返回值,a
和b
的类型都是T
。
· 先说说实例化:在函数模板中,指根据模板参数在需要时生成特定类型或值的代码。还是上面那个例子。
如果你调用了max(1,2)
,通过推导,T
是int
,那么编译器就会生成一个int
版本的max()
:
int max(int a, int b)
{
return (a > b ? a : b);
}
类型被推导后,就会直接将T
替换为int
如果加上修饰符,那么推导后的类型也会修饰。
template<typename T> //函数模板
T max(const T& a, const T& b)
{
return (a > b ? a : b);
}
int max(const int& a, const int& b) //实例化
{
return (a > b ? a : b);
}
此外,实例化还可以手动自己写,这里不再赘述。
其他注意事项:
· 形参列表里可以有多个形参,比如
template<typename T1, typename T2>
T1 f(T1 a, T2 b) {}
· 一般地, 一个模板只管一个函数
· 模板里的形参可以在函数体的内部使用,比如
template<typename T>
void swap(const T& a, const T& b)
{
T t = a;
a = b;
b = t;
}
关于函数模板的调用
其实函数模板调用时是可以手动指定推导的类型
f<类型>(实参列表)
看以下几个例子
template<typename T>
void swap(const T& a, const T& b){}
int main()
{
int a = 0;
int b = 1;
swap(a, b); // OK, 完全由编译器推导
swap<int>(a, b); // OK, 手动指定类型
swap<double>(a, b); // OK, 隐式转换为double类型
swap<std::string>(std::string{}, std::string{}); // OK, 两个string
swap<std::string>(a, b); // 报错,a和b是int,int不能直接转换为string
return 0;
}