什么是泛型编程
泛型编程就是传递变量类型的编程
主要作用1:节省代码
主要作用2:传递参数类型
主要作用3:写底层架构
一 节省代码
// ConsoleApplication9.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
using namespace std;
template<typename T> // 或 template<class T>
void ShowT(T a) {
cout << a << endl;
}
void Show(int a)
{
cout << a << endl;
}
void Show(double a)
{
cout << a << endl;
}
void Show(char a)
{
cout << a << endl;
}
int main()
{
int a = 99;
double b = 9.6666;
char c = 'a';
//Show(a);
//Show(b);
//Show(c);
ShowT(a);
ShowT(b);
ShowT(c);
}
// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单
// 入门使用技巧:
// 1. 使用解决方案资源管理器窗口添加/管理文件
// 2. 使用团队资源管理器窗口连接到源代码管理
// 3. 使用输出窗口查看生成输出和其他消息
// 4. 使用错误列表窗口查看错误
// 5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
// 6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件
传递参数类型(泛型编程又称传递参数编程,可见之重要)
// ConsoleApplication9.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
using namespace std;
template<typename T> // 或 template<class T>
void ShowT(T a) {
cout << a << endl;
}
void Show(int a)
{
cout << a << endl;
}
void Show(double a)
{
cout << a << endl;
}
void Show(char a)
{
cout << a << endl;
}
int main()
{
int a = 99;
double b = 9.6666;
char c = 'a';
//Show(a);
//Show(b);
//Show(c);
ShowT(a);
ShowT(b);
ShowT(c);
ShowT<int>(a);
ShowT<double>(b);
ShowT<char>(c);
}
// ConsoleApplication9.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
using namespace std;
template<typename T> // 或 template<class T>
void ShowT(T a) {
cout << a << endl;
}
void Show(int a)
{
cout << a << endl;
}
void Show(double a)
{
cout << a << endl;
}
void Show(char a)
{
cout << a << endl;
}
template<typename T1, typename T2> // 或 template<class T>
void MyTest(T1 a, T2 b)
{
std::cout << "Type of T1: " << typeid(T1).name() << std::endl;
std::cout << "Type of T2: " << typeid(T2).name() << std::endl;
}
int main()
{
int a = 99;
double b = 9.6666;
char c = 'a';
//Show(a);
//Show(b);
//Show(c);
//ShowT(a);
//ShowT(b);
//ShowT(c);
//ShowT<int>(a);
//ShowT<double>(b);
//ShowT<char>(c);
MyTest<int,double>(1,2.1);
}
在 C++ 中,获取类的类型(即类型信息)可以通过多种方式实现,具体取决于使用场景。以下是常见的方法:
1. 使用 typeid
运算符(RTTI)
typeid
是 C++ 运行时类型识别(RTTI)机制的一部分,返回一个 std::type_info
对象,可用于获取类型名称或比较类型。
#include <iostream>
#include <typeinfo>
class MyClass {};
int main() {
MyClass obj;
const std::type_info& ti = typeid(obj);
std::cout << "Type name: " << ti.name() << std::endl; // 输出类型名称(可能是编译器修饰的)
std::cout << "Is MyClass? " << (ti == typeid(MyClass)) << std::endl; // 比较类型
return 0;
}
注意事项:
ti.name()
返回的名称可能是编译器修饰的(如4MyClass
),不同编译器表现不同。- 需要启用 RTTI(默认开启,可通过编译器选项
-fno-rtti
禁用)。 - 适用于多态类型(有虚函数的类)时能获取动态类型,否则获取静态类型。
2. 使用 decltype
(编译时类型推导)
decltype
用于推导表达式的类型,常用于模板元编程或返回类型推导。
#include <iostream>
class MyClass {};
int main() {
MyClass obj;
decltype(obj) obj2; // obj2 的类型是 MyClass
std::cout << "Type of obj2 is MyClass" << std::endl;
return 0;
}
典型用途:
- 在模板中推导参数类型:
template<typename T> auto get_value(T t) -> decltype(t.get_value()) { return t.get_value(); }
- C++14 起用于泛型 lambda:
auto lambda = [](auto x) { return decltype(x)(0); };
3. 使用 auto
和类型别名
通过 auto
自动推导变量类型,或使用 using
/typedef
定义类型别名。
#include <iostream>
class MyClass {};
int main() {
MyClass obj;
auto obj2 = obj; // obj2 的类型自动推导为 MyClass
using MyType = MyClass; // 类型别名
MyType obj3;
std::cout << "obj2 is of type MyClass" << std::endl;
return 0;
}
4. 模板参数类型提取
在模板中,可以通过 typename
或 class
获取模板参数的类型。
#include <iostream>
template<typename T>
class Wrapper {
public:
using value_type = T; // 定义内部类型别名
};
int main() {
Wrapper<int> w;
Wrapper<int>::value_type x = 42; // x 的类型是 int
std::cout << "x = " << x << std::endl;
return 0;
}
5. C++17 的 std::void_t
和 SFINAE 类型检测
通过模板技巧在编译时检测类的成员类型或属性。
#include <iostream>
#include <type_traits>
class MyClass {
public:
using value_type = int;
};
// 检测类是否有 value_type 成员
template<typename T, typename = void>
struct has_value_type : std::false_type {};
template<typename T>
struct has_value_type<T, std::void_t<typename T::value_type>> : std::true_type {};
int main() {
std::cout << "MyClass has value_type? "
<< has_value_type<MyClass>::value << std::endl; // 输出 1 (true)
return 0;
}
6. 获取类的 this
类型(C++11 起)
使用 decltype
或 std::declval
获取类的 this
类型。
#include <iostream>
#include <utility>
class MyClass {
public:
auto get_this_type() -> decltype(*this) {
return *this;
}
// C++14 起可简化为:
// auto get_this_type() { return *this; }
};
int main() {
MyClass obj;
auto obj2 = obj.get_this_type(); // obj2 的类型是 MyClass
return 0;
}
7. C++20 的 Concepts(类型约束)
通过 Concepts 显式约束模板参数的类型。
#include <iostream>
#include <concepts>
template<typename T>
concept IsMyClass = requires(T t) {
{ t } -> std::convertible_to<T>; // 简单约束
};
class MyClass {};
void print_type(IsMyClass auto obj) {
std::cout << "obj is of type MyClass or compatible" << std::endl;
}
int main() {
MyClass obj;
print_type(obj); // 匹配成功
return 0;
}
总结
方法 | 适用场景 | 特点 |
---|---|---|
typeid | 运行时类型识别 | 需要 RTTI,名称可能被修饰 |
decltype | 编译时类型推导 | 适用于表达式和变量 |
auto | 自动类型推导 | 简化代码,但隐藏具体类型 |
模板参数 | 泛型编程 | 通过 typename 提取类型 |
SFINAE/void_t | 编译时类型检测 | 复杂但强大 |
C++20 Concepts | 类型约束 | 语义清晰,替代部分 SFINAE |
根据需求选择合适的方法:
- 需要运行时信息 →
typeid
。 - 需要编译时类型推导 →
decltype
或模板参数。 - 需要类型约束 → C++20 Concepts。
- 需要检测类型特征 → SFINAE 或
void_t
。
C++泛型编程教程:从模板到STL的深度实践
一、泛型编程的核心思想
泛型编程(Generic Programming)通过参数化类型实现代码复用,其核心在于编写与具体数据类型无关的通用算法。例如,一个排序算法无需为int
、double
或自定义类型重复实现,只需通过模板机制生成不同版本的代码。这种范式在C++中通过**模板(Templates)**实现,其优势体现在:
- 代码复用性:STL中的
vector<T>
可存储任意类型,避免为每种类型重复实现动态数组。 - 类型安全:编译期类型检查消除运行时类型错误(如Java泛型的类型擦除问题)。
- 性能优化:生成的代码与原生类型操作效率相当,如
vector<int>
的访问速度接近数组。
二、函数模板:通用算法的基石
1. 基本语法与实例化
template<typename T> // 或 template<class T>
T max(T a, T b) {
return a > b ? a : b;
}
int main() {
cout << max(3, 5); // 隐式实例化:T=int
cout << max<double>(3.14, 2.71); // 显式实例化:T=double
}
- 隐式推导:编译器根据参数类型自动确定
T
。 - 显式指定:通过
<类型>
明确指定模板参数,解决多参数类型不一致问题。
2. 模板特化:定制化实现
为特定类型提供优化逻辑:
// 全特化:针对char*类型
template<>
const char* max<const char*>(const char* a, const char* b) {
return strcmp(a, b) > 0 ? a : b;
}
// 偏特化(C++不支持直接偏特化函数模板,需通过类模板间接实现)
3. 非类型模板参数
传递编译期常量:
template<typename T, int N>
class FixedArray {
T data[N];
public:
T& operator[](int i) { return data[i]; }
};
FixedArray<int, 10> arr; // 创建固定大小为10的整型数组
三、类模板:通用数据结构的实现
1. 基本类模板
template<typename T>
class Stack {
T* data;
int top;
public:
Stack(int size) : data(new T[size]), top(0) {}
void push(T val) { data[top++] = val; }
T pop() { return data[--top]; }
};
Stack<string> strStack; // 存储字符串的栈
2. 模板参数默认值
template<typename T = int, int N = 100>
class Buffer {
T data[N];
};
Buffer<> buf1; // T=int, N=100
Buffer<double, 200> buf2; // T=double, N=200
3. 类模板的特化
为指针类型优化内存管理:
template<typename T>
class SmartPtr {
T* ptr;
public:
SmartPtr(T* p) : ptr(p) {}
~SmartPtr() { delete ptr; }
};
// 特化版本:处理字符指针(不调用delete)
template<>
class SmartPtr<char*> {
char* ptr;
public:
SmartPtr(char* p) : ptr(p) {}
~SmartPtr() { /* 不释放内存 */ }
};
四、可变参数模板:C++11的扩展
1. 参数包展开
// 递归展开
void print() {} // 终止函数
template<typename T, typename... Args>
void print(T first, Args... args) {
cout << first << " ";
print(args...); // 递归调用
}
print(1, 3.14, "hello"); // 输出:1 3.14 hello
2. 折叠表达式(C++17)
简化参数包运算:
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // 展开为 (a1 + a2 + ... + an)
}
cout << sum(1, 2, 3); // 输出6
五、STL:泛型编程的典范
1. 容器(Containers)
- 顺序容器:
vector<int> vec = {1, 2, 3}; // 动态数组 deque<double> dq; // 双端队列 list<string> lst; // 双向链表
- 关联容器:
set<int> s; // 红黑树实现的有序集合 map<string, int> m; // 键值对映射 unordered_map<int, string> um; // 哈希表实现的无序映射
2. 算法(Algorithms)
vector<int> v = {3, 1, 4, 1, 5};
sort(v.begin(), v.end()); // 排序
find(v.begin(), v.end(), 4); // 查找
for_each(v.begin(), v.end(), [](int x) { cout << x << " "; });
3. 迭代器(Iterators)
vector<int>::iterator it = v.begin(); // 随机访问迭代器
list<string>::iterator lit = lst.begin(); // 双向迭代器
六、高级技巧与注意事项
1. 显式实例化与分离编译
- 显式实例化:
template class Stack<float>; // 强制生成Stack<float>的代码
- 分离编译:将模板声明放在
.h
文件,定义放在.tpp
或.h
中(需包含实现)。
2. 模板元编程(TMP)
编译期计算示例:
template<int N>
struct Factorial {
static const int value = N * Factorial<N-1>::value;
};
template<>
struct Factorial<0> {
static const int value = 1;
};
cout << Factorial<5>::value; // 输出120
3. 性能权衡
- 代码膨胀:每个模板实例化生成独立代码,需权衡复用性与二进制体积。
- 编译时间:复杂模板(如元编程)显著增加编译时间,可通过
extern template
减少重复实例化。
七、实战案例:通用矩阵运算库
template<typename T>
class Matrix {
vector<vector<T>> data;
public:
Matrix(int rows, int cols) : data(rows, vector<T>(cols)) {}
Matrix operator+(const Matrix& other) {
Matrix result(data.size(), data[0].size());
for (int i = 0; i < data.size(); ++i) {
for (int j = 0; j < data[0].size(); ++j) {
result.data[i][j] = data[i][j] + other.data[i][j];
}
}
return result;
}
// 可添加运算符*、转置、行列式计算等通用方法
};
Matrix<double> m1(3, 3), m2(3, 3);
auto m3 = m1 + m2; // 支持double类型矩阵运算
八、总结
C++泛型编程通过模板机制实现了算法与数据类型的解耦,其核心在于:
- 模板语法:函数模板、类模板、可变参数模板。
- 特化与偏特化:为特定类型提供优化实现。
- STL应用:容器、算法、迭代器的协同工作。
- 高级特性:模板元编程、编译期计算、性能优化技巧。
掌握泛型编程需结合实践,建议从STL源码分析入手,逐步深入模板元编程等高级主题。