B. Stroustrup - The C++ Programming Language (794319), страница 49
Текст из файла (страница 49)
Ideally, we will make our choices so as to minimize the probability of error and in particular to maximize readability of code.If you need a notion of ‘‘no value,’’ pointers offer nullptr. There is no equivalent ‘‘null reference,’’ so if you need a ‘‘no value,’’ using a pointer may be most appropriate. For example:void fp(X∗ p){if (p == nullptr) {// no value}else {// use *p}}void fr(X& r) // common style{// assume that r is valid and use it}If you really want to, you can construct and check for a ‘‘null reference’’ for a particular type:void fr2(X& r){if (&r == &nullX) {// no value}else {// use r}}// or maybe r==nullXObviously, you need to have suitably defined nullX. The style is not idiomatic and I don’t recommend it.
A programmer is allowed to assume that a reference is valid. It is possible to create aninvalid reference, but you have to go out of your way to do so. For example:char∗ ident(char ∗ p) { return p; }char& r {∗ident(nullptr)}; // invalid codeThis code is not valid C++ code. Don’t write such code even if your current implementationdoesn’t catch it.Section 7.8Advice1997.8 Advice[1][2][3][4][5][6][7][8][9][10][11][12][13][14]Keep use of pointers simple and straightforward; §7.4.1.Avoid nontrivial pointer arithmetic; §7.4.Take care not to write beyond the bounds of an array; §7.4.1.Avoid multidimensional arrays; define suitable containers instead; §7.4.2.Use nullptr rather than 0 or NULL; §7.2.2.Use containers (e.g., vector, array, and valarray) rather than built-in (C-style) arrays; §7.4.1.Use string rather than zero-terminated arrays of char; §7.4.Use raw strings for string literals with complicated uses of backslash; §7.3.2.1.Prefer const reference arguments to plain reference arguments; §7.7.3.Use rvalue references (only) for forwarding and move semantics; §7.7.2.Keep pointers that represent ownership inside handle classes; §7.6.Avoid void∗ except in low-level code; §7.2.1.Use const pointers and const references to express immutability in interfaces; §7.5.Prefer references to pointers as arguments, except where ‘‘no object’’ is a reasonable option;§7.7.4.This page intentionally left blank8Structures, Unions, and EnumerationsForm a more perfect Union.– The people•••••IntroductionStructuresstruct Layout; struct Names; Structures and Classes; Structures and Arrays; Type Equivalence; Plain Old Data; FieldsUnionsUnions and Classes; Anonymous unionsEnumerationsenum classes; Plain enums; Unnamed enumsAdvice8.1 IntroductionThe key to effective use of C++ is the definition and use of user-defined types.
This chapter introduces the three most primitive variants of the notion of a user-defined type:• A struct (a structure) is a sequence of elements (called members) of arbitrary types.• A union is a struct that holds the value of just one of its elements at any one time.• An enum (an enumeration) is a type with a set of named constants (called enumerators).• enum class (a scoped enumeration) is an enum where the enumerators are within the scopeof the enumeration and no implicit conversions to other types are provided.Variants of these kinds of simple types have existed since the earliest days of C++.
They are primarily focused on the representation of data and are the backbone of most C-style programming.The notion of a struct as described here is a simple form of a class (§3.2, Chapter 16).202Structures, Unions, and EnumerationsChapter 88.2 StructuresAn array is an aggregate of elements of the same type. In its simplest form, a struct is an aggregateof elements of arbitrary types. For example:struct Address {const char∗ name;int number;const char∗ street;const char∗ town;char state[2];const char∗ zip;};// "Jim Dandy"// 61// "South St"// "New Providence"// 'N' 'J'// "07974"This defines a type called Address consisting of the items you need in order to send mail to someone within the USA.
Note the terminating semicolon.Variables of type Address can be declared exactly like other variables, and the individual members can be accessed using the . (dot) operator. For example:void f(){Address jd;jd.name = "Jim Dandy";jd.number = 61;}Variables of struct types can be initialized using the {} notation (§6.3.5). For example:Address jd = {"Jim Dandy",61, "South St","New Providence",{'N','J'}, "07974"};Note that jd.state could not be initialized by the string "NJ".
Strings are terminated by a zero character, '\0', so "NJ" has three characters – one more than will fit into jd.state. I deliberately use ratherlow-level types for the members to illustrate how that can be done and what kinds of problems itcan cause.Structures are often accessed through pointers using the −> (struct pointer dereference) operator.For example:void print_addr(Address∗ p){cout << p−>name << '\n'<< p−>number << ' ' << p−>street << '\n'<< p−>town << '\n'<< p−>state[0] << p−>state[1] << ' ' << p−>zip << '\n';}When p is a pointer, p−>m is equivalent to (∗p).m.Section 8.2Alternatively, aaccess) operator:Structuresstructcan be passed by reference and accessed using the.203(struct membervoid print_addr2(const Address& r){cout << r.name << '\n'<< r.number << ' ' << r.street << '\n'<< r.town << '\n'<< r.state[0] << r.state[1] << ' ' << r.zip << '\n';}Argument passing is discussed in §12.2.Objects of structure types can be assigned, passed as function arguments, and returned as theresult from a function.
For example:Address current;Address set_current(Address next){address prev = current;current = next;return prev;}Other plausible operations, such as comparison (== and !=), are not available by default. However,the user can define such operators (§3.2.1.1, Chapter 18).8.2.1 struct LayoutAn object of a struct holds its members in the order they are declared. For example, we might storeprimitive equipment readout in a structure like this:struct Readout {char hour;int value;char seq;};// [0:23]// sequence mark ['a':'z']You could imagine the members of a Readout object laid out in memory like this:hour: value:seq:Members are allocated in memory in declaration order, so the address of hour must be less than theaddress of value.
See also §8.2.6.However, the size of an object of a struct is not necessarily the sum of the sizes of its members.This is because many machines require objects of certain types to be allocated on architecturedependent boundaries or handle such objects much more efficiently if they are. For example, integers are often allocated on word boundaries. On such machines, objects are said to have to beproperly aligned (§6.2.9). This leads to ‘‘holes’’ in the structures. A more realistic layout of a204Structures, Unions, and EnumerationsReadoutChapter 8on a machine with 4-byte int would be:hour:value:seq:In this case, as on many machines, sizeof(Readout) is 12, and not 6 as one would naively expectfrom simply adding the sizes of the individual members.You can minimize wasted space by simply ordering members by size (largest member first).For example:struct Readout {int value;char hour;char seq;};// [0:23]// sequence mark ['a':'z']This would give us:value:(hour,seq):Note that this still leaves a 2-byte ‘‘hole’’ (unused space) in a Readout and sizeof(Readout)==8.
Thereason is that we need to maintain alignment when we put two objects next to each other, say, in anarray of Readouts. The size of an array of 10 Readout objects is 10∗sizeof(Readout).It is usually best to order members for readability and sort them by size only if there is ademonstrated need to optimize.Use of multiple access specifiers (i.e., public, private, or protected) can affect layout (§20.5).8.2.2 struct NamesThe name of a type becomes available for use immediately after it has been encountered and notjust after the complete declaration has been seen.
For example:struct Link {Link∗ previous;Link∗ successor;};However, it is not possible to declare new objects of a struct until its complete declaration has beenseen. For example:struct No_good {No_good member; // error : recursive definition};This is an error because the compiler is not able to determine the size of No_good. To allow two (orSection 8.2.2structNames205more) structs to refer to each other, we can declare a name to be the name of a struct. For example:struct List;// struct name declaration: List to be defined laterstruct Link {Link∗ pre;Link∗ suc;List∗ member_of;int data;};struct List {Link∗ head;};Without the first declaration of List, use of the pointer type List∗ in the declaration of Link wouldhave been a syntax error.The name of a struct can be used before the type is defined as long as that use does not requirethe name of a member or the size of the structure to be known.