在 C++ 中,如果函数具有不同数量的参数或不同的参数类型,则多个函数可以具有相同的名称。对多个函数使用相同的名字被称为重载,在 C++ 中很常见。
所有编程语言都在某种程度上使用重载。例如,大多数语言使用+
进行整数加法和浮点加法。有些语言,如 Pascal,对整数除法(div
)和浮点除法(/
)使用不同的运算符,但其他语言,如 C 和 Java,使用相同的运算符(/
)。
C++ 将重载向前推进了一步,允许重载自己的函数名。明智地使用重载可以大大降低程序的复杂性,使程序更容易阅读和理解。
例如,C++ 从标准 C 库中继承了几个计算绝对值的函数:abs
接受一个int
参数;fabs
采取浮点论证;而labs
需要一个长整型参数。
Note
不要担心我还没有介绍这些其他类型。对于这个讨论的目的来说,重要的是它们与int
不同。下一次探索将开始更仔细地检查它们,所以请耐心等待。
C++ 对于复数也有自己的complex
类型,有自己的绝对值函数。然而在 C++ 中,它们都有相同的名字,std::abs
。对不同的类型使用不同的名称只会使思维混乱,对代码的清晰性没有任何帮助。
仅举一个例子,sort
函数有两种重载形式:
std::sort(start, end);
std::sort(start, end, compare);
(std::ranges::sort
功能因使用ranges
而不同,这将在探索 47 中解释。)第一个表单按升序排序,用<
操作符比较元素,第二个表单通过调用compare
比较元素。重载出现在标准库中的许多其他地方。例如,当您创建一个locale
对象时,您可以通过不传递参数来复制全局语言环境
std::isalpha('X', std::locale{});
或者通过传递空字符串参数来创建本机区域设置对象
std::isalpha('X', std::locale{""});
重载函数很容易,为什么不直接加入呢?**写一组函数,都命名为 print。**它们都有一个void
返回类型并接受各种参数:
-
一个将一个
int
作为参数。它将参数打印到标准输出。 -
另一个需要两个
int
参数。它将第一个参数打印到标准输出,并将第二个参数用作字段宽度。 -
另一个将一个
vector<int>
作为第一个参数,后面跟着三个string_view
参数。打印第一个string_view
参数,然后是vector
的每个元素(通过调用print
),元素之间是第二个string_view
参数,第三个string_view
参数在vector
之后。如果vector
为空,仅打印第一个和第三个string_view
参数。 -
另一个与
vector
表单具有相同的参数,但是也采用一个int
作为每个vector
元素的字段宽度。
使用打印功能编写一个程序来打印矢量。将你的功能和程序与清单 25-1 中我的进行比较。
import <iostream>;
import <string_view>;
import <vector>;
void print(int i)
{
std::cout << i;
}
void print(int i, int width)
{
std::cout.width(width);
std::cout << i;
}
void print(std::vector<int> const& vec,
int width,
std::string_view prefix,
std::string_view separator,
std::string_view postfix)
{
std::cout << prefix;
bool print_separator{false};
for (auto x : vec)
{
if (print_separator)
std::cout << separator;
else
print_separator = true;
print(x, width);
}
std::cout << postfix;
}
void print(std::vector<int> const& vec,
std::string_view prefix,
std::string_view separator,
std::string_view postfix)
{
print(vec, 0, prefix, separator, postfix);
}
int main()
{
std::vector<int> data{ 10, 20, 30, 40, 100, 1000, };
std::cout << "columnar data:\n";
print(data, 10, "", "\n", "\n");
std::cout << "row data:\n";
print(data, "{", ", ", "}\n");
}
Listing 25-1.Printing Vectors by Using Overloaded Functions
C++ 库经常使用重载。例如,您可以通过调用resize
成员函数来改变vector
的大小。您可以传递一两个参数:第一个参数是vector
的新大小。如果您传递第二个参数,它是一个用于新元素的值,以防新的大小大于旧的大小。
data.resize(10); // if the old size < 10, use default of 0 for new elements
data.resize(20, -42); // if the old size < 20, use -42 for new elements
库作者经常使用重载,但是应用程序程序员很少使用它。通过编写以下函数来练习编写库:
如果ch
是全球语言环境中的字母字符,则返回true
;如果没有,返回false
。
如果str
只包含全球语言环境中的字母字符,则返回true
;如果任何字符不是字母,则返回false
。如果str
为空,则返回true
。
如果可能,将其转换为小写后返回ch
;否则,返回ch
。使用全球语言环境。
将str
的内容转换成小写字母后,每次返回一个字符。逐字复制任何不能转换成小写的字符。
如果可能的话,转换成大写后返回ch
;否则,返回ch
。使用全球语言环境。
将str
的内容转换成大写后,返回其副本,一次一个字符。逐字复制任何不能转换为大写的字符。
将您的解决方案与我的进行比较,如清单 25-2 所示。
import <algorithm>;
import <iostream>;
import <locale>;
import <ranges>;
import <string>;
import <string_view>;
bool is_alpha(char ch)
{
return std::isalpha(ch, std::locale{});
}
bool is_alpha(std::string_view str)
{
return std::ranges::all_of(str, [](char c) { return is_alpha(c); });
}
char to_lower(char ch)
{
return std::tolower(ch, std::locale{});
}
std::string to_lower(std::string_view str)
{
auto data{str | std::views::transform([](char c) { return to_lower(c); })};
return std::string{ std::ranges::begin(data), std::ranges::end(data) };
}
char to_upper(char ch)
{
return std::toupper(ch, std::locale{});
}
std::string to_upper(std::string str)
{
for (char& ch : str)
ch = to_upper(ch);
return str;
}
int main()
{
std::string str{};
while (std::cin >> str)
{
if (is_alpha(str))
std::cout << "alpha\n";
else
std::cout << "not alpha\n";
std::cout << "lower: " << to_lower(str) << "\n"
"upper: " << to_upper(str) << '\n';
}
}
Listing 25-2.Overloading Functions in the Manner of a Library Writer
我通过调用std::ranges::all_of
算法实现了to_lower
,该算法为一个范围内的每个元素调用一个谓词,如果谓词为所有元素返回 true,则返回 true。如果谓词返回 false,那么 all_of 将停止对该范围的迭代,并立即返回 false。
为了多样化,我实现了完全不同的to_upper
。我使用了一个扭转的远程for
循环。当循环迭代字符串时,这个循环实际上修改了字符元素。像按引用传递函数参数一样,声明char& ch
是对范围内字符的引用。因此,赋值给ch
会改变字符串中的字符。注意auto
也可以声明一个引用。如果范围中的每个元素都很大,应该使用引用来避免不必要的复制。如果不需要修改元素,使用const
参考,如下所示:
for (auto const& big_item : container_full_of_big_things)
另一个区别是to_upper
采用一个普通的string
作为它的参数。这意味着参数通过值传递,这又意味着编译器在将参数传递给函数时安排复制字符串。该函数需要复制,因此这种技术有助于编译器生成复制参数的最佳代码,并为您节省了编写函数的步骤。这是一个小技巧,但很有用。这项技术在本书的后面会特别有用——所以不要忘记它。
is_alpha
字符串函数不修改它的参数,所以它可以使用string_view
类型。
重载的一个常见用途是重载不同类型的函数,包括不同的整数类型,如长整型。下一篇文章将研究这些其他类型。