首页 > 编程语言 >C++函数模板详解,轻松实现通用函数

C++函数模板详解,轻松实现通用函数

时间:2024-01-04 12:05:04浏览次数:44  
标签:const 函数 C++ Grid Find 模板 size


C++函数模板详解,轻松实现通用函数

函数模板

编写通用函数

您也可以为独立的函数编写模板。其语法与类模板类似。例如,您可以编写以下通用函数来在数组中查找一个值并返回其索引:

static const size_t NOT_FOUND { static_cast<size_t>(-1) };

template <typename T>
size_t Find(const T& value, const T* arr, size_t size) {
    for (size_t i { 0 }; i < size; i++) {
        if (arr[i] == value) {
            return i; // 找到了; 返回索引。
        }
    }
    return NOT_FOUND; // 没找到; 返回 NOT_FOUND。
}

注意:当然,当元素未找到时,您可以不返回某种哨兵值(如 NOT_FOUND),而是改写此代码以返回 std::optional<size_t> 而不是 size_t。这将是使用 optional 的有趣练习。

Find() 函数模板的应用

Find() 函数模板可以在任何类型的数组上工作。例如,您可以用它来在 int 数组中查找 int 的索引,或者在 SpreadsheetCell 数组中查找 SpreadsheetCell。您可以通过两种方式调用该函数:显式地用尖括号指定类型参数,或者省略类型并让编译器从参数中推断出类型参数。以下是一些示例:

int myInt { 3 }, intArray[] {1, 2, 3, 4};
const size_t sizeIntArray { size(intArray) };
size_t res;

// 通过推断调用 Find<int>。
res = Find(myInt, intArray, sizeIntArray);

// 显式地调用 Find<int>。
res = Find<int>(myInt, intArray, sizeIntArray);

// 其他示例
double myDouble { 5.6 }, doubleArray[] {1.2, 3.4, 5.7, 7.5};
const size_t sizeDoubleArray { size(doubleArray) };

// 通过推断调用 Find<double>。
res = Find(myDouble, doubleArray, sizeDoubleArray);

// 显式地调用 Find<double>。
res = Find<double>(myDouble, doubleArray, sizeDoubleArray);

SpreadsheetCell cell1 { 10 }
SpreadsheetCell cellArray[] { SpreadsheetCell { 4 }, SpreadsheetCell { 10 } };
const size_t sizeCellArray { size(cellArray) };
res = Find(cell1, cellArray, sizeCellArray);
res = Find<SpreadsheetCell>(cell1, cellArray, sizeCellArray);
数组大小的自动推断

之前的 Find() 函数实现需要作为参数之一的数组大小。有时编译器知道数组的确切大小,例如,对于基于堆栈的数组。能够在不需要传递数组大小的情况下调用 Find() 会很方便。这可以通过添加以下函数模板来实现。实现只是将调用转发到之前的 Find() 函数模板。这也表明,函数模板可以像类模板一样接受非类型参数。

template <typename T, size_t N>
size_t Find(const T& value, const T(&arr)[N]) {
    return Find(value, arr, N);
}

这个版本的 Find() 语法看起来有点奇怪,但其使用非常直接,如下例所示:

int myInt { 3 }, intArray[] {1, 2, 3, 4};
size_t res { Find(myInt, intArray) };
函数模板的定义和导出

与类模板方法定义一样,函数模板的定义(不仅仅是原型)必须对使用它们的所有源文件可用。因此,如果多个源文件使用它们,您应该将定义放在模

块接口文件中并导出它们。

最后,函数模板的模板参数可以像类模板一样有默认值。

注意:C++ 标准库提供了一个比这里展示的 Find() 函数模板更强大的 std::find() 函数模板。

函数模板重载

函数模板与特化

理论上,C++ 语言允许您编写函数模板特化,就像您可以编写类模板特化一样。然而,您很少需要这样做,因为这样的函数模板特化不参与重载解析,因此可能表现出意外的行为。相反,您可以用非模板函数重载函数模板。例如,您可能想为 const char* 类型的 C 风格字符串编写一个 Find() 重载,这个重载使用 strcmp()来比较,而不是用 operator==,因为 == 只会比较指针,而不是实际的字符串。以下是这样的重载:

size_t Find(const char* value, const char** arr, size_t size) {
    for (size_t i { 0 }; i < size; i++) {
        if (strcmp(arr[i], value) == 0) {
            return i; // 找到了; 返回索引。
        }
    }
    return NOT_FOUND; // 没找到; 返回 NOT_FOUND。
}

这个函数重载的使用方式如下:

const char* word { "two" };
const char* words[] { "one", "two", "three", "four" };
const size_t sizeWords { size(words) };
size_t res { Find(word, words, sizeWords) }; // 调用非模板函数。

如果您显式指定模板类型参数,如下所示,那么将调用函数模板,其中 T=const char*,而不是 const char* 的重载:

res = Find<const char*>(word, words, sizeWords);
重载与特化的选择

在选择重载函数模板时,应该考虑到函数模板特化可能不参与重载解析的规则。通常,重载函数模板与非模板函数是一种更安全且可预测的方法,特别是当涉及到特定类型的特定处理,如在处理 C 风格字符串时使用 strcmp() 而不是默认的等号运算符。

类模板的友元函数模板

重载运算符的函数模板

当您想在类模板中重载运算符时,函数模板非常有用。例如,您可能想为 Grid 类模板重载加法运算符(operator+),以便将两个网格相加。结果将是一个与两个操作数中较小的 Grid 同等大小的 Grid。仅当两个单元格都包含实际值时,才会相加对应的单元格。假设您想让您的 operator+ 成为一个独立的函数模板。定义应该放在 Grid.cppm 模块接口文件中,如下所示。该实现使用 <algorithm> 中定义的 std::min() 来返回两个给定参数的最小值:

export template <typename T>
Grid<T> operator+(const Grid<T>& lhs, const Grid<T>& rhs) {
    size_t minWidth { std::min(lhs.getWidth(), rhs.getWidth()) };
    size_t minHeight { std::min(lhs.getHeight(), rhs.getHeight()) };
    Grid<T> result { minWidth, minHeight };
    
    for (size_t y { 0 }; y < minHeight; ++y) {
        for (size_t x { 0 }; x < minWidth; ++x) {
            const auto& leftElement { lhs.m_cells[x][y] };
            const auto& rightElement { rhs.m_cells[x][y] };
            if (leftElement.has_value() && rightElement.has_value()) {
                result.at(x, y) = leftElement.value() + rightElement.value();
            }
        }
    }
    return result;
}

要查询一个 optional 是否包含实际值,您使用 has_value() 方法,而 value() 用于检索这个值。这个函数模板适用于任何 Grid,只要网格中存储的元素类型有加法运算符。这个实现的唯一问题是它访问了 Grid 类的私有成员 m_cells。一个显而易见的解决方案是使用公共 at() 方法,但让我们看看如何让函数模板成为类模板的友元。

使函数模板成为类模板的友元

在此示例中,您可以使运算符成为 Grid 类的友元。然而,Grid 类和 operator+ 都是模板。您真正想要的是,对于特定类型 T 的每个 operator+ 实例化,都是同一类型的 Grid 模板实例化的友元。语法如下:

// 前向声明 Grid 模板。
export template <typename T>
class Grid;

// templatized operator+ 的原型。
export template <typename T>
Grid<T> operator+(const Grid<T>& lhs, const Grid<T>& rhs);

export template <typename T>
class Grid {
public:
    friend Grid operator+<T>(const Grid& lhs, const Grid& rhs);
    // 省略其他内容
};

这种友元声明很微妙:您在说,对于类型 T 的模板实例,operator+T 实例化是一个友元。换句话说,类实例化和函数实例化之间存在一对一的友元映射。特别注意 operator+ 上的显式模板规范 <T>。这个语法告诉编译器 operator+ 本身是一个模板。

模板参数推断

编译器根据传递给函数模板的参数推断函数模板参数的类型。无法推断的模板参数必须明确指定。例如,以下 add() 函数模板需要三个模板参数:返回值的类型和两个操作数的类型:

template <typename RetType, typename T1

, typename T2>
RetType add(const T1& t1, const T2& t2) {
    return t1 + t2;
}

您可以通过以下方式调用此函数模板,明确指定所有三个参数:

auto result { add<long long, int, int>(1, 2) };

然而,因为模板参数 T1T2 是函数的参数,编译器可以推断这两个参数,所以您可以只指定返回值类型来调用 add()

auto result { add<long long>(1, 2) };

这只有在要推断的参数位于参数列表的最后时才有效。假设函数模板如下定义:

template <typename T1, typename RetType, typename T2>
RetType add(const T1& t1, const T2& t2) {
    return t1 + t2;
}

您必须指定 RetType,因为编译器无法推断该类型。然而,由于 RetType 是第二个参数,您也必须显式指定 T1

auto result { add<int, long long>(1, 2) };

您还可以为返回类型模板参数提供默认值,以便在不指定任何类型的情况下调用 add()

template <typename RetType = long long, typename T1, typename T2>
RetType add(const T1& t1, const T2& t2) {
    return t1 + t2;
}
...
auto result { add(1, 2) };


标签:const,函数,C++,Grid,Find,模板,size
From: https://blog.51cto.com/u_16062556/9098364

相关文章

  • 刷题笔记——顺序表(C++)
    665.非递减数列-力扣(LeetCode)给你一个长度为 n 的整数数组 nums ,请你判断在 最多 改变 1 个元素的情况下,该数组能否变成一个非递减数列。我们是这样定义一个非递减数列的: 对于数组中任意的 i (0<=i<=n-2),总满足 nums[i]<=nums[i+1]。解题思路遍历数组,计算递......
  • C++汇总路径下全部文件名并提取出指定类型或名称的文件
      本文介绍基于C++语言,遍历文件夹中的全部文件,并从中获取指定类型的文件的方法。  首先,我们来明确一下本文所需实现的需求。现在有一个文件夹,其中包含了很多文件,如下图所示;我们如果想获取其中所有类型为.bmp格式的文件的名称,如果文件数量比较多的话,手动筛选就会很麻烦。而借......
  • mysql8.0存储函数
    4、存储函数的使用4.1、语法分析学过的函数:LENGTH、SUBSTR、CONCAT等语法格式CREATEFUNCTION函数名(参数名参数类型,...)RETURNS返回值类型[characteristics...]BEGIN函数体#函数体中肯定有RETURN语句END说明:1、参数列表:指定参数为IN、OUT或INOUT只对PROCE......
  • 无涯教程-Java 正则 - XY 匹配函数
    逻辑运算符[XY]匹配X,后跟Y。XY-示例以下示例显示了逻辑运算符的用法。packagecom.learnfk;importjava.util.regex.Matcher;importjava.util.regex.Pattern;publicclassLogicalOperatorDemo{privatestaticfinalStringREGEX="to";privatestaticfin......
  • C++11中的匿名函数用法
    C++11中引用了匿名函数这一个新的特性,它的使用方法如下:[capture](parameters)->return_type{body} 其中:capture 指定了Lambda表达式可以访问的外部变量parameters 是Lambda表达式的参数列表return_type 是返回类型(可选)body 是Lambda函数体下面是一个简单......
  • React函数式组件避免无用渲染的方案
    在class组件中可以使用shouldComponentUpdate钩子函数,但是函数式组件中是没有这种钩子函数的,那么在函数式组件中来达到类似的效果呢?答案是:React.Memo,如以下使用案例://父组件const[values,setValues]=useState({a:1,b:1,});functionincrement(){......
  • 无涯教程-Java 正则 - X|Y 匹配函数
    逻辑运算符[X|Y]匹配X或Y。X|Y-示例以下示例显示了逻辑运算符的用法。packagecom.learnfk;importjava.util.regex.Matcher;importjava.util.regex.Pattern;publicclassLogicalOperatorDemo{privatestaticfinalStringREGEX="t|o";privatestatic......
  • 无涯教程-Java 正则 - X{n}+ 匹配函数
    PossesiveQuantifier[X{n}+]与存在的X个精确匹配n次。X{n}+-示例packagecom.learnfk;importjava.util.regex.Matcher;importjava.util.regex.Pattern;publicclassPossesiveQuantifierDemo{privatestaticfinalStringREGEX="T{2}+";privatestat......
  • 无涯教程-Java 正则 - X++ 匹配函数
    PossesiveQuantifier[X++]与X匹配一次或多次。X++-示例packagecom.learnfk;importjava.util.regex.Matcher;importjava.util.regex.Pattern;publicclassPossesiveQuantifierDemo{privatestaticfinalStringREGEX="T++";privatestaticfinalS......
  • 深入理解 LockWindowUpdate: 该函数的作用
    今天说说被误解的LockWindowUpdate。这是LockWindowUpdate系列中的第一篇,我将会讲讲它的作用、用途以及(也许最重要的是)对它的误用。LockWindowUpdate的作用非常简单。当一个窗口被锁定时,所有试图绘制它或其子窗口的尝试都会失败。窗口管理器不会进行绘制,而是记住应用程序尝试......