program-defined (user-defined) types 如果 C++ 内置了分数类型,那就完美了——可惜没有。此外,还有数百种其他潜在有用的类型 C++ 没有包含,因为根本不可能预见到用户可能需要的所有类型(更不用说实现和测试这些类型了)。 C++并没有采用传统方法解决这类问题,而是采用了不同的方式:它允许创建全新的自定义类型,供我们在程序中使用!这类类型被称为用户自定义类型 。不过,正如我们将在本课后面讨论的那样,对于我们自己创建的、用于程序中的这类类型,我们更倾向于使用 “程序自定义类型 ”这个术语。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// Define a program-defined type named Fraction so the compiler understands what a Fraction is // (we'll explain what a struct is and how to use them later in this chapter) // This only defines what a Fraction type looks like, it doesn't create one structFraction { int numerator {}; int denominator {}; };
// Now we can make use of our Fraction type intmain() { Fraction f { 3, 4 }; // this actually instantiates a Fraction object named f
// Define a new unscoped enumeration named Color enumColor { // Here are the enumerators // These symbolic constants define all the possible values this type can hold // Each enumerator is separated by a comma, not a semicolon red, green, blue, // trailing comma optional but recommended }; // the enum definition must end with a semicolon
intmain() { // Define a few variables of enumerated type Color Color apple { red }; // my apple is red Color shirt { green }; // my shirt is green Color cup { blue }; // my cup is blue
Color socks { white }; // error: white is not an enumerator of Color Color hat { 2 }; // error: 2 is not an enumerator of Color
constexpr std::string_view getPetName(Pet pet) { switch (pet) { case cat: return"cat"; case dog: return"dog"; case pig: return"pig"; case whale: return"whale"; default: return"???"; } }
constexpr std::optional<Pet> getPetFromString(std::string_view sv) { // We can only switch on an integral value (or enum), not a string // so we have to use if-statements here if (sv == "cat") return cat; if (sv == "dog") return dog; if (sv == "pig") return pig; if (sv == "whale") return whale;
return {}; }
intmain() { std::cout << "Enter a pet: cat, dog, pig, or whale: "; std::string s{}; std::cin >> s;
std::optional<Pet> pet { getPetFromString(s) };
if (!pet) std::cout << "You entered an invalid pet\n"; else std::cout << "You entered: " << getPetName(*pet) << '\n';
constexpr std::string_view getColorName(Color color) { switch (color) { case black: return"black"; case red: return"red"; case blue: return"blue"; default: return"???"; } }
// Teach operator<< how to print a Color // std::ostream is the type of std::cout, std::cerr, etc... // The return type and parameter type are references (to prevent copies from being made) std::ostream& operator<<(std::ostream& out, Color color) { out << getColorName(color); // print our color's name to whatever output stream out return out; // operator<< conventionally returns its left operand
// The above can be condensed to the following single line: // return out << getColorName(color) }
intmain() { Color shirt{ blue }; std::cout << "Your shirt is " << shirt << '\n'; // it works!
constexpr std::string_view getPetName(Pet pet) { switch (pet) { case cat: return"cat"; case dog: return"dog"; case pig: return"pig"; case whale: return"whale"; default: return"???"; } }
constexpr std::optional<Pet> getPetFromString(std::string_view sv) { if (sv == "cat") return cat; if (sv == "dog") return dog; if (sv == "pig") return pig; if (sv == "whale") return whale;
return {}; }
// pet is an in/out parameter std::istream& operator>>(std::istream& in, Pet& pet) { std::string s{}; in >> s; // get input string from user
std::optional<Pet> match { getPetFromString(s) }; if (match) // if we found a match { pet = *match; // dereference std::optional to get matching enumerator return in; }
// We didn't find a match, so input must have been invalid // so we will set input stream to fail state in.setstate(std::ios_base::failbit);
// On an extraction failure, operator>> zero-initializes fundamental types // Uncomment the following line to make this operator do the same thing // pet = {};
return in; }
intmain() { std::cout << "Enter a pet: cat, dog, pig, or whale: "; Pet pet{}; std::cin >> pet;
if (std::cin) // if we found a match std::cout << "You chose: " << getPetName(pet) << '\n'; else { std::cin.clear(); // reset the input stream to good std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); std::cout << "Your pet was not valid\n"; }
return0; }
13.6 — 作用域枚举(枚举类)
作用域枚举 (在 C++ 中通常称为枚举类 ,原因很快就会变得很明显) 要创建作用域枚举,我们使用关键字 enum class 。作用域枚举定义的其余部分与非作用域枚举定义相同。以下是一个示例:
#include<iostream> intmain() { enum classColor// "enum class" defines this as a scoped enumeration rather than an unscoped enumeration { red, // red is considered part of Color's scope region blue, };
enum classFruit { banana, // banana is considered part of Fruit's scope region apple, };
Color color { Color::red }; // note: red is not directly accessible, we have to use Color::red Fruit fruit { Fruit::banana }; // note: banana is not directly accessible, we have to use Fruit::banana
if (color == fruit) // compile error: the compiler doesn't know how to compare different types Color and Fruit std::cout << "color and fruit are equal\n"; else std::cout << "color and fruit are not equal\n";
return0; }
该程序在第 19 行产生编译错误,因为作用域枚举无法转换为任何可以与其他类型进行比较的类型。
作用域枚举不会隐式转换为整数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#include<iostream> #include<utility>// for std::to_underlying() (C++23)
intmain() { enum classColor { red, blue, };
Color color { Color::blue };
std::cout << color << '\n'; // won't work, because there's no implicit conversion to int std::cout << static_cast<int>(color) << '\n'; // explicit conversion to int, will print 1 std::cout << std::to_underlying(color) << '\n'; // convert to underlying type, will print 1 (C++23)
constexpr std::string_view getColor(Color color) { usingenumColor; // bring all Color enumerators into current scope (C++20) // We can now access the enumerators of Color without using a Color:: prefix
switch (color) { case black: return"black"; // note: black instead of Color::black case red: return"red"; case blue: return"blue"; default: return"???"; } }
intmain() { Color shirt{ Color::blue };
std::cout << "Your shirt is " << getColor(shirt) << '\n';
return0; }
添加了 using enum Color; 语句,这使得我们可以在不使用 Color:: 前缀的情况下访问这些枚举值。
13.7 — 结构体、成员和成员选择简介
1 2 3 4 5 6
structEmployee { int id {}; int age {}; double wage {}; };
structEmployee { int id {}; int age {}; double wage {}; };
intmain() { Employee frank = { 1, 32, 60000.0 }; // copy-list initialization using braced list Employee joe { 2, 28, 45000.0 }; // list initialization using braced list (preferred)
structSomething { int x; // no default initialization value (bad) int y {}; // value-initialized by default int z { 2 }; // explicit default value };
intmain() { Something s1; // No initializer list: s1.x is uninitialized, s1.y and s1.z use defaults Something s2 { 5, 6, 7 }; // Explicit initializers: s2.x, s2.y, and s2.z use explicit values (no default values are used) Something s3 {}; // Missing initializers: s3.x is value initialized, s3.y and s3.z use defaults
return0; }
最佳实践
为所有成员提供默认值。这样即使变量定义中没有包含初始化列表,也能确保成员被初始化。
对于聚合,优先使用值初始化(使用空花括号初始化器)而不是默认初始化(不使用花括号)。
1 2
Fraction f1; // f1.numerator value initialized to 0, f1.denominator defaulted to 1 Fraction f2 {}; // f2.numerator value initialized to 0, f2.denominator defaulted to 1
structMonster { // Define our different monster types as an enum enumType { ogre, dragon, orc, giant_spider, slime, };
Type type{}; std::string name{}; // the Monster should be an owner of its name int health{}; };
// Return the name of the monster's type as a string // Since this could be used elsewhere, it's better to make this its own function constexpr std::string_view getMonsterTypeString(Monster::Type type) { switch (type) { case Monster::ogre: return"Ogre"; case Monster::dragon: return"Dragon"; case Monster::orc: return"Orc"; case Monster::giant_spider: return"Giant Spider"; case Monster::slime: return"Slime"; }