当前位置: 首页 > article >正文

【C++】模板与特化技术全面教程(claude sonnet 4)



第一章:模板的基础概念 (Template Fundamentals)

1.1 什么是模板?

模板 (Template) 是C++中的一种泛型编程 (Generic Programming) 机制,它允许我们编写与类型无关的代码。想象一下,如果我们要为不同的数据类型编写相同逻辑的函数,没有模板的话,我们需要为每种类型都写一遍代码。

模板的核心思想是代码复用 (Code Reuse) 和类型安全 (Type Safety)。编译器会根据实际使用的类型自动生成相应的代码,这个过程称为模板实例化 (Template Instantiation)。

1.2 模板的基本语法

// 基本模板语法结构template<typename T>  // 或者使用 template<class T>返回类型 函数名(参数列表) {    // 函数体}

注意typename 和 class 在这里是等价的,但现代C++推荐使用 typename,因为它更清晰地表达了T是一个类型参数。

第二章:函数模板 (Function Templates)

2.1 简单函数模板

让我们从一个简单的例子开始理解函数模板:

#include <iostream>
// 找出两个值中的最大值的模板函数template<typename T>T max_value(T a, T b) {    return (a > b) ? a : b;}
int main() {    // 编译器会自动推导类型 (Type Deduction)    std::cout << max_value(10, 20) << std::endl;        // T = int    std::cout << max_value(3.14, 2.71) << std::endl;    // T = double    std::cout << max_value('a', 'z') << std::endl;      // T = char// 也可以显式指定类型 (Explicit Type Specification)    std::cout << max_value<int>(10.5, 20.7) << std::endl;  // 强制转换为intreturn 0;}

2.2 多参数模板

模板可以有多个类型参数:

// 多类型参数的模板函数template<typename T1, typename T2>auto add_different_types(T1 a, T2 b) -> decltype(a + b) {    return a + b;}
// C++14及以后可以简化为:template<typename T1, typename T2>auto add_different_types_v2(T1 a, T2 b) {    return a + b;  // 编译器自动推导返回类型}


2.3 非类型模板参数 (Non-type Template Parameters)

除了类型参数,模板还可以接受非类型参数:

#include <array>// 创建固定大小数组并初始化的模板函数template<typename T, std::size_t N>std::array<T, N> create_array(const T& initial_value) {    std::array<T, N> arr;    arr.fill(initial_value);    return arr;}int main() {    auto int_array = create_array<int, 5>(42);    // 创建5个元素的int数组,都初始化为42    auto double_array = create_array<double, 3>(3.14);  // 创建3个元素的double数组    return 0;}


第三章:类模板 (Class Templates)

3.1 基本类模板

类模板允许我们创建泛型类。让我们创建一个简单的栈 (Stack) 类:

#include <vector>#include <stdexcept>
template<typename T>class Stack {private:    std::vector<T> elements;  // 使用vector存储元素
public:    // 入栈操作    void push(const T& element) {        elements.push_back(element);    }// 出栈操作    T pop() {        if (elements.empty()) {            throw std::runtime_error("Stack is empty");        }        T result = elements.back();        elements.pop_back();        return result;    }// 查看栈顶元素    const T& top() const {        if (elements.empty()) {            throw std::runtime_error("Stack is empty");        }        return elements.back();    }// 检查栈是否为空    bool empty() const {        return elements.empty();    }// 获取栈的大小    std::size_t size() const {        return elements.size();    }};
// 使用示例int main() {    Stack<int> int_stack;        // 整数栈    Stack<std::string> str_stack; // 字符串栈int_stack.push(10);    int_stack.push(20);str_stack.push("Hello");    str_stack.push("World");std::cout << int_stack.pop() << std::endl;  // 输出: 20    std::cout << str_stack.pop() << std::endl;  // 输出: Worldreturn 0;}


3.2 模板类的成员函数定义

有时我们需要将类模板的成员函数定义在类外部:

template<typename T>class Matrix {private:    std::vector<std::vector<T>> data;    std::size_t rows, cols;
public:    Matrix(std::size_t r, std::size_t c);  // 构造函数声明    T& at(std::size_t row, std::size_t col);  // 元素访问函数声明    void print() const;  // 打印函数声明};
// 构造函数定义(注意模板语法)template<typename T>Matrix<T>::Matrix(std::size_t r, std::size_t c)     : rows(r), cols(c), data(r, std::vector<T>(c)) {    // 初始化矩阵}
// 成员函数定义template<typename T>T& Matrix<T>::at(std::size_t row, std::size_t col) {    if (row >= rows || col >= cols) {        throw std::out_of_range("Matrix index out of range");    }    return data[row][col];}
template<typename T>void Matrix<T>::print() const {    for (const auto& row : data) {        for (const auto& element : row) {            std::cout << element << " ";        }        std::cout << std::endl;    }}


第四章:模板特化 (Template Specialization)

4.1 全特化 (Full Specialization)

有时候,我们需要为特定的类型提供特殊的实现。这就是模板特化的用处:

#include <iostream>#include <string>
// 通用模板函数template<typename T>void process_data(const T& data) {    std::cout << "处理通用数据: " << data << std::endl;}
// 为字符串类型提供特化版本template<>void process_data<std::string>(const std::string& data) {    std::cout << "处理字符串数据 (特化版本): \"" << data << "\" (长度: " << data.length() << ")" << std::endl;}
// 为指针类型提供特化版本template<>void process_data<int*>(int* const& data) {    if (data) {        std::cout << "处理指针数据 (特化版本): 地址=" << data << ", 值=" << *data << std::endl;    } else {        std::cout << "处理空指针" << std::endl;    }}
int main() {    process_data(42);           // 使用通用版本    process_data(3.14);         // 使用通用版本    process_data(std::string("Hello World"));  // 使用字符串特化版本int value = 100;    process_data(&value);       // 使用指针特化版本return 0;}


4.2 类模板的全特化

类模板也可以进行全特化:

#include <iostream>#include <cstring>// 通用的存储类模板template<typename T>class Storage {private:    T data;public:    Storage(const T& value) : data(value) {}    void print() const {        std::cout << "存储的数据: " << data << std::endl;    }    T get() const { return data; }};// 为const char*提供特化版本(处理C风格字符串)template<>class Storage<const char*> {private:    char* data;public:    Storage(const char* str) {        data = new char[std::strlen(str) + 1];        std::strcpy(data, str);    }    ~Storage() {        delete[] data;    }    // 复制构造函数    Storage(const Storage& other) {        data = new char[std::strlen(other.data) + 1];        std::strcpy(data, other.data);    }    // 赋值操作符    Storage& operator=(const Storage& other) {        if (this != &other) {            delete[] data;            data = new char[std::strlen(other.data) + 1];            std::strcpy(data, other.data);        }        return *this;    }    void print() const {        std::cout << "存储的C字符串 (特化版本): \"" << data << "\"" << std::endl;    }    const char* get() const { return data; }};int main() {    Storage<int> int_storage(42);    Storage<const char*> str_storage("Hello World");    int_storage.print();  // 使用通用版本    str_storage.print();  // 使用特化版本    return 0;}

4.3 偏特化 (Partial Specialization)

偏特化只能用于类模板,不能用于函数模板。它允许我们为模板参数的某些组合提供特殊实现:

#include <iostream>  // 包含标准输入输出库,用于后续的输入和输出操作
// 通用的配对类模板// 定义一个模板类 Pair,它能够存储一个配对数据,支持两种不同类型 T1 和 T2template<typename T1, typename T2>class Pair {private:    T1 first;   // 定义一个变量 first,类型为 T1,用来存储配对中的第一个元素    T2 second;  // 定义一个变量 second,类型为 T2,用来存储配对中的第二个元素public:    // 构造函数,使用常量引用传参,并利用初始化列表初始化成员变量    Pair(const T1& f, const T2& s) : first(f), second(s) {}// 成员函数 print,用于打印配对的内容(不修改成员变量,故为 const 成员函数)    void print() const {        // 输出格式为:通用配对: (first, second)        std::cout << "通用配对: (" << first << ", " << second << ")" << std::endl;    }};
// 偏特化:当两个类型相同时// 下面定义 Pair 的偏特化版本,当两个模板参数类型相同时(即 T1 和 T2 都是 T)使用此版本template<typename T>class Pair<T, T> {private:    T first;   // 定义变量 first,类型为 T    T second;  // 定义变量 second,类型为 Tpublic:    // 构造函数,同样使用初始化列表初始化两个成员变量    Pair(const T& f, const T& s) : first(f), second(s) {}// 重写 print 函数,以显示偏特化的信息    void print() const {        // 输出格式为:相同类型配对 (偏特化): (first, second)        std::cout << "相同类型配对 (偏特化): (" << first << ", " << second << ")" << std::endl;    }// 额外功能:检查两个值是否相等    // 定义一个成员函数 are_equal(),用于比较 first 和 second 是否相等,并返回布尔值    bool are_equal() const {         return first == second;    }};
// 偏特化:当第二个类型是指针时// 定义 Pair 的另一个偏特化版本,当第二个模板参数为指针类型 T2* 时使用此版本template<typename T1, typename T2>class Pair<T1, T2*> {private:    T1 first;      // 定义变量 first,类型为 T1    T2* second;    // 定义变量 second,类型为 T2*,即指针类型public:    // 构造函数,接受 T1 类型的值和 T2 指针,通过初始化列表初始化成员变量    Pair(const T1& f, T2* s) : first(f), second(s) {}// 重写 print 函数,专门处理指针类型的输出    void print() const {        // 输出起始部分:显示 "指针配对 (偏特化): (first, "        std::cout << "指针配对 (偏特化): (" << first << ", ";        // 判断指针 second 是否为空        if (second) {            // 如果 second 非空,输出指针所指向的数据(解引用)            std::cout << *second;        } else {            // 如果 second 为空,输出 "nullptr"            std::cout << "nullptr";        }        // 输出闭合括号并换行        std::cout << ")" << std::endl;    }};
int main() {    // 创建一个 Pair 对象 p1,模板参数为 <int, double>,调用通用版本构造函数    Pair<int, double> p1(1, 2.5);      // 使用通用版本:当类型不同    // 创建一个 Pair 对象 p2,模板参数为 <int, int>,因此调用相同类型的偏特化版本    Pair<int, int> p2(3, 4);           // 使用偏特化版本(相同类型)int value = 100;  // 定义一个 int 类型的变量 value,用于测试指针配对// 创建一个 Pair 对象 p3,模板参数为 <std::string, int*>,调用第二个偏特化版(指针类型)    Pair<std::string, int*> p3("数值", &value);  // 使用偏特化版本(指针类型)// 调用 p1 的 print 函数打印通用配对数据    p1.print();// 调用 p2 的 print 函数打印相同类型偏特化配对数据    p2.print();// 调用 p2 的 are_equal 函数检查 p2 中两个值是否相等,并将结果打印出来    std::cout << "p2中的值相等吗? " << (p2.are_equal() ? "是" : "否") << std::endl;// 调用 p3 的 print 函数打印指针配对数据,并正确显示指针指向的值    p3.print();return 0;  // 返回 0 表示程序正常结束}

第五章:高级模板技术

5.1 SFINAE (Substitution Failure Is Not An Error)

SFINAE是C++模板元编程中的一个重要概念。当模板参数替换失败时,编译器不会报错,而是简单地从重载候选中移除该模板:

#include <iostream>#include <type_traits>// 使用SFINAE检测类型是否有特定成员函数template<typename T>class has_size_method {private:    // 这个技巧用于检测T是否有size()方法    template<typename U>    static auto test(int) -> decltype(std::declval<U>().size(), std::true_type{});    template<typename>    static std::false_type test(...);public:    static constexpr bool value = decltype(test<T>(0))::value;};// 根据是否有size方法选择不同的实现template<typename T>typename std::enable_if<has_size_method<T>::value, std::size_t>::typeget_container_info(const T& container) {    std::cout << "容器有size方法,大小为: ";    return container.size();}template<typename T>typename std::enable_if<!has_size_method<T>::value, std::size_t>::typeget_container_info(const T& container) {    std::cout << "容器没有size方法,无法获取大小。返回: ";    return 0;}int main() {    std::vector<int> vec{1, 2, 3, 4, 5};    int arr[] = {1, 2, 3};    std::cout << get_container_info(vec) << std::endl;  // vector有size方法    std::cout << get_container_info(arr) << std::endl;  // 数组没有size方法    return 0;}

5.2 变参模板 (Variadic Templates)

C++11引入了变参模板,允许模板接受可变数量的参数:

#include <iostream>  // 包含标准输入输出流库,用于后续的输入输出操作
// 递归版本的变参模板函数// 定义处理只有一个参数的模板函数,当只剩下最后一个参数时调用该函数template<typename T>void print_values(const T& value) {    // 递归的基础情况:输出传入的单个参数并换行    std::cout << value << std::endl;}
// 定义处理多个参数的模板函数,利用变参模板实现递归调用template<typename T, typename... Args>void print_values(const T& first, const Args&... rest) {    // 输出第一个参数并在后面接一个空格    std::cout << first << " ";    // 递归调用print_values,将剩余参数传入    print_values(rest...);  // 递归调用,逐个处理剩余参数}
// C++17折叠表达式版本 (Fold Expression)// 定义使用折叠表达式实现的变参函数,一次性展开所有参数进行打印template<typename... Args>void print_values_cpp17(const Args&... args) {    // 利用折叠表达式对每个参数执行 std::cout << 参数 << " " 的操作    ((std::cout << args << " "), ...);    // 所有参数打印完成后输出换行符    std::cout << std::endl;}
// 计算多个值的和// 定义一个使用折叠表达式计算传入所有参数之和的模板函数template<typename... Args>auto sum(Args... args) {    // 利用折叠表达式 (args + ...) 计算所有参数累加的结果    return (args + ...);  // C++17折叠表达式,依次累加所有参数}
// 变参模板类:元组的简单实现// 声明模板类SimpleTuple,可以接收任意数量和类型的参数,用于保存一组数据template<typename... Types>class SimpleTuple;
// 特化:空元组// 当没有传入任何类型参数时,提供一个空特化版本,作为递归终止条件template<>class SimpleTuple<> {};
// 递归定义:头部+尾部// 当有至少一个参数时,将第一个元素作为头部,其余元素构成尾部元组template<typename Head, typename... Tail>class SimpleTuple<Head, Tail...> {private:    Head head;                  // 保存元组的第一个元素,即头部    SimpleTuple<Tail...> tail;    // 保存剩余的元素(尾部),以一个SimpleTuple表示public:    // 构造函数,使用初始化列表依次初始化头部元素和尾部元组    SimpleTuple(Head h, Tail... t) : head(h), tail(t...) {}// 返回头部元素的引用,可供外部访问和修改    Head& get_head() { return head; }    // 常量版本,返回头部元素的常量引用,保护数据不被修改    const Head& get_head() const { return head; }// 返回尾部(子元组)的引用,用于递归访问余下元素    SimpleTuple<Tail...>& get_tail() { return tail; }    // 常量版本,返回尾部(子元组)的常量引用    const SimpleTuple<Tail...>& get_tail() const { return tail; }};
int main() {    // 测试变参函数:使用递归版本打印多个参数    print_values(1, 2.5, "Hello", 'A');// 测试变参函数:使用 C++17 折叠表达式版本打印多个参数    print_values_cpp17(1, 2.5, "World", 'B');// 测试求和函数:依次累加整数和浮点数    std::cout << "1+2+3+4+5 = " << sum(1, 2, 3, 4, 5) << std::endl;    std::cout << "1.5+2.7+3.2 = " << sum(1.5, 2.7, 3.2) << std::endl;// 测试简单元组的实现    // 创建一个SimpleTuple对象,包含 int、double 和 std::string 三种类型的元素    SimpleTuple<int, double, std::string> tuple(42, 3.14, "Hello");    // 访问并打印元组中的头部元素    std::cout << "元组第一个元素: " << tuple.get_head() << std::endl;    return 0;  // 返回0表示程序正常结束}

第六章:模板元编程 (Template Metaprogramming)

6.1 编译期计算

模板可以在编译期进行计算,这是模板元编程的基础:

#include <iostream>  // 包含标准输入输出流库,用于后续在终端输出结果
// 编译期计算阶乘// 定义模板结构体 Factorial,通过模板递归在编译期计算阶乘的值template<int N>struct Factorial {    // 定义静态 constexpr 成员 value,其值为 N 乘以 Factorial<N - 1> 的值    static constexpr int value = N * Factorial<N - 1>::value;};
// 特化:递归终止条件// 当 N == 0 时,阶乘定义为1,此特化用于递归终止,防止无限递归template<>struct Factorial<0> {    // 定义静态 constexpr 成员 value,其值为 1    static constexpr int value = 1;};
// 编译期计算斐波那契数列// 定义模板结构体 Fibonacci,通过模板递归在编译期计算斐波那契数template<int N>struct Fibonacci {    // 定义静态 constexpr 成员 value,其值为 Fibonacci<N - 1> 与 Fibonacci<N - 2> 的和    static constexpr int value = Fibonacci<N - 1>::value + Fibonacci<N - 2>::value;};
// 特化:递归终止条件(第一种情况)// 当 N == 0 时,斐波那契数值定义为 0,此特化用于递归终止template<>struct Fibonacci<0> {    // 定义静态 constexpr 成员 value,其值为 0    static constexpr int value = 0;};
// 特化:递归终止条件(第二种情况)// 当 N == 1 时,斐波那契数值定义为 1,此特化用于递归终止template<>struct Fibonacci<1> {    // 定义静态 constexpr 成员 value,其值为 1    static constexpr int value = 1;};
// 使用constexpr函数 (C++11及以后的现代方法)// 定义 constexpr 函数 factorial_func,通过递归在编译期或运行期计算阶乘constexpr int factorial_func(int n) {    // 如果 n 小于等于1,则返回1;否则返回 n 乘以 factorial_func(n - 1)    return (n <= 1) ? 1 : n * factorial_func(n - 1);}
// 定义 constexpr 函数 fibonacci_func,通过递归在编译期或运行期计算斐波那契数列的值constexpr int fibonacci_func(int n) {    // 如果 n 小于等于1,则直接返回 n;否则返回 fibonacci_func(n - 1) 与 fibonacci_func(n - 2) 的和    return (n <= 1) ? n : fibonacci_func(n - 1) + fibonacci_func(n - 2);}
int main() {    // 这些值在编译期就计算好了    // 通过模板方式计算5的阶乘,并将结果赋予 fact5,其值应为120    constexpr int fact5 = Factorial<5>::value;      // 120    // 通过模板方式计算第10个斐波那契数,并将结果赋予 fib10,其值应为55    constexpr int fib10 = Fibonacci<10>::value;       // 55    // 使用 constexpr 函数计算5的阶乘,赋值给 fact5_func,结果同样为120    constexpr int fact5_func = factorial_func(5);     // 120    // 使用 constexpr 函数计算第10个斐波那契数,赋值给 fib10_func,结果同样为55    constexpr int fib10_func = fibonacci_func(10);    // 55// 输出模板版本和 constexpr函数版本计算的结果    std::cout << "5! = " << fact5 << " (模板版本)" << std::endl;    std::cout << "5! = " << fact5_func << " (constexpr函数版本)" << std::endl;    std::cout << "fibonacci(10) = " << fib10 << " (模板版本)" << std::endl;    std::cout << "fibonacci(10) = " << fib10_func << " (constexpr函数版本)" << std::endl;return 0;  // 返回0表示程序正常结束}

6.2 类型萃取 (Type Traits)

类型萃取用于在编译期获取类型的信息:

#include <iostream>       // 包含输入输出流库,用于输出信息#include <type_traits>    // 包含标准类型萃取工具,比如 std::is_integral 等
// 自定义的类型萃取:检查是否是指针类型// 默认情况下,所有类型都不是指针,所以 value 为 falsetemplate<typename T>struct is_pointer {    static constexpr bool value = false;  // 非指针类型时,值为 false};
// 当类型为指针时(T*),特化版本将 value 设置为 truetemplate<typename T>struct is_pointer<T*> {    static constexpr bool value = true;   // 指针类型时,值为 true};
// 移除指针的类型萃取:如果类型是指针,则返回指针指向的类型;否则返回原类型// 默认情况,不是指针的类型,移除指针后仍为 Ttemplate<typename T>struct remove_pointer {    using type = T;};
// 特化版本:对于指针类型 T*,移除指针后得到的类型为 Ttemplate<typename T>struct remove_pointer<T*> {    using type = T;};
// 辅助模板别名 (C++14):简化 remove_pointer<T>::type 的写法template<typename T>using remove_pointer_t = typename remove_pointer<T>::type;
// 条件类型选择:根据布尔条件选择类型// 当 Condition 为 true 时,type 定义为 TrueTypetemplate<bool Condition, typename TrueType, typename FalseType>struct conditional {    using type = TrueType;};
// 当 Condition 为 false 时,特化版本定义 type 为 FalseTypetemplate<typename TrueType, typename FalseType>struct conditional<false, TrueType, FalseType> {    using type = FalseType;};
// 辅助模板别名 (C++14):简化 conditional 的写法template<bool Condition, typename TrueType, typename FalseType>using conditional_t = typename conditional<Condition, TrueType, FalseType>::type;
// 使用类型萃取的函数模板// 此函数分析传入类型 T 的特性,并输出相关信息template<typename T>void analyze_type(const T& value) {    std::cout << "分析类型信息:" << std::endl;    // 判断类型 T 是否为指针类型,输出结果    std::cout << "  是否是指针: " << (is_pointer<T>::value ? "是" : "否") << std::endl;    // 利用标准类型萃取判断是否为整数类型    std::cout << "  是否是整数: " << (std::is_integral<T>::value ? "是" : "否") << std::endl;    // 利用标准类型萃取判断是否为浮点数类型    std::cout << "  是否是浮点数: " << (std::is_floating_point<T>::value ? "是" : "否") << std::endl;    // 输出类型 T 的大小(字节数)    std::cout << "  类型大小: " << sizeof(T) << " 字节" << std::endl;    // 如果 T 是指针类型,则输出指针指向的类型大小    if constexpr (is_pointer<T>::value) {        std::cout << "  指向的类型大小: " << sizeof(remove_pointer_t<T>) << " 字节" << std::endl;    }    // 换行分割    std::cout << std::endl;}
int main() {    int x = 42;         // 定义一个 int 类型变量 x,并赋值 42    int* px = &x;       // 定义一个 int* 类型指针 px,指向变量 x    double y = 3.14;    // 定义一个 double 类型变量 y,并赋值 3.14analyze_type(x);    // 分析 int 类型的信息    analyze_type(px);   // 分析 int* 指针类型的信息    analyze_type(y);    // 分析 double 类型的信息// 使用条件类型选择:当 sizeof(int) >= 4 为真时,选择 int 类型;否则选择 double 类型    using int_or_double = conditional_t<sizeof(int) >= 4, int, double>;    std::cout << "条件选择的类型大小: " << sizeof(int_or_double) << " 字节" << std::endl;return 0;           // 程序正常结束,返回 0}

第七章:现代C++模板特性

7.1 概念 (Concepts) - C++20

概念允许我们对模板参数施加约束,使模板更易于使用和理解:

// 注意:这需要C++20支持// 这行注释说明本代码需要使用C++20标准,因为接下来将使用 concepts 相关的语言特性
#include <iostream>      // 引入输入输出流库,用于输出信息#include <concepts>       // 引入 C++20 的 concepts 库,用于定义概念
// 定义一个概念:可打印的类型template<typename T>concept Printable = requires(T a) {    std::cout << a;      // 要求类型 T 能够被输出到 std::cout,必须支持 << 运算符};
// 定义一个概念:有 size 方法的类型template<typename T>concept HasSize = requires(T a) {    a.size();            // 要求类型 T 必须有一个 size() 方法    { a.size() } -> std::convertible_to<std::size_t>;    // 并且 size() 方法的返回值必须可以转换为 std::size_t 类型};
// 使用概念约束的函数模板:只对 Printable 类型有效template<Printable T>void safe_print(const T& value) {    // 当传入的类型符合 Printable 概念时,可以安全地打印该值    std::cout << "安全打印: " << value << std::endl;}
// 使用概念约束的函数模板:只对具有 size() 方法的容器类型有效template<HasSize Container>void print_container_size(const Container& container) {    // 输出容器的大小    std::cout << "容器大小: " << container.size() << std::endl;}
// 更复杂的概念组合// 定义一个 Number 概念,要求类型 T 必须是整数类型或浮点类型template<typename T>concept Number = std::integral<T> || std::floating_point<T>;// 使用标准库中的 std::integral 和 std::floating_point 概念进行判断
// 定义一个平方函数 square,接受符合 Number 概念的类型参数template<Number T>T square(T value) {    // 返回传入值的平方    return value * value;}

7.2 自动类型推导的改进

现代C++在类型推导方面有很多改进:

#include <iostream>  // 引入输入输出流库,用于输出信息#include <vector>    // 引入 vector 容器,用于存储动态数组数据#include <map>       // 引入 map 容器,尽管在本示例中未被使用,通常用于存储键值对
// C++17 类模板参数推导 (CTAD) 示例int main() {    // 不需要显式指定模板参数,编译器会根据初始化列表中的元素类型推导模板参数类型    std::vector vec{1, 2, 3, 4, 5};      // 上述语句中,编译器会推导出 vec 为 std::vector<int>,因为所有元素都是 int 类型// 使用 std::pair,也可利用 CTAD 自动推导类型    std::pair p{42, 3.14};      // 这里 p 被推导为 std::pair<int, double>,因为第一个元素是 int,第二个元素是 double// 定义一个泛型 lambda 表达式,利用 auto 参数来实现模板化的函数    auto lambda = [](auto x, auto y) {        return x + y;    };    // lambda 是一个泛型函数对象,可以接受任意类型参数,只要它们支持加法运算// 调用 lambda,下面的调用将自动推导出参数类型,并根据实际参数执行相应的加法操作    std::cout << lambda(1, 2) << std::endl;       // 传入两个 int 类型的参数,结果为 int 类型相加    std::cout << lambda(1.5, 2.7) << std::endl;     // 传入两个 double 类型的参数,结果为 double 类型相加    std::cout << lambda(1, 2.5) << std::endl;       // 传入 int 与 double 类型的参数,编译器会进行类型提升,结果为 doublereturn 0;  // 程序正常结束}

总结与最佳实践

通过这个教程,我们深入了解了C++模板系统的各个方面。模板是C++中最强大的特性之一,它支持泛型编程、元编程和类型安全的代码复用。

关键要点回顾:

  1. 模板基础: 模板允许编写与类型无关的代码,编译器根据使用情况自动生成具体的代码实例。

  2. 函数模板: 用于创建泛型函数,支持类型推导和显式类型指定。

  3. 类模板: 用于创建泛型类,可以有多个模板参数,包括类型参数和非类型参数。

  4. 模板特化

  • 全特化:为特定类型提供完全不同的实现

  • 偏特化:为模板参数的某些组合提供特殊实现(仅限类模板)

  • 高级技术

    • SFINAE:利用替换失败进行条件编译

    • 变参模板:处理可变数量的参数

    • 模板元编程:在编译期进行计算和类型操作

  • 现代特性

    • 概念 (C++20):为模板参数添加约束

    • 改进的类型推导:CTAD、auto、泛型lambda

    使用建议:

    • 优先使用标准库提供的模板和算法

    • 编写模板时要考虑错误信息的可读性

    • 合理使用特化,不要过度复杂化

    • 在现代C++中,优先使用constexpr而不是模板元编程进行编译期计算

    • 使用概念 (如果有C++20支持) 来使模板更易于理解和使用

    模板是C++的核心特性,掌握它们将大大提升您的C++编程能力。记住,模板的学习是一个渐进的过程,从简单的泛型函数开始,逐步深入到更高级的技术。

相关文章:

【C++】模板与特化技术全面教程(claude sonnet 4)

第一章&#xff1a;模板的基础概念 (Template Fundamentals) 1.1 什么是模板&#xff1f; 模板 (Template) 是C中的一种泛型编程 (Generic Programming) 机制&#xff0c;它允许我们编写与类型无关的代码。想象一下&#xff0c;如果我们要为不同的数据类型编写相同逻辑的函数&a…...

ABAP设计模式之---“高内聚,低耦合(High Cohesion Low Coupling)”

“高内聚、低耦合”是面向对象编程中非常重要的设计原则&#xff0c;它有助于提高代码的可维护性、扩展性和复用性。 1. 初衷&#xff1a;为什么会有这个原则&#xff1f; 在软件开发中&#xff0c;随着业务需求的复杂化&#xff0c;代码难免会变得越来越庞大。如果开发者将一…...

RagFlow优化代码解析(一)

引子 前文写到RagFlow的环境搭建&推理测试&#xff0c;感兴趣的童鞋可以移步&#xff08;RagFlow环境搭建&推理测试-CSDN博客&#xff09;。前文也写过RagFLow参数配置&测试的文档&#xff0c;详见&#xff08;&#xff09;。很少写关于具体代码的blog&#xff0c…...

【python与生活】用 Python 从视频中提取音轨:一个实用脚本的开发与应用

在当今数字化的时代&#xff0c;视频内容无处不在。无论是学习教程、会议记录、在线讲座还是娱乐视频&#xff0c;我们每天都会接触到大量的视频资源。有时候&#xff0c;我们可能只对视频中的音频部分感兴趣&#xff0c;比如提取讲座的音频用于后续收听&#xff0c;或者从电影…...

深度强化学习赋能城市消防优化,中科院团队提出DRL新方法破解设施配置难题

在城市建设与发展中&#xff0c;地理空间优化至关重要。从工业园区选址&#xff0c;到公共服务设施布局&#xff0c;它都发挥着关键作用。但传统求解方法存在诸多局限&#xff0c;如今&#xff0c;深度学习技术为其带来了新的转机。 近日&#xff0c;在中国地理学会地理模型与…...

云原生周刊:探索 Gateway API v1.3.0

开源项目推荐 WatchAlert WatchAlert 是一个轻量级、云原生的多数据源监控告警引擎&#xff0c;支持 AI 驱动的智能告警分析&#xff0c;旨在帮助升级您的监控系统架构。该项目基于 Go 和 React 开发&#xff0c;提供了现代化的前后端架构。后端使用 Go 语言&#xff0c;结合…...

008房屋租赁系统技术揭秘:构建智能租赁服务生态

房屋租赁系统技术揭秘&#xff1a;构建智能租赁服务生态 在房地产租赁市场日益活跃的当下&#xff0c;房屋租赁系统成为连接房东与租客的重要数字化桥梁。该系统集成用户管理、房屋信息等多个核心模块&#xff0c;面向管理员、房东和用户三类角色&#xff0c;通过前台展示与后…...

Python训练打卡Day41

简单CNN 知识回顾 数据增强卷积神经网络定义的写法batch归一化&#xff1a;调整一个批次的分布&#xff0c;常用与图像数据特征图&#xff1a;只有卷积操作输出的才叫特征图调度器&#xff1a;直接修改基础学习率 卷积操作常见流程如下&#xff1a; 1. 输入 → 卷积层 → Batch…...

spring-boot-admin实现对微服务监控

spring-boot-admin可以对微服务的状态进行监控&#xff0c;步骤如下&#xff1a; 1、添加spring-boot-admin和nacos依赖 <!-- nacos注册中心 --> <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-n…...

Linux 权限管理入门:从基础到实践

文章目录 引言一、Linux 权限管理概述二、文件权限值的表示方法三、文件访问权限的设置&#xff08;chmod&#xff09;四、file指令&#xff1a;快速识别文件类型五、目录的权限六、普通文件的权限七、权限总结八、粘滞位 引言 在 Linux 系统中&#xff0c;权限管理是确保多用…...

Mycat的监控

参考资料&#xff1a; 参考视频 参考博客 Mysql分库分表&#xff08;基于Mycat&#xff09;的基本部署 MySQL垂直分库&#xff08;基于MyCat&#xff09; Mysql水平分表&#xff08;基于Mycat&#xff09;及常用分片规则 视频参考资料及安装包&#xff1a; https://pan.b…...

Glide源码解析

前言 Glide是一款专为Android设计的开源图片加载库。有以下特点&#xff1a;1.支持高效加载网络、本地及资源图片&#xff1b;2.具备良好的缓存策略及生命周期管理策略&#xff1b;3.提供了简易的API和强大的功能。本文将对其源码进行剖析。 基本使用 dependencies {compile …...

7.RV1126-OPENCV cvtColor 和 putText

一.cvtColor 1.作用 cvtColor 是 OPENCV 里面颜色转换的转换函数。能够实现 RGB 图像转换成灰度图、灰度图转换成 RGB 图像、RGB 转换成 HSV 等等 2.API CV_EXPORTS_W void cvtColor( InputArray src, OutputArray dst, int code, int dstCn 0 ); 第一个参数&#xff1a;…...

Android 之 kotlin 语言学习笔记二(编码样式)

参考官方文档&#xff1a;https://developer.android.google.cn/kotlin/style-guide?hlzh-cn#whitespace 1、源文件命名 所有源文件都必须编码为 UTF-8。如果源文件只包含一个顶级类&#xff0c;则文件名应为该类的名称&#xff08;区分大小写&#xff09;加上 .kt 扩展名。…...

Redisson单机模式

redisson调用unlock的过程 Redisson 是一个基于 Redis 的 Java 驻内存数据网格&#xff08;In-Memory Data Grid&#xff09;框架&#xff0c;提供了分布式和可扩展的数据结构和服务。Redisson 的 unlock 方法用于释放锁。下面是 unlock 方法的调用过程&#xff1a; 获取锁的状…...

数据结构第6章 图(竟成)

第 6 章 图 【考纲内容】 1.图的基本概念 2.图的存储及基本操作&#xff1a;(1) 邻接矩阵法&#xff1b;(2) 邻接表法&#xff1b;(3) 邻接多重表、十字链表 3.图的遍历&#xff1a;(1) 深度优先搜索&#xff1b;(2) 广度优先搜索 4.图的基本应用&#xff1a;(1) 最小 (代价) 生…...

机器人现可完全破解验证码:未来安全技术何去何从?

引言 随着计算机视觉技术的飞速发展&#xff0c;机器学习模型现已能够100%可靠地解决Google的视觉reCAPTCHAv2验证码。这标志着一个时代的结束——自2000年代初以来&#xff0c;CAPTCHA&#xff08;"全自动区分计算机与人类的图灵测试"的缩写&#xff09;一直是区分…...

CppCon 2014 学习:(Costless)Software Abstractions for Parallel Architectures

硬件和科学计算的演变关系&#xff1a; 几十年来的硬件进步&#xff1a;计算机硬件不断快速发展&#xff0c;从提升单核速度&#xff0c;到多核并行。科学计算的驱动力&#xff1a;科学计算需求推动硬件创新&#xff0c;比如需要更多计算能力、更高性能。当前的解决方案是并行…...

网络爬虫 - App爬虫及代理的使用(十一)

App爬虫及代理的使用 一、App抓包1. App爬虫原理2. reqable的安装与配置1. reqable安装教程2. reqable的配置3. 模拟器的安装与配置1. 夜神模拟器的安装2. 夜神模拟器的配置4. 内联调试及注意事项1. 软件启动顺序2. 开启抓包功能3. reqable面板功能4. 夜神模拟器设置项5. 注意事…...

Kafka集群部署(docker容器方式)SASL认证(zookeeper)

一、服务器环境 序号 部署版本 版本 1 操作系统 CentOS Linux release 7.9.2009 (Core) 2 docker Docker version 20.10.6 3 docker-compose docker-compose version 1.28.2 二、服务规划 序号 服务 名称 端口 1 zookeeper zookeeper 2181,2888,3888 2 ka…...

【python爬虫】利用代理IP爬取filckr网站数据

亮数据官网链接&#xff1a;亮数据官网...

群晖 NAS 如何帮助培训学校解决文件管理难题

在现代教育环境中&#xff0c;数据管理和协同办公的效率直接影响到教学质量和工作流畅性。某培训学校通过引入群晖 NAS&#xff0c;显著提升了部门的协同办公效率。借助群晖的在线协作、自动备份和快照功能&#xff0c;该校不仅解决了数据散乱和丢失的问题&#xff0c;还大幅节…...

NLP学习路线图(十八):Word2Vec (CBOW Skip-gram)

自然语言处理&#xff08;NLP&#xff09;的核心挑战在于让机器“理解”人类语言。传统方法依赖独热编码&#xff08;One-hot Encoding&#xff09; 表示单词&#xff0c;但它存在严重缺陷&#xff1a;每个单词被视为孤立的符号&#xff0c;无法捕捉词义关联&#xff08;如“国…...

P1438 无聊的数列/P1253 扶苏的问题

因为这两天在写线性代数的作业&#xff0c;没怎么写题…… P1438 无聊的数列 题目背景 无聊的 YYB 总喜欢搞出一些正常人无法搞出的东西。有一天&#xff0c;无聊的 YYB 想出了一道无聊的题&#xff1a;无聊的数列。。。 题目描述 维护一个数列 ai​&#xff0c;支持两种操…...

嵌入式学习笔记 - 新版Keil软件模拟时钟Xtal灰色不可更改的问题

在新版Keil软件中&#xff0c;模拟时钟无法修改XTAL频率&#xff0c;默认只能使用12MHz时钟。‌这是因为Keil MDK从5.36版本开始&#xff0c;参数配置界面不再支持修改系统XTAL频率&#xff0c;XTAL选项变为灰色&#xff0c;无法修改。这会导致在软件仿真时出现时间错误的问题&…...

k8s的出现解决了java并发编程胡问题了

Kubernetes&#xff08;K8s&#xff09;作为一种开源的容器编排平台&#xff0c;极大地简化了应用程序的部署、管理和扩展。这不仅解决了很多基础设施方面的问题&#xff0c;也间接解决了Java并发编程中的一些复杂问题。本文将详细探讨Kubernetes是如何帮助解决Java并发编程中的…...

如何利用大语言模型生成特定格式文风的报告类文章

在这个算法渗透万物的时代,我们不再仅仅满足于大语言模型(LLM)能“写”,更追求它能“写出精髓,写出风格”。尤其在专业且高度格式化的报告类文章领域,仅仅是内容正确已远远不够,文风的精准复刻才是决定报告是否“对味儿”、能否被目标受众有效接受的关键。这不再是简单的…...

黑马Java面试笔记之 集合篇(算法复杂度+ArrayList+)

一. 算法复杂度分析 1.1 时间复杂度 时间复杂度分析&#xff1a;来评估代码的执行耗时的 常见的复杂度表示形式 常见复杂度 1.2 空间复杂度 空间复杂度全称是渐进空间复杂度&#xff0c;表示算法占用的额外存储空间与数据规模之间的增长关系 二. 数组 数组&#xff08;Array&a…...

【从0-1的HTML】第2篇:HTML标签

文章目录 1.标题标签2.段落标签3.文本标签brbstrongsubsup 4.超链接标签5.图片标签6.表格标签7.列表标签有序列表ol无序列表ul定义列表dl 8.表单标签9.音频标签10.视频标签11.HTML元素分类块级元素内联元素 12.HTML布局13.内联框架13.内联框架 1.标题标签 标题标签&#xff1a…...

从“Bucharest”谈起:词语翻译的音译与意译之路

在翻译中&#xff0c;面对地名、人名或新兴术语时&#xff0c;我们常常会遇到一个抉择&#xff1a;到底是“音译”&#xff0c;保留其原发音风貌&#xff0c;还是“意译”&#xff0c;让它意义通达&#xff1f; 今天我们以“Bucharest”为例&#xff0c;展开一次语言与文化的微…...