《 C++ 点滴漫谈: 三十四 》从重复到泛型,C++ 函数模板的诞生之路

news2025/4/23 10:35:11

一、引言

在 C++ 编程的世界里,类型是一切的基础。我们为 int 写一个求最大值的函数,为 double 写一个相似的函数,为 std::string 又写一个……看似合理的行为,逐渐堆积成了难以维护的 “函数墙”。这些函数逻辑几乎一致,仅仅是参数类型不同,却不得不反复实现。这种 “代码冗余” 是传统 C 语言开发中普遍存在的问题。

为了彻底解决这一难题,C++ 提出了 “模板编程”(Template Programming)这一强大机制,其中最基础也最常用的,就是函数模板(Function Template)。它允许我们写出与类型无关的函数逻辑,编译器会在调用时根据传入的类型生成对应的函数版本,从而实现 “一次编写,多处复用” 的理想目标。

函数模板不仅极大地提高了代码复用率,还成为 C++ 泛型编程(Generic Programming)的基石,是构建现代 C++ 标准库(如 STL)中不可或缺的核心工具。你所熟知的 std::sortstd::swapstd::max 等,其实都是函数模板的杰出代表。

此外,随着 C++11、C++14、C++17 乃至 C++20 的演进,函数模板的语法和功能也不断增强,从支持 auto 类型推导、decltype 辅助判断,到引入 if constexpr 和 Concepts 等高级特性,使得模板不仅易用,而且更强大、更安全、更灵活。

在这篇文章中,我们将从最基础的函数模板语法出发,深入探讨其背后的类型推导机制、与普通函数的协作方式、特化技巧,以及与现代 C++ 特性的完美结合。同时,我们还将结合真实项目中的案例,解析函数模板在工程实践中的作用与价值,帮助你真正掌握这一泛型编程的利器。

二、函数模板基础语法

C++ 中的函数模板(Function Template)是一种可以处理不同类型参数的函数定义方式,是泛型编程的核心工具。通过模板机制,开发者可以将函数逻辑抽象成与 “类型” 无关的通用形式,由编译器根据调用时的实际类型自动生成对应的函数版本,从而提高代码的复用性与可维护性。

2.1、函数模板的基本语法

函数模板的声明和定义通常使用以下语法结构:

template<typename T>
返回类型 函数名(参数列表) {
    // 函数体
}
  • template 是关键字,标志我们正在定义一个模板。
  • typename T 表示类型参数 T,T 可以是任意的类型。
    • 也可以使用 class T,与 typename T 在此上下文中是完全等价的。
  • T 可以在函数参数、返回值中使用,也可以在函数体中使用。

🔸 示例:一个通用的 swap 函数

#include <iostream>
using namespace std;

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

int main() {
    int x = 10, y = 20;
    mySwap(x, y);
    cout << "x = " << x << ", y = " << y << endl;

    double p = 3.14, q = 2.71;
    mySwap(p, q);
    cout << "p = " << p << ", q = " << q << endl;

    return 0;
}

运行结果:

x = 20, y = 10
p = 2.71, q = 3.14

编译器根据不同类型自动生成对应的函数版本,大大减少了重复代码。

2.2、多类型模板参数

函数模板不仅支持一个类型参数,还可以使用多个类型参数来适应更复杂的函数签名:

template<typename T1, typename T2>
void printPair(T1 a, T2 b) {
    std::cout << "First: " << a << ", Second: " << b << std::endl;
}

int main() {
    printPair(42, "Hello");
    printPair(3.14, true);
}

输出:

First: 42, Second: Hello
First: 3.14, Second: 1

2.3、模板函数的调用方式

✅ 自动类型推导

编译器会根据实参自动推导模板参数类型:

mySwap(x, y); // 自动推导 T = int
✅ 显式指定模板参数

有时候推导失败或不够明确,可以手动指定类型:

mySwap<int>(x, y); // 显式指定 T 为 int

这对于有类型转换或歧义的情况非常有用。

2.4、typenameclass 的区别?

在函数模板的定义中,template<typename T>template<class T> 是完全等价的。两者只是语义上的不同,C++ 标准推荐使用 typename 来强调这是一个 “类型参数”,而不是一个类。以下两个写法效果一致:

template<typename T> void func1(T val); // 推荐
template<class T> void func2(T val);    // 等价

2.5、模板函数不能自动实例化为所有类型

虽然模板非常强大,但它不是 “魔法函数工厂”。如果模板内部对类型 T 做了某些操作(如使用操作符 <),那么该类型必须支持该操作:

template<typename T>
bool compare(T a, T b) {
    return a < b; // T 必须支持 operator<
}

如果你对一个不支持 < 的类型调用此模板,会在编译阶段报错,这就是 模板实例化错误 的一部分。

2.6、小结小贴士 ✅

要点内容
template<typename T>声明一个函数模板
多类型参数使用 template<typename T1, typename T2>
类型推导自动进行,也可显式指定
使用限制类型 T 必须支持模板中涉及的操作
class vs typename没有本质区别,推荐用 typename

函数模板是 C++ 中泛型编程的起点,它让我们写出类型无关的逻辑。掌握了基本语法后,我们将在下一节继续探索模板的 类型推导机制模板与普通函数之间的互动规则,逐步走向更高级的使用方式。

三、模板的类型推导与显式指定

在上一节中,我们学习了函数模板的基本语法。真正让模板函数强大和灵活的,是 C++ 的 类型推导机制(Type Deduction),以及支持开发者手动 显式指定模板参数(Explicit Specification) 的能力。

本节将从规则、细节和陷阱出发,全面揭示模板类型推导与显式指定的底层逻辑。

3.1、什么是类型推导?

当我们调用一个函数模板时,如果没有显式指定模板参数,编译器会根据函数参数自动推导出模板类型

示例

template<typename T>
void print(T value) {
    std::cout << "Value: " << value << std::endl;
}

int main() {
    print(42);       // T 被推导为 int
    print(3.14);     // T 被推导为 double
    print("Hello");  // T 被推导为 const char*
}

推导发生在编译期间,编译器根据参数类型生成对应版本的函数定义。

3.2、显式指定模板参数

开发者也可以在调用函数模板时,明确地指定模板参数类型,这种做法被称为显式指定

print<int>(42);         // 显式指定 T 为 int
print<double>(42);      // 显式将 42 转为 double

这在某些类型无法正确推导,或需要强制转换的场景中尤为重要。

3.3、推导的限制与陷阱

虽然 C++ 的类型推导很强大,但也存在一些限制和 “坑”:

✅ 引用与 const 的推导规则
template<typename T>
void func(T arg);  // T 是值传递

int x = 10;
const int y = 20;
func(x);  // T 推导为 int
func(y);  // T 推导为 int(不是 const int)
  • 值传递会去掉引用和 const 修饰符
  • 若要保持引用类型,需显式声明为引用参数:
template<typename T>
void func_ref(T& arg);   // T 是引用

func_ref(x);  // T 推导为 int,参数类型为 int&
func_ref(y);  // T 推导为 const int,参数类型为 const int&
✅ 数组、指针类型的推导
template<typename T>
void showSize(T arg) {
    std::cout << sizeof(arg) << std::endl;
}

int arr[10];
showSize(arr);  // T 推导为 int*,数组退化为指针
  • 数组作为函数参数会退化为指针,需要使用引用以保持数组大小:
template<typename T, size_t N>
void showArray(T (&arr)[N]) {
    std::cout << "Array size: " << N << std::endl;
}

3.4、函数模板参数与非模板参数混用

C++ 允许模板函数中混合使用 模板参数非模板参数

template<typename T>
void fillArray(T value, int count) {
    for (int i = 0; i < count; ++i)
        std::cout << value << " ";
    std::cout << std::endl;
}

其中 T 是模板参数,而 count 是普通的 int 类型参数。

3.5、模板参数不能从返回值推导

函数模板只能从参数列表推导类型,返回值不参与类型推导

template<typename T>
T identity() {
    return T();
}

int x = identity();      // 错误!不能推导出 T
int y = identity<int>(); // 正确,显式指定 T 为 int

3.6、多个参数的推导规则

当模板函数有多个模板参数时,每个参数都可能有不同的推导规则

template<typename T1, typename T2>
void showPair(T1 a, T2 b);

showPair(1, 2.0);       // T1=int, T2=double
showPair("Hi", 'a');    // T1=const char*, T2=char

若参数类型之间存在冲突,例如传入 (int, int) 但显式指定为 (T, T*) 则会导致编译错误。

3.7、默认模板参数(C++11 起)

C++11 起允许为函数模板提供默认模板参数:

template<typename T = int>
void printDefault(T value) {
    std::cout << value << std::endl;
}

printDefault(123);        // 推导为 int
printDefault<double>(3.14); // 显式指定为 double

3.8、结合 auto 与模板推导(C++14/17)

C++14 起允许函数返回类型为 auto,并通过模板参数推导:

template<typename T1, typename T2>
auto add(T1 a, T2 b) -> decltype(a + b) {
    return a + b;
}

C++17 起支持更简洁写法:

template<typename T1, typename T2>
auto add(T1 a, T2 b) {
    return a + b;
}

3.9、小结对照表:类型推导中的常见规则

场景推导结果是否保留修饰符
值传递去掉 const、引用❌ 否
引用传递保留 const、引用✅ 是
数组传参退化为指针❌ 否
返回值不参与推导❌ 否
显式指定手动确定类型✅ 是
  • 类型推导是函数模板的核心机制,编译器会根据参数自动分析模板类型。
  • 推导会移除 const 和引用,数组会退化为指针,注意这些 “隐性转换”。
  • 当推导失败或有歧义时,显式指定模板参数是最佳方式。
  • C++11 之后支持默认模板参数、decltype、返回类型推导等新特性,极大提升模板的表达力。

四、模板函数与普通函数的共存与重载

在 C++ 中,函数模板为通用编程提供了强大工具,但这并不意味着它们会取代所有普通函数。在实际开发中,我们常常希望模板函数和普通函数共存,并且根据不同的参数类型进行自动的重载选择

这一节将深入探讨模板函数与普通函数如何相互协作,包括它们的优先级机制、匹配细节以及开发中常见的陷阱。

4.1、模板函数与普通函数可以共存吗?

答案是:可以,并且它们之间可以自由重载。

当一个函数模板与一个普通函数同名且参数形式相似时,编译器会优先选择与调用参数最匹配的函数版本。优先级的顺序如下:

  1. 完全匹配的普通函数(non-template)
  2. 可匹配的模板函数(template)
  3. 更特化的模板函数(partial specialization,见后节)

4.2、基本示例:普通函数优先

#include <iostream>

template<typename T>
void print(T value) {
    std::cout << "Template: " << value << std::endl;
}

void print(int value) {
    std::cout << "Normal: " << value << std::endl;
}

int main() {
    print(10);       // 调用普通函数
    print("Hello");  // 调用模板函数
}

输出:

Normal: 10
Template: Hello
  • 对于 print(10),普通函数 print(int) 更匹配,因此被优先选择。
  • 对于 print("Hello"),没有对应的普通函数,因此选择模板版本。

4.3、模板函数之间的重载

模板函数之间也可以重载,例如根据参数个数或参数类型:

template<typename T>
void show(T x) {
    std::cout << "One parameter: " << x << std::endl;
}

template<typename T1, typename T2>
void show(T1 x, T2 y) {
    std::cout << "Two parameters: " << x << ", " << y << std::endl;
}

int main() {
    show(10);         // 匹配第一个模板
    show(3.14, "Pi"); // 匹配第二个模板
}

4.4、模板与普通函数重载的歧义

有时,模板函数与普通函数的匹配度可能相近,从而引发编译器 “歧义错误”:

template<typename T>
void func(T a) {
    std::cout << "Template func" << std::endl;
}

void func(double a) {
    std::cout << "Non-template func" << std::endl;
}

int main() {
    func(3.14f);  // float 类型对模板和普通函数都可以匹配
}

结果:

  • float 能被转换为 double,也能推导出 T = float
  • 若匹配度接近,可能出现模棱两可的情况,需要显式指定

4.5、如何解决歧义?

✅ 显式指定模板参数
func<float>(3.14f);  // 明确选择模板版本
✅ 添加模板专属参数或限制

使用 std::enable_ifconcepts 限制模板参数范围,以避免被普通函数抢先匹配(C++11/C++20):

template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type
func(T a) {
    std::cout << "Integral only" << std::endl;
}

4.6、与函数默认参数配合使用

如果普通函数带有默认参数,而模板函数没有,可能导致模板意外落后:

void hello(int x = 10) {
    std::cout << "hello(int)" << std::endl;
}

template<typename T>
void hello(T t) {
    std::cout << "hello(T)" << std::endl;
}

int main() {
    hello();	// hello(int)
}

4.6、模板函数不能重载只靠返回值区分

返回值类型不参与重载决议,因此以下代码非法:

template<typename T>
T convert(int value);

template<typename T>
double convert(int value);  // 错误:仅返回值不同

4.7、推荐实践

场景推荐方式
有具体类型的函数实现需求使用普通函数
泛型实现、类型未知使用模板函数
模板与普通函数同时存在确保参数签名不同
模板选择性启用使用 std::enable_ifconcept 限制

小结

  • 模板函数和普通函数可以共存,编译器会根据 “匹配度优先” 规则进行选择。
  • 普通函数优先,模板是备选项;但在参数不匹配时,模板能提供兜底能力。
  • 避免歧义的关键在于:区分参数类型、个数或借助 SFINAE 技术屏蔽某些模板实例化路径
  • 利用 C++11/14/20 的新特性可以更精确地控制重载行为。

五、模板特化与偏特化

在实际开发中,虽然函数模板通过泛型机制实现了代码复用,但有时我们仍希望为特定类型编写专门的函数实现,这就是模板特化(Template Specialization)与偏特化(Partial Specialization)发挥作用的地方。

5.1、什么是模板特化?

模板特化是指为特定类型的参数提供专门的模板实现。C++ 支持类模板和函数模板的特化,但注意:函数模板不能进行偏特化,只能进行全特化。而类模板则两者都支持。

5.2、函数模板的全特化(Function Template Specialization)

🔹 定义形式
template<typename T>
void print(T value);  // 通用模板

// 特化版本
template<>
void print<int>(int value) {
    std::cout << "int: " << value << std::endl;
}
🔹 使用示例
#include <iostream>

template<typename T>
void print(T value) {
    std::cout << "Generic: " << value << std::endl;
}

template<>
void print<int>(int value) {
    std::cout << "Specialized for int: " << value << std::endl;
}

int main() {
    print(42);        // 调用特化版本
    print(3.14);      // 调用通用模板
    print("Hello");   // 调用通用模板
}
✅ 输出:
Specialized for int: 42  
Generic: 3.14  
Generic: Hello

✅ 特化的版本完全替代了通用模板在该类型上的实现,具有最高优先级。

5.3、偏特化是啥?为什么函数模板不能偏特化?

🔹 偏特化(Partial Specialization)

偏特化是指只对部分模板参数或部分类型结构进行特化处理,是类模板的一种强大功能。

template<typename T1, typename T2>
class Pair;

// 偏特化版本:当第二个类型是 int
template<typename T1>
class Pair<T1, int> {
public:
    void show() {
        std::cout << "Second type is int" << std::endl;
    }
};

🚫 函数模板不能进行偏特化,因为编译器无法根据调用上下文唯一选择匹配度最高的偏特化版本,会导致二义性。

5.4、类模板偏特化的典型使用场景

示例:对不同类型的处理逻辑不同

#include <iostream>

template<typename T>
struct TypeTrait {
    static void print() {
        std::cout << "Generic type" << std::endl;
    }
};

// 偏特化:指针类型
template<typename T>
struct TypeTrait<T*> {
    static void print() {
        std::cout << "Pointer type" << std::endl;
    }
};

int main() {
    TypeTrait<int>::print();     // 输出:Generic type
    TypeTrait<int*>::print();    // 输出:Pointer type
}

✅ 类模板的偏特化让我们能够以结构性方式区分类型特征、启用不同实现策略,这是泛型编程中的重要技巧。

5.5、函数模板的伪偏特化方案

虽然函数模板不能偏特化,但我们可以借助类模板的偏特化 + 函数封装间接实现类似效果。

template<typename T>
struct PrintHelper {
    static void print(T value) {
        std::cout << "Generic: " << value << std::endl;
    }
};

template<>
struct PrintHelper<int> {
    static void print(int value) {
        std::cout << "Specialized for int: " << value << std::endl;
    }
};

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

int main() {
    print(100);       // 特化版本
    print(3.14);      // 通用版本
}

5.6、模板特化的小细节

细节点说明
函数模板只能全特化无法偏特化,使用类模板辅助
模板参数顺序要一致特化模板时需完全匹配原始模板参数结构
特化版本不会自动继承默认参数必须重新定义所有默认参数
特化优先级最高编译器会优先选择完全特化版本,而不是通用模板或普通重载函数

5.7、现代 C++ 特化替代方案:if constexprconcepts

自 C++17 起,引入了 if constexpr 可在编译期实现类型判断逻辑,从而在模板函数中内联不同类型的实现分支。

template<typename T>
void print(T value) {
    if constexpr (std::is_integral<T>::value) {
        std::cout << "Integral type: " << value << std::endl;
    } else {
        std::cout << "Other type: " << value << std::endl;
    }
}

✅ 这是一种现代、高效、无须额外特化的做法,推荐用于轻量逻辑分支。

5.8、小结建议

需求推荐做法
为某类型提供完全不同实现使用函数模板的全特化
为某类类型(如指针、整型)提供差异行为使用类模板偏特化
想让函数模板支持结构差异类模板偏特化 + 函数封装
仅需少量分支使用 if constexpr 或 concepts

小结

  • 函数模板只能进行全特化,不能偏特化。
  • 类模板可以进行偏特化,非常适合设计策略类、类型特征提取等。
  • 模板特化是泛型编程的高级技巧,允许你兼顾“通用性”与“定制性”。
  • 结合 if constexprconcepts,可以更现代化地表达“特化行为”。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2340699.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络基础概念(下)

网络基础概念&#xff08;上&#xff09;https://blog.csdn.net/Small_entreprene/article/details/147261091?sharetypeblogdetail&sharerId147261091&sharereferPC&sharesourceSmall_entreprene&sharefrommp_from_link 网络传输的基本流程 局域网网络传输流…

一个关于相对速度的假想的故事-4

回到公式&#xff0c; 正写速度叠加和倒写速度叠加的倒写相等&#xff0c;这就是这个表达式所要表达的意思。但倒写叠加用的是减法&#xff0c;而正写叠加用的是加法。当然是这样&#xff0c;因为正写叠加要的是单位时间上完成更远的距离&#xff0c;而倒写叠加说的是单位距离需…

Idea创建项目的搭建方式

目录 一、普通Java项目 二、普通JavaWeb项目 三、maven的JavaWeb项目 四、maven的Java项目 一、普通Java项目 1. 点击 Create New Project 2. 选择Java项目&#xff0c;选择JDK&#xff0c;点击Next 3. 输入项目名称&#xff08;驼峰式命名法&#xff09;&#xff0c;可选…

【DeepSeek 学习推理】Llumnix: Dynamic Scheduling for Large Language Model Serving实验部分

6.1 实验设置 测试平台。我们使用阿里云上的16-GPU集群&#xff08;包含4个GPU虚拟机&#xff0c;类型为ecs.gn7i-c32g1.32xlarge&#xff09;。每台虚拟机配备4个NVIDIA A10&#xff08;24 GB&#xff09;GPU&#xff08;通过PCI-e 4.0连接&#xff09;、128个vCPU、752 GB内…

Kubernetes相关的名词解释kubeadm(19)

kubeadm是什么&#xff1f; kubeadm 是 Kubernetes 官方提供的一个用于快速部署和管理 Kubernetes 集群的命令行工具。它简化了集群的初始化、节点加入和升级过程&#xff0c;特别适合在生产环境或学习环境中快速搭建符合最佳实践的 Kubernetes 集群。 kubeadm 的定位 不是完整…

什么是负载均衡?NGINX是如何实现负载均衡的?

大家好&#xff0c;我是锋哥。今天分享关于【什么是负载均衡&#xff1f;NGINX是如何实现负载均衡的&#xff1f;】面试题。希望对大家有帮助&#xff1b; 什么是负载均衡&#xff1f;NGINX是如何实现负载均衡的&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源…

基于Python(Django)+SQLite实现(Web)校园助手

校园助手 本校园助手采用 B/S 架构。并已将其部署到服务器上。在网址上输入 db.uplei.com 即可访问。 使用说明 可使用如下账号体验&#xff1a; 学生界面: 账号1&#xff1a;123 密码1&#xff1a;123 账户2&#xff1a;201805301348 密码2&#xff1a;1 # --------------…

从零开始搭建Django博客②--Django的服务器内容搭建

本文主要在Ubuntu环境上搭建&#xff0c;为便于研究理解&#xff0c;采用SSH连接在虚拟机里的ubuntu-24.04.2-desktop系统搭建&#xff0c;当涉及一些文件操作部分便于通过桌面化进行理解&#xff0c;通过Nginx代理绑定域名&#xff0c;对外发布。 此为从零开始搭建Django博客…

【读论文】HM-RAG:分层多智能体多模态检索增强生成

如何在多模态信息检索和生成中&#xff0c;通过协作式多智能体系统来处理复杂的多模态查询。传统的单代理RAG系统在处理需要跨异构数据生态系统进行协调推理的复杂查询时存在根本性限制:处理多种查询类型、数据格式异质性和检索任务目标的多样性&#xff1b;在视觉内容和文本内…

文件操作和IO(上)

绝对路径和相对路径 文件按照层级结构进行组织&#xff08;类似于数据结构中的树型结构&#xff09;&#xff0c;将专门用来存放管理信息的特殊文件称为文件夹或目录。对于文件系统中文件的定位有两种方式&#xff0c;一种是绝对路径&#xff0c;另一种是相对路径。 绝对路径…

JavaFX深度实践:从零构建高级打地鼠游戏(含多物品与反馈机制)

大家好&#xff01;经典的“打地鼠”游戏是许多人童年的回忆&#xff0c;也是学习 GUI 编程一个非常好的切入点。但仅仅是“地鼠出来就打”未免有些单调。今天&#xff0c;我们来点不一样的——用 JavaFX 打造一个高级版的打地鼠游戏&#xff01;在这个版本中&#xff0c;洞里钻…

Python 简介与入门

目录 一、Python 初识 1、Python 的优势 2、Python 的特性 3、Python 的应用领域 二、Linux 环境中安装 Python 1、下载 Python3.11.6 2、安装依赖包 3、解压 Python 压缩包 4、安装 Python 5、编译及安装 6、建立软链接 7、测试 Python3 运行 8、设置国内 pip 更…

理解RAG第六部分:有效的检索优化

在RAG系统中&#xff0c;识别相关上下文的检索器组件的性能与语言模型在生成有效响应方面的性能同样重要&#xff0c;甚至更为重要。因此&#xff0c;一些改进RAG系统的努力将重点放在优化检索过程上。 从检索方面提高RAG系统性能的一些常见方法。通过实施高级检索技术&#x…

实训Day-2 流量分析与安全杂项

目录 实训Day-2-1流量分析实战 实训目的 实训任务1 SYN半链接攻击流量分析 实训任务2 SQL注入攻击流量分析一 实训任务3 SQL注入攻击流量分析二 实训任务4 Web入侵溯源一 实训任务5 Web入侵溯源二 ​编辑 实训Day-2-1安全杂项实战 实训目的 实训任务1 流量分析 FTP…

几种电气绝缘类型

1. 基本绝缘 1.1 绝缘等级 1.2 I类设备 2. 附加绝缘 3. 双重绝缘 4. 加强绝缘 5. 功能性绝缘 1. 基本绝缘 用于防止触及带电部件的初级保护,该防护是由绝缘材料完成的 基本绝缘的目的在于为防电击提供一个基本的保护,以避免触电的危险,不过此类绝缘只能保证正常状态下…

char32_t、char16_t、wchar_t 用于 c++ 语言里存储 unicode 编码的字符,给出它们的具体定义

&#xff08;1&#xff09; #include <iostream> #include <string>int main() { std::u16string s u"C11 引入 char16_t"; // 定义 UTF-16 字符串for (char16_t c : s) // 遍历输出每个 char16_t 的值std::cout << std::hex << (…

Java Set/List 知识点 Java面试 基础面试题

Java Set/List 知识点 Set与List区别 List 有序、值可重复,内部数据结构 Obejct[ ] 数组Set 无序、值不重复,内部数据结构 HashMap keyobject value固定new Object() ArrayList 有序存储元素允许元素重复&#xff0c;允许存储 null 值支持动态扩容非线程安全 HashSet、LinkedHa…

Oracle Database Resident Connection Pooling (DRCP) 白皮书阅读笔记

本文为“Extreme Oracle Database Connection Scalability with Database Resident Connection Pooling (DRCP)”的中文翻译加阅读笔记。觉得是重点的就用粗体表示了。 白皮书版本为March 2025, Version 3.3&#xff0c;副标题为&#xff1a;Optimizing Oracle Database resou…

FastAPI WebSocket 聊天应用详细教程

项目简介 这是一个基于 FastAPI 和 WebSocket 实现的实时聊天应用&#xff0c;支持一对一聊天、离线消息存储等功能。 技术栈 后端&#xff1a;FastAPI (Python)前端&#xff1a;HTML、JavaScript、CSS通信&#xff1a;WebSocket认证&#xff1a;简单的 token 认证 项目结构…

vue3+canvas裁剪框样式【前端】

目录 canvas绘制裁剪框&#xff1a;拖拽改变框的大小&#xff1a;圆圈样式&#xff1a;方块样式&#xff1a; canvas绘制裁剪框&#xff1a; // 绘制裁剪框 const drawCropRect (ctx: CanvasRenderingContext2D): void > {if (cropRect.value.width > 0 && crop…