最近工作需要,学习C++ 11的知识,学习中总结了一些知识点,分享给大家。
函数模板
简介
建立一个通用函数,这个通用函数的形参类型不具体指定,用一个虚拟类型来代表,这个通用函数就被称为函数模板。
区别
模板区别在于其函数声明,前面加了个template<typename T>,这句话告诉编译器,函数体出现类型T时,不要报错,T是一个通用类型。
格式
template<parameter-list> function-declaration
函数模板在形式上分为两部分:模板、函数。在函数前面加上template<...>就成为函数模板,因此对函数的各种修饰(inline、constexpr等)需要加在function-declaration上,而不是template前。如
template<typename T>
inline T min(const T &, const T &);
注意
- 函数模板不是函数;
- 函数模板用来定义一族函数,而不是一个函数;
- 当用具体类型去替换T时,才会生成具体函数,该过程叫做函数模板的实例化;
- 如果我们只是编写了函数模板,但不在任何地方使用它(也不显式实例化),则编译器不会为该函数模板生成任何代码。
详细解说
1)显式实例化
简介
显式实例化的方式告诉编译器生成指定实参的函数,显式实例化声明会阻止隐式实例化
例子
template<typename R, typename T1, typename T2>
R add(T1 a, T2 b) {
return static_cast<R>(a + b);
}
// 显式实例化
template double add<double, int, double>(int, double);
// 显式实例化, 推导出第三个模板实参
template int add<int, int>(int, int);
// 全部由编译器推导
template double add(double, double);
在显式实例化时,只指定部分模板实参,则指定顺序必须s自左至右依次指定,不能越过前参模板形参,直接指定后面的。
2)隐式实例化
简介
当函数模板被调用,且在之前没有显式实例化时,即发生函数模板的隐式实例化。
例子
#include <iostream>
template<typename T>
void print(const T &r) {
std::cout << r << std::endl;
}
int main() {
// 隐式实例化print<int>(int)
print(1);
// 实例化print<char>(char)
print<>('c');
// 仍然是隐式实例化,我们希望编译器生成print<double>(double)
print<double>(1);
}
3)返回值为auto
简介
函数的返回值类型取决于函数参数某种运算后的类型。对于这种情况可以采用auto关键字作为返回值占位符
例子
template<typename T1, typename T2>
auto multi(T a, T b) -> decltype(a * b) {
return a * b;
}
decltype操作符用于查询表达式的数据类型,也是C++11标准引入的新的运算符,其目的是解决泛型编程中有些类型由模板参数决定,而难以表示的问题。
4)类成员函数模板
简介
类成员中定义函数模板
例子
#include <iostream>
class object {
public:
template<typename T>
void print(const char *name, const T &v) {
std::cout << name << ": " << v << std::endl;
}
};
int main() {
object o;
o.print("name", "Crystal");
o.print("age", 18);
}
注意:函数模板不能用作虚函数。这是因为C++编译器在解析类的时候就要确定虚函数表(vtable)的大小。
5)函数模板重载
简介
函数模板之间、普通函数和模板函数之间可以重载
调用顺序
顺序 | 行为 |
1 | 最符合函数名和参数类型的普通函数 |
2 | 特殊模板(具有非类型形参的模板,即对T有类型限制) |
3 | 普通模板(对T没有任何限制的) |
4 | 通过类型转换进行参数匹配的重载函数 |
例子
#include <iostream>
template<typename T>
const T &max(const T &a, const T &b) {
std::cout << "max(&, &) = ";
return a > b ? a : b;
}
// 函数模板重载
template<typename T>
const T *max(T *a, T *b) {
std::cout << "max(*, *) = ";
return *a > *b ? a : b;
}
// 函数模板重载
template<typename T>
const T &max(const T &a, const T &b, const T &c) {
std::cout << "max(&, &, &) = ";
const T &t = (a > b ? a : b);
return t > c ? t : c;
}
// 普通函数
const char *max(const char *a, const char *b) {
std::cout << "max(const char *, const char *) = ";
return strcmp(a, b) > 0 ? a : b;
}
int main() {
int a = 1, b = 2;
std::cout << max(a, b) << std::endl;
std::cout << *max(&a, &b) << std::endl;
std::cout << max(a, b, 3) << std::endl;
std::cout << max("en", "ch") << std::endl;
// 可以通过空模板实参列表来限定编译器只匹配函数模板
std::cout << max<>("en", "ch") << std::endl;
}
可以通过空模板实参列表来限定编译器只匹配函数模板。
6)普通函数模板
#include <iostream>
// N必须是编译时的常量表达式
template<typename T, int N>
void printArray(const T (&a)[N]) {
std::cout << "[";
const char *sep = "";
for (int i = 0; i < N; i++, (sep = ", ")) {
std::cout << sep << a[i];
}
std::cout << "]" << std::endl;
}
int main() {
// T: int, N: 3
printArray({1, 2, 3});
}
//输出:[1, 2, 3]
函数模板 .vs. 模板函数
函数模板重点在模板。表示这是一个模板,用来生成函数。
模板函数重点在函数。表示的是由一个模板生成而来的函数。
类模板
简介
C++中类模板通常是容器(如std::vector)或行为的封装,类模板只是定义了一组通用的操作,在具体实例化前是不占用程序空间的。
语法格式
template < parameter-list > class-declaration
parameter-list 形参
class-declaration 类声明
说明:构成类模板的形参(parameter-list)约束与函数模板相同,但是函数模板的类型自动推演不同,类模板实例化时,需要显式指定,例如:
std::vector<int> intArr;
详细解说
- 类模板--成员函数
成员函数(含成员函数模板)只有在使用时才生成。
template<typename T>
class A {
T a_;
public:
void add(int n) {
a_ += n;
}
}
class M{};
int main() {
A<M> s; // s未用到add函数,因此整个程序得以成功编译
// s.add(1); // 如果有这句话,则会编译失败
}
A<T>::add在变量s中未使用到,因此虽然a_ += n不合法,但整个程序仍然通过了编译。
2)类模板--虚函数
类模板中可以有虚函数,虚函数在类模板实例化为模板类时由编译器生成。
类模板的虚函数可以访问模板类中的泛型成员(变量、成员函数模板都可以访问)。 例子:
#include <iostream>
template<typename T>
class A {
T a_;
public:
virtual void say() {
std::cout << "a -> " << a_ << std::endl;
} };
class M{};
int main() {
// 尽管say函数未被使用,此处会编译仍会失败,因为std::cout << m.a_操作是非法的
A<M> m;
}
3)成员函数模板
类模板和函数模板结合就是成员函数模板。
#include <iostream>
template<typename T>
class Printer {
T prefix_;
public:
explicit Printer(const T &prefix) :prefix_(prefix) {
}
// 成员函数模板
template<typename U, typename ...Args> void print(const U &u, Args... args);
void print() {
std::cout << std::endl;
}
};
template<typename T> template<typename U, typename ...Args>
void Printer<T>::print(const U &u, Args... args) {
std::cout << this->prefix_ << u << std::endl;
print(args...);
}