Google
 

Monday, January 10, 2011

C++ Primer, Fourth Edition Notes:Chapter 6. Statements

Section 6.1 Simple Statements

  1. Most statements in C++ end with a semicolon.
  2. Null Statements
    1. The simplest form of statement is the empty, or null statement. It takes the following form (a single semicolon)
      ;  // null statement

    2. A null statement is useful where the language requires a statement but the program's logic does not. Such usage is most common when a loop's work can be done within the condition.
    3. A null statement should be commented, so that anyone reading the code can see that the statement was omitted intentionally.
    4. Extraneous null statements are not always harmless
    // EXE-6.1.cpp : Defines the entry point for the console application.
    //

    #include "stdafx.h"
    #include <iostream>
    #include <string>
    #include <vector>

    using namespace std;

    int main(int argc, _TCHAR* argv[])
    {
    // read until we hit end-of-file or find an input equal to sought
    string s;
    cout << "Please input stop to stop your input!" << endl;
    const string sought = "stop";
    while (cin >> s && s != sought)
    ; // null statement

    cout << s << endl;

    int i;
    cin >> i;

    return 0;
    }

Section 6.2 Declaration Statements


Section 6.3 Compound Statements (Blocks)



  1. A compound statement, usually referred to as a block, is a (possibly empty) sequence of statements surrounded by a pair of curly braces.
  2. A block is a scope. Names introduced within a block are accessible only from within that block or from blocks nested inside the block. As usual, a name is visible only from its point of definition until the end of the enclosing block.
  3. Compound statements can be used where the rules of the language require a single statement, but the logic of our program needs to execute more than one.
  4. Unlike most other statements, a block is not terminated by a semicolon.
  5. Just as there is a null statement, we also can define an empty block
    // EXE-6.3.cpp : Defines the entry point for the console application.
    //

    #include "stdafx.h"
    #include <iostream>
    #include <string>
    #include <vector>

    using namespace std;

    int main(int argc, _TCHAR* argv[])
    {
    // if so, read the transaction records
    while (std::cin >> trans)
    {
    if (total.same_isbn(trans))
    // match: update the running total
    total = total + trans;
    else {
    // no match: print & assign to total
    std::cout << total << std::endl;
    total = trans;
    }
    }

    string s;
    string sought = "break";
    while (cin >> s && s != sought)
    { } // empty block


    return 0;
    }

Section 6.4 Statement Scope



  1. Some statements permit variable definitions within their control structure
  2. Variables defined in a condition must be initialized. The value tested by the condition is the value of the initialized object
  3. Variables defined as part of the control structure of a statement are visible only until the end of the statement in which they are defined. The scope of such variables is limited to the statement body.
  4. Earlier versions of C++ treated the scope of variables defined inside a for differently: Variables defined in the for header were treated as if they were defined just before the for. Older C++ programs may have code that relies on being able to access these control variables outside the scope of the for.

Section 6.5 The if Statement



  1. An if statement conditionally executes another statement based on whether a specified expression is true.
    if (condition)
    statement
    // ival only accessible within the if statement
    if (int ival = compute_value()) {/* ... */}

  2. When a condition defines a variable, the variable must be initialized. The value of the initialized variable is converted to bool (Section 5.12.3, p. 181) and the resulting bool determines the value of the condition. The variable can be of any type that can be converted to bool, which means it can be an arithmetic or pointer type.
  3. As we'll see in Chapter 14, whether a class type can be used in a condition depends on the class. Of the types we've used so far, the IO types can be used in a condition, but the vector and string types may not be used as a condition
  4. Statement Block as Target of an if

  5. 6.5.1. The if Statement else Branch

    1. Dangling else

      1. This version illustrates a source of potential ambiguity common to if statements in all languages. The problem, usually referred to as the dangling-else problem, occurs when a statement contains more if clauses than else clauses. The question then arises: To which if does each else clause belong?
        // oops: incorrect rewrite: This code won't work!
        if (minVal <= ivec[i])
        if (minVal == ivec[i])
        ++occurs;
        else { // this else goes with the inner if, not the outer one!
        minVal = ivec[i];
        occurs = 1;
        }

      2. The indentation in our code indicates the expectation that the else should match up with the outer if clause. In C++, however, the dangling-else ambiguity is resolved by matching the else with the last occurring unmatched if.
        // oops: still wrong, but now the indentation matches execution path
        if (minVal <= ivec[i])
        // indented to match handling of dangling-else
        if (minVal == ivec[i])
        ++occurs;
        else {
        minVal = ivec[i];
        occurs = 1;
        }

      3. We can force an else to match an outer if by enclosing the inner if in a compound statement
        if (minVal <= ivec[i]) {
        if (minVal == ivec[i])
        ++occurs;
        } else {
        minVal = ivec[i];
        occurs = 1;
        }

      4. Some coding styles recommend always using braces after any if. Doing so avoids any possible confusion and error in later modifications of the code. At a minimum, it is nearly always a good idea to use braces after an if (or while) when the statement in the body is anything other than a simple expression statement, such as an assignment or output expression.

    2. This version illustrates a source of potential ambiguity common to if statements in all languages. The problem, usually referred to as the dangling-else problem, occurs when a statement contains more if clauses than else clauses. The question then arises: To which if does each else clause belong?
    3. Some coding styles recommend always using braces after any if. Doing so avoids any possible confusion and error in later modifications of the code. At a minimum, it is nearly always a good idea to use braces after an if (or while) when the statement in the body is anything other than a simple expression statement, such as an assignment or output expression.


Section 6.6 The switch Statement



  1. A switch statement provides a more convenient way to write deeply nested if/else logic.
  2. It is a common misunderstanding to expect that only the statements associated with the matched case label are executed. However, execution continues across case boundaries until the end of the switch statement or a break is encountered.
  3. Forgetting to provide a break is a common source of bugs in switch statements.
  4. Although it is not strictly necessary to specify a break statement after the last label of a switch, the safest course is to provide a break after every label, even the last. If an additional case label is added later, then the break is already in place.
  5. The default label provides the equivalent of an else clause. If no case label matches the value of the switch expression and there is a default label, then the statements following the default are executed.
  6. It can be useful always to define a default label even if there is no processing to be done in the default case. Defining the label indicates to subsequent readers that the case was considered but that there is no work to be done.
  7. The expression evaluated by a switch can be arbitrarily complex
  8. Case labels must be constant integral expressions (Section 2.7, p. 62).
  9. It is also an error for any two case labels to have the same value.
  10. Variables can be defined following only the last case or default label:
  11. The reason for this rule is to prevent code that might jump over the definition and initialization of a variable.

Section 6.7 The while Statement



  1. The statement (which is often a block) is executed as long as the condition evaluates as true. The condition may not be empty
  2. Variables defined in the condition are created and destroyed on each trip through the loop.
  3. The assignment in the while loop represents a very common usage. Because such code is widespread, it is important to study this expression until its meaning is immediately clear

Section 6.8 The for Loop Statement



  1. The init-statement must be a declaration statement, an expression statement, or a null statement
  2. It is worth remembering that the visibility of any object defined within the for header is limited to the body of the for loop.
  3. A for header can omit any (or all) of init-statement, condition, or expression
  4. The init-statement is omitted if an initialization is unnecessary or occurs elsewhere
  5. Note that the semicolon is necessary to indicate the absence of the init-statement more precisely, the semicolon represents a null init-statement.
  6. Multiple objects may be defined in the init-statement; however, only one statement may appear, so all the objects must be of the same general type:

Section 6.9 The do while Statement



    It guarantees that its body is always executed at least once. The syntactic form is as follows:

         do
    statement
    while (condition);

  1. Unlike a while statement, a do-while statement always ends with a semicolon.

 


Section 6.10 The break Statement



  1. A break statement terminates the nearest enclosing while, do while, for, or switch statement. Execution resumes at the statement immediately following the terminated statement.
  2. A break can appear only within a loop or switch statement or in a statement nested inside a loop or switch. A break may appear within an if only when the if is inside a switch or a loop. A break occurring outside a loop or switch is a compile-time error. When a break occurs inside a nested switch or loop statement, the enclosing loop or switch statement is unaffected by the termination of the inner switch or loop

Section 6.11 The continue Statement



    A continue statement causes the current iteration of the nearest enclosing loop to terminate. Execution resumes with the evaluation of the condition in the case of a while or do while statement. In a for loop, execution continues by evaluating the expression inside the for header.

    A continue can appear only inside a for, while, or do while loop, including inside blocks nested inside such loops.




      Section 6.12 The goto Statement



        A goto statement provides an unconditional jump from the goto to a labeled statement in the same function.

        Use of gotos has been discouraged since the late 1960s. gotos make it difficult to trace the control flow of a program, making the program hard to understand and hard to modify. Any program that uses a goto can be rewritten so that it doesn't need the goto.

        The syntactic form of a goto statement is

             goto label;

        where label is an identifier that identifies a labeled statement.




          Section 6.13 try Blocks and Exception Handling



          1. Exceptions are run-time anomalies, such as running out of memory or encountering unexpected input. Exceptions exist outside the normal functioning of the program and require immediate handling by the program.
          2. In well-designed systems, exceptions represent a subset of the program's error handling. Exceptions are most useful when the code that detects a problem cannot handle it. In such cases, the part of the program that detects the problem needs a way to transfer control to the part of the program that can handle the problem. The error-detecting part also needs to be able to indicate what kind of problem occurred and may want to provide additional information.
          3. tHRow expressions, which the error-detecting part uses to indicate that it encountered an error that it cannot handle. We say that a throw raises an exceptional condition.
          4. try blocks, which the error-handling part uses to deal with an exception. A TRy block starts with keyword TRy and ends with one or more catch clauses. Exceptions thrown from code executed inside a try block are usually handled by one of the catch clauses. Because they "handle" the exception, catch clauses are known as handlers.
          5. A set of exception classes defined by the library, which are used to pass the information about an error between a throw and an associated catch.
          6. 6.13.1. A tHRow Expression

            1. An exception is thrown using a throw expression, which consists of the keyword tHRow followed by an expression. A throw expression is usually followed by a semicolon, making it into an expression statement. The type of the expression determines what kind of exception is thrown.
            2. The runtime_error type is one of the standard library exception types and is defined in the stdexcept header.

          7. 6.13.2. The try Block

              The general form of a try block is

                   try {
              program-statements
              } catch (exception-specifier) {
              handler-statements
              } catch (exception-specifier) {
              handler-statements
              } //...

            1. If the catch clause is selected to handle an exception, the associated block is executed. Once the catch clause finishes, execution continues with the statement immediately following the last catch clause.
            2. Writing a Handler
            3. Functions Are Exited during the Search for a Handler

              1. The search for a handler reverses the call chain. When an exception is thrown, the function that threw the exception is searched first. If no matching catch is found, the function terminates, and the function that called the one that threw is searched for a matching catch. If no handler is found, then that function also exits and the function that called it is searched; and so on back up the execution path until a catch of an appropriate type is found.
              2. If no catch clause capable of handling the exception exists, program execution is transferred to a library function named terminate, which is defined in the exception header. The behavior of that function is system dependent, but it usually aborts the program.
              3. Exceptions that occur in programs that define no TRy blocks are handled in the same manner: After all, if there are no try blocks, there can be no handlers for any exception that might be thrown. If an exception occurs, then terminate is called and the program (ordinarily) is aborted.


          8. 6.13.3. Standard Exceptions

            1. The C++ library defines a set of classes that it uses to report problems encountered in the functions in the standard library.
            2. The exception header defines the most general kind of exception class named exception. It communicates only that an exception occurs but provides no additional information.
            3. The stdexcept header defines several general purpose exception classes. These types are listed in Table 6.1 on the following page.














































              1. Table 6.1. Standard Exception Classes Defined in <stdexcept>

                exception


                The most general kind of problem.


                runtime_error


                Problem that can be detected only at run time.


                range_error


                Run-time error: result generated outside the range of values that are meaningful.


                overflow_error


                Run-time error: computation that overflowed.


                underflow_error


                Run-time error: computation that underflowed.


                logic_error


                Problem that could be detected before run time.


                domain_error


                Logic error: argument for which no result exists.


                invalid_argument


                Logic error: inappropriate argument.


                length_error


                Logic error: attempt to create an object larger than the maximum size for that type.


                out_of_range


                Logic error: used a value outside the valid range.



            4. The new header defines the bad_alloc exception type, which is the exception thrown by new (Section 5.11, p. 174) if it cannot allocate memory.

              The type_info header defines the bad_cast exception type, which we will discuss in Section 18.2 (p. 772).



            1. Standard Library Exception Classes

              1. The library exception classes have only a few operations. We can create, copy, and assign objects of any of the exception types. The exception, bad_alloc, and bad_cast types define only a default constructor (Section 2.3.4, p. 50); it is not possible to provide an initializer for objects of these types. The other exception types define only a single constructor that takes a string initializer. When we define any of these other exception types, we must supply a string argument. That string initializer is used to provide additional information about the error that occurred.
              2. The exception types define only a single operation named what. That function takes no arguments and returns a const char*. The pointer it returns points to a C-style character string (Section 4.3, p. 130). The purpose of this C-style character string is to provide some sort of textual description of the exception thrown.
              3. The contents of the C-style character array to which what returns a pointer depends on the type of the exception object. For the types that take a string initializer, the what function returns that string as a C-style character array. For the other types, the value returned varies by compiler


            Section 6.14 Using the Preprocessor for Debugging



            1. C++ programmers sometimes use a technique similar to header guards to conditionally execute debugging code. The idea is that the program will contain debugging code that is executed only while the program is being developed. When the application is completed and ready to ship, the debugging code is turned off.

              The preprocessor defines four other constants that can be useful in debugging:

              _ _FILE_ _ name of the file.

              _ _LINE_ _ current line number.

              _ _TIME_ _ time the file was compiled.

              _ _DATE_ _ date the file was compiled.


            2. The assert macro is defined in the cassert header, which we must include in any file that uses assert.A preprocessor macro acts something like a function call. The assert macro takes a single expression, which it uses as a condition:
              assert(expr)

            3. Unlike exceptions, which deal with errors that a program expects might happen in production, programmers use assert to test conditions that "cannot happen."
            4. Of course, there is also no run-time check. assert should be used only to verify things that truly should not be possible. It can be useful as an aid in getting a program debugged but should not be used to substitute for run-time logic checks or error checking that the program should be doing.
            5. // EXE-6.14.cpp : Defines the entry point for the console application.
              //

              #include "stdafx.h"
              //#define NDEBUG
              #include <iostream>
              #include <string>
              #include <assert.h>
              using namespace std;

              int main(int argc, _TCHAR* argv[])
              {
              #ifndef NDEBUG
              cerr << "starting main" << endl;
              #endif

              const int maxLength(5);
              cout << "Please input a string with length " << maxLength << ":" << endl;
              string input;
              cin >> input;
              if (input.size() < maxLength)
              cerr << "Error: " << __FILE__
              << " : line " << __LINE__ << endl
              << " Compiled on " << __DATE__
              << " at " << __TIME__ << endl
              << " String input was " << input
              << ": Length too short" << endl;

              #ifndef NDEBUG
              assert(input.size() < 10);
              #endif

              int i;
              cin >> i;
              return 0;
              }

            No comments: