B. Stroustrup - The C++ Programming Language (794319), страница 54
Текст из файла (страница 54)
If we need to introduce a name in abranch, it must be enclosed in a block (§9.2). For example:230Statementsvoid f1(int i){if (i)int x = i+2;}Chapter 9// error : declaration of if-statement branch9.4.2 switch StatementsA switch-statement selects among a set of alternatives (case-labels). The expression in the caselabels must be a constant expression of integral or enumeration type. A value may not be usedmore than once for case-labels in a switch-statement. For example:void f(int i){switch (i) {case 2.7: // error: floating point uses for case// ...case 2:// ...case 4−2: // error: 2 used twice in case labels// ...};A switch-statement can alternatively be written as a set of if-statements. For example:switch (val) {case 1:f();break;case 2:g();break;default:h();break;}This could be expressed as:if (val == 1)f();else if (val == 2)g();elseh();The meaning is the same, but the first (switch) version is preferred because the nature of the operation (testing a single value against a set of constants) is explicit.
This makes the switch-statementeasier to read for nontrivial examples. It typically also leads to the generation of better codebecause there is no reason to repeatedly check individual values. Instead, a jump table can be used.Section 9.4.2switchStatements231Beware that a case of a switch must be terminated somehow unless you want to carry on executing the next case. Consider:switch (val) {// bewarecase 1:cout << "case 1\n";case 2:cout << "case 2\n";default:cout << "default: case not found\n";}Invoked with val==1, the output will greatly surprise the uninitiated:case 1case 2default: case not foundIt is a good idea to comment the (rare) cases in which a fall-through is intentional so that an uncommented fall-through can be assumed to be an error.
For example:switch (action) {// handle (action,value) paircase do_and_print:act(value);// no break: fall through to printcase print:print(value);break;// ...}A break is the most common way of terminating a case, but a return is often useful (§10.2.1).When should a switch-statement have a default? There is no single answer that covers all situations. One use is for the default to handle the most common case. Another common use is theexact opposite: the default: action is simply a way to catch errors; every valid alternative is coveredby the cases.
However, there is one case where a default should not be used: if a switch is intendedto have one case for each enumerator of an enumeration. If so, leaving out the default gives thecompiler a chance to warn against a set of cases that almost but not quite match the set of enumerators. For example, this is almost certainly an error:enum class Vessel { cup, glass, goblet, chalice };void problematic(Vessel v){switch (v) {case Vessel::cup:case Vessel::glass:case Vessel::goblet:}}/* ... *//* ...
*//* ... */break;break;break;232StatementsChapter 9Such a mistake can easily occur when a new enumerator is added during maintenance.Testing for an ‘‘impossible’’ enumerator value is best done separately.9.4.2.1 Declarations in CasesIt is possible, and common, to declare variables within the block of a switch-statement. However, itis not possible to bypass an initialization. For example:void f(int i){switch (i) {case 0:int x;int y = 3;string s;case 1:++x;++y;s = "nasty!";}}// uninitialized// error: declaration can be bypassed (explicitly initialized)// error: declaration can be bypassed (implicitly initialized)// error: use of uninitialized objectHere, if i==1, the thread of execution would bypass the initializations of y and s, so f() will not compile.
Unfortunately, because an int needn’t be initialized, the declaration of x is not an error. However, its use is an error: we read an uninitialized variable. Unfortunately, compilers often give just awarning for the use of an uninitialized variable and cannot reliably catch all such misuses. Asusual, avoid uninitialized variables (§6.3.5.1).If we need a variable within a switch-statement, we can limit its scope by enclosing its declaration and its use in a block. For an example, see prim() in §10.2.1.9.4.3 Declarations in ConditionsTo avoid accidental misuse of a variable, it is usually a good idea to introduce the variable into thesmallest scope possible.
In particular, it is usually best to delay the definition of a local variableuntil one can give it an initial value. That way, one cannot get into trouble by using the variablebefore its initial value is assigned.One of the most elegant applications of these two principles is to declare a variable in a condition. Consider:if (double d = prim(true)) {left /= d;break;}Here, d is declared and initialized and the value of d after initialization is tested as the value of thecondition. The scope of d extends from its point of declaration to the end of the statement that thecondition controls. For example, had there been an else-branch to the if-statement, d would be inscope on both branches.Section 9.4.3Declarations in Conditions233The obvious and traditional alternative is to declare d before the condition. However, this opensthe scope (literally) for the use of d before its initialization or after its intended useful life:double d;// ...d2 = d;// oops!// ...if (d = prim(true)) {left /= d;break;}// ...d = 2.0; // two unrelated uses of dIn addition to the logical benefits of declaring variables in conditions, doing so also yields the mostcompact source code.A declaration in a condition must declare and initialize a single variable or const.9.5 Iteration StatementsA loop can be expressed as a for-, while-, or do-statement:while (condition ) statementdo statement while ( expression ) ;for ( for-init-statement conditionopt ; expressionopt )for ( for-declaration : expression ) statementstatementA for-init-statement must be either a declaration or an expression-statement.
Note that both endwith a semicolon.The statement of a for-statement (called the controlled statement or the loop body) is executedrepeatedly until the condition becomes false or the programmer breaks out of the loop some otherway (such as a break, a return, a throw, or a goto).More complicated loops can be expressed as an algorithm plus a lambda expression (§11.4.2).9.5.1 Range-for StatementsThe simplest loop is a range-for-statement; it simply gives the programmer access to each elementof a range. For example:int sum(vector<int>& v){int s = 0;for (int x : v)s+=x;return s;}234StatementsChapter 9The for (int x : v) can be read as ‘‘for each element x in the range v’’ or just ‘‘for each x in v.’’ Theelements of v are visited in order from the first to the last.The scope of the variable naming the element (here, x) is the for-statement.The expression after the colon must denote a sequence (a range); that is, it must yield a valuefor which we can call v.begin() and v.end() or begin(v) and end(v) to obtain an iterators (§4.5):[1] the compiler first looks for members begin and end and tries to use those.
If a begin or anend is found that cannot be used as a range (e.g., because a member begin is a variablerather than a function), the range-for is an error.[2] Otherwise, the compiler looks for a begin/end member pair in the enclosing scope. Ifnone is found or if what is found cannot be used (e.g., because the begin did not take anargument of the sequence’s type), the range-for is an error.The compiler uses v and v+N as begin(v) and end(v) for a built-in array T v[N]. The <iterator> headerprovides begin(c) and end(c) for built-in arrays and for all standard-library containers. Forsequences of our own design, we can define begin() and end() in the same way as it is done for standard-library containers (§4.4.5).The controlled variable, x in the example, that refers to the current element is equivalent to ∗pwhen using an equivalent for-statement:int sum2(vector<int>& v){int s = 0;for (auto p = begin(v); p!=end(v); ++p)s+=∗p;return s;}If you need to modify an element in a range-for loop, the element variable should be a reference.For example, we can increment each element of a vector like this:void incr(vector<int>& v){for (int& x : v)++x;}References are also appropriate for elements that might be large, so that copying them to the element value could be costly.
For example:template<class T> T accum(vector<T>& v){T sum = 0;for (const T& x : v)sum += x;return sum;}Note that a range-for loop is a deliberately simple construct. For example, using it you can’t touchtwo elements at the same time and can’t effectively traverse two ranges simultaneously. For thatwe need a general for-statement.Section 9.5.2forStatements2359.5.2 for StatementsThere is also a more general for-statement allowing greater control of the iteration.