Chapter 3. Control Statements, Part 1; Intro to C++20 Text Formatting

Images

Objectives

In this chapter, you’ll:

Use the if and if…else selection statements to choose between alternative actions.

Use the while iteration statement to execute statements in a program repeatedly.

Use counter-controlled iteration and sentinel-controlled iteration.

Use nested control statements.

Use the compound assignment operators and the increment and decrement operators.

Learn why fundamental data types are not portable.

Continue learning with our objects natural approach with a case study on creating and manipulating integers as large as you want them to be.

Use C++20’s new text formatting capabilities, which are more concise and more powerful than those in earlier C++ versions.

3.1 Introduction

In this chapter and the next, we present the theory and principles of structured programming. The concepts presented here are crucial in building classes and manipulating objects. We discuss C++’s if statement in additional detail and introduce the if…else

and while statements. We also introduce the compound assignment operators and the increment and decrement operators.

20 We discuss why C++’s fundamental types are not portable. We continue our object natural approach with a case study on arbitrary sized integers that support values beyond the ranges of integers supported by computer hardware.

We begin introducing C++20’s new text-formatting capabilities, which are based on those in Python, Microsoft’s .NET languages (like C# and Visual Basic) and Rust.1 The C++20 capabilities are more concise and more powerful than those in earlier C++ versions. In Chapter 14, you’ll see that these new capabilities are extensible, so you can use them to format objects of custom class types.

1. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r10.html.

“Rough-Cut” E-Book for O’Reilly Online Learning Subscribers

You are viewing an early-access “rough cut” of C++20 for Programmers. We prepared this content carefully, but it has not yet been reviewed or copy edited and is subject to change. As we complete each chapter, we’ll post it here. Please send any corrections, comments, questions and suggestions for improvement to [email protected] and I’ll respond promptly. Check here frequently for updates.

“Sneak Peek” Videos for O’Reilly Online Learning Subscribers

As an O’Reilly Online Learning subscriber, you also have access to the “sneak peek” of our new C++20 Fundamentals LiveLessons videos at:

https://learning.oreilly.com/videos/c-20-fundamentals-parts/9780136875185

Co-author Paul Deitel immediately records each video lesson as we complete each rough-cut e-book chapter. Lessons go live on O’Reilly Online Learning a few days later. Again, check here frequently for updates.

3.2 Control Structures

During the 1960s, it became clear that the indiscriminate use of transfers of control was the root of many problems experienced by software development groups. The blame was pointed at the goto statement (used in most programming languages of the time), which allows you to specify a transfer of control to one of a wide range of destinations in a program.

The research of Bohm and Jacopini2 had demonstrated that programs could be written without any goto statements. The challenge for programmers of the era was to shift their styles to “goto-less programming.” The term structured programming became almost synonymous with “goto elimination.” The results were impressive. Software development groups reported shorter development times, more frequent on-time delivery of systems and more frequent within-budget completion of software projects. The key to these successes was that structured programs were clearer, easier to debug and modify, and more likely to be bug-free in the first place.

2. C. Bohm and G. Jacopini, “Flow Diagrams, Turing Machines, and Languages with Only Two Formation Rules,” Communications of the ACM, Vol. 9, No. 5, May 1966, pp. 336–371.

Bohm and Jacopini’s work demonstrated that all programs could be written in terms of only three control structures—the sequence structure, the selection structure and the iteration structure. We’ll discuss how C++ implements each of these.

3.2.1 Sequence Structure

The sequence structure is built into C++. Unless directed otherwise, the computer executes C++ statements one after the other in the order in which they appear in the program—that is, in sequence. The following UML3 activity diagram illustrates a typical sequence structure in which two calculations are performed in order:

Images

3. We use the UML in this chapter and Chapter 4 to show the flow of control in control statements, then use UML again in Chapters 1013 when we present custom class development.

C++ lets you have as many actions as you want in a sequence structure. As you’ll soon see, anywhere you may place a single action, you may place several actions in sequence.

An activity diagram models the workflow (also called the activity) of a portion of a software system. Such workflows may include a portion of an algorithm, like the sequence structure in the preceding diagram. Activity diagrams are composed of symbols, such as action-state symbols (rectangles with their left and right sides replaced with outward arcs), diamonds and small circles. These symbols are connected by transition arrows, which represent the flow of the activity—that is, the order in which the actions should occur.

The preceding sequence-structure activity diagram contains two action states, each containing an action expression—for example, “add grade to total” or “add 1 to counter”—that specifies a particular action to perform. The arrows in the activity diagram represent transitions, which indicate the order in which the actions represented by the action states occur.

The solid circle at the top of the activity diagram represents the initial state—the beginning of the workflow before the program performs the modeled actions. The solid circle surrounded by a hollow circle at the bottom of the diagram represents the final state—that is, the end of the workflow after the program performs its actions.

The sequence structure activity diagram also includes rectangles with the upper-right corners folded over. These are UML notes (like comments in C++)—explanatory remarks that describe the purpose of symbols in the diagram. A dotted line connects each note with the element it describes. We used the UML notes here to illustrate how the diagram relates to the C++ code for each action state. Activity diagrams usually do not show the C++ code.

3.2.2 Selection Statements

C++ has three types of selection statements. The if statement performs (selects) an action (or group of actions) if a condition is true, or skips it if the condition is false. The if…else statement performs an action (or group of actions) if a condition is true and performs a different action (or group of actions) if the condition is false. The switch statement (Chapter 4) performs one of many different actions (or group of actions), depending on the value of an expression.

The if statement is called a single-selection statement because it selects or ignores a single action (or group of actions). The if…else statement is called a double-selection statement because it selects between two different actions (or groups of actions). The switch statement is called a multiple-selection statement because it selects among many different actions (or groups of actions).

3.2.3 Iteration Statements

C++ provides four iteration statements—also called repetition statements or looping statements—for performing statements repeatedly while a loop-continuation condition remains true. The iteration statements are the while, dowhile, for and range-based for. The while and for statements perform the action (or group of actions) in their bodies zero or more times. If the loop-continuation condition is initially false, the action (or group of actions) does not execute. The dowhile statement performs the action (or group of actions) in its body one or more times. Chapter 4 presents the dowhile and for statements. Chapter 6 presents the range-based for statement.

Keywords

Each of the words if, else, switch, while, do and for are C++ keywords. Keywords cannot be used as identifiers, such as variable names, and must contain only lowercase letters. The following table shows the complete list of C++ keywords:

Images
Other Special Identifiers

20 The C++20 standard indicates that the following identifiers should not be used in your code because they have special meanings in some contexts or are identifiers that may become keywords in the future:

• Non-keyword identifiers with special meaning—final, import, module, override, transaction_safe and transaction_safe_dynamic.

• Experimental keywords—atomic_cancel, atomic_commit, atomic_noexcept, reflexpr and synchronized.

3.2.4 Summary of Control Statements

C++ has only three kinds of control structures, which from this point forward, we refer to as control statements—the sequence statement, selection statements (three types) and iteration statements (four types). Every program is formed by combining these statements as appropriate for the algorithm the program implements. We can model each control statement as an activity diagram. Each diagram contains an initial state and a final state that represent a control statement’s entry point and exit point, respectively. Single-entry/single-exit control statements make it easy to build programs—we simply connect the exit point of one to the entry point of the next using control-statement stacking. There’s only one other way in which you may connect control statements—control-statement nesting in which one control statement appears inside another. Thus, algorithms in C++ programs are constructed from only three kinds of control statements, combined in only two ways. This is the essence of simplicity.

3.3 if Single-Selection Statement

We introduced the if single-selection statement briefly in Section 2.6. Programs use selection statements to choose among alternative courses of action. For example, suppose that the passing grade on an exam is 60. The C++ statement

if (studentGrade >= 60) {
   cout << "Passed";
}

determines whether the condition studentGrade >= 60 is true. If so, "Passed" is printed, and the next statement in order is performed. If the condition is false, the output statement is ignored, and the next statement in order is performed. The indentation of the second line of this selection statement is optional, but recommended for program clarity.

bool Data Type

In Chapter 2, you created conditions using the relational or equality operators. Actually, any expression that evaluates to zero or nonzero can be used as a condition. Zero is treated as false, and nonzero is treated as true. C++ also provides the data type bool for Boolean variables that can hold only the values true and false—each of these is a C++ keyword.

For compatibility with earlier versions of C, which used integers for Boolean values, the bool value true also can be represented by any nonzero value (compilers typically use 1), and the bool value false also can be represented as zero.

UML Activity Diagram for an if Statement

The following diagram illustrates the single-selection if statement.

Images

This figure contains the most important symbol in an activity diagram—the diamond, or decision symbol, which indicates that a decision is to be made. The workflow continues along a path determined by the symbol’s associated guard conditions, which can be true or false. Each transition arrow emerging from a decision symbol has a guard condition (specified in square brackets next to the arrow). If a guard condition is true, the workflow enters the action state to which the transition arrow points. The diagram shows that if the grade is greater than or equal to 60 (i.e., the condition is true), the program prints “Passed,” then transitions to the activity’s final state. If the grade is less than 60 (i.e., the condition is false), the program immediately transitions to the final state without displaying a message. The if statement is a single-entry/single-exit control statement.

3.4 if…else Double-Selection Statement

The if single-selection statement performs an indicated action only when the condition is true. The ifelse double-selection statement allows you to specify an action to perform when the condition is true and another action when the condition is false. For example, the C++ statement

if (grade >= 60) {
   cout << "Passed";
}
else {
   cout << "Failed";
}

prints “Passed” if grade >= 60, but prints “Failed” if it’s less than 60. In either case, after printing occurs, the next statement in sequence is performed.

The body of the else is also indented. Whatever indentation convention you choose should be applied consistently throughout your programs.

UML Activity Diagram for an if…else Statement

The following diagram illustrates the flow of control in the preceding if…else statement:

Images

3.4.1 Nested if…else Statements

A program can test multiple cases by placing if…else statements inside other if…else statements to create nested ifelse statements. For example, the following nested if…else prints "A" for exam grades greater than or equal to 90, "B" for grades 80 to 89, "C" for grades 70 to 79, "D" for grades 60 to 69 and "F" for all other grades. We use shading to highlight the nesting.

Images

If variable studentGrade is greater than or equal to 90, the first four conditions in the nested if…else statement will be true, but only the statement in the if-part of the first if…else statement will execute. After that statement executes, the else-part of the “outermost” if…else statement is skipped. The preceding nested if…else statement also can be written in the following form, which is identical except for the spacing and indentation that the compiler ignores:

if (studentGrade >= 90) {
   cout << "A";
}
else if (studentGrade >= 80) {
   cout << "B";
}
else if (studentGrade >= 70) {
   cout << "C";
}
else if (studentGrade >= 60) {
   cout << "D";
}
else {
   cout << "F";
}

This form avoids deep indentation of the code to the right, which can force lines to wrap. Throughout the text, we always enclose control statement bodies in braces ({ and }), which avoids a logic error called the “dangling-else” problem.

3.4.2 Blocks

The if statement expects only one statement in its body. To include several statements in its body (or the body of an else for an if…else statement), enclose the statements in braces. It’s good practice always to use the braces. Statements contained in a pair of braces (such as the body of a control statement or function) form a block. A block can be placed anywhere in a function that a single statement can be placed.

The following example includes a block of multiple statements in the else part of an if…else statement:

if (grade >= 60) {
   cout << "Passed";
}
else
{
   cout << "Failed
";
   cout << "You must retake this course.";
}

In this case, if grade is less than 60, the program executes both statements in the body of the else and prints

Failed
You must retake this course.

Without the braces surrounding the two statements in the else clause, the statement

cout << "You must retake this course.";

would be outside the body of the else part of the if…else statement and would execute regardless of whether the grade was less than 60—a logic error.

Empty Statement

Just as a block can be placed anywhere a single statement can be placed, it’s also possible to have an empty statement, which is represented by placing a semicolon (;) where a statement typically would be.

3.4.3 Conditional Operator (?:)

C++ provides the conditional operator (?:) that can be used in place of an if…else statement. This can make your code shorter and clearer. The conditional operator is C++’s only ternary operator (i.e., an operator that takes three operands). Together, the operands and the ?: symbol form a conditional expression. For example, the statement

cout << (studentGrade >= 60 ? "Passed" : "Failed");

prints the value of the conditional expression. The operand to the left of the ? is a condition. The second operand (between the ? and :) is the value of the conditional expression if the condition is true. The operand to the right of the : is the value of the conditional expression if the condition is false. The conditional expression in this statement evaluates to the string "Passed" if the condition

studentGrade >= 60

is true and to the string "Failed" if it’s false. Thus, this statement with the conditional operator performs essentially the same function as the first if…else statement in Section 3.4. The precedence of the conditional operator is low, so the entire conditional expression is normally placed in parentheses.

The values in a conditional expression also can be actions to execute. For example, the following conditional expression also prints "Passed" or "Failed":

grade >= 60 ? cout << "Passed" : cout << "Failed";

The preceding is read, “If grade is greater than or equal to 60, then cout << "Passed"; otherwise, cout << "Failed".” This is comparable to an if…else statement. Conditional expressions can appear in some program locations where if…else statements cannot.

3.5 while Iteration Statement

An iteration statement allows you to specify that a program should repeat an action while some condition remains true.

As an example of C++’s while iteration statement, consider a program segment that finds the first power of 3 larger than 100. After the following while statement executes, the variable product contains the result:

int product{3};

while (product <= 100) {
   product = 3 * product;
}

Each iteration of the while statement multiplies product by 3, so product takes on the values 9, 27, 81 and 243 successively. When product becomes 243, product <= 100 becomes false. This terminates the iteration, so the final value of product is 243. At this point, program execution continues with the next statement after the while statement.

UML Activity Diagram for a while Statement

The UML activity diagram for the preceding while statement introduces the UML’s merge symbol:

Images

The UML represents both the merge symbol and the decision symbol as diamonds. The merge symbol joins two flows of activity into one. In this diagram, the merge symbol joins the transitions from the initial state and from the action state, so they both flow into the decision that determines whether the loop should begin (or continue) executing.

You can distinguish the decision and merge symbols by the number of incoming and outgoing transition arrows. A decision symbol has one transition arrow pointing to the diamond and two or more pointing out from it to indicate possible transitions from that point. In addition, each transition arrow pointing out of a decision symbol has a guard condition next to it. A merge symbol has two or more transition arrows pointing to the diamond and only one pointing from the diamond, to indicate multiple activity flows merging to continue the activity. None of the transition arrows associated with a merge symbol has a guard condition.

3.6 Counter-Controlled Iteration

Consider the following problem statement:

A class of ten students took a quiz. The grades (integers in the range 0–100) for this quiz are available to you. Determine the class average on the quiz.

The class average is equal to the sum of the grades divided by the number of students. The program must input each grade, keep track of the total of all grades entered, perform the averaging calculation and print the result.

We use counter-controlled iteration to input the grades one at a time. This technique uses a counter to control the number of times a set of statements will execute. In this example, iteration terminates when the counter exceeds 10.

3.6.1 Implementing Counter-Controlled Iteration

In Fig. 3.1, the main function implements the class-averaging algorithm with counter-controlled iteration. It allows the user to enter 10 grades, then calculates and displays the average.

 1  fig03_01.cpp
 2  // Solving the class-average problem using counter-controlled iteration.
 3  #include <iostream>
 4  using namespace std;
 5
 6  int main() {
 7     // initialization phase
 8     int total{0}; // initialize sum of grades entered by the user
 9     int gradeCounter{1}; // initialize grade # to be entered next
10
11     // processing phase uses counter-controlled iteration
12     while (gradeCounter <= 10) { // loop 10 times
13        cout << "Enter grade: "; // prompt
14        int grade;
15        cin >> grade; // input next grade
16        total = total + grade; // add grade to total
17        gradeCounter = gradeCounter + 1; // increment counter by 1
18     }
19
20     // termination phase
21     int average{total / 10}; // int division yields int result
22
23     // display total and average of grades
24     cout << "
Total of all 10 grades is " << total;
25     cout << "
Class average is " << average << endl;
26   }
Enter grade: 67
Enter grade: 78
Enter grade: 89
Enter grade: 67
Enter grade: 87
Enter grade: 98
Enter grade: 93
Enter grade: 85
Enter grade: 82
Enter grade: 100

Total of all 10 grades is 846
Class average is 84

Fig. 3.1 Solving the class-average problem using counter-controlled iteration.

Local Variables in main

Lines 8, 9, 14 and 21 declare int variables total, gradeCounter, grade and average, respectively. Variable grade stores the user input. A variable declared in a function body is a local variable. It can be used only from the line of its declaration to the closing right brace of the block in which the variable is declared. A local variable’s declaration must appear before the variable is used. Variable grade—declared in the body of the while loop—can be used only in that block.

Initializing Variables total and gradeCounter

Lines 8–9 declare and initialize total to 0 and gradeCounter to 1. These initializations occur before the variables are used in calculations.

Reading 10 Grades from the User

The while statement (lines 12–18) continues iterating as long as gradeCounter’s value is less than or equal to 10. Line 13 displays the prompt "Enter grade: ". Line 15 inputs the grade entered by the user and assigns it to variable grade. Then line 16 adds the new grade entered by the user to the total and assigns the result to total, replacing its previous value. Line 17 adds 1 to gradeCounter to indicate that the program has processed a grade and is ready to input the next grade from the user. Incrementing gradeCounter eventually causes it to exceed 10, which terminates the loop.

Calculating and Displaying the Class Average

When the loop terminates, line 21 performs the averaging calculation in the average variable’s initializer. Line 24 displays the text "Total of all 10 grades is " followed by variable total’s value. Then, line 25 displays the text "Class average is " followed by average’s value. When execution reaches line 26, the program terminates.

3.6.2 Integer Division and Truncation

This example’s averaging calculation produces an integer result. The program’s output indicates that the sum of the grade values in the sample execution is 846, which, when divided by 10, should yield 84.6. Numbers like 84.6 that contain decimal points are floating-point numbers. In the class-average program, however, the result of total / 10 is the integer 84, because total and 10 are both integers. Dividing two integers results in integer division—any fractional part of the calculation is truncated. In the next section, we’ll see how to obtain a floating-point result from the averaging calculation.

Assuming that integer division rounds (rather than truncates) can lead to incorrect results. For example, 7 / 4, which yields 1.75 in conventional arithmetic, truncates to 1 in integer arithmetic, rather than rounding to 2.

3.7 Sentinel-Controlled Iteration

Let’s generalize Section 3.6’s class-average problem. Consider the following problem:

Develop a class-averaging program that processes grades for an arbitrary number of students each time it’s run.

In the previous class-average example, the problem statement specified the number of students, so the number of grades (10) was known in advance. In this example, no indication is given of how many grades the user will enter during the program’s execution. The program must process an arbitrary number of grades.

One way to solve this problem is to use a special value called a sentinel value (also called a signal value, a dummy value or a flag value) to indicate “end of data entry.” The user enters grades until all legitimate grades have been entered. The user then types the sentinel value to indicate that no more grades will be entered.

You must choose a sentinel value that cannot be confused with an acceptable input value. Grades on a quiz are nonnegative integers, so –1 is an acceptable sentinel value for this problem. Thus, a run of the class-averaging program might process a stream of inputs such as 95, 96, 75, 74, 89 and –1. The program would then compute and print the class average for the grades 95, 96, 75, 74 and 89; since –1 is the sentinel value, it should not enter into the averaging calculation.

It’s possible that the user could enter –1 before entering grades, in which case the number of grades will be zero. We must test for this case before calculating the class average. According to the C++ standard, the result of division by zero in floating-point arithmetic is undefined. When performing division (/) or remainder (%) calculations in which the right operand could be zero, test for this and handle it (e.g., display an error message) rather than allowing the calculation to proceed.

3.7.1 Implementing Sentinel-Controlled Iteration

In Fig. 3.2, the main function implements sentinel-controlled iteration. Although each grade entered by the user is an integer, the averaging calculation is likely to produce a floating-point number. The type int cannot represent such a number. C++ provides data types float and double to store floating-point numbers in memory. The primary difference between these types is that double variables typically store numbers with larger magnitude and finer detail—that is, more digits to the right of the decimal point, which is also known as the number’s precision. C++ also supports type long double for floating-point values with larger magnitude and more precision than double. We say more about floating-point types in Chapter 4.

 1  // fig03_02.cpp
 2  // Solving the class-average problem using sentinel-controlled iteration.
 3  #include <iostream>
 4  #include <iomanip> // parameterized stream manipulators
 5  using namespace std;
 6
 7  int main() {
 8     // initialization phase
 9     int total{0}; // initialize sum of grades
10     int gradeCounter{0}; // initialize # of grades entered so far
11
12     // processing phase
13     // prompt for input and read grade from user
14     cout << "Enter grade or -1 to quit: ";
15     int grade;                            
16     cin >> grade;                         
17
18     // loop until sentinel value is read from user
19     while (grade != -1) {
20        total = total + grade; // add grade to total
21        gradeCounter = gradeCounter + 1; // increment counter
22
23        // prompt for input and read next grade from user
24        cout << "Enter grade or -1 to quit: ";
25        cin >> grade;                         
26     }
27
28     // termination phase
29     // if user entered at least one grade...
30     if (gradeCounter != 0) {
31        // use number with decimal point to calculate average of grades
32        double average{static_cast<double>(total) / gradeCounter};
33
34        // display total and average (with two digits of precision)
35        cout << "
Total of the " << gradeCounter
36           << " grades entered is " << total;
37        cout << setprecision(2) << fixed;
38        cout << "
Class average is " << average << endl;
39     }
40     else { // no grades were entered, so output appropriate message
41        cout << "No grades were entered" << endl;
42     }
43   }
Enter grade or -1 to quit: 97
Enter grade or -1 to quit: 88
Enter grade or -1 to quit: 72
Enter grade or -1 to quit: -1

Total of the 3 grades entered is 257
Class average is 85.67

Fig. 3.2 Solving the class-average problem using sentinel-controlled iteration.

Recall that integer division produces an integer result. This program introduces a cast operator to force the averaging calculation to produce a floating-point numeric result. This program also stacks control statements on top of one another (in sequence)—the while statement (lines 19–26) is followed in sequence by an if…else statement (lines 30–42). Much of the code in this program is identical to that in Fig. 3.1, so we concentrate on only the new concepts.

Program Logic for Sentinel-Controlled Iteration vs. Counter-Controlled Iteration

Line 10 initializes gradeCounter to 0 because no grades have been entered yet. Remember that this program uses sentinel-controlled iteration to input the grades. The program increments gradeCounter only when the user enters a valid grade. Line 32 declares double variable average, which stores the calculated class average as a floating-point number.

Compare the program logic for sentinel-controlled iteration in this program with that for counter-controlled iteration in Fig. 3.1. In counter-controlled iteration, each iteration of the while statement (lines 12–18 of Fig. 3.1) reads a value from the user, for the specified number of iterations. In sentinel-controlled iteration, the program prompts for and reads the first value (lines 14 and 16 of Fig. 3.2) before reaching the while. This value determines whether the flow of control should enter the while’s body. If the condition is false, the user entered the sentinel value, so no grades were entered and the body does not execute. If the condition is true, the body begins execution, and the loop adds the grade value to the total and increments the gradeCounter. Then lines 24–25 in the loop body input the next value from the user. Next, program control reaches the closing right brace of the loop at line 26, so execution continues with the test of the while’s condition (line 19). The condition uses the most recent grade entered by the user to determine whether the loop body should execute again.

The next grade is always input from the user immediately before the while condition is tested. This allows the program to determine whether the value just input is the sentinel value before the program processes that value (i.e., adds it to the total). If the sentinel value is input, the loop terminates, and the program does not add –1 to the total.

After the loop terminates, the if…else statement at lines 30–42 executes. The condition at line 30 determines whether any grades were input. If none were input, the if…else statement’s else part executes and displays the message "No grades were entered". After the if…else executes, the program terminates.

3.7.2 Converting Between Fundamental Types Explicitly and Implicitly

If at least one grade was entered, line 32 of Fig. 3.2

double average{static_cast<double>(total) / gradeCounter};

calculates the average. Recall from Fig. 3.1 that integer division yields an integer result. Even though variable average is declared as a double, if we had written line 32 as

double average{total / gradeCounter};

it would lose the fractional part of the quotient before the result of the division was used to initialize average.

static_cast Operator

To perform a floating-point calculation with integers in this example, you first create temporary floating-point values using the static_cast operator. Line 32 converts a temporary copy of its operand in parentheses (total) to the type in angle brackets (double). The value stored in the original int variable total is still an integer. Using a cast operator in this manner is called explicit conversion. static_cast is one of several cast operators we’ll discuss.

Promotions

After the cast operation, the calculation consists of the temporary double copy of total divided by the integer gradeCounter. For arithmetic, the compiler knows how to evaluate only expressions in which all the operand types are identical. To ensure this, the compiler performs an operation called promotion (also called implicit conversion) on selected operands. In an expression containing values of data types int and double, C++ promotes int operands to double values. So in line 32, C++ promotes a temporary copy of grade-Counter’s value to type double, then performs the division. Finally, average is initialized with the floating-point result. Section 5.5 discusses the allowed fundamental-type promotions.

Cast Operators for Any Type

Cast operators are available for use with every fundamental type and for other types, as you’ll see beginning in Chapter 9. Simply specify the type in the angle brackets (< and >) that follow the static_cast keyword. It’s a unary operator—that is, it has only one operand. Other unary operators include the unary plus (+) and minus (-) operators for expressions such as -7 or +5. Cast operators have the second highest precedence.

3.7.3 Formatting Floating-Point Numbers

The formatting capabilities in Fig. 3.2 are introduced here briefly and explained in depth in Chapter 15.

setprecision Parameterized Stream Manipulator

Line 37’s call to setprecisionsetprecision(2)—indicates that floating-point values should be output with two digits of precision to the right of the decimal point (e.g., 92.37). setprecision is a parameterized stream manipulator because it requires an argument (in this case, 2) to perform its task. Programs that use parameterized stream manipulators must include the header <iomanip>. The manipulator endl (lines 38 and 41) from <iostream> is a nonparameterized stream manipulator because it does not require an argument.

fixed Nonparameterized Stream Manipulator

The stream manipulator fixed (line 37) indicates that floating-point values should be output in fixed-point format. This is as opposed to scientific notation4, which displays a number between the values of 1.0 and 10.0, multiplied by a power of 10. So, in scientific notation, the value 3,100.0 is displayed as 3.1e+03 (that is, 3.1 × 103). This format is useful for displaying very large or very small values.

4. Formatting using scientific notation is discussed further in Chapter 15.

Fixed-point formatting forces a floating-point number to display without scientific notation. Fixed-point formatting also forces the decimal point and trailing zeros to print, even if the value is a whole-number amount, such as 88.00. Without the fixed-point formatting option, 88.00 prints as 88 without the trailing zeros and decimal point.

The stream manipulators setprecision and fixed perform sticky settings. Once they’re specified, all floating-point values formatted in your program will use those settings until you change them. Chapter 15 shows how to capture the stream format settings before applying sticky settings, so you can restore the original format settings later.

Rounding Floating-Point Numbers

When the stream manipulators fixed and setprecision are used, the printed value is rounded to the number of decimal positions specified by the current precision. The value in memory remains unaltered. For a precision of 2, the values 87.946 and 67.543 are rounded to 87.95 and 67.54, respectively.5

5. In Fig. 3.2, if you do not specify setprecision and fixed, C++ uses four digits of precision by default. If you specify only setprecision, C++ uses six digits of precision.

Together, lines 37 and 38 of Fig. 3.2 output the class average rounded to the nearest hundredth and with exactly two digits to the right of the decimal point. The three grades entered during the execution of the program in Fig. 3.2 total 257, which yields the average 85.666… and displays the rounded value 85.67.

3.8 Nested Control Statements

We’ve seen that control statements can be stacked on top of one another (in sequence). In this case study, we examine the only other structured way control statements can be connected—namely, by nesting one control statement within another.

3.8.1 Problem Statement

Consider the following problem statement:

A college offers a course that prepares students for the state licensing exam for realestate brokers. Last year, 10 of the students who completed this course took the exam. The college wants to know how well its students did on the exam. You’ve been asked to write a program to summarize the results. You’ve been given a list of these 10 students. Next to each name is written a 1 if the student passed the exam or a 2 if the student failed.

Your program should analyze the results of the exam as follows:

1. Input each test result (i.e., a 1 or a 2). Display the message “Enter result” on the screen each time the program requests another test result.

2. Count the number of test results of each type.

3. Display a summary of the test results, indicating the number of students who passed and the number who failed.

4. If more than eight students passed the exam, print “Bonus to instructor!”

3.8.2 Implementing the Program

Figure 3.3 implements the program with counter-controlled iteration and shows two sample executions. Lines 8–10 and 16 of main declare the variables that are used to process the examination results.

 1  // fig03_03.cpp
 2  // Analysis of examination results using nested control statements.
 3  #include <iostream>
 4  using namespace std;
 5
 6  int main() {
 7     // initializing variables in declarations
 8     int passes{0};
 9     int failures{0};
10     int studentCounter{1};
11
12     // process 10 students using counter-controlled loop
13     while (studentCounter <= 10) {
14        // prompt user for input and obtain value from user
15        cout << "Enter result (1 = pass, 2 = fail): ";
16        int result;
17        cin >> result;
18
19        // if...else is nested in the while statement
20        if (result == 1) {         
21           passes = passes + 1;    
22        }                          
23        else {                     
24           failures = failures + 1;
25        }                          
26
27        // increment studentCounter so loop eventually terminates
28        studentCounter = studentCounter + 1;
29     }
30
31     // termination phase; prepare and display results
32     cout << "Passed: " << passes << "
Failed: " << failures << endl;
33
34     // determine whether more than 8 students passed
35     if (passes > 8) {
36        cout << "Bonus to instructor!" << endl;
37     }
38   }
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 2
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 1
Passed: 9
Failed: 1
Bonus to instructor!
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 2
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 2
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 2
Enter result (1 = pass, 2 = fail): 2
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 1
Enter result (1 = pass, 2 = fail): 1
Passed: 6
Failed: 4

Fig. 3.3 Analysis of examination results using nested control statements.

The while statement (lines 13–29) loops 10 times. During each iteration, the loop inputs and processes one exam result. Notice that the if…else statement (lines 20–25) for processing each result is nested in the while statement. If the result is 1, the if…else statement increments passes; otherwise, it assumes the result is 26 and increments failures. Line 28 increments studentCounter before the loop condition is tested again at line 13. After 10 values have been input, the loop terminates and line 32 displays the number of passes and failures. The if statement at lines 35–37 determines whether more than eight students passed the exam and, if so, outputs the message "Bonus to instructor!"

6. This could be a bad assumption if invalid data is entered. We’ll discuss data validation techniques later.

Figure 3.3 shows the input and output from two sample executions. During the first, the condition at line 35 is true—more than eight students passed the exam, so the program outputs a message to bonus the instructor.

11 3.8.3 Preventing Narrowing Conversions with C++11 List Initialization

Consider the C++11 list initialization in line 10 of Fig. 3.3:

int studentCounter{1};

Prior to C++11, you would have written this as

int studentCounter = 1;

For fundamental-type variables, list-initialization syntax prevents narrowing conversions that could result in data loss. For example, the declaration

int x = 12.7;

attempts to assign the double value 12.7 to the int variable x. Here, C++ converts the double value to an int by truncating the floating-point part (.7). This is a narrowing conversion that loses data. So, this declaration assigns 12 to x. Compilers typically issue a warning for this, but still compile the code.

However, using list initialization, as in

int x{12.7};

yields a compilation error, helping you avoid a potentially subtle logic error. If you specify a whole-number double value, like 12.0, you’ll still get a compilation error. The initial-izer’s type (double), not it’s value (12.0), determines whether a compilation error occurs.

The C++ standard document does not specify the wording of error messages. For the preceding declaration, Apple’s Xcode compiler gives the error

Type 'double' cannot be narrowed to 'int' in initializer list

Visual Studio gives the error

conversion from 'double' to 'int' requires a narrowing conversion

and GNU C++ gives the error

type 'double' cannot be narrowed to 'int' in initializer list
[-Wc++11-narrowing]

We’ll discuss additional list-initializer features in later chapters.

A Look Back at Fig. 3.1

You might think that the following statement from Fig. 3.1

int average{total / 10}; // int division yields int result

contains a narrowing conversion, but total and 10 are both int values, so the initializer value is an int. If in the preceding statement total were a double variable or if we used the double literal value 10.0 for the denominator, then the initializer value would have type double and the compiler would issue an error message for a narrowing conversion.

3.9 Compound Assignment Operators

You can abbreviate the statement

c = c + 3;

with the addition compound assignment operator, +=, as

c += 3;

The += operator adds the value of the expression on its right to the value of the variable on its left and stores the result in the variable on the left. Thus, the assignment expression c += 3 adds 3 to c. The following table shows all the arithmetic compound assignment operators, sample expressions and explanations of what the operators do:

Images

Later, we’ll discuss other types of compound assignment operators.

3.10 Increment and Decrement Operators

The following table summarizes C++’s two unary operators for adding 1 to or subtracting 1 from the value of a numeric variable—these are the unary increment operator, ++, and the unary decrement operator, --:

Images

An increment or decrement operator that’s prefixed to (placed before) a variable is referred to as the prefix increment or prefix decrement operator, respectively. An increment or decrement operator that’s postfixed to (placed after) a variable is referred to as the postfix increment or postfix decrement operator, respectively.

Using the prefix increment (or decrement) operator to add 1 to (or subtract 1 from) a variable preincrements (or predecrements) the variable. The variable is incremented (or decremented) by 1 then its new value is used in the expression in which it appears.

Using the postfix increment (or decrement) operator to add 1 to (or subtract 1 from) a variable postincrements (or postdecrements) the variable. The variable’s current value is used in the expression in which it appears then its value is incremented (or decremented) by 1. Unlike binary operators, the unary increment and decrement operators should be placed next to their operands, with no intervening spaces.

Prefix Increment vs. Postfix Increment

Figure 3.4 demonstrates the difference between the prefix increment and postfix increment versions of the ++ increment operator. The decrement operator (--) works similarly.

 1  // fig03_04.cpp
 2  // Prefix increment and postfix increment operators.
 3  #include <iostream>
 4  using namespace std;
 5
 6  int main() {
 7     // demonstrate postfix increment operator
 8     int c{5};
 9     cout << "c before postincrement: " << c << endl; // prints 5
10     cout << " postincrementing c: " << c++ << endl; // prints 5
11     cout << " c after postincrement: " << c << endl; // prints 6
12
13     cout << endl; // skip a line
14
15     // demonstrate prefix increment operator
16     c = 5;
17     cout << " c before preincrement: " << c << endl; // prints 5
18     cout << " preincrementing c: " << ++c << endl; // prints 6
19     cout << " c after preincrement: " << c << endl; // prints 6
20   }
c before postincrement: 5
    postincrementing c: 5
 c after postincrement: 6

 c before preincrement: 5
     preincrementing c: 6
  c after preincrement: 6

Fig. 3.4 Prefix increment and postfix increment operators.

Line 8 initializes the variable c to 5, and line 9 outputs c’s initial value. Line 10 outputs the value of the expression c++. This expression postincrements the variable c, so c’s original value (5) is output, then c’s value is incremented (to 6). Thus, line 10 outputs c’s initial value (5) again. Line 11 outputs c’s new value (6) to prove that the variable’s value was indeed incremented in line 10.

Line 16 resets c’s value to 5, and line 17 outputs c’s value. Line 18 outputs the value of the expression ++c. This expression preincrements c, so its value is incremented; then the new value (6) is output. Line 19 outputs c’s value again to show that the value of c is still 6 after line 18 executes.

Simplifying Statements with the Arithmetic Compound Assignment, Increment and Decrement Operators

The arithmetic compound assignment operators and the increment and decrement operators can be used to simplify program statements. For example, the three assignment statements in Fig. 3.3 (lines 21, 24 and 28)

passes = passes + 1;
failures = failures + 1;
studentCounter = studentCounter + 1;

can be written more concisely with compound assignment operators as

passes += 1;
failures += 1;
studentCounter += 1;

with prefix increment operators as

++passes;
++failures;
++studentCounter;

or with postfix increment operators as

passes++;
failures++;
studentCounter++;

When incrementing or decrementing a variable in a statement by itself, the prefix increment and postfix increment forms have the same effect, and the prefix decrement and postfix decrement forms have the same effect. Only when a variable appears in the context of a larger expression does preincrementing or postincrementing the variable have a different effect (and similarly for predecrementing or postdecrementing).

Attempting to use the increment or decrement operator on an expression other than one to which a value can be assigned is a syntax error. For example, writing ++(x + 1) is a syntax error, because (x + 1) is not a variable.

Operator Precedence and Grouping

The following table shows the precedence and grouping of the operators introduced to this point. The operators are shown top-to-bottom in decreasing order of precedence. The second column indicates the grouping of the operators at each level of precedence. Notice that the conditional operator (?:), the unary operators preincrement (++), predecrement (--), plus (+) and minus (-), and the assignment operators =, +=, -=, *=, /= and %= group from right-to-left. All other operators in this table group from left-to-right. The third column names the various groups of operators.

Images

3.11 Fundamental Types Are Not Portable

You can view the complete list of C++ fundamental types and their typical ranges at

https://en.cppreference.com/w/cpp/language/types

In C and C++, an int on one machine might be represented by 16 bits (2 bytes) of memory, on a second machine by 32 bits (4 bytes), and on another machine by 64 bits (8 bytes). For this reason, code using integers is not always portable across platforms. You could write multiple versions of your programs to use different integer types on different platforms. Or you could use techniques to achieve various levels of portability.7 In the next section, we’ll show one way to achieve portability.

7. The integer types in the header <cstdint> (https://en.cppreference.com/w/cpp/types/integer) can be used to ensure that integer variables are correct size for your application across platforms.

PERF Among C++’s integer types are int, long and long long. The C++ standard requires type int to be at least 16 bits, type long to be at least 32 bits and type long long to be at least 64 bits. The standard also requires that an int’s size be less than or equal to a long’s size and that a long’s size be less than or equal to a long long’s size. Such “squishy” requirements create portability challenges, but allow compiler implementers to optimize performance by matching fundamental types sizes to your machine’s hardware.

3.12 Objects Natural Case Study: Arbitrary Sized Integers

The range of values an integer type supports depends on the number of bytes used to represent the type on a particular computer. For example, a four-byte int can store 232 possible values in the range –2,147,483,648 to 2,147,483,647. On most systems, a long long integer is 8 bytes and can store 264 possible values in the range –9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.

Some Applications Need Numbers Outside a long long Integer’s Range

Consider factorial calculations. A factorial is the product of the integers from 1 to a given value. The factorial of 5 (written 5!) is 1 * 2 * 3 * 4 * 5, which is 120. The highest factorial value we can represent in a 64-bit integer is 20!, which is 2,432,902,008,176,640,000. Factorials quickly grow outside the range representable by a long long integer. With big data getting bigger quickly, an increasing number of real-world applications will exceed the limitations of long long integers.

Images Security Another application requiring extremely large integers is cryptography—an important aspect of securing data that’s transmitted between computers over the Internet. Many cryptography algorithms perform calculations using 128-bit or 256-bit integer values—far larger than we can represent with C++’s fundamental types.

Arbitrary Precision Integers with Class BigNumber

Any application requiring integers outside long long’s range requires special processing. Unfortunately, the C++ standard library does not (yet) have a class for arbitrary precision integers. So, for this example, we’ll dive into the vast world of open-source class libraries to demonstrate one of the many C++ classes that you can use to can create and manipulate arbitrary precision integers. We’ll use the class BigNumber from:

https://github.com/limeoats/BigNumber

For your convenience, we included the download in this example’s fig03_05 folder. Be sure to read the license terms included in the provided LICENSE.md file.

To use BigNumber, you don’t have to understand how it’s implemented.8 You simply include its header file (bignumber.h), create objects of the class then use them in your code. Figure 3.5 demonstrates BigNumber and shows a sample output. For this example, we’ll use the maximum long long integer value to show that we can create an even bigger integer with BigNumber. At the end of this section, we show how to compile and run the code.

8. After you get deeper into C++, you might want to peek at BigNumber’s source code (approximately 1000 lines) to see how it’s implemented. In our object-oriented programming presentation later in this book, you’ll learn a variety of techniques that you can use to create your own big integer class.

 1  // fig03_05.cpp
 2  // Integer ranges and arbitrary precision integers.
 3  #include <iostream>
 4  #include "bignumber.h"
 5  using namespace std;
 6
 7  int main() {
 8     // use the maximum long long fundamental type value in calculations
 9     long long value1{9'223'372'036'854'775'807LL}; // max long long value
10     cout << "long long value1: " << value1
11        << "
value1 - 1 = " << value1 - 1 // OK
12        << "
value1 + 1 = " << value1 + 1; // result is undefined
13
14     // use an arbitrary precision integer
15     BigNumber value2{value1};
16     cout << "

BigNumber value2: " << value2
17        << "
value2 - 1 = " << value2 - 1 // OK
18        << "
value2 + 1 = " << value2 + 1; // OK
19
20     // powers of 100,000,000 with long long
21     long long value3{100'000'000};
22     cout << "

value3: " << value3;
23
24     int counter{2};
25
26     while (counter <= 5) {
27        value3 *= 100'000'000; // quickly exceeds maximum long long value
28        cout << "
value3 to the power " << counter << ": " << value3;
29        ++counter;
30     }
31
32     // powers of 100,000,000 with BigNumber
33     BigNumber value4{100'000'000};
34     cout << "

value4: " << value4 << endl;
35
36     counter = 2;
37
38     while (counter <= 5) {
39        cout << "value4.pow(" << counter << "): "
40           << value4.pow(counter) << endl;
41        ++counter;
42     }
43
44     cout << endl;
45   }
long long value1: 9223372036854775807
value1 - 1: 9223372036854775806                          OK
value1 + 1: -9223372036854775808                         Incorrect result

BigNumber value2: 9223372036854775807
value2 - 1: 9223372036854775806                          OK
value2 + 1: 9223372036854775808                          OK

value3: 100000000
value3 to the power 2: 10000000000000000                 OK
value3 to the power 3: 2003764205206896640               Incorrect result
value3 to the power 4: -8814407033341083648              Incorrect result
value3 to the power 5: -5047021154770878464              Incorrect result

value4: 100000000
value4.pow(2): 10000000000000000                         OK
value4.pow(3): 1000000000000000000000000                 OK
value4.pow(4): 100000000000000000000000000000000         OK
value4.pow(5): 10000000000000000000000000000000000000000 OK

Fig. 3.5 Integer ranges and arbitrary precision integers.

Including a Header That Is Not in the C++ Standard Library

In an #include directive, headers that are not from the C++ Standard Library typically are placed in double quotes (""), rather than the angle brackets (<>). The double quotes tell the compiler that header is in your application’s folder or another folder that you specify.

What Happens When You Exceed the Maximum long long Integer Value?

Line 9 initializes the variable value1 with the maximum long long value on our system:9

long long value1{9'223'372'036'854'775'807LL}; // max long long value

9. The platforms on which we tested this book’s code each have as their maximum long long integer value 9,223,372,036,854,775,807. You can determine this value programmatically with the expression std::numeric_limits<long long>::max(), which uses class numeric_limits from the C++ standard library header <limits>. The <> and :: notations used in this expression are covered in later chapters, so we used the literal value 9,223,372,036,854,775,807 in Fig. 3.5.

20 Typing numeric literals with many digits can be error prone. To make such literals more readable and reduce errors, C++14 introduced the digit separator ' (a single-quote character), which you insert between groups of digits in numeric literals—we used it so separate groups of three digits. Also, note the LL (“el el”) at the end of the literal value—this indicates that the literal is a long long integer.

Line 10 displays value1, then line 11 subtracts one from it to demonstrate a valid calculation. Next, we attempt to add 1 to value1, which already contains the maximum long long value. All our compilers displayed as the result the minimum long long value. The C++ standard actually says the result of this calculation is undefined behavior. Such behaviors can differ between systems—ours displayed an incorrect value but other systems could terminate the program and display an error message. This is another example of why the fundamental integer types are not portable.

Performing the Same Operations with a BigNumber Object

Lines 15–18 use a BigNumber object to repeat the operations from lines 9–12. We create a BigNumber object named value2 and initialize it with value1, which contains the maximum value of a long long integer:

BigNumber value2{value1};

Next, we display the BigNumber then subtract one from it and display the result. Line 18 adds one to value2, which contains the maximum value of a long long. BigNumber handles arbitrary precision integers, so it correctly performs this calculation. The result is a value that C++’s fundamental integer types cannot handle on our systems.

BigNumber supports all the typical arithmetic operations, including + and - used in this program. The compiler already knows how to use arithmetic operators with fundamental numeric types, but it has to be taught how to handle those operators for class objects. We discuss that process—called operator overloading—in Chapter 14.

Powers of 100,000,000 with long long Integers

Lines 21–30 calculate powers of 100,000,000 using long long integers. First, we create the variable value3 and display its value. Lines 26–30 loop five times. The calculation

value3 *= 100'000'000; // quickly exceeds maximum long long value

multiples value3’s current value by 100,000,000 to raise value3 to the next power. As you can see in the program’s output, only the loop’s first iteration produces a correct result.

Powers of 100,000,000 with BigNumber Objects

To demonstrate that BigNumber can handle significantly larger values than the fundamental integer types, lines 33–42 calculate powers of 100,000,000 using a BigNumber object. First, we create BigNumber value4 and display its initial value. Lines 38–42 loop five times. The calculation

value4.pow(counter)

calls BigNumber member function pow to raise value4 to the power counter. BigNumber correctly handles each calculation, producing massive values that are far outside the ranges supported by our Windows, macOS and Linux systems’ fundamental integer types.

Images Though a BigNumber can represent any integer value, it does not match your system’s hardware. So you’ll likely sacrifice some performance in exchange for the flexibility Big-Number provides.

Compiling and Running the Example in Microsoft Visual Studio

In Microsoft Visual Studio:

1. Create a new project, as described in Section 1.9.

2. In the Solution Explorer, right-click the project’s Source Files folder and select Add > Existing Item….

3. Navigate to the fig03_05 folder, select fig03_05.cpp and click Add.

4. Repeat Steps 2–3 for bignumber.cpp from the fig03_05BigNumbersrc folder.

5. In the Solution Explorer, right-click the project’s name and select Properties….

6. Under Configuration Properties, select C/C++, then on the right side of the dialog, add to the Additional Include Directories the full path to the BigNumbersrc folder on your system. For our system, this was

C:UsersaccountDocumentsexamplesch03fig03_05BigNumbersrc

7. Click OK.

8. Type Ctrl + F5 to compile and run the program.

Compiling and Running the Example in GNU g++

For GNU g++ (these instructions also work from a Terminal window on macOS):

1. At your command line, change to this example’s fig03_05 folder.

2. Type the following command to compile the program—the -I option specifies additional folders in which the compiler should search for header files:

g++ -std=c++2a -I BigNumber/src fig03_05.cpp 
BigNumber/src/bignumber.cpp -o fig03_05

3. Type the following command to execute the program:

./fig03_05
Compiling and Running the Example in Apple Xcode

In Apple Xcode:

1. Create a new project, as described in Section 1.9, and delete main.cpp.

2. Drag fig03_05.cpp from the fig03_05 folder in the Finder onto your project’s source code folder in Xcode, then click Finish in the dialog that appears.

3. Drag bignumber.h and bignumber.cpp from the fig03_05/BigNumber/src folder onto your project’s source code folder in Xcode, then click Finish in the dialog that appears.

4. Type Images + R to compile and run the program.

3.13 C++20 Feature Mock-Up—Text Formatting with Function format

20 C++20 introduces powerful new string formatting capabilities via the format function (in header <format>). These capabilities greatly simplify C++ formatting by using a syntax similar to that used in Python, Microsoft’s .NET languages (like C# and Visual Basic) and the up-and-coming newer language Rust.10 You’ll see throughout the book that the C++20 text-formatting capabilities are more concise and more powerful than those in earlier C++ versions. In Chapter 14, we’ll also show that these new capabilities can be customized to work with your own new class types. We’ll show both old- and new-style formatting because in your career you may work with software that uses the old style.

10. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r10.html.

C++20 String Formatting Is Not Yet Implemented

At the time of this writing (April 2020), the C++ standard was about to be approved (May 2020) and C++ compilers had not yet fully implemented many of C++20’s features, including text formatting. However, the {fmt} library at

https://github.com/fmtlib/fmt

provides a full implementation of the new text-formatting features.11,12 So, we’ll use this library until the C++20 compilers implement text formatting.13 For your convenience, we included the complete download in the examples folder’s libraries subfolder, then included only the required files in the fig03_06 folder. Be sure to read the library’s license terms included in the provided format.h file.

11. According to http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r10.html, which is the C++ standard committee proposal for C++20 text formatting.

12. C++20’s text formatting features are a subset of the features provided by the {fmt} library.

13. Some of our C++20 Feature Mock-Up sections present code that does not compile or run. Once the compilers implement those features, we’ll retest the code, update our digital products and post updates for our print products at https://deitel.com/c-plus-plus-20-for-programmers. The code in this example runs, but uses the {fmt} open-source library to demonstrate features that C++20 compilers will support eventually.

Format String Placeholders

The format function’s first argument is a format string containing placeholders delimited by curly braces ({ and }). The function replaces the placeholders with the values of the function’s other arguments, as demonstrated in Fig. 3.6.

 1  // fig03_06.cpp
 2  // C++20 string formatting.
 3  #include <iostream>
 4  #include "fmt/format.h" // C++20: This will be #include <format>
 5  using namespace std;
 6  using namespace fmt; // not needed in C++20
 7
 8  int main() {
 9     string student{"Paul"};
10     int grade{87};
11
12     cout << format("{}'s grade is {}", student, grade) << endl;
13   }
Paul's grade is 87

Fig. 3.6 C++20 string formatting.

Placeholders Are Replaced Left-to-Right

The format function replaces its format string argument’s placeholders left-to-right by default. So, line 11’s format call inserts into the format string

"{}'s grade is {}"

student’s value ("Paul") in the first placeholder and grade’s value (87) in the second placeholder, then returns the string

"Paul's grade is 87"
Compiling and Running the Example in Microsoft Visual Studio

The steps below are the same as those in Section 3.12 with the following changes:

• In Step 3, navigate to the fig03_06 folder, add both the files fig03_06.cpp and format.cc to your project’s Source Files folder.

• In Step 6, add to the Additional Include Directories the full path to the fig03_06 folder on your system. For our system, this was

C:UsersaccountDocumentsexamplesch03fig03_06

Compiling and Running the Example in GNU g++

For GNU g++ (these instructions also work from a Terminal window on macOS):

1. At your command line, change folders to this example’s fig03_06 folder.

2. Type the following command to compile the program:

g++ -std=c++2a -I fmt fig03_06.cpp format.cc -o fig03_06

3. Type the following command to execute the program:

./fig03_06
Compiling and Running the Example in Apple Xcode

The steps for this example are the same as those in Section 3.12 with the following change:

• In Step 2, drag the files fig03_06.cpp, format.cc and the folder fmt from to the fig03_06 folder onto your project’s source code folder in Xcode.

3.14 Wrap-Up

Only three types of control statements—sequence, selection and iteration—are needed to develop any algorithm. We demonstrated the if single-selection statement, the if…else double-selection statement and the while iteration statement. We used control-statement stacking to total and compute the average of a set of student grades with counter- and sentinel-controlled iteration, and we used control-statement nesting to analyze and make decisions based on a set of exam results. We introduced C++’s compound assignment operators and its increment and decrement operators. We discussed why C++’s fundamental types are not portable, then used objects of the open-source class BigNumber to perform integer arithmetic with values outside the range supported by our systems. Finally, we introduced C++20’s new text formatting in the context of the open-source {fmt} library. In Chapter 4, we continue our discussion of control statements, introducing the for, dowhile and switch statements, and we introduce the logical operators for creating compound conditions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset