C++自动推导与函数模板

本文详细介绍了C++中的自动类型推导(auto关键字),函数模板及其具体化,以及decltype用于查询表达式类型的用法。讨论了auto在变量声明、模板和lambda表达式中的应用,同时提到了函数模板的实例化和重载。还提及了C++11及后续标准中关于函数返回类型的后置声明功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

自动推导、函数模板、类模板

目录

1. 自动推导出数据类型

auto关键字

linux 系统下使用的话,要在编译时 —std==c++11

注意:

  1. auto声明的变量必须在定义时初始化
  2. 初始化的右值可以是具体的数值,也可以是表达式和函数的返回值等
  3. auto不能作为函数的形参类型
  4. auto不能直接声明数组
  5. auto不能定义类的非静态成员

不要滥用auto,auto在编程时真正的用途如下

  1. 代替冗长复杂的变量声明:例如函数指针
  2. 模板中,用于声明依赖模板参数的变量
  3. 函数模板依赖模板参数的返回值
  4. 用于lambda表达式

2. 函数模板

基本概念

注意:库函数中有swap()函数,声明函数不要重名,可以首字母大写

虽然我们可以重载多个交换函数,来满足交换不同数据类型的要求,但是这种交换的方式好像不是很高明,需要很多重复代码,而且如果有新的自定义的数据类型,还需要新的交换函数

函数模板是通用的函数描述,使用任意类型(泛型)来描述函数

编译的时候,编译器推导实参的数据类型,根据实参的数据类型和函数模板,生成该类型的函数定义

生成函数定义的过程称为实例化

在这里插入图片描述

函数模板实例化

在这里插入图片描述

函数模板在实例化的过程中,编译器会在函数名中加入一些乱七八糟的东西,再用编译后的函数名替换代码中对应的函数调用。

如果不想编译器自动类型推导,可以再函数名后面加参数类型

Swap<String>(a, b);
注意事项
  1. 可以为类的成员函数创建模板,但不能是虚函数和析构函数

  2. 使用函数模板时,必须明确数据类型,确保实参与函数模板能匹配得上

    都是T类型的形参,传入的实参类型应该相同

    下面这里如果在调用函数的时候不明确数据类型的话就会报错
    在这里插入图片描述

  3. 使用函数模板时,推导的数据类型必须适应函数模板中的代码

    意思是,函数模板可以推导任意数据类型,但是函数中的代码不一定适应数据类型

    例如:我们传入函数模板中一个自定义类型,但并没有重载+操作符,而在函数模板代码中又使用了相应的+操作,编译器就会报错

  4. 使用函数模板时,如果是自动类型推导,不会发生隐式类型转换,如果显示指定了函数模板的数据类型,可以隐式类型转换

    如果传入参数的类型与显式定义的类型不同,可以隐式类型转换成显式定义的类型

  5. 函数模板支持多个通用数据类型的参数

    模板函数可以有多个通用参数

  6. 函数模板支持重载,并且可以有非通用类型的参数

函数模板的具体化

可以提供一个具体化的函数定义,当编译器找到与函数调用匹配的具体化定义时,将使用该定义,不再寻找模板

就是函数模板通用化之外的一个具体化版本

有具体化定义的时候,会优先使用具体化定义

声明和定义可以分开写

语法

// template<> void 函数模板名<Gril>(参数列表)
template<> void 函数模板名(参数列表) {
  // 函数体
}

使用顺序

普通函数、函数模板、具体化的函数模板以及它们的重载版本

规则

  1. 具体化函数先于常规模板,普通函数优先于具体化和常规模板
  2. 如果希望使用函数模板,可以用空模板参数强制使用函数模板
  3. 如果函数模板能产生更好的匹配,将优先于非模板函数
函数模板分文件编写

函数模板一般放在头文件中

如果有函数模板具体化的情况,则不同

函数模板只是函数的描述,没有实体,创建函数模板的代码放在头文件中

函数模板的具体化有实体,编译的原理和普通函数一样,所以,声明放在头文件中,定义放在源文件中

函数模板高级

decltype 关键字

在这里插入图片描述

auto关键字可以一定程度上解决上面提到的tmp的类型无法确定的问题,但如果我们需要返回tmp的话,又该怎么办?

在C++11中,**decltype**关键字用于查询表达式的数据类型

语法:decltype(expression) var;

decltype分析表达式并得到它的类型,不会计算执行表达式函数调用也是一种表达式,因此不必担心在使用decltype的时候执行了函数。

推导规则

  1. 如果expression是没有用括号括起来的标识符,则var的类型与该标识符相同,包括const等限定符
  2. 如果expression是函数调用,则var的类型与函数的返回值相同(函数不能返回void,但是可以返回void * )
  3. 如果expression是左值(能取地址)(要排除第一种情况)、或者用括号括起来的标识符,则var的类型是expression的引用
    decltype(func()) da;
    decltype(func) fa;
    decltype((func)) da = func;
    
    decltype(a) da;
    // 下面中da是引用类型,需要进行初始化
    decltype((a)) da = a;
    
  4. 如果上面的条件都不满足,则var的类型与expression的类型相同

在实际应用的时候,只要记住**decltype**可以用来推导表达式的类型,返回的是表达式类型或者表达式类型的引用即可,如果报错了就多试几次

decltypeauto都可以用来类型推导,但是两者是不同的

  1. 用auto推导的时候需要初始化,decltype不需要
  2. 下面的例子中,auto会执行函数或者表达式,decltype不会
    decltype(func()) da;
    auto dd = func();
    

左值和右值的区别

函数后置返回类型

在C++11标准中,下面的两种形式是等价的

int func(int x, double y);
// 等同于
auto func(int x, double y) -> int;

这里的auto是一个占位符,C++11中给auto新增的角色,为函数返回值占了一个位置

在这里插入图片描述

C++14标准中,auto可以直接推导函数的返回类型,不需要再使用decltype

<think>我们正在讨论C++中的类模板函数模板,用户希望了解它们的使用场景及语法区别。根据提供的引用,我们可以总结如下:1.函数模板:-定义格式:`template<typenameT>返回类型函数名(参数列表){...}`(或使用class关键字,但通常用typename)-显示实例化:在调用时可以在函数名后的`<>`中指定模板参数的实际类型,例如:`Add<int>(a,b);`-编译器可以根据参数类型自动推导模板参数,因此有时可以省略显式指定。2.类模板:-定义格式:`template<classT1,classT2,...,classTn>class类名{...};`-类模板的成员函数如果在类外定义,需要在每个成员函数定义前加上模板声明,并且使用类名时加上模板参数,如:`template<classT>voidScore<T>::Print(){...}`-类模板不支持分离编译(即声明在头文件,定义在源文件),通常需要将实现也放在头文件中。-实例化类模板时必须显式指定模板参数,例如:`Score<int>s;`区别:-函数模板在调用时通常可以自动推导模板参数,而类模板实例化时必须显式指定模板参数。-类模板的成员函数定义有特殊要求(类外定义必须加模板参数列表),而函数模板则没有这样的额外要求。-使用场景:函数模板主要用于编写通用函数(如交换函数、比较函数等),类模板主要用于编写通用类(如容器、智能指针等)。我们将按照这些要点组织回答,并生成相关问题。</think> ### C++模板 vs 函数模板:使用场景语法区别分析 #### **1. 基本语法对比** | 特性 | 函数模板 | 类模板 | |------|----------|--------| | **定义格式** | `template <typename T> 返回类型 函数名(参数) {...}` | `template <class T> class 类名 {...}` | | **显式实例化** | `函数名<T>(参数)`<br>(例如 `Add<int>(a, b)`) | `类名<T> 对象`<br>(例如 `Score<int> s;`) | | **参数推导** | 支持类型自动推导<br>(如 `Max(10, 20)` 无需指定 `int`) | 必须显式指定类型参数 | | **成员函数定义** | 无特殊要求 | 类外定义需加模板参数:<br>`template <class T> void Score<T>::Print() {...}`[^1][^2] | #### **2. 使用场景对比** - **函数模板适用场景**: - 通用算法实现(如比较、交换、数学运算) - 类型无关的独立函数(如 `std::swap`, `std::max`) - 需参数推导的简单操作 ```cpp // 示例:通用比较函数 [^3] template <typename T> T Max(T a, T b) { return (a > b) ? a : b; } ``` - **类模板适用场景**: - 容器类实现(如 `std::vector`, `std::list`)[^2] - 封装多类型关联数据(如坐标点 `Point<T>`, 成绩单 `Score<T>`) - 需要状态管理的复杂数据结构 ```cpp // 示例:成绩单类 [^1] template <class T> class Score { public: void Print() { /* 输出各科成绩 */ } private: T _Math, _Chinese, _English; }; ``` #### **3. 关键区别详解** | 差异维度 | 函数模板 | 类模板 | |----------|----------|--------| | **编译机制** | 支持分离编译 | 通常需整个定义在头文件中(不支持分离编译)[^1] | | **实例化单位** | 按调用实例化 | 按类型组合实例化(如 `Score<int>` `Score<double>` 为不同类) | | **扩展性** | 可通过重载扩展 | 需通过模板特化或嵌套模板扩展 | | **默认参数** | C++11开始支持 | 支持(如 `template <class T = int> class MyArray`) | #### **4. 选择指南** - **优先函数模板**:当满足 - 逻辑是无状态的独立操作 - 输入/输出类型可自动推导 - 功能不依赖类内部状态 - **优先类模板**:当满足 - 需管理多个关联数据成员 - 需要封装状态和行为(如容器迭代) - 多个函数共享相同类型约束 > **典型案例**: > `std::sort()`(函数模板)实现排序算法,而 `std::vector`(类模板)存储被排序的数据[^2]。二者常结合使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值