上篇聊了友元和运算符重载,今天进入模板的世界。

模板是C++里最强大的特性之一,也是面试里区分候选人水平的分水岭。能把模板讲明白的人,C++基本不会差。

讲个面试场景。

面试官说:"写一个swap函数,交换两个变量的值。"

大部分人写:

void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

面试官追问:"如果我要交换double呢?交换两个string呢?"

你总不能为每种类型都写一遍吧?

这时候就需要模板函数。

模板函数的基本语法

把刚才的swap改成模板版本:

template<typename T>
void swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

template<typename T>告诉编译器:这是一个模板,T是一个类型参数。编译器会根据你传入的实参类型,自动生成对应的函数。

int x = 1, y = 2;
swap(x, y);           // 编译器生成 swap<int>

double a = 1.5, b = 2.5;
swap(a, b);           // 编译器生成 swap<double>

string s1 = "hello", s2 = "world";
swap(s1, s2);         // 编译器生成 swap<string>

这个过程叫模板实例化——编译器根据实际类型生成具体函数的过程。

模板在机器人开发里的实际应用

你可能会想:swap这种东西有什么实际意义?STL里已经有了。

对,STL的std::swap就是模板函数。但模板的应用远不止swap。

在机器人开发里,我们经常需要写通用的数学工具函数。比如求两个值中的较小值:

template<typename T>
T min_value(T a, T b) {
    return (a < b) ? a : b;
}

// 用在不同的数据类型上
double min_speed = min_value(3.14, 2.71);
int min_iter = min_value(100, 200);

再比如一个通用的数组求和函数:

template<typename T, int N>
T array_sum(const T (&arr)[N]) {
    T sum = 0;
    for (int i = 0; i < N; i++) {
        sum += arr[i];
    }
    return sum;
}

double lidar_data[] = {1.2, 3.4, 5.6, 7.8};
double total = array_sum(lidar_data);  // N自动推导为4

模板让一套代码适配所有类型,这在写算法库的时候特别有用。

模板参数的推导

面试经常考的一个点:编译器怎么知道T是什么类型?

大多数情况下,编译器会自动推导。但有些时候推导会失败:

template<typename T>
void print(T value) {}

print(10);       // T推导为int
print(3.14);     // T推导为double
print("hello");  // T推导为const char*

但如果你这么写:

template<typename T>
void process(T a, T b) {}

process(10, 3.14);   // 编译错误!T不能同时是int和double

两个参数的类型不一致,编译器不知道该推导成什么。解决办法是显式指定类型:

process<double>(10, 3.14);   // 明确告诉编译器T是double,10会被提升为10.0

这个知识点面试偶尔会考。实际开发中也很常见——你以为编译器能推导,结果它报了一堆看不懂的错误信息。

模板特化:给特定类型"开小灶"

有时候,通用版本的模板对某些类型不适用,需要特殊处理。这就是模板特化。

// 通用版本
template<typename T>
void process(T value) {
    cout << "Generic: " << value << endl;
}

// int的特化版本
template<>
void process<int>(int value) {
    cout << "Int special: " << value * 2 << endl;
}

process(3.14);    // 调用通用版本,输出: Generic: 3.14
process(42);      // 调用特化版本,输出: Int special: 84

在机器人开发里,模板特化常用于优化。比如通用的矩阵乘法用三层循环,但对特定大小的矩阵(比如3x3、4x4),可以手写展开版本来提升性能。

非类型模板参数:模板不只是类型

刚才的array_sum里用到了int N,这就是非类型模板参数。模板参数不一定是类型,也可以是整数、指针、枚举等编译期常量。

template<typename T, int SIZE>
class FixedBuffer {
    T data_[SIZE];
    int count_ = 0;
public:
    void push(const T& val) {
        if (count_ < SIZE) data_[count_++] = val;
    }
    T get(int index) const { return data_[index]; }
    int size() const { return count_; }
};

FixedBuffer<double, 100> lidar_buf;   // 固定100个double的缓冲区
FixedBuffer<int, 50> status_buf;      // 固定50个int的缓冲区

在机器人开发里,固定大小的缓冲区很常见——比如传感器数据环形缓冲、IMU采样队列。用非类型模板参数,大小在编译期就确定了,不用动态分配内存,性能比vector好。

这也是为什么STL的std::array是模板类——std::array<int, 5>里的5就是非类型模板参数。

面试中的模板陷阱

面试官考模板,有几个经典的坑。

第一个:模板函数不支持隐式类型转换。

template<typename T>
void print(T value) { cout << value; }

void func(int x) { cout << x; }

func(3.14);       // 合法,double隐式转为int
print(3.14);      // 合法,T推导为double
// 但如果:
template<typename T>
void add(T a, T b) { cout << a + b; }
add(1, 2.5);      // 错误!T推导冲突

第二个:模板函数的定义必须放在头文件里。因为编译器在实例化的时候需要看到完整的定义,不是声明。如果你把模板函数放在.cpp里,链接的时候会报"未定义的引用"。

这个坑我当年踩了好几次。新手写模板,经常习惯性地把声明放.h、实现放.cpp,结果编译通过但链接失败。

第三个:模板不是虚函数。模板在编译期实例化,虚函数在运行期绑定,两者是不同的机制。不能把模板和虚函数混在一起用。

再补充一个面试加分话题:SFINAE和if constexpr。SFINAE(Substitution Failure Is Not An Error)是模板编程里的核心机制,意思是模板参数替换失败不是错误,编译器会尝试其他重载。你可以用std::enable_if来限制模板函数只对特定类型生效,比如写一个只对数值类型有效的average()函数。C++17之后更推荐用if constexpr来做编译期分支判断,代码写起来更直观。比如你可以在一个模板函数里用if constexpr (std::is_arithmetic_v<T>)来判断T是不是数值类型,不是的话编译期直接跳过那段代码。面试时能聊到if constexpr说明你了解现代C++特性,比只谈模板特化更有说服力。

给正在准备面试的你一点建议

模板函数在面试里考的不深,但一定要会手写。

最基础的:能写一个模板函数,理解类型推导,知道模板定义要放头文件。这三点做到了,面试就不会丢分。

进阶一点:知道模板特化是什么,能举出一个实际应用的例子。如果能讲到"STL的sort就是模板函数",面试官会觉得你对STL也有了解。

模板函数是模板类的基础。下篇会讲模板类——STL容器的底层设计思路。两者结合起来,你对模板的理解就完整了。


如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。

「机器人软件开发面试·从入门到精通」连载系列 

上一篇:第25篇 友元与运算符重载——让自定义类型"好用"的秘密

下一篇预告:第27篇 模板类——STL容器的底层设计思路

有任何问题欢迎评论区留言,我会尽量回复。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐