网站首页 > 博客文章 正文
之前研究了 ROS2(Jazzy)机器人开发系统,并将官网中比较重要的教程和概念,按照自己的学习顺序翻译成了中文,进行了整理和记录。到目前为止,已经整理了20多篇文章。如果想回顾之前的内容,可以查阅主页中 ROS2(Jazzy)相关文章。
在研究 ROS2 的过程中,发现它使用了不少 C++11 的新特性。因此,开启了现代 C++11+ 新特性的总结系列。目前已经完成了以下几篇:
- C++11移动语义‘偷梁换柱’实战
- C++11 Lambda 表达式 以及 std::function和std::bind
- C++11 智能指针:unique_ptr、shared_ptr 和 weak_ptr
- C++11 的线程管理(std::thread)
- C++11 原子操作 std::atomic
- C++11 同步机制:互斥锁和条件变量
这篇是关于 C++ 泛型编程(模板)的总结。
C++ 泛型编程(Generic Programming)是一种编程范式,它允许你编写独立于具体数据类型的代码,通过参数化类型(Type Parameterization)来实现代码复用。这种范式的核心是模板(Templates),它是 C++ 标准模板库(STL)的基石。在这里的参数化意思是数据类型在编译时不确定,在运行时以参数的形式传给模类或函数。
一、泛型编程的核心思想
“编写与类型无关的通用算法”
过去传统编程时,需要为每个数据类型编写特定的代码和算法,比如,分别为 int、double 编写的排序函数。 而在泛型编程中,你可以编写一次代码,只要这些类型满足特定的接口要求,就能用于多种数据类型。
二、模板(Templates):泛型编程的基石
模板是 C++ 实现泛型编程的核心机制,允许你编写与类型无关的代码,实现“一次编写,多类型复用”。
1. 函数模板(Function Templates)
1)基础语法
template <typename T> // 声明模板参数 T
T max(T a, T b) { // 泛型函数:返回两个值中的较大值
return (a > b) ? a : b;
}
- template <typename T>:声明一个模板参数 T,typename 也可用 class 替代
- 函数体内 T 可作为普通类型使用
2) 调用方式
int x = max(1, 2); // 自动推导 T 为 int
double y = max(3.14, 2.71); // 自动推导 T 为 double
// 显式指定模板参数
int z = max<int>(1, 2.5); // 将 2.5 截断为 int 类型
3)多模板参数
template <typename T, typename U>
auto add(T a, U b) { // C++14 自动推导返回类型
return a + b;
}
// 使用
auto result = add(1, 3.14); // T=int, U=double, 返回 double 类型
2. 类模板(Class Templates)
1) 基础语法
template <typename T>
class Vector {
private:
T* data;
size_t size;
public:
Vector(size_t n) : data(new T[n]), size(n) {}
~Vector() { delete[] data; }
T& operator[](size_t i) { return data[i]; }
const T& operator[](size_t i) const { return data[i]; }
};
2) 实例化与使用
Vector<int> intVec(5); // 存储 int 的向量
Vector<std::string> strVec(3); // 存储 string 的向量
intVec[0] = 100;
strVec[1] = "hello";
3) 类模板的成员函数
成员函数可在类内定义,也可在类外定义(需显式指定模板参数):
template <typename T>
class Vector {
// ... 类定义同上 ...
// 类外定义的成员函数声明
void resize(size_t newSize);
};
// 类外定义成员函数
template <typename T>
void Vector<T>::resize(size_t newSize) {
// 实现略
}
3. 非类型模板参数(Non-Type Template Parameters)
模板参数可以是类型,也可以是常量值,非类型的模板参数支持:整数、枚举、指针、引用等
template <typename T, size_t N>
class Array {
private:
T data[N]; // 使用常量值 N 作为数组大小
public:
size_t size() const { return N; }
};
// 使用
Array<int, 5> arr; // 创建包含 5 个 int 的数组
4. 模板特化(Template Specialization)
为特定类型提供定制的模板代码实现:
// 通用模板
template <typename T>
struct IsPointer {
static constexpr bool value = false;
};
// 特化版本:针对指针类型
template <typename T>
struct IsPointer<T*> {
static constexpr bool value = true;
};
// 使用
IsPointer<int>::value; // false
IsPointer<int*>::value; // true
这里还用到了 constexpr 关键字,它允许在编译期执行计算,将运行时开销转移到编译时,从而提高程序性能并增强类型安全性。 它可以修饰:
- 变量:确保变量的值在编译期确定
- 函数:允许函数在编译期调用(如果参数是编译期常量)
- 构造函数:允许对象在编译期构造
5. 部分特化(Partial Specialization)
对模板参数的部分组合进行特化:
// 原始模板
template <typename T, typename U>
struct Pair {};
// 部分特化:当 U 是指针类型时
template <typename T, typename U>
struct Pair<T, U*> {};
6. 模板默认参数
template <typename T = int, size_t N = 10>
class Stack {
// ... 实现略 ...
};
// 使用
Stack<> s1; // 默认 T=int, N=10
Stack<double, 20> s2; // 指定 T=double, N=20
7. 可变参数模板(Variadic Templates)
处理任意数量和类型的参数:
// 终止函数(递归终点)
void print() {
std::cout << "\n";
}
// 可变参数模板函数
template <typename T, typename... Args>
void print(T first, Args... args) {
std::cout << first;
if constexpr (sizeof...(args) > 0) { // C++17
std::cout << ", ";
}
print(args...); // 递归展开参数包
}
// 使用
print(1, 2.5, "hello"); // 输出: 1, 2.5, hello
8. 模板元编程(Template Metaprogramming)
元编程(Metaprogramming)是一种编程技术,允许程序在编译时进行计算、生成代码或操作类型信息,具体的说就是程序在编译时处理类型和常量表达式,生成优化后的代码,而不是在运行时执行这些操作。C++ 的元编程主要通过模板(Templates)和类型系统实现,是泛型编程的高级应用。
你可以用元编程在编译期执行计算阶乘:
// 编译期计算阶乘
template <int N>
struct Factorial {
static constexpr int value = N * Factorial<N-1>::value;
};
template <>
struct Factorial<0> {
static constexpr int value = 1;
};
// 使用
constexpr int x = Factorial<5>::value; // 编译期计算为 120
或者使用 constexpr 关键字在编译时执行函数。 比如编译时斐波那契数列
运行
constexpr int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n-1) + fibonacci(n-2);
}
// 使用:constexpr int x = fibonacci(10); // 编译时计算为55
元编程是 C++ 中强大但高级的技术,但需要谨慎使用,避免过度复杂。随着 C++ 标准的演进(如 Concepts 和编译时反射),元编程的语法和易用性正在不断改进。
需要注意元编程技术与泛型编程的区别:
- 泛型编程是一种编写独立于具体数据类型的通用代码的编程范式,通过参数化类型(如模板)实现 “一次编写,多种类型复用”,它的目标是代码复用,让同一套逻辑(如容器、算法)能适配任意数据类型(int、string、自定义类等),同时保证类型安全和运行效率。它执行时机是运行时。
- 元编程是是一种以代码作为数据,在编译期执行计算或生成代码的编程范式。简单说,是“用代码生成代码” 或 “在编译时做计算”。它的核心目标:编译期优化(如提前计算常量、消除分支)、类型操纵(如类型检查、自动生成适配特定类型的代码),最终减少运行时开销或实现复杂的类型安全逻辑。它的执行时机是编译时。
元编程主要依赖了以下关键技术:
1) 模板特化(Template Specialization)
针对特定类型提供定制实现。
示例:类型检查
template <typename T>
struct is_integral {
static constexpr bool value = false;
};
template <>
struct is_integral<int> {
static constexpr bool value = true;
};
template <>
struct is_integral<char> {
static constexpr bool value = true;
};
// 使用:is_integral<double>::value // false
2) 递归模板实例化
通过模板递归实现编译时循环。
示例:编译时数组求和
template <int... Values>
struct Sum;
template <>
struct Sum<> {
static constexpr int value = 0;
};
template <int Head, int... Tail>
struct Sum<Head, Tail...> {
static constexpr int value = Head + Sum<Tail...>::value;
};
// 使用:Sum<1, 2, 3>::value // 编译时计算为6
3) 类型萃取(Type Traits)
在编译时查询或转换类型属性。
示例:获取函数返回类型
template <typename Func>
struct function_traits;
template <typename R, typename... Args>
struct function_traits<R(Args...)> {
using return_type = R;
};
// 使用:function_traits<int(double, char)>::return_type // int
4) 编译时条件判断(C++11+)
使用 std::conditional 或 if constexpr(C++17+)。
示例:条件选择类型
template <bool Condition, typename T, typename U>
using conditional_t = typename std::conditional<Condition, T, U>::type;
// 使用:conditional_t<true, int, double> // int
#include <type_traits>
// 元编程:编译期判断类型是否为指针
template <typename T>
void print_type_info() {
if constexpr (std::is_pointer_v<T>) { // constexpr if在编译期分支选择, 通过 “类型 traits”(如std::is_pointer<T>)判断类型属性
std::cout << "Type is a pointer\n";
} else {
std::cout << "Type is not a pointer\n";
}
}
int main() {
print_type_info<int>(); // 编译期确定输出“not a pointer”
print_type_info<int*>(); // 编译期确定输出“is a pointer”
return 0;
}
9. C++20 新特性:概念(Concepts)
概念(Concepts)可以用于约束模板参数的类型要求:
// 定义概念:要求类型支持加法
template <typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>; // 要求 a+b 返回 T 类型
};
// 使用概念约束的函数模板
template <Addable T>
T add(T a, T b) {
return a + b;
}
三、标准模板库(STL):泛型编程的典范
STL 是 C++ 泛型编程的集大成者,建议对 STL 的源码进行剖析,深入理解容器(vector、map、unordered_map)的实现原理,迭代器失效机制及空间配置器设计。然后了解算法(如sort、find)的底层优化策略,并结合C++20 Ranges库实现链式调用。
这里简要但要一下 STL 四大组件:
1. 容器(Containers)
存储数据的泛型类,如:
- 序列容器:vector、list、deque
- 关联容器:set、map、unordered_set、unordered_map
- 适配器:stack、queue、priority_queue
2. 算法(Algorithms)
操作容器的泛型函数,如:
- sort、find、transform、accumulate
- 位于 <algorithm> 和 <numeric> 头文件中
3. 迭代器(Iterators)
容器和算法之间的接口,如:
- begin()、end()、rbegin()、rend()
- 特殊迭代器:back_inserter、istream_iterator
4. 函数对象(Function Objects)
可调用对象,用于自定义算法行为,如:
- std::less、std::greater
- Lambda 表达式(C++11+)
示例:使用 STL 算法和容器
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
std::vector<int> v = {3, 1, 4, 1, 5, 9};
// 排序
std::sort(v.begin(), v.end()); // 使用随机访问迭代器
// 查找
auto it = std::find(v.begin(), v.end(), 5);
// 使用 Lambda 表达式过滤元素
std::for_each(v.begin(), v.end(), [](int x) {
std::cout << x << " ";
});
return 0;
}
四、迭代器(Iterators):泛型算法的接口
这里将 STL 中迭代器单列出来说一下,迭代器是一种抽象的指针,用于遍历容器中的元素,是连接算法和容器的桥梁。
1. 迭代器分类(按功能从弱到强)
- 输入迭代器(Input Iterator):只读,单向移动(如 istream_iterator)
- 输出迭代器(Output Iterator):只写,单向移动(如 ostream_iterator)
- 前向迭代器(Forward Iterator):可读可写,单向多次移动(如 std::forward_list 的迭代器)
- 双向迭代器(Bidirectional Iterator):支持双向移动(如 std::list 的迭代器)
- 随机访问迭代器(Random Access Iterator):支持随机访问(如 std::vector 的迭代器)
2. 泛型算法通过迭代器操作数据
示例:泛型查找算法
template <typename InputIt, typename T>
InputIt find(InputIt first, InputIt last, const T& value) {
for (; first != last; ++first) {
if (*first == value) {
return first;
}
}
return last;
}
// 使用:
std::vector<int> vec = {1, 2, 3, 4};
auto it = find(vec.begin(), vec.end(), 3); // 找到元素3的迭代器
五、泛型编程的应用场景
- 容器和数据结构:如 vector、map、queue
- 算法库:如排序、查找、过滤算法
- 智能指针:如 std::unique_ptr、std::shared_ptr
- 并发编程:如 std::thread、std::future
- 元编程:在编译时执行计算(见前文元编程部分)
六、注意事项
- 编译时错误:模板错误信息可能晦涩难懂
- 代码膨胀:大量模板实例化可能增加可执行文件大小
- 过度泛化:避免为了泛型而牺牲代码可读性
- 性能权衡:某些情况下,特定类型的实现可能比泛型更高效
总结
泛型编程是 C++ 的核心特性之一,通过模板机制实现了类型无关的代码复用。它是 STL 的基础,使 C++ 能够提供强大而灵活的容器和算法库。掌握泛型编程需要理解模板、迭代器和类型系统的深层交互,以及如何平衡代码的通用性和可读性。随着 C++ 标准的演进(如 Concepts 的引入),泛型编程的表达力和易用性正在不断提升。
作者:小芝,干了二十多年的C++开发。开发过桌面软件,干过古早功能手机游戏开发,做过几个IOS/Android客户端APP。现在对AI开发和机器人开发有兴趣,同时也在了解产品相关知识。若喜欢本文,欢迎点赞、在看、留言交流。
欢迎关注 【智践行】 一起学习机器人开发,发送【C++】获得学习资料。
- 上一篇: C++20并发库新成员jthread(续)
- 下一篇: 看完侯捷老师所有C++视频之后的总结
猜你喜欢
- 2025-08-03 C++语法进阶-字符:字符变量(char)
- 2025-08-03 c++26新功能—Read-Copy-Update
- 2025-08-03 为什么Linux之父那么讨厌C++ 他骂的这几点。句句扎心
- 2025-08-03 为什么Linux之父那么讨厌C++ 他骂的这几点!句句扎心
- 2025-08-03 20道qiao牛逼的c++/c面试题
- 2025-08-03 C++学习教程_C++语言随到随学_不耽误上班_0基础
- 2025-08-03 20天轻松入门《C++第四章——函数》——4经坛教育
- 2025-08-03 看完侯捷老师所有C++视频之后的总结
- 2025-08-03 C++20并发库新成员jthread(续)
- 2025-08-03 C++20 四大特性之一:Module 特性详解
你 发表评论:
欢迎- 08-03 Docker 命令入门实战:搞懂这些才算真正入门!
- 08-03Docker 常用命令分类汇总
- 08-03docker常用命令大全,看这一篇就够了
- 08-03Docker命令大全详解(39个常用命令)
- 08-03Docker 常用命令手册
- 08-03Docker命令最全详解(39个最常用命令)
- 08-03Docker命令最全详解(29个最常用命令)
- 08-03C++语法进阶-字符:字符变量(char)
- 最近发表
- 标签列表
-
- ifneq (61)
- 字符串长度在线 (61)
- googlecloud (64)
- flutterrun (59)
- powershellfor (73)
- messagesource (71)
- plsql64位 (73)
- vueproxytable (64)
- npminstallsave (63)
- promise.race (63)
- 2019cad序列号和密钥激活码 (62)
- window.performance (66)
- qt删除文件夹 (72)
- mysqlcaching_sha2_password (64)
- nacos启动失败 (64)
- ssh-add (70)
- yarnnode (62)
- abstractqueuedsynchronizer (64)
- source~/.bashrc没有那个文件或目录 (65)
- springboot整合activiti工作流 (70)
- jmeter插件下载 (61)
- 抓包分析 (60)
- idea创建mavenweb项目 (65)
- qcombobox样式表 (68)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)