
上篇聊了友元和运算符重载今天进入模板的世界。模板是C里最强大的特性之一也是面试里区分候选人水平的分水岭。能把模板讲明白的人C基本不会差。讲个面试场景。面试官说写一个swap函数交换两个变量的值。大部分人写void swap(int a, int b) { int temp a; a b; b temp; }面试官追问如果我要交换double呢交换两个string呢你总不能为每种类型都写一遍吧这时候就需要模板函数。模板函数的基本语法把刚才的swap改成模板版本templatetypename T void swap(T a, T b) { T temp a; a b; b temp; }templatetypename T告诉编译器这是一个模板T是一个类型参数。编译器会根据你传入的实参类型自动生成对应的函数。int x 1, y 2; swap(x, y); // 编译器生成 swapint double a 1.5, b 2.5; swap(a, b); // 编译器生成 swapdouble string s1 hello, s2 world; swap(s1, s2); // 编译器生成 swapstring这个过程叫模板实例化——编译器根据实际类型生成具体函数的过程。模板在机器人开发里的实际应用你可能会想swap这种东西有什么实际意义STL里已经有了。对STL的std::swap就是模板函数。但模板的应用远不止swap。在机器人开发里我们经常需要写通用的数学工具函数。比如求两个值中的较小值templatetypename 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);再比如一个通用的数组求和函数templatetypename 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是什么类型大多数情况下编译器会自动推导。但有些时候推导会失败templatetypename T void print(T value) {} print(10); // T推导为int print(3.14); // T推导为double print(hello); // T推导为const char*但如果你这么写templatetypename T void process(T a, T b) {} process(10, 3.14); // 编译错误T不能同时是int和double两个参数的类型不一致编译器不知道该推导成什么。解决办法是显式指定类型processdouble(10, 3.14); // 明确告诉编译器T是double10会被提升为10.0这个知识点面试偶尔会考。实际开发中也很常见——你以为编译器能推导结果它报了一堆看不懂的错误信息。模板特化给特定类型开小灶有时候通用版本的模板对某些类型不适用需要特殊处理。这就是模板特化。// 通用版本 templatetypename T void process(T value) { cout Generic: value endl; } // int的特化版本 template void processint(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这就是非类型模板参数。模板参数不一定是类型也可以是整数、指针、枚举等编译期常量。templatetypename 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_; } }; FixedBufferdouble, 100 lidar_buf; // 固定100个double的缓冲区 FixedBufferint, 50 status_buf; // 固定50个int的缓冲区在机器人开发里固定大小的缓冲区很常见——比如传感器数据环形缓冲、IMU采样队列。用非类型模板参数大小在编译期就确定了不用动态分配内存性能比vector好。这也是为什么STL的std::array是模板类——std::arrayint, 5里的5就是非类型模板参数。面试中的模板陷阱面试官考模板有几个经典的坑。第一个模板函数不支持隐式类型转换。templatetypename T void print(T value) { cout value; } void func(int x) { cout x; } func(3.14); // 合法double隐式转为int print(3.14); // 合法T推导为double // 但如果 templatetypename T void add(T a, T b) { cout a b; } add(1, 2.5); // 错误T推导冲突第二个模板函数的定义必须放在头文件里。因为编译器在实例化的时候需要看到完整的定义不是声明。如果你把模板函数放在.cpp里链接的时候会报未定义的引用。这个坑我当年踩了好几次。新手写模板经常习惯性地把声明放.h、实现放.cpp结果编译通过但链接失败。第三个模板不是虚函数。模板在编译期实例化虚函数在运行期绑定两者是不同的机制。不能把模板和虚函数混在一起用。再补充一个面试加分话题SFINAE和if constexpr。SFINAESubstitution Failure Is Not An Error是模板编程里的核心机制意思是模板参数替换失败不是错误编译器会尝试其他重载。你可以用std::enable_if来限制模板函数只对特定类型生效比如写一个只对数值类型有效的average()函数。C17之后更推荐用if constexpr来做编译期分支判断代码写起来更直观。比如你可以在一个模板函数里用if constexpr (std::is_arithmetic_vT)来判断T是不是数值类型不是的话编译期直接跳过那段代码。面试时能聊到if constexpr说明你了解现代C特性比只谈模板特化更有说服力。给正在准备面试的你一点建议模板函数在面试里考的不深但一定要会手写。最基础的能写一个模板函数理解类型推导知道模板定义要放头文件。这三点做到了面试就不会丢分。进阶一点知道模板特化是什么能举出一个实际应用的例子。如果能讲到STL的sort就是模板函数面试官会觉得你对STL也有了解。模板函数是模板类的基础。下篇会讲模板类——STL容器的底层设计思路。两者结合起来你对模板的理解就完整了。如果这篇文章对你有帮助欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。「机器人软件开发面试·从入门到精通」连载系列上一篇第25篇 友元与运算符重载——让自定义类型好用的秘密下一篇预告第27篇 模板类——STL容器的底层设计思路有任何问题欢迎评论区留言我会尽量回复。