由于编译器已经必须从 return 语句中推导出返回类型,因此在 C++14 中,该auto关键字被扩展为进行函数返回类型推导。这是通过使用auto关键字代替函数的返回类型来实现的。
auto add(int x, int y)
{
return x + y;
}
因为 return 语句是返回一个int值,所以编译器会推断出这个函数的返回类型是int。使用auto返回类型时,所有返回值的类型必须相同,否则会导致错误。例如:
auto someFcn(bool b)
{
if (b)
return 5; // return type int
else
return 6.7; // return type double
}
注意: 使用auto返回类型的函数的一个主要缺点是这些函数在使用之前必须完全定义(前向声明是不够的)。
#include
auto foo();
int main()
{
std::cout << foo(); // the compiler has only seen a forward declaration at this point
return 0;
}
auto foo()
{
return 5;
}
//错误 C3779:“foo”:在定义之前不能使用返回“auto”的函数。
直接将函数定义放在主函数之前不会出现报错。
原因:向前声明没有足够的信息提供给编译器推断函数的返回类型,这意味着返回的普通函数auto通常只能从定义它们的文件中调用。
该auto关键字还可用于使用尾随返回语法声明函数,其中返回类型在函数原型的其余部分之后指定。
参考以下函数
int add(int x, int y)
{
return (x + y);
}
//使用尾随语法等效
auto add(int x, int y) -> int
{
return (x + y);
}
//在这种情况下,auto不执行类型推导——它只是使用尾随返回类型的语法的一部分。
那我们为什么要使用这种语法结构呢?
#include
void addAndPrint(auto x, auto y)
{
std::cout << x + y;
}
int main()
{
addAndPrint(2, 3); // case 1: call addAndPrint with int parameters
addAndPrint(4.5, 6.7); // case 2: call addAndPrint with double parameters
}
类型推导对于函数参数并不适用,并且在C++20之前上述程序无法编译成功(函数参数无法具有自动类型的错误)
而在C++20中,auto关键字被拓展,以便于程序能运行成功,但是auto在此类情况下不会调用类型推导,而是会触发一个function templates,此功能在实际处理此类情况。
关于function templates:链接:
模板(Templates)使得我们可以生成通用的函数,这些函数能够接受任意数据类型的参数,可返回任意类型的值,而不需要对所有可能的数据类型进行函数重载。这在一定程度上实现了宏(macro)的作用。它们的原型定义可以是下面两种中的任何一个:
template function_declaration;
template function_declaration;
函数重载允许我们创建多个同名函数,只要每个同名的函数都有不同的参数(或者是参数可以其他方式区分),名称共享的函数称为重载函数。
我们现在add()在同一范围内有两个版本:
int add(int x, int y) // integer version
{
return x + y;
}
double add(double x, double y) // floating point version
{
return x + y;
}
int main()
{
return 0;
}
本质:要使编译器可以区分每个重载函数,就可以重载函数,如果无法区分,则会导致编译错误。
相关补充:C++中的运算符只是函数,所以运算符也可以重载(13.1运算符重载中可以讨论这一点)
1.当对已重载的函数进行函数调用时,编译器将尝试根据函数调用中使用的参数将函数调用与适当的重载相匹配。这称为重载决议。
#include
int add(int x, int y)
{
return x + y;
}
double add(double x, double y)
{
return x + y;
}
int main()
{
std::cout << add(1, 2); // calls add(int, int)
std::cout << '\n';
std::cout << add(1.2, 3.4); // calls add(double, double)
return 0;
}
//编译结果 3 4.6
2.编译
为了编译使用重载函数的程序,有两件事必须是正确的:
区分重载函数的最简单方法是确保每个重载函数具有不同的参数集(数量和/或类型)。
基于参数数量的重载
int add(int x, int y)
{
return x + y;
}
int add(int x, int y, int z)
{
return x + y + z;
}
//有几个参数转到那个函数那里去
//基于参数类型的重载
//只要每个重载函数的参数类型集是不同的,也可以区分。
int add(int x, int y); // integer version
double add(double x, double y); // floating point version
double add(int x, double y); // mixed version
double add(double x, int y); // mixed version
因为类型别名(或 typedef)不是不同的类型,所以使用类型别名的重载函数与使用别名类型的重载没有区别。例如,以下所有重载都没有区别(并且会导致编译错误):
typedef int height_t; // typedef
using age_t = int; // type alias
void print(int value);
void print(age_t value); // not differentiated from print(int)
void print(height_t value); // not differentiated from print(int)
对于按值传递的参数,也不考虑 const 限定符。因此,以下功能不被认为是有区别的:
void print(int);
void print(const int); // not differentiated from print(int)
//其中”.......“参数被认为是一种独特的参数类型
void foo(int x, int y);
void foo(int x, ...); // differentiated from foo(int, int)
注意:函数的返回类型不考虑微分,也就是说”double add()“和”int add()“编译器会出现报错行为,根据返回类型不足以编译器识别并确定是哪一个重载函数,意味着还需要更多的分析。
编译器将函数调用与特定的重载函数匹配的过程称为重载解析。
分析一下例子,编译器是不是真的没法匹配到重载函数
#include
void print(int x)
{
std::cout << x;
}
void print(double d)
{
std::cout << d;
}
int main()
{
print('a'); // char does not match int or double
print(5l); // long does not match int or double
return 0;
}
答案是否定,没有完全匹配并不代表着找不到匹配,char或者是long可以隐式转换为int或者是double,但在哪一种情况下,哪一种是最好的转换?
当对重载函数进行函数调用时,编译器会逐步执行一系列规则,以确定哪个(如果有)重载函数最匹配。
在每一步,编译器都会对函数调用中的参数应用一系列不同的类型转换。对于应用的每个转换,编译器检查现在是否有任何重载函数匹配。在应用了所有不同的类型转换并检查匹配之后,该步骤就完成了。结果将是三种可能的结果之一:
1.编译器尝试找到完全匹配。
首先,查看是否存在重载函数,查看其调用的参数类型与重载函数中的参数类型完全匹配。
举例:
void print(int)
{
}
void print(double)
{
}
int main()
{
print(0); // exact match with print(int)
print(3.4); // exact match with print(double)
return 0;
}
//其中0是和int对应,3.4是和double对应的,所以能够完全匹配
其次,编译器对函数调用中的参数应用一些简单的转换,例如非常量类型可以简单的转换为常量类型。
void print(const int)
{
}
void print(double)
{
}
int main()
{
int x {
0 };//int x = 0;
print(x); // x trivially converted to const int
return 0;
}
//在此次示例中,调用print(x),x是int类型,编译器将简单的x从int转换为了const int ,然后匹配到了print(const int)。
通过简单转换完成的匹配将被视为完全匹配。
2.如果没有找到完全匹配,编译器将尝试通过参数应用数字提升来找匹配。
void print(int)
{
}
void print(double)
{
}
int main()
{
print('a'); // promoted to match print(int)
print(true); // promoted to match print(int)
print(4.5f); // promoted to match print(double)
return 0;
}
//对于print('a'),因为print(char)在前面的步骤中找不到与的完全匹配,编译器将字符'a'提升为int,并寻找匹配。这匹配print(int),因此函数调用解析为print(int)。
```3.如果通过数字提升找不到匹配项,编译器会尝试通过数字转换应该于参数来找到匹配项。
```c
#include // for std::string
void print(double)
{
}
void print(std::string)
{
}
int main()
{
print('a'); // 'a' converted to match print(double)
return 0;
}
//在这种情况下,因为没有print(char)(完全匹配),也没有print(int)(促销匹配),将'a'数字转换为双精度并与print(double).
4.如果通过数字转换未找到匹配项,编译器将尝试通过任何用户定义的转换找到匹配项。尽管我们还没有介绍用户定义的转换,但某些类型(例如类)可以定义到其他可以隐式调用的类型的转换。这是一个例子,只是为了说明这一点:
// We haven't covered classes yet, so don't worry if this doesn't make sense
class X
{
public:
operator int() {
return 0; } // Here's a user-defined conversion from X to int
};
void print(int)
{
}
void print(double)
{
}
int main()
{
X x;
print(x); // x is converted to type int using the user-defined conversion from X to int
return 0;
}
//在这个例子中,编译器会先检查精确匹配是否print(X)存在。我们还没有定义的。接下来,编译器会检查是否x可以促进数字,它不能。然后,编译器将检查是否x可以数字转换,它也不能。最后,编译器会再寻找任何用户定义的转换。因为我们从定义一个用户定义的转换X到int,编译器将其转换X成int相匹配print(int)。
//应用用户定义的转换之后,编译器可以应用隐式转换为找到一个匹配。所以,如果我们的用户定义的转换已经输入char相反,编译器会使用用户定义的转换char,然后推动的结果为int匹配。
5.如果通过用户定义的转换没有发现匹配,则编译器将寻找一个匹配的函数,它使用省略号。
6.如果此时没有找到匹配项,编译器将放弃并发出关于无法找到匹配函数的编译错误。
编译器不能达到完全匹配的效果时,会尝试进行转换进而匹配,但是转换之后发现两个重载函数都可以,那么这个时候函数的调用将被视为是不明确的。
void print(int x)
{
}
void print(double d)
{
}
int main()
{
print(5l); // 5l is type long
return 0;
}
由于文字5l是类型long,编译器会先看看,看看它是否可以找到完全匹配print(long),但它不会找到一个。接下来,编译器将尝试数值提升,但类型的值long不能被提拔,所以这里没有比赛无论是。
随后,编译器将尝试通过应用数字转换到找到匹配long的说法。在检查所有的数字转换规则的过程中,编译器会发现两个潜在的匹配。如果long参数进行数值转换成一个int,则该函数调用将匹配print(int)。如果long参数,而不是转换成一个double,那么它将匹配print(double)代替。由于通过数字转换两种可能的比赛已经发现,函数调用被认为是不明确的。
另一个例子
void print(unsigned int x)
{
}
void print(float y)
{
}
int main()
{
print(0); // int can be numerically converted to unsigned int or to float
print(3.14159); // double can be numerically converted to unsigned int or to float
return 0;
}
尽管您可能希望0解析为print(unsigned int)并3.14159解析为print(float),但这两个调用都会导致不明确的匹配。该int值0可以在数字上转换为 anunsigned int或 a float,因此任何一个重载都同样匹配,结果是一个不明确的函数调用。
这同样适用于一个转换double到任何一个float或unsigned int。无论是数字转换,因此无论是过载相匹配同样出色,其结果是再次明确。
提问:假如我们需要使用一个比较大小的函数,该如何实现呢?
int max(int x, int y)
{
return (x > y) ? x : y;
}
double max(double x, double y)
{
return (x > y) ? x: y;
}
//我们意识到对于不同的类型需要重新构造函数,尽管他们的结构一模一样。
所以欢迎来到C++模板!
目的: 简化创建能够与不同的数据类型的功函数(或类)的过程。
我们不是手动创建一堆几乎相同的函数或类(每组不同类型一个),而是创建一个template就像普通定义一样,模板描述了函数或类的样子。与普通定义(必须指定所有类型)不同,在模板中我们可以使用一种或多种占位符类型。占位符类型表示在编写模板时未知的某种类型,但稍后会提供。
一旦定义了模板,编译器就可以根据需要使用模板生成尽可能多的重载函数(或类),每个函数使用不同的实际类型!
最终结果是一样的——我们最终得到了一堆几乎相同的函数或类(每组不同类型一个)。但是我们只需要创建和维护一个模板,编译器就会为我们完成所有繁重的工作。
注意:
函数模板是一个函数类定义是,用于生成一个或多个重载函数,每个具有一组不同的实际类型。
当我们创建函数模板时,我们将占位符类型(也称为模板类型)用于任何参数类型、返回类型或稍后要指定的函数体中使用的类型。
要创建函数模板,我们要做两件事。首先,我们将用模板类型替换我们的特定类型。在这种情况下,因为我们只有一种类型需要替换 ( int),所以我们只需要一种模板类型。使用单个大写字母(以 T 开头)来表示模板类型是常见的约定。
int max(int x, int y)
{
return (x > y) ? x : y;
}
//模板
T max(T x, T y) // won't compile because we haven't defined T
{
return (x > y) ? x : y;
}
//这是一个好的开始——但是,它不会编译,因为编译器不知道是什么T!而且这还是一个普通的函数,不是函数模板。
其次,我们要告诉编译器这是一个函数模板,这T是一个模板类型。这是使用所谓的模板参数声明完成的:
template <typename T> // this is the template parameter declaration
T max(T x, T y) // this is the function template definition for max
{
return (x > y) ? x : y;
}
因为这个函数模板有一个名为 的模板类型T,我们将其称为max。
让我们稍微仔细看看模板参数声明。我们从关键字开始template,它告诉编译器我们正在创建一个模板。接下来,我们在尖括号 () 内指定我们的模板将使用的所有模板类型。对于每个模板类型,我们使用关键字typenameor class,后跟模板类型的名称(例如T)。
每个模板函数(或模板类)都需要自己的模板参数声明。
本节内容主要是学习如何使用函数模板
函数模板实际上并不是函数——它们的代码不是直接编译或执行的。相反,函数模板只有一项工作:生成函数(编译和执行)。
关于在上节中使用的max函数,使用语法为:
max<actual_type>(arg1, arg2);
// actual_type 是某种实际类型,如 int 或 double
举例:
#include
template <typename T>
T max(T x, T y)//函数模板
{
return (x > y) ? x : y;
}
int main()
{
std::cout << max<int>(1, 2) << '\n'; // instantiates and calls function max(int, int)调用函数模板
return 0;
}
//当编译器遇到函数调用时max(1, 2),它会确定max(int, int)不存在的函数定义。
//因此,编译器将使用我们的max函数模板来创建一个。
从函数模板(具有模板类型)创建函数(具有特定类型)的过程称为函数模板实例化(或简称实例化)。当这个过程由于函数调用而发生时,它被称为隐式实例化。实例化的函数通常称为函数实例(简称实例)或模板函数。函数实例在各方面都是普通函数。
在参数类型与我们想要的实际类型匹配的情况下,我们不需要指定实际类型——相反,我们可以使用模板参数推导让编译器从参数类型中推导出应该使用的实际类型在函数调用中。
std::cout << max<int>(1, 2) << '\n';
// specifying we want to call max
//函数推导可以修改为
std::cout << max<>(1, 2) << '\n';
std::cout << max(1, 2) << '\n';
在任何一种情况下,编译器都会看到我们没有提供实际类型,因此它将尝试从函数参数中推导出实际类型,这将允许它生成一个max()函数,其中所有模板参数都与所提供参数的类型相匹配. 在此示例中,编译器将推断使用max具有实际类型的函数模板int允许它实例化max(int, int)两个模板参数 (int) 的类型与提供的参数 (int)的类型匹配的函数。
这两种情况之间的区别与编译器如何解析一组重载函数的函数调用有关。在最上面的情况下(带有空的尖括号),编译器max在确定调用哪个重载函数时只会考虑模板函数重载。在底部情况下(没有尖括号),编译器将同时考虑max模板函数重载和max非模板函数重载。
#include
template <typename T>
T max(T x, T y)
{
return (x > y) ? x : y;
}
int max(int x, int y)
{
return (x > y) ? x : y;
}
int main()
{
std::cout << max<int>(1, 2) << '\n'; // selects max
std::cout << max<>(1, 2) << '\n'; // deduces max(int, int) (non-template functions not considered)
std::cout << max(1, 2) << '\n'; // calls function max(int, int)
return 0;
}
具有非模板参数的函数模板,可以创建具有模板类型和非模板类型参数的函数模板。模板参数可以匹配任何类型,非模板参数就像普通函数的参数一样工作。
template <typename T>
int someFcn (T x, double y)
{
return 5;
}
int main()
{
someFcn(1, 3.4); // matches someFcn(int, double)
someFcn(1, 3.4f); // matches someFcn(int, double) -- the float is promoted to a double
someFcn(1.2, 3.4); // matches someFcn(double, double)
someFcn(1.2f, 3.4); // matches someFcn(float, double)
someFcn(1.2f, 3.4f); // matches someFcn(float, double) -- the float is promoted to a double
return 0;
}
这个函数模板有一个模板化的第一个参数,但第二个参数固定为 type double。请注意,返回类型也可以是任何类型。在这种情况下,我们的函数将始终返回一个int值。
实例化的函数可能并不总是编译的
#include
template <typename T>
T addOne(T x)
{
return x + 1;
}
int main()
{
std::cout << addOne(1) << '\n';
std::cout << addOne(2.3) << '\n';
return 0;
}
//2 3.3
但是如果我们尝试做以下的事情呢?
#include
#include
template <typename T>
T addOne(T x)
{
return x + 1;
}
int main()
{
std::string hello {
"Hello, world!" };
std::cout << addOne(hello) << '\n';
return 0;
}
当编译器尝试解析时,addOne(hello)它不会找到与之对应的模板函数匹配addOne(std::string),但它会找到我们的函数模板addOne(T),并确定它可以从中生成一个addOne(std::string)函数。因此,编译器将生成并编译:
#include
#include
template <typename T>
T addOne(T x);
template<>
std::string addOne<std::string>(std::string x)
{
return x + 1;
}
int main()
{
std::string hello{
"Hello, world!" };
std::cout << addOne(hello) << '\n';
return 0;
}
然而,这将产生一个编译错误,因为x + 1当没有意义x是std::string。这里显而易见的解决方案是不addOne()使用 type 参数进行调用std::string。
因为模板类型可以替换为任何实际类型,所以模板类型有时被称为泛型类型。并且因为模板可以不可知地编写为特定类型,所以使用模板编程有时称为泛型编程。C++通常非常关注类型和类型检查,相比,泛型编程让我们专注于算法的逻辑和数据结构的设计,而不必太担心类型信息。
在上面几节的学习中我们学习了函数模板的相关知识
分析一下例子,程序是否能够运行
#include
template <typename T>
T max(T x, T y)
{
return (x > y) ? x : y;
}
int main()
{
std::cout << max(2, 3.5) << '\n'; // compile error
return 0;
}
编译之后你还会发现报错,出现"int",“double”,"不明确"等字样,实际上,我们使用了两种参数类型,一种是int和double。因为我们在不使用尖括号指定实际类型的情况下进行函数调用,所以编译器将首先查看是否存在与max(int, double),它不会找到一个。
接下来,编译器将查看它是否可以找到一个函数模板匹配(使用模板参数推导,我们在第8.14- 函数模板实例化中介绍过)。但是,这也会失败,原因很简单:T只能表示单一类型。没有任何类型T允许编译器将函数模板实例max(T, T)化为具有两种不同参数类型的函数。换句话说,因为函数模板中的两个参数都是 type T,所以它们必须解析为相同的实际类型。
但是此时存在一个疑问,为什么编译器不能生成函数max(double,double),然后将类型int转换成double呢?答案很简单:类型转换仅在解析函数重载的时候进行,而不是在执行模板参数推导的时候进行。这类模式使得编程没有相对复杂化,确保参数类型一致。
下面我们将学习三种方法解决此类问题:
#include
template <typename T>
T max(T x, T y)
{
return (x > y) ? x : y;
}
int main()
{
std::cout << max(static_cast<double>(2), 3.5) << '\n'; // convert our int to a double so we can call max(double, double)
return 0;
}
//现在两个参数都是 type double,编译器将能够实例化
//max(double, double)以满足此函数调用。
include <iostream>
double max(double x, double y)
{
return (x > y) ? x : y;
}
int main()
{
std::cout << max(2, 3.5) << '\n'; // the int argument will be converted to a double
return 0;
}
如果我们编写了一个非模板max(double, double)函数,那么我们将能够调用max(int, double)并让隐式类型转换规则将我们的int参数转换为 adouble以便可以解析函数调用:
但是,当编译器进行模板参数推导时,它不会进行任何类型转换。幸运的是,如果我们指定要使用的实际类型,则不必使用模板参数推导:
#include
template <typename T>
T max(T x, T y)
{
return (x > y) ? x : y;
}
int main()
{
std::cout << max<double>(2, 3.5) << '\n'; // we've provided actual type double, so the compiler won't use template argument deduction
return 0;
}
在上面的例子中,我们调用max(2, 3.5). 因为我们已明确指定T应替换为double,所以编译器不会使用模板参数推导。相反,它只会实例化 function max(double, double),然后键入 convert 任何不匹配的参数。我们的int参数将被隐式转换为double。虽然这比static_cast更具可读性,但如果我们在进行函数调用时根本不必考虑类型,那就更好。
#include
template <typename T, typename U> // We're using two template type parameters named T and U
T max(T x, U y) // x can resolve to type T, and y can resolve to type U
{
return (x > y) ? x : y; // uh oh, we have a narrowing conversion problem here
}
int main()
{
std::cout << max(2, 3.5) << '\n';
return 0;
}
实质就是定义了多个模板,T和U可以分别替换成多个类型。但是,上面的代码仍然有一个问题:使用通常的算术规则(8.4 - 算术转换),double优先于int,因此我们的条件运算符将返回double。但是我们的函数被定义为返回T,在T解析为int的情况下,我们的double返回值将进行缩小转换为int,这将产生警告(并可能丢失数据)。
我们如何解决这个问题?这是auto返回类型的一个很好的用途——我们将让编译器从 return 语句推断返回类型应该是什么:
#include
template <typename T, typename U>
auto max(T x, U y)
{
return (x > y) ? x : y;
}
int main()
{
std::cout << max(2, 3.5) << '\n';
return 0;
}
C++20 引入了auto关键字的新用法:当auto关键字在普通函数中用作参数类型时,编译器会自动将函数转换为函数模板,每个自动参数成为独立的模板类型参数。这种创建函数模板的方法称为缩写函数模板。
auto max(auto x, auto y)
{
return (x > y) ? x : y;
}
//C++中的简写
template <typename T, typename U>
auto max(T x, U y)
{
return (x > y) ? x : y;
}//增加可读性
2021年8月16日
举例
// Define a new enumeration named Color
enum Color
{
color_black,
color_red,
color_blue,
color_green,
color_white,
color_cyan,
color_yellow,
color_magenta,
};
Color paint = color_white;
Color house(color_blue);
Color apple {
color_red };
定义枚举(或任何用户定义的数据类型)不会分配任何内存。当定义了枚举类型的变量(如上例中的变量paint)时,此时会为该变量分配内存。请注意,每个枚举器以逗号分隔,整个枚举以分号结束。
为枚举提供的名称是可以自行选择的,没有名字的枚举有时被称为匿名枚举,通常枚举我们都用大写开头。
我们必须为枚举器指定名称,并且一般使用和常量变量相同的名称样式,有时枚举器以ALL_CAPS命名,但一般不这样做,因为他可能会与预处理器宏有名称冲突的危险。
由于枚举器与枚举放置在同一命名空间中,因此不能在同一命名空间内的多个枚举中使用枚举器名称:
enum Color
{
red,
blue, // blue is put into the global namespace
green
};
enum Feeling
{
happy,
tired,
blue // error, blue was already used in enum Color in the global namespace
};
因此,使用标准前缀作为枚举器的前缀是很常见的,防止命名冲突也可以用于代码目的。
每个枚举器会根据其在枚举列表中的位置自动分配一个整数值。默认情况下,第一个枚举器被分配整数值 0,并且每个后续枚举器的值都比前一个枚举器大 1:
enum Color
{
color_black, // assigned 0
color_red, // assigned 1
color_blue, // assigned 2
color_green, // assigned 3
color_white, // assigned 4
color_cyan, // assigned 5
color_yellow, // assigned 6
color_magenta // assigned 7
};
Color paint{
color_white };
std::cout << paint;
//cout输出打印4
可以显式定义枚举器的值。这些整数值可以是正数或负数,并且可以与其他枚举数共享相同的值。任何未定义的枚举器都被赋予一个比前一个枚举器大 1 的值。
也就是说,可以自由定义枚举器的值,任何数值都可以,并且一个值可以被多个枚举器共享使用,任何未被定义的枚举器都被赋予一个比前一个枚举器大1的值。
// define a new enum named Animal
enum Animal
{
animal_cat = -3,
animal_dog, // assigned -2
animal_pig, // assigned -1
animal_horse = 5,
animal_giraffe = 5, // shares same value as animal_horse
animal_chicken // assigned 6
};
请注意,在这种情况下,animal_horse 和animal_giraffe 被赋予了相同的值。当这种情况发生时,枚举变得不明确——本质上,animal_horse 和animal_giraffe 是可以互换的。尽管 C++ 允许这样做,但通常应避免为同一枚举中的两个枚举器分配相同的值。
在原则上我们不为枚举器分配特定的值,除非你一定有理由为他赋值。
int mypet{
animal_pig };
std::cout << animal_horse;
// evaluates to integer before being passed to std::cout
//输出结果为5
//编译器不会将整数隐式转换为枚举值。以下将产生编译器错误:
Animal animal{
5 }; // will cause compiler error
编译器也不会让您使用 std::cin 输入枚举:
enum Color
{
color_black, // assigned 0
color_red, // assigned 1
color_blue, // assigned 2
color_green, // assigned 3
color_white, // assigned 4
color_cyan, // assigned 5
color_yellow, // assigned 6
color_magenta // assigned 7
};
Color color{
};
std::cin >> color; // will cause compiler error
一种解决方法是读入一个整数,并使用 static_cast 强制编译器将整数值放入枚举类型:
int inputColor{
};
std::cin >> inputColor;
Color color{
static_cast<Color>(inputColor) };
每个枚举类型都被视为不同的类型。因此,尝试将枚举类型从一种枚举类型分配给另一种枚举类型将导致编译错误:
Animal animal{
color_blue };
// will cause compiler error
如果您想为枚举器使用不同的整数类型,例如在网络枚举器时节省带宽,您可以在 enum 声明中指定它。
// Use an 8 bit unsigned integer as the enum base.
enum Color : std::uint_least8_t
{
color_black,
color_red,
// ...
};
由于枚举数通常不用于算术或比较,因此使用无符号整数是安全的。当我们想要转发声明一个枚举时,我们还需要指定枚举基数。
enum Color; // Error
enum Color : int; // Okay
// ...
// Because Color was forward declared with a fixed base, we
// need to specify the base again at the definition.
enum Color : int
{
color_black,
color_red,
// ...
};
与常量变量一样,枚举类型会出现在调试器中,这使得它们在这方面比 #defined 值更有用。
正如我们在上面看到的,尝试使用 std::cout 打印枚举值会导致打印枚举器的整数值。那么如何将枚举器本身打印为文本呢?一种方法是编写一个函数并使用 if 或 switch 语句:
enum Color
{
color_black, // assigned 0
color_red, // assigned 1
color_blue