Professional Documents
Culture Documents
Joel Murach, Mary Delamater - Murach's C++ Programming-Mike Murach & Associates (2018) PDF
Joel Murach, Mary Delamater - Murach's C++ Programming-Mike Murach & Associates (2018) PDF
C++
Programming
Mary Delamater
Joel Murach
TRAINING & REFERENCE
murach's
C++
Programming
Mary Delamater
Joel Murach
10 987654321
ISBN-13: 978-1-943872-27-5
Contents
Introduction xvii
Reference aids
Appendix A How to set up Windows for this book 743
Appendix B How to set up macOS for this book 749
Index 757
Expanded contents VI i
Expanded contents
Section 1 Essential skills for modern C++
programs, your students will get more and better practice in lab. And because
our paired pages are so good for reference, your students will be able to review
for tests and do their projects more efficiently.
Essential skills
for modern C++
The eight chapters in this section get you off to a fast start by presenting
a complete subset of the essential concepts and skills that you need for
C++ programming. First, chapter 1 introduces you to C++ programming
and shows you how to use an IDE (Integrated Development Environment)
to get started with development. Then, chapters 2 through 7 present the
programming skills that you need to develop substantial programs of your
own.
After the first seven chapters, you’re going to need to improve your
testing and debugging skills, and you may want to deploy a program to
see how that works. So. that’s what chapter 8 shows you how to do. When
you complete this section, you’ll be able to design, code, test, debug, and
deploy C++ programs that can work with data that’s stored in files.
1
An introduction
to C++ programming
This chapter starts by presenting some background information about C++.
Although this information isn’t essential to developing C++ programs, it does
show how C++ works and how it compares to other languages. So it’s impor
tant for you to at least skim this information.
After the background information, this chapter shows how to use an IDE
(Integrated Development Environment) to develop a C++ program. If you’re
using a Windows computer, we recommend using the Visual Studio IDE
that’s developed by Microsoft. If you’re using a Mac. we recommend using
the Xcode IDE that’s developed by Apple. As you’ll see, there are many other
IDEs that support C++ development, including the open-source Eclipse and
NetBeans IDEs. These IDEs run on all modem operating systems, and you
can use them if you prefer.
Description
• C++ is a general-purpose programming language that was originally based on
the C language.
• Many other programming languages have been influenced by C++ including
Java and C#.
C++ history
Year Event
1979 Bjarne Stroustrup begins work on “C with Classes".
1983 “C with Classes" is renamed to C++.
1985 The first commercial implementation of C++ is released.
1985 The first edition of The C++ Programming Language is published. This book became
the definitive reference for the language, as there was not yet an official standard.
1990 The Annotated C++ Reference Manual is published. This book became the basis for
the 1998 standard.
1998 The C++98 standard is finalized.
2003 The C++03 standard is released, fixing some bugs with the previous standard.
2011 The C++11 standard is released, adding many new features.
2014 The C++14 standard is released, fixing some bugs with the previous standard.
2017 The C++17 standard is released, adding many new features.
Description
• Since 1998, C++ has been standardized by the International Organization for
Standardization (ISO).
• C++ 2020 is the next planned standard.
A console application
Running in the console for Windows 10
Description
• An application, or app, is computer software that performs a task or related set
of tasks.
• An application can also be referred to as a program.
• A console application uses the console to interact with the user.
• The appearance of the console may differ from one operating system to
another, but the functionality should be the same.
• A GUI application uses a graphical user interface to interact with the user.
Examples of GUI applications include web browsers, word processors, and
spreadsheets.
• Console applications are easier to code than GUI applications. As a result,
they’re often used by programmers when learning a language.
int mainf)
<
// print name of program
cout « "Circle Calculator" « endl « endl;
// make calculations
double pi ■ 3.14159;
double diameter ■ 2 * radius;
double circumference ■ diameter * pi;
double area « pi * pow(radius, 2.0);
Description
• When you develop a C++ application, you write the source code for the
program. Later, a program known as a compiler converts the source code into
machine code that can be run by a computer.
• The two #include directives specify that this code uses two header files (iostream
and cmath). These headers are available from the C++ standard library.
• The main() function contains the code that's run when the program starts.
• This code uses the cout, cin. and endl objects that are available from the
iostream header to write output to the console (cout), read input from the
console (cin), and specify the end of a line (endl).
• This code uses the pow() and round() functions that are available from the
cmath header to raise a number to the power of 2 and to round a number to 1
decimal place.
• The return statement returns a value of 0 to the operating system to indicate
that the program has ended normally.
Description
• Machine language consists of the Is and Os that a computer can run.
• A low-level language such as machine language is hard for humans to read and
understand but can be run by a computer.
• A high-level language such as C++ is relatively easy for humans to read and
understand but can’t be run by a computer until it is translated into a low-level
language.
• A compiler is a program that translates a high-level language to a low-level language.
• When you develop a C++ application, you typically use an Integrated
Development Environment (IDE) to enter and edit the source code for the
application. Files that contain C++ source code often have a .cpp extension.
• The C++ compilation process translates C++ source code into a machine
language known as object code and then into machine language that can be run
directly by an operating system.
• Libraries that contain prewritten and precompiled C++ code that perform
various operations and tasks are called the runtime libraries.
• For a C++ program to run on different operating systems, you must compile
the source code once for each operating system.
Description
• To develop C++ applications, you typically use an Integrated Development
Environment (IDE). Many other IDEs exist beyond those listed above,
including some commercial products such as CLion and more open-source
offerings such as Code::Blocks.
• Each IDE must use a compiler to compile the source code for a particular
operating system. Many vendors provide C++ compilers, including the Free
Software Foundation. Microsoft. Apple, Intel. Oracle, and IBM.
• All of the IDEs and compilers listed here are either free or have free editions.
• Different C++ compilers provide different levels of support for the ISO
standards. When choosing a compiler, it’s important to choose one that
provides good support for the ISO standards.
Description
• A Visual Studio solution consists of one or more projects where each project
consists of a folder that contains the folders and files for a program.
Figure 1 -7 How to open an existing project and work with its source code
18 Section 1 Essential skills for modem C++
settings, select the Tools->Import and Export Settings menu item to display
the Import and Export Settings Wizard. In the first dialog, select the "Reset all
settings” option and click the Next button. Then, in the second dialog, select
the “No” option and click the Next button. When the third dialog is displayed,
the current settings will be highlighted. If those settings aren’t General, select
General and then click the Finish button. Otherwise, click the Cancel button.
-f Add to Soiree *
Description
• To run a project, press Ctrl+F5 or select the Debug->Start Without Debugging
menu item. If a dialog is displayed that asks if you want to build the project,
click the Yes button to build the project before you run it.
• When you run a project. Visual Studio automatically compiles it. As a result,
you don’t need to manually compile a project before you run it. This is known
as the automatic compilation feature.
• The Start Without Debugging menu item automatically displays a “Press any
key to continue” message when the program exits. This keeps the console open
until you press any key.
• If you accidentally press F5 or select the Debug->Start Debugging menu item,
the console closes when the program exits. This might prevent you from seeing
the output of the program. You’ll learn more about how this works in chapter 8.
Fte Edit View Pieject Buikt Drt-jg Ted Analyze Window Help JodMuiech •
O • 3 ’* HJ- 9 ’ -| - > Local WndcwsOebuggw - p - > ’J M -
Procedure
1. Click the New Project button in the toolbar or select the File->New->Project
menu item to display the New Project dialog.
2. Select the Windows Console Application item, enter a name for the project,
select a location for the project, and click the OK button to create the project.
3. Select the project in the Solution Explorer and then select the
Project-^ Properties menu item to display the Property Pages dialog. Expand
the C/C++ group, select the Precompiled Headers item, and set the Precompiled
Header property to “Not Using Precompiled Headers”.
4. Optionally, delete all unnecessary files related to the precompiled header
(targetver.h. stdafx.h. and stdafx.cpp).
5. Optionally, rename the .cpp file that contains the main() function to main.cpp.
6. Open the file that contains the main() function and edit the code so it’s like the
code presented throughout this book.
Description
• By default. Visual Studio creates a starting source code file that has the same
name as the project. However, it’s a common practice to use the name main.cpp
for this file, as shown by the programs in this book.
• By default. Visual Studio creates a new project that uses a precompiled header
file named stdafx.h. This header file can improve performance of the compiler,
especially if you’re using many large libraries. However, it isn’t standard, and
it isn’t necessary for the programs in this book, which don’t use a lot of large
libraries.
• If you set the Precompiled Header property to “Not Using Precompiled
Headers” as described in part 1 of this figure, you can use standard headers as
shown throughout this book. In addition, you can optionally use the Solution
Explorer to delete all files related to the precompiled header, including
targetver.h. stdafx.h. and stdafx.cpp.
Description
• An Xcode project consists of a folder that contains the folders and files for a
program.
Figure 1 -11 How to open an existing project and work with its source code
28 Section 1 Essential skills for modem C++
Description
• To run a project, press Command+R or select the Project->Run menu item.
• When you run a project, Xcode automatically compiles it. As a result, you don’t
need to manually compile a project before you run it. This is known as the
automatic compilation feature.
• When you run a project that writes data to the console, that data is displayed in
the Console window in the lower right comer of the Xcode window.
• To display or hide the Console window, you can click the icon on the bottom
right comer of the Xcode window.
• When you run a project that requests input from the console, the Console
window pauses to accept the input. Then, you can click in the Console window,
type the input, and press the Enter key.
• In addition to displaying output and accepting input, the Console window can
display other information. For example, the Console window automatically
displays a message when the program finishes running, and it can display errors
that are encountered when an application is run.
Procedure
1. Select the File->New->Project menu item to display the first dialog for creating
a new project.
2. In the first dialog, select the macOS operating system, select the Command
Line Tool item, and click the Next button.
3. In the second dialog, enter a product name, select the C++ language, and click
the Next button. If necessary, enter an organization name and an identifier for
the organization that uniquely identifies it.
4. In the third dialog, navigate to the folder you want to use to store your project,
and click the Create button to create the folder for the project.
When you create a new project. Xcode generates some starting code for you.
For example, Xcode generated some of the code for the mainf) function shown
in this figure. However, it also generated some extra code that was unneces
sary. As a result, I edited this file so it only contains the starting code shown
in this figure. At this point, running this program would display a message of
“Success!” on the console, which is a good starting point for a new program.
Chapter 1 An introduction to C+ + programming 35
Procedure (continued)
5. In the Xcode window, expand the folder for the project if necessary. Then, click
on the main.cpp file to show the main() function that was generated for the
project.
6. Edit the generated code so it’s like the code presented throughout this book.
Description
• By default, Xcode generates some starting code for you. You can edit this code
so it contains C++ code like the code shown throughout this book.
Perspective
In this chapter, you were introduced to C++ programming. In addition, you
learned how to use an IDE such as Visual Studio or Xcode to create and run a
C++ program on Windows or macOS. With that as background, you’re ready
to learn how to write your own C++ programs. But first, I recommend that you
familiarize yourself with the IDE of your choice by doing the exercises at the
end of this chapter.
Terms
International Organization for compiler
Standardization (ISO) object code
application (app) linker
program runtime library
desktop application cross compiling
GUI application Integrated Development
graphical user interface (GUI) Environment (IDE)
console application code editor
console Visual Studio solution
command prompt project
terminal automatic compilation
source code code completion
machine language IntelliSense
low-level language syntax error
high-level language error detection
preprocessor
Summary
• Since 1998. C++ has been standardized by the International
Organization for Standardization (ISO).
• An application, or app, is computer software that performs a task or a
related set of tasks. Applications can also be referred to as programs, even
though one application may actually consist of many related programs.
• A desktop application is an application that runs on your computer.
A desktop application can use a graphical user interface (GUI) or the
console to display output and get user input.
• Applications that use a command line window known as the console to
interact with the user are known as console applications.
• A high-level language such as C++ is relatively easy for humans to read
and understand.
• A low-level language such as machine language is hard for humans to
read and understand but efficient for a computer to use.
• A compiler is a program that translates a high-level language to a
low-level language.
Chapter 1 An introduction to C+ + programming 37
• The C++ compiler translates C++ source code into machine language that
consists of the 1 s and Os that can be run directly by an operating system.
• An Integrated Development Environment (IDE) can make working with C++
easier by providing code completion, error detection, automatic compilation,
and a debugger.
• Visual Studio and Xcode are two of the most commonly used IDEs for C++
development.
• A Visual Studio solution can contain one or more projects where each
project is a folder that contains the folders and files for a program.
• An Xcode project is a folder that contains the folders and files for a program.
• The main() function of a program is the starting point of the program.
How to write
your first programs
Once you’ve installed an IDE and the C++ compiler the quickest and best way
to learn C++ programming is to do C++ programming. That’s why this chapter
shows you how to write complete C++ programs that get input from a user,
make calculations, and display output. When you finish this chapter, you should
be able to write comparable programs of your own.
int mainf)
<
cout « "Welcome to the Calorie Calculator\n\n";
Description
• Statements direct the operations of a program, and comments typically document
what the statements do.
• Most statements end with a semicolon.
• You can start a statement at any point in a line and continue the statement from one
line to the next. To make a program easier to read, you should use indentation and
extra spaces to align statements and parts of statements.
• Code within a set of braces ({}) can be referred to as a block of code.
• To code a single-line comment, type // followed by the comment. You can code a
single-line comment on a line by itself or after a statement. A comment that’s coded
after a statement is sometimes called an end-of-line comment.
• A block comment typically consists of multiple lines. To code a block comment,
type /* at the start of the block and */ at the end. You can also code asterisks to
identify the lines in the block, but that isn’t required.
One problem with comments is that they may not accurately represent what
the code does. This often happens when a programmer changes the code, but
doesn’t change the comments that go along with it. Then, it’s even harder to
understand the code because the comments are misleading. So if you change the
code that you’ve written comments for. be sure to change the comments too.
Description
• Every C++ program contains one main() function that is called by the operating
system.
• The statements between the braces in the main() function are run when the
program is executed.
• In most cases, you can code the main() function with no parameters. To do that,
type an empty set of parentheses after you type the function’s name.
• If you need the execution environment to pass arguments to the main()
function, you can code the parameters named argc and argv as shown in the
second example. However, these parameters aren't necessary for any of the
programs presented in this book.
• You can code the opening brace of the main() function on the same line as the
parentheses or on the next line. Many C++ style guides recommend coding the
opening brace on its own line, and this book follows that convention whenever
possible.
• You can end the main() function by coding a return statement that returns an
integer value that tells the operating system how the program exited. If you omit
this return statement, the main() function automatically returns a value of 0,
which indicates a normal exit.
The last line of code for the main() function is the closing brace. Unlike
the opening brace, this one should always be on its own line. That’s because a
common syntax error is forgetting to code a closing brace. Thus, leaving the
closing brace on its own line makes it easier to see whether the opening brace
has a corresponding closing brace.
Valid identifiers
subtotal i TITLE
subTotal x TAX_RATE
SubTotal it eml
sub_total item2
_sub_total June2011
C++ keywords
alignas const_cast module static_cast
alignof continue mutable struct
and decitype namespace switch
and_eq default new synchroni zed
asm delete noexcept template
atomic_cancel do not this
atom!c_commit double not_eq thread_local
atomic_noexcept dynamic_cast nullptr throw
auto else operator true
bitand enum or try
bitor explicit or_eq typedef
bool export private typeid
break extern protected typename
case false public union
catch float register unsigned
char for reinterpret_cast using
charl6_t friend requires virtual
char32_t goto return void
class if short volatile
compl import signed wchar_t
concept inline sizeof while
const int static xor
constexpr long static_assert xor_eq
Additional C keywords
Alignas Bool .Imaginary _Thread_local
Alignof Cooplex Noreturn restrict
Atomic Generic ,Static_assert
Description
• An identifier is any name that you create in a C++ program. These can be the
names of variables, functions, classes, and so on.
• A keyword is a word that’s reserved by the C++ language. As a result, you can’t
use keywords as identifiers. For backward compatibility, you should also avoid
C keywords.
• When you refer to an identifier, be sure to use the correct uppercase and lower
case letters because C++ is a case-sensitive language.
Description
• A variable stores a value that can change, or vary, as a program executes.
• Before you can use a variable, you must define the type of data it will store (its
data type) and its name. Then, you can initialize the variable by assigning an
initial value to the variable.
• An assignment statement assigns a value to a variable. If the data type has already
been defined, an assignment statement does not include the data type.
• A value can be a literal value, another variable, or an expression like the arithmetic
expressions shown in the next figure.
• It’s considered a good practice to initialize a variable before you use it since that
allows you to be sure of the initial value that’s assigned to the variable.
// decimal arithmetic
double a ■ 8.5;
double b = 3.4;
double result6 = a + b; // result6 = 11.9
double result7 = a - b; // result7 = 5.1
double result8 = a * b; // result8 = 28.9
double result9 = a / b; // result9 = 2.5
Description
• An arithmetic expression consists of one or more operands and arithmetic operators.
• When expressions mix int and double variables, the compiler converts, or casts,
the int types to double types.
• When both operands in a division expression are integers, the result is an integer.
This is known as integer division.
A typical way to include header files and make them easily accessible
tinclude <iostream>
#include <cmath>
int main()
<
// code that uses the headers goes here
}
Description
• The standard library for C++ provides a library of code that you can use. This
library is organized into header files.
• To use the code that’s available from the standard library, you code an #include
preprocessor directive for the header file that contains the code you want to use.
• A namespace provides a way to organize code to avoid naming conflicts. All of
the code that’s available from the header files of the standard library is stored in
a namespace named std.
• To make it easy to access the code in the std namespace, it’s common to add
a using directive for the entire namespace. However, to improve efficiency
and avoid naming conflicts, you can use other techniques for accessing the std
namespace as shown later in this chapter.
Description
• The stream insertion operator (<<) lets you add characters to the output stream.
Figure 2-7 How to use the cout object to write output to the console
54 Section 1 Essential skills for modern C++
The console when the user presses Enter after the first number
Please enter two numbers: 4
5
4 times 5 equals 20
Description
• The stream extraction operator (») gets characters from the input stream.
• When you use the cin object, it doesn’t matter if the user enters one value per
line or all the values on one line.
• When working with strings and characters, you may need to use some other
techniques for reading input that are described later in this chapter.
Figure 2-8 How to use the cin object to read input from the console
56 Section 1 Essential skills for modern C++
* **
The console
The code
/*
* Author: M. Delamater
* Purpose: This program uses the console to get gallons
* from the user. Then, it converts gallons to liters
* and displays the result of the conversion.
*/
tinclude <iostream>
int main()
<
// print name of program
cout « "Gallons Converter" << endl « endl;
Description
• When you call a function, you code its name followed by a pair of parentheses.
If the function requires arguments, you code the arguments within the paren
theses. These arguments must be of the correct data type, and they must be
coded in the correct sequence separated by commas.
• A function can return a value to the code that calls it. If a function returns a
value, you can store the return value in a variable.
the number, and then divide by the same multiple of ten. For example, to get two
decimal places, you can multiply the number by 100, round the number to the
nearest whole number, and then divide the number by 1(X). If you review these
three examples, you should get a feel for how this works.
As you progress through this book, you’ll learn how to use dozens of
functions and objects. You’ll also learn how to create your own.
int main() {
int num ■ 7;
double root - sqrt(num);
root - round(root * 1000) I 1000; // round to 3 decimal places
cout « "The square root of " << num << ■ is " « root « endl;
)
int main()
<
int num =7;
double root - std::sqrt(num);
root ■ std::round(root * 1000) I 1000; // round to 3 decimal places
std::cout « "The square root of ■ << num << " is " « root « std::endl;
)
Description
• The members of the standard library are grouped in a namespace named std.
• You can identify members of namespaces by using the scope resolution operator (::).
• You can code a using directive for a namespace to make it easy to access all members
of that namespace. However, this can lead to naming conflicts.
• You can code a using declaration for a specific member of a namespace to make it
easy to access that member of the namespace. This significantly reduces the chance of
a naming conflict.
The console
Circle Calculator
Enter radius: 9
Diameter: 18
Circumference: 56.5
Area: 254.5
Bye!
The code
tinclude <iostream>
tinclude <cmath>
using std::cout;
using std::cin;
using std::endl;
using std::pow;
using std::round;
int main()
<
// print name of program
cout « "Circle Calculator" « endl « endl;
// make calculations
double pi ■ 3.14159;
double diameter - 2 * radius;
double circumference ■ diameter * pi;
double area - pi * pow(radius, 2.0);
int main()
{
// use time() to get an int value
int elapsed_seconds ■ time(nullptr);
Description
• The rand() and srand() functions provide a simple way to generate a series of
pseudorandom numbers, which is a series of numbers that appears to be random
but is actually the same if you specify the same seed value for the series.
• If you’re using C++ 11 or later, you can use the random header to generate
random numbers that are cry ptographically secure. However, this requires
advanced programming techniques that aren’t presented until later in this book.
Description
• To specify a character value, you can enclose the character in single quotation
marks. This is known as a character literal.
• A class defines a type of object. When you create an object from a class, you
can assign it to a variable of the class type.
• To create a string object, you enclose text in double quotation marks. This is
known as a string literal.
• To assign an empty string to a string variable, you can code a set of quotation
marks with nothing between them. This means that the string doesn’t contain
any characters.
• To concatenate a string with another string or a char, use a plus sign.
• The +■ operator is a shortcut for appending a string or char to a string variable.
• If you only need to store a single character, the char type works more efficiently
than the string type.
the ♦ operator, you need to include the string variable on both sides of the ■
operator. Otherwise, the assignment statement replaces the old value with the
new value.
When working with char and string variables, a string uses slightly more
overhead than a char. As a result, if you’re working with a single character, you
should use a char variable or literal, not a string variable or literal.
String examples
A string that uses a newline character
■Code: CPP\nPrice: $49.50"
Result
Code: CPP
Price: $49.50
Result
Type "x" to exit
Char examples
char newline = ' \n';
char tab ■ '\t• ;
char sep ■ '\\'; // use backslash as a separator
Description
• You can use escape sequences to identify certain types of special characters.
• Within a string, you can code the escape sequence anywhere you want to use
the special character.
• A special character is a single character of the char type. As a result, to code a char
literal, you code single quotes around the two characters of the escape sequence.
Description
• Many objects in the standard library have memberfunctions. To call a member
function, code the name of the object, the dot operator ( . ). and the name of the
member function.
• You pass arguments to and get return values from a member function just like
you do a regular function.
Figure 2-16 How to read strings and chars from the console
72 Section 1 Essential skills for modern C++
string name;
cout << "Enter full name: n 9•
getline(cin, name); // reads the newline character left in the stream
The data in the cin object after the user enters “1234”
1 2 3 4 \n
string name;
cout << "Enter full name:
cin.ignore(); // discards the newline character left in the stream
getline(cin, name); // reads the next line
Description
• When the user presses the Enter key at the console, C++ inserts a newline character
into the input stream.
• When you use the extraction operator to extract data, it skips over any leading
whitespace characters (such as spaces and newline characters) and extracts the data
up to the next whitespace character, leaving the whitespace character in the stream.
• When you use the getlineO function to read data, it doesn’t skip over a leading
newline character. Instead, it returns an empty string. To skip over a leading newline
character, you can call the ignoreO member function of the cin object to ignore the
leading newline character.
The console
Guest Book
ENTRY
Dave W. Von Ronk
New York, United States
The code
tinclude <iostream>
#include <string>
int main()
<
cout « "Guest Book\n\n";
string first_name;
cout « "First name:\t";
cin >> first_name; // get first string only
cin.ignore(100, ’\n'); // discard leftover chars and newline
char middle_initial;
cout « "Middle initial:\t n 9•
middle_initial ■ cin.getO 9 // get first char only
cin.ignore(100, '\n'); // discard leftover chars and newline
string last_name;
cout « "Last name:\t";
getline(cin, last_name); // get entire line
string city;
cout « "City:\t\t";
getline(cin, city); // get entire line
string country;
cout « "Country:\t";
getline(cin, country); // get entire line
Enter radius: 9
Diameter: 18
Circumference: 28.3
Area: 254.5
Description
• A syntax or compile-time error occurs when a statement can’t be compiled.
Before you can test a program, you must fix the syntax errors.
• To test a program, you run it to make sure that it works properly no matter
what combinations of valid and invalid data you enter. The goal of testing is to
find the errors (or bugs') in the program.
• To debug a program, you find the causes of the bugs and fix them.
• One type of bug leads to a runtime error (also known as a runtime exception)
that causes the program to end prematurely. This type of bug must be fixed
before testing can continue.
• Even if a program runs to completion, the results may be incorrect due to logic
errors. These bugs must also be fixed.
Debugging tips
• For a runtime error, go to the line in the source code that was running when the
program crashed. In most IDEs, you can do that by clicking on the link to the line
of source code. That should give you a strong indication of what caused the error.
• For a logical error, first figure out how the source code produced that output.
Then, fix the code and test the program again.
Perspective
The goal of this chapter has been to get you off to a fast start with C++
programming. Now. if you understand how the programs presented in this
chapter work, you’ve come a long way. You should also be able to write compa
rable programs of your own.
Keep in mind, though, that this chapter is just an introduction to C++
programming. The next few chapters will build upon what you learned in this
chapter to expand your knowledge of C++ programming. This will include
learning some new skills as well as learning more about data types, arithmetic
expressions, input and output streams, strings, and debugging.
Terms
statement stream insertion operator
block of code chained expressions
comment stream manipulator
single-line comment standard input stream
end-of-line comment stream extraction operator
block comment function
main() function argument
parameter using directive
body of a function using declaration
return statement scope resolution operator
identifier fully qualified name
snake case pseudorandom number
camel case char
keyword ASCII character set
data type character literal
variable string
define a variable class
initialize a variable object
literal value string literal
assignment statement empty string
arithmetic expression concatenate strings
arithmetic operator append to a string
operand escape sequence
cast a data type whitespace
integer division token
modulus operator member function
remainder operator dot operator
decimal division syntax error
console compile-time error
preprocessor directive test a program
standard library bug
header file runtime error
namespace logic error
stream debug a program
standard output stream
Chapter 2 How to write your first programs 79
Summary
• Statements direct the operations of a program, and comments typically
document what the statements do.
• The main() function is a special type of function that’s called by the
operating system when it starts your program.
• Variables are used to store data that changes, or varies, as a program runs.
• When you define a variable, you must code its data type and its name. Two
of the most common data types for numeric variables are the int and double
types.
• After you define a variable, you can initialize it by assigning an initial value
to it.
• You can use assignment statements to assign new values to variables.
• You can use arithmetic operators to code arithmetic expressions that
perform operations on one or more operands.
• The standard library uses header files to organize and store its classes,
objects, and functions.
• The standard library groups its members in the std namespace. You can use a
using directive, using declarations, or fully qualified names to work with the
members of a namespace.
• You call a function by typing the function name and a set of parentheses. A
function may require one or more arguments, and it may return a value.
• You can use objects from the iostream header file to work with the standard
input and output streams of the console. The cout object writes output to the
console, and the cin object reads input from the console.
• A char is a built-in data type that contains a single character.
• A string is an object that contains zero or more characters. A string object is
created from the string class that’s available from the string header file.
• You can use the + operator to concatenate one string with another string or a
char, and you can use the + or operator to append one string to another.
• To include special characters in strings, you can use escape sequences.
• Testing is the process of finding the errors or hugs in a program. Debugging
is the process of locating and fixing the bugs.
80 Section 1 Essential skills for modern C++
Open the project, review the code, and run the program
1. Use your IDE to open the Gallons Converter project or solution in this folder:
ex_starts\ch02_ex2_gallons_converter
2. Review the code in the main.cpp file. Then, run the program to see how it
works.
Add code to round the number of liters
3. Add an include directive for the cmath header file.
4. Add a statement that rounds the number of liters to two decimal places.
5. Run the program to be sure this works correctly. If any errors occur, debug the
program, fix the errors, and then run the program again.
Add code to calculate the quarts and ounces
6. Define a variable with the double data type to store the number of quarts.
7. Calculate the number of quarts, and assign it to this variable. (One gallon is
equal to four quarts.) The number of quarts should be rounded to two decimal
places.
8. Modify the statement that writes the number of liters to the console so it also
writes the number of quarts.
9. Run the program, and fix any errors that occur.
10. Repeat steps 6 through 9 to calculate and display the number of ounces in a
gallon. (One gallon is equal to 128 ounces.)
Replace the using directive with using declarations
11. Replace the using directive for the std namespace with the using declarations
needed by this program.
12. Run the program one more time to be sure it still works.
82 Section 1 Essential skills for modern C++
Guest Book
ENTRY
Dave W. Von Ronk
123 Wall St.
New York, NY 10001
United States
Open the project, review the code, and run the program
1. Use your IDE to open the Guest Book project or solution in this folder:
ex_starts\ch02_ex3_guest_book
2. Review the code in the main.cpp file. Then, run the program to see how it
works.
Add code to accept and display the address, state code, and zip code
3. Define three string variables to store an address, a state code, and a zip code.
4. Add statements that prompt the user for the address, state code, and zip code.
5. Add statements that get the data entered by the user for the address, state
code, and zip code and assign the values to the appropriate variables. The
entire line of data should be retrieved for the address and zip code, but only
the first two characters should be retrieved for the state code.
6. Modify the statement that writes the entry to the console so it displays the
data as shown above.
7. Run the program and test it with the data shown above. If the program doesn’t
pause for you to enter a value for the zip code, that’s because the newline
character after the state code hasn’t been retrieved from the console. In that
case, you can fix this problem by adding a statement that extracts and discards
any additional characters in the input stream.
8. Run the program again to be sure it works correctly.
3
How to make decisions
In chapter 2, you learned how to write programs that get input from a user,
process that input, and display output to the user. In this chapter, you’ll learn
how to write code so your program can make decisions. For example, it's
common to make decisions based on the input from a user. To do that, you’ll
learn how to code control statements such as the if and switch statements.
Relational operators
Operator Name Description
== Equality Returns a true value if the two operands are equal.
!■ Inequality Returns a true value if the two operands are not equal.
> Greater Than Returns a true value if the left operand (lvalue) is
greater than the right operand (rvalue).
< Less Than Returns a true value if lvalue is less than rvalue.
>■ Greater Than Or Equal Returns a true value if lvalue is greater than or equal to
rvalue.
<■ Less Than Or Equal Returns a true value if lvalue is less than or equal to
rvalue.
Description
• A Boolean expression is an expression that evaluates to either true or false. To
create a Boolean expression, you can use the relational operators to compare
two operands.
• To compare two operands for equality, make sure to use two equals signs. If you
only use one equals sign, you are coding an assignment statement, not a Boolean
expression.
• If you compare an int value to a double value, the compiler casts the int value to
the double type.
• When you test chars and strings for equality, the test is case-sensitive. For chars,
you can make the test case-insensitive by adding the tolowerf) or toupperO
function.
Description
• An if statement always contains an if clause. In addition, it can contain one or
more else if clauses and one else clause.
• A pair of braces defines a block of code. Any variables declared within a block
have block scope. As a result, they can only be used within that block.
• C++ evaluates the clauses of an if statement from the top down. Once a clause
evaluates to true, C++ skips the rest of the clauses. As a result, it’s most
efficient to code the most likely conditions first and the least likely conditions
last.
The third example in figure 3-2 shows an if statement that contains multiple
else if clauses. When coding a statement like this, it’s important to remember
that C++ evaluates if statements from the top down. Once a clause evaluates
to true, C++ executes the statements for that clause and skips the rest of the if
statement. In the third example, for instance, the if clause checks whether the
subtotal is greater than or equal to 3(X). If it is. the discount_percent variable is
set to .3 and the rest of the if statement is skipped. Otherwise, the first else if
clause checks whether the subtotal is greater than or equal to 2(X). If it is, the
discount_percent variable is set to .2 and the rest of the if statement is skipped.
And so on.
Since these conditions are coded in a logical sequence, they’re easy to read
and understand. As always, the easier your code is to read and understand, the
easier it is to test, debug, and maintain.
Description
• If a clause in an if statement contains just one statement, you don’t have to enclose
the statement in braces. You can just end the clause with a semicolon.
The console
Invoice Total Calculator 1.0
Bye!
The code
ftinclude <iostream>
#include <cmath>
int main()
{
cout « "Invoice Total Calculator 1.0\n\n";
cout « "Bye!\n\n";
}
Logical operators
1 Operator Name Description
&& AND Returns a true value if both expressions are true. This operator only
evaluates the second expression if necessary.
II OR Returns a true value if either expression is true. This operator only
evaluates the second expression if necessary.
i NOT Reverses the value of the expression.
Order of precedence
1. NOT operator
2. AND operator
3. OR operator
// The OR operator
city ■ » "Greenville" || age >■ 65
// Two OR operators
age >=65 || age <=18 || status == "retired"
Description
• You can use the logical operators to create a Boolean expression that combines
two or more Boolean expressions.
• Since the && and | | operators only evaluate the second expression if necessary,
they’re sometimes referred to as short-circuit operators.
• By default, NOT operations are performed first, followed by AND operations,
and then OR operations. These operations are performed after arithmetic opera
tions and relational operations.
• You can use parentheses to change the sequence of operations. In addition, you
can use parentheses to clarify the sequence of operations.
Description
• If you can code an if statement more than one way and get the same results,
you should usually choose the way that’s easiest to read and understand.
• If efficiency is a primary concern, you should code the most likely conditions
first and the least likely conditions last.
• If statements are often used to choose one option out of several. They are also
used to make sure that user entries are valid. This is called data validation.
Description
• It’s possible to code one if statement within a clause of another if statement.
The result is known as nested if statements.
• In some cases, you can use the logical operators to get the same results that
you get with nested if statements.
• To improve the readability of if statements, you can use comments to describe
what various clauses do.
Figure 3-7 How to code nested if statements
98 Section 1 Essential skills for modern C++
The console
The Invoice Total Calculator 2.0
Bye!
The code
tinclude <iostream>
tinclude <cmath>
int main() {
// display title
cout « "The Invoice Total Calculator 2.0\n\n";
// get input
char customer_type;
cout « "Enter customer type (r/w): ";
cin >> customer_type;
double subtotal;
cout « "Enter subtotal: ";
cin >> subtotal;
Once the discount percent is established, the program calculates the discount
amount and the invoice total. Then, the invoice total is displayed along with the
discount percent and discount amount.
This program assumes that if the user doesn’t enter a customer type of ‘r’
or ‘w’. another type of customer is being processed and the discount percent is
set to zero. However, what if you wanted a customer type other than ‘r’ or ‘w’ to
be considered invalid? Then, you could use an if statement like the last one you
saw in figure 3-6 to validate the customer type. In that case, the program would
display an error message and end if the customer type was invalid.
In most cases, you wouldn't want a program to end because of an invalid
entry. Instead, you would want the program to prompt the user for another entry
after displaying an error message. You’ll learn how to do that in the next chapter
using a while loop.
Chapter 3 How to make decisions 101
// display output
cout « "Discount percent: ■ « discount_percent << endl
« "Discount amount: ■ « discount_amount « endl
« "Invoice total: ■ « invoice_total « endl << endl;
cout « "Bye!\n\n";
}
A conditional expression
subtotal >■ 100 ? .1 : .05 // returns .1 or .05 depending on subtotal
Description
• The conditional operator consists of three expressions that are separated by
the question mark (?) and colon (:) symbols. The first expression is a Boolean
expression, the second expression is evaluated and returned if the Boolean
expression is true, and the third expression is evaluated and returned if the
Boolean expression is false.
• The conditional operator provides a way to code simple if...else statements on a
single line.
• An operator that works on two operands is known as a binary operator. The
relational operators such as the less than (<) and greater than (>) operators are
examples of binary operators.
• An operator that works on three operands is known as a ternary operator. The
conditional operator is an example of a ternary operator.
Description
• The switch statement can only be used with an expression that evaluates to an
integer type such as the int type or the char type.
• The switch statement transfers control to the appropriate case label. If control
isn't transferred to one of the case labels, the optional default label is executed.
• The case labels can be coded in any sequence.
Description
• If a case label doesn't contain a break statement, code execution falls through
to the next label. Otherwise, the break statement ends the switch statement.
// WHOLESALE
case 'w':
case 'W':
if (subtotal < 500)
discount_percent - .4;
else
discount_percent - .5;
break;
// OTHER
default:
discount_percent - .0;
break;
}
Description
• You can often use a switch statement or an if statement to perform the same task.
• You can nest other control statements within a switch statement.
Perspective
Now that you’ve completed this chapter, you should be able to code if
statements and switch statements. These are the C++ statements that allow
your program to make decisions. In the next chapter, you’ll learn how to code
loops. Like if and switch statements, loops are a control structure that you can
use to control the logic of your program. Like if statements, loops also rely on
Boolean expressions to determine how they’re executed.
Terms
control statements short-circuit operators
Boolean expression data validation
relational operators nested if statements
case-sensitive comparison binary operators
case-insensitive comparison ternary operator
if statement conditional expression
if...else statement switch statement
selection structure case structure
block of statements case label
block scope break statement
compound conditional expression default label
logical operators execution fall through
order of precedence
Summary
• A Boolean expression is an expression that evaluates to either true or false.
To create a Boolean expression, you can use the relational operators to
compare two operands.
• You can use if statements and switch statements to control the logic of an
application, and you can nest these statements whenever necessary.
• A pair of braces defines a block of code. Any variables declared within a
block have block scope. As a result, they can only be used within that block.
• You can use the logical operators to connect two or more Boolean
expressions.
• Since the && and 11 operators only evaluate the second expression if neces
sary, they’re sometimes referred to as short-circuit operators.
• The conditional operator consists of three expressions that are separated
by the question mark (?) and colon (:) symbols. It provides a way to code
simple if...else statements on a single line.
• An operator that works on two operands is known as a binary operator. The
relational operators such as the less than (<) and greater than (>) operators
are examples of binary operators.
Chapter 3 How to make decisions 111
Bye!
Rectangle Calculator
Description
• Unary operators operate on just one operand.
• When you use an increment or decrement operator as a prefix to a variable,
C++ performs the increment or decrement operation before other operations.
• When you use an increment or decrement operator as a postfix to a variable.
C++ performs the increment or decrement operation after other operations.
• If you code an increment or decrement operation as a single statement, not
as part of an expression, it doesn’t matter whether the operator is prefixed or
postfixed.
• Unless there’s a specific reason to postfix an operand, it’s considered a best
practice to prefix the operand. That’s because postfixing an operand can lead to
the creation of temporary variables in memory, which causes the operation to
run less efficiently than prefixing the operand.
which is equivalent to
sum ♦■ next_number;
The technique that you use is mostly a matter of preference, because all of
these techniques are easy to read and maintain. However, the compound assign
ment operators have two advantages over the regular assignment operator. First,
the code is slightly shorter. Second, the variable that’s being updated is only
coded in one place, not two. As a result, the code is slightly easier to write and
maintain.
Chapter 4 How to code loops 117
Code that uses compound assignment operators to get the same results
count ♦- 1; // count is increased by i
count -= 1; // count is decreased by i
total += 100.0; // total is increased by 100.0
total — 100.0; // total is decreased by 100.0
price *- .8; // price is multipled by .8
rate / - 12; // rate is divided by 12
remainder %= 7; // remainder is set to the value that's left
// after dividing remainder by 7
sum += next_number; // sum is increased by the value of next_number
Description
• Besides the assignment operator (■), C++ provides for five compound assignment
operators. These operators provide a shorthand way to code common assignment
operations.
• These operators make your code easier to type, and also easier to read. That’s
because the variable doesn't appear on both sides of the assignment operator, so it’s
less confusing.
If you have trouble creating an expression like this for a difficult calculation,
you can often break the expression down into a series of statements as shown
in the last four lines of code. Here, the first statement adds the monthly invest
ment amount to the current value. The second statement calculates the monthly
interest rate. The third statement calculates the monthly interest amount. And
the fourth statement adds the interest to the current value. This takes away the
need for parentheses, and it makes the code easier to read and debug. On the
other hand, after coding statements like these, an experienced programmer might
combine them into a more concise expression that’s easier to maintain.
Chapter 4 How to code loops 119
Description
• Unless parentheses are used, the operations in an expression take place from left to
right in the order ofprecedence.
• To specify the sequence of operations, you can use parentheses. Then, the operations
in the innermost set of parentheses are done first, followed by the operations in the
next set. and so on.
• Another way to specify the sequence of operations is to break a complex expression
down into a series of statements. Then, the statements are executed in the sequence in
which they’re coded.
Description
• In a while loop, the condition is tested before the loop is executed. Because of
that, a while loop can be referred to as a pre-test loop.
• A while loop executes the block of statements within the loop as long as its
Boolean expression is true. If the expression is false when the loop starts, its
code is never executed.
• If a loop requires more than one statement, you must enclose the statements in
braces. This identifies the block of statements that are executed by the loop, and
any variables or constants that are declared in that block have block scope.
• If a loop requires just one statement, you don’t have to enclose the statement
in braces. However, it’s generally considered a good practice to use braces to
identify the statements that are executed by the loop.
Description
• If the condition for a loop never becomes false, the loop never ends. This
is known as an infinite loop, and beginning programmers often code them
accidentally. In most IDEs, you can cancel an infinite loop by clicking on a
Stop button to stop the execution of the program. Otherwise, you may be able
to cancel the loop by closing the console.
Description
• A do-while loop works like a while loop, except that the condition is tested after
the loop is executed. Because of that, its code is always executed at least once.
The console
Enter test scores from 0 to 100.
To end the program, enter -1.
Enter score: 90
Enter score: 80
Enter score: 150
Score must be from 0 to 100. Try again
Enter score: 75
Enter score: -1
Score count: 3
Score total: 245
Average score: 81.6667
The code
tinclude <iostream>
int main()
<
cout « "Enter test scores from 0 to 100.\n"
« "To end the program, enter -l.\n\n";
// define variables
int score ■ 0;
int count ■ 0;
double total - 0.0;
while (score !■ -1) { // get all scores from the user
cout << "Enter score: ";
cin >> score;
if (score >100) (
cout « "Score must be from 0 to 100. Try again.\n";
) else if (score > -1) {
♦♦count;
total +■ score;
) else if (score < -1) {
cout << "Score can't be a negative number. Try again.\n";
}
)
Description
• A for loop is useful when you need to increment or decrement a counter that
determines how many times the loop is executed.
• Within the parentheses of a for loop, you code an initialization expression that
gives the starting value for the counter, a Boolean expression that determines
when the loop ends, and an increment expression that increments or decrements
the counter.
• If necessary, you can declare the counter variable before the for loop. Then, this
variable is in scope after the loop finishes executing.
The fourth example in figure 4-8 shows how to code a loop that calculates
the future value for a series of monthly investments. Here, the loop executes one
time for each month.
The console
The Future Value Calculator
Continue? (y/n):
The code
finclude <iostream>
finclude <cmath>
int main() {
cout << "The Future Value Calculator\n\n" ;
double yearly_rate;
cout << "Enter yearly interest rate: ";
cin >> yearly_rate;
int years;
cout « "Enter number of years: ";
cin >> years;
The console
Monthly investment: 200
Year 5% 5.5% 6% 6.5%
1 2466 2473 2479 2486
2 5058 5085 5112 5139
3 7783 7844 7907 7969
4 10647 10760 10874 10989
5 13658 13839 14024 14211
6 16823 17093 17368 17649
cout « "Bye!";
The console
Continue? (y/n):
Description
• To jump to the end of the current loop, you can use the break statement.
• To skip the rest of the statements in the current loop and jump to the top of the
current loop, you can use the continue statement.
The console
Guess the number!
I'm thinking of a number from 1 to 10
Your guess: 11
Invalid guess. Try again.
Your guess: 5
Too low.
Your guess: 7
You guessed it in 2 tries.
Bye!
The code
tinclude <iostream>
#include <cstdlib>
#include <ctime>
int main() {
int upper_limit ■ 10;
cout « "Guess the number!\n";
cout « "I'm thinking of a number from 1 to " << upper_limit << "\n\n";
int count - 1;
while (true) {
int guess;
cout << "Your guess: ";
cin » guess;
Perspective
Now that you’ve completed this chapter, you should be able to code while,
do-while, and for loops. These are the C++ statements that implement the itera
tion structure. You can use them to perform repetitive processing. In addition,
you should be able to use break and continue statements to control how loops
operate.
Terms
unary operators
prefix an operand
postfix an operand
compound assignment operators
order of precedence
iteration structure
repetition structure
while statement
while loop
pre-test loop
counter variable
infinite loop
do-while loop
do-while statement
post-test loop
sentinel value
for statement
for loop
break statement
continue statement
Summary
• An operator that works on a single operand is known as a unary' operator.
The increment and decrement operators, which can prefix or postfix an
operand, are examples of unary operators.
• The compound assignment operators provide a shorthand way of assigning
a simple arithmetic expression to a variable when the variable is part of the
expression.
• You can use while, do-while, and for loops to repeatedly execute one or
more statements until a Boolean expression evaluates to false, and you can
nest these statements whenever necessary.
• You can use a break statement to jump out of the current loop, and you can
use a continue statement to jump to the start of the current loop.
Chapter 4 How to code loops 139
Circle Calculator
Enter radius: 11
Diameter: 22
Circumference: 69.1
Area: 380.1
Enter radius: -1
Radius must be a positive number.
Bye!
Continue? (y/n):
A program that reads input from and writes output to the console
int i;
cout << "Enter an integer: ■;
cin >> i;
double d;
cout << "Enter a double: ■;
cin >> d;
char c;
cout << "Enter a character:
cin >> c;
string s;
cout << "Enter a string: ■;
cin >> s;
The console
Enter an integer: 22
Enter a double: 3.14
Enter a character: d
Enter a string: hey
You entered:
22 3.14 d hey
Description
• A stream is a sequence of characters that can be used to work with input or
output for your program. Input and output is commonly abbreviated as I/O or IO.
• An output stream contains character data that is being written to an object,
and an input stream contains data that is being read from an object. When data
is read from an input stream, whitespace such as spaces, tabs, or new lines is
typically ignored.
• A buffer is a location in memory that temporarily stores stream data as it is
being written or read. When a buffer is flushed, all the data temporarily in the
buffer is sent to its next destination.
• C++ provides stream objects that let you write to and read from many destina
tions. including the console, a file on disk, or a buffer in memory.
• All stream objects provided by C++ share a similar interface. As a result, you
can use the same operators and member functions for all types of C++ streams.
The program when a user enters all the data on one line
Enter an integer: 22 3.14 d hey
Enter a double: Enter a character: Enter a string:
You entered:
22 3.14 d hey
The program when a user enters the wrong data types at two prompts
Enter an integer: 3.14
Enter a double: Enter a character: hey
Enter a string:
You entered:
3 0.14 h ey
The two strings entered by the user and the extracted values
CD CD CD CD
BBPCE E e ■ \n
Description
• Extra data on a line or incorrect data types can cause unexpected results in a program.
The program updated to clear the buffer after each value is extracted
int i;
cout << "Enter an integer: ■;
cin >> i;
cin.ignore(numeric_limits<streamsize>::max(), ’\n');
double d;
cout << "Enter a double: ■;
cin >> d;
cin.ignore(numeric_limits<streamsize>::max(), ’\n');
char c;
cout << "Enter a character: ";
cin >> c;
cin.ignore(numeric_limits<streamsize>::max(), ’\n');
string s;
cout << "Enter a string: ■;
cin >> s;
cin.ignore(numeric_limits<streamsize>::max(), ’\n');
The program when a user enters all the data on one line
Enter an integer: 22 3.14 d hey
Enter a double: 3.14 d hey
Enter a character: d hey
Enter a string: hey
You entered:
22 3.14 d hey
The program when a user enters the wrong data types at two prompts
Enter an integer: 3.14
Enter a double: 3.14
Enter a character: hey
Enter a string: hey
You entered:
3 3.14 h hey
Description
• You can use the ignore() member function of a stream object and the max()
function of the numericjimits class to discard all the data that remains in a buffer.
Code that uses the cin object to check the state of a stream
double num;
cout << "Enter a number:
cin >> num;
if (cin) {
cout « "Your number is " « num « endl;
} else {
cout « "That's not a valid number!\n";
)
A more concise way to extract a value and check the state of a stream
double num;
cout << "Enter a number: ";
if (cin >> num) {
cout « "Your number is " « num « endl;
) else {
cout « "That's not a valid number!\n";
)
Description
• You can use the cin object or the member functions of an input stream to check the
error bits of the stream. You can also use a member function to reset the error bits
of a stream.
Figure 5-4 How to detect data input errors
150 Section 1 Essential skills for modern C++
int main()
<
double num.-
while (true) ( // loop continues until break statement
cout << "Enter a number (1-100):
cin >> num;
The console
Enter a number (1-100): eighty six
That's not a valid number. Try again.
Enter a number (1-100): -1
Number must be greater than 0. Try again.
Enter a number (1-100): 101
Number must be 100 or less. Try again.
Enter a number (1-100): 86
Your number is 86
Description
• You can use a loop to continue to prompt the user until that user enters valid
data. This is known as data validation.
Description
• You can use stream manipulators with the stream insertion («) operator to
format output streams.
• Both the endl manipulator and the *\n’ special character add a new line to a
stream, but endl also flushes the buffer. This can degrade performance when
you’re working with files.
• When you use the setw() manipulator, the column uses right justification by
default. This is often what you want when displaying numbers in columns.
Figure 5-6 How to specify the width of a column
154 Section 1 Essential skills for modern C++
Description
• When using the setw() manipulator, you can use the left and right manipulators
to left and right justify the data in a column.
• It’s common to use left justification for text and right justification for numbers.
• The left and right manipulators are sticky. This means that once you set them,
they stay in effect until you change them or the program ends.
5 significant digits
1.0123
1.0123e+ll
Description
• The setprecision() manipulator rounds floating-point numbers to the specified
number of significant digits, which are the digits that carry meaning about the
accuracy of a measurement.
• The fixed manipulator forces the output of floating-point numbers to be in
fixed-point notation, rather than the default scientific notation.
• The showpoint manipulator forces the display of trailing zeros.
• The setprecision(). fixed, and showpoint manipulators are sticky. As a result,
they stay in effect until you change them or the program ends.
The console
The Invoice 3.0 program
INVOICE
Subtotal: 973.00
Discount percent: 0.20
Discount amount: 194.60
Invoice total: 778.40
Bye!
The code
#include <iostream>
#include <iomanip>
#include <limits>
#include <cmath>
int main() (
cout << "The Invoice 3.0 program\n\n";
// get input
char customer_type;
while (true) (
cout << "Enter customer type (r/w):
cin >> customer_type;
cin.ignore(numeric_limits<streamsize>::max(), '\n');
if (tolower(customer_type) -- 'r'
tolower(customer_type) -- 'w') (
break;
}
else (
cout << "Invalid customer type! Please try again.\n";
}
double subtotal;
while (true) (
cout << "Enter subtotal: ";
cin >> subtotal;
If the input stream is bad. this code displays a message that indicates that an
unrecoverable error has occurred and that the program is exiting. Then, it uses a
return statement to end the main() function, which ends the program. Since it’s
extremely rare for an input stream to be bad. you don't need to handle this state
for all programs. However, for mission-critical programs, it’s a good practice to
handle this state by using code like the code shown in this figure.
Notice that this loop doesn’t validate the range for the subtotal. However,
you could easily add code that makes sure the user enters a reasonable number
for the subtotal, such as a number that’s greater than 0 and less than 1 ,000,000.
After getting the subtotal from the user, this code sets the discount percent
depending on the customer type and the subtotal. To do that, it uses the same
nested if statement described in chapter 3. Then, it uses the discount percent to
calculate the discount amount and invoice total.
After calculating and rounding the results, this code displays the output in
two columns. Here, the first column is 18 characters wide and left justified, and
the second column is 8 characters wide and right justified. In addition, this code
displays all numbers with fixed-point notation and 2 decimal places. That makes
it easy to read the results of the calculations and check them to make sure they’re
accurate.
Chapter 5 How to work with I/O streams and files 161
// calculate results
double discount_amount - subtotal * discount_percent;
double invoice_total - subtotal - discount_amount;
// display output
int coll ■ 18;
int col2 ■ 8;
cout << fixed « setprecision(2) << endl // use 2 decimal places
« -INVOICE" « endl
« left « setw(coll) << "Subtotal:"
« right << setw(col2) « subtotal << endl
« left « setw(coll) << "Discount percent:"
<< right << setw(col2) « discount_percent << endl
« left « setw(coll) << "Discount amount:"
<< right << setw(col2) « discount_amount << endl
« left « setw(coll) << "Invoice total:"
« right << setw(col2) « invoice_total << endl;
How to include the header file for the file stream classes
tinclude <fstream>
line on the console, followed by a newline character. After the while loop, this
code closes the ifstream object, which flushes the buffer and frees all resources
being used by this object.
If the file doesn’t open successfully, this code doesn’t do anything. That can
happen, for example, if the file doesn't exist. In this case, it isn’t necessary to
close the ifstream object since the code wasn't able to open that object in the first
place.
The last example shows how to create an object for a file stream and open
a file in one statement. To do that, you code the name of the file in parentheses
after the name of the file stream. This coding technique is more concise than the
technique presented in the first two examples, and it’s commonly used by C++
programmers.
The syntax for the open() member function of a file stream object
object.open(file_name, file_access_flag [ | file_access_flag] ...);
Description
• The open() member function of the ofstream and fstream classes accept two
arguments. The first is the name of the file to open, and the second is a file
access flag that indicates the type of stream to create.
• To pass more than one flag to the open() function, separate each flag with the | operator.
Figure 5-11 How to append data to a file and use the fstream object
166 Section 1 Essential skills for modern C++
the time. However, an fstream object provides more flexibility since it allows
you to change the input/output mode for a stream. So, you might want to use
it for a program that needs to open a file in input mode, read and process input
data, switch to output mode, and write the processed data to the file.
The console
100|200|400|
Description
• You can use the error bits described in figure 5-4 to check the state of the file stream
after operations that might fail, such as extracting data.
• You can use the eof() member function to check whether the operation has reached
the end of the file.
• You can use the fail() member function to check whether the operation failed.
Figure 5-12 How to check for errors when working with files
168 Section 1 Essential skills for modern C++
ifstream input_file;
input_file.open(filename);
double low;
double high;
if (input_file) { // if file opened successfully...
cout << fixed << setprecision(l);
while (input_file >> low >> high) (
cout << setw(8) « low << setw(8) « high « •\n';
}
input_file.close();
}
The console
TEMPERATURES
Low High
48.4 57.2
46.0 50.0
78.3 101.4
Description
• When storing a table of data in a file, it’s common to separate the columns and
rows of the table with characters known as delimiters.
• A file that uses tab (\t) and newline (\n) characters to separate columns and
rows is known as a tab-delimited file.
The console
The Temperature Manager program
COMMANDS
v - View temperatures
a - Add a temperature
x - Exit
Command: v
TEMPERATURES
Low High
48.4 57.2
46.0 50.0
78.3 101.4
Command: a
Command: x
Bye!
The code
#include <iostream>
#include <iomanip>
#include <fstream>
#include <limits>
#include <string>
int main()
<
string filename ■ "temps.txt";
cout « "COMMANDS\n"
« "v - View temperatures\n"
« "a - Add a temperature\n"
« "x - Exit\n";
After getting the command character, this code checks whether that character
is equal to ‘v’. If it is, the code continues by reading all the low and high
temperatures stored in the file and displaying them on the console. But first, it
attempts to open the file and then checks that it was opened successfully. Since
this code is similar to code you saw in figure 5-13, you shouldn't have much
trouble understanding how it works. After executing this code, program execu
tion reaches the end of the while loop and jumps back to the top of the loop,
which causes the program to get another command from the user.
If the command character is equal to ‘a’, this code gets a low and a high
temperature from the user. Then, it defines and opens an ofstream object and
uses it to append the low and high temperatures to the end of the file. Since this
file is a tab-delimited file, this code separates the two temperatures with a tab and
follows them with a new line. Again, after this code is executed, program execu
tion reaches the end of the while loop and jumps back to the top of the loop,
which causes the program to get another command from the user.
If the command character is equal to ‘x’. the code displays a “Bye!” message
on the console to indicate that the program is ending. Then, program execution
jumps back to the top of the while loop, and the condition for the loop evaluates
to false. This causes the loop to end. which ends the main() function and the
program.
If the command character isn’t one of the characters in the previous if or
else if clauses, the else clause displays a message on the console. This message
indicates that the command is invalid and that the user should try again. Because
this is the last statement in the while loop, code execution jumps to the top of the
loop, and the code prompts the user to enter another command.
As you review this code, note that the code that adds the new low and high
temperatures to the file needs to use the ios:app file input flag to open the file in
append mode. Otherwise, all existing data in the file would be deleted when the
file was opened.
Also, note that this code doesn’t validate the temperature data that’s entered
by the user. This helps keep the focus on working with file streams. In general,
though, validating this data would be a good practice. In addition, it’s a good
practice to code functions for data validation and to store those functions in a
separate file. You’ll learn how to do that in chapter 7.
Chapter 5 How to work with I/O streams and files 173
ifstream input_file;
input_file.open(filename);
double low;
double high;
if (input_file) { // if file opened successfully...
cout << fixed « setprecision(1);
while (input_file >> low >> high) {
cout « setw(8) << low « setw(8) << high << '\n';
}
input_file.close();
}
else <
cout << "Unable to open file.\n";
}
}
else if (command ■■ 'a') {
double low;
cout « "Enter low temp: ■;
cin >> low;
double high;
cout << "Enter high temp: ";
cin >> high;
ofstream output_file;
output_file.open(filename, ios::app);
output_file << low « •\t' « high << '\n';
output_file.close();
cout << "Temps have been saved.\n";
}
else if (command ■■ 'x') {
cout « "Bye!\n\n";
}
else <
cout << "Invalid command. Try again.\n";
}
)
)
istringstream Creates an input stream object that reads characters from a string,
ostringstream Creates an output stream object that writes characters to a string.
stringstream Creates a stream object that can read characters from and write characters
to a string.
str() Returns a string object that’s a copy of the contents of the buffer.
str(string) Replaces the contents of the buffer with the specified string.
How to include the header file for the string stream classes
tinclude <sstream>
n/a 64
78.3 101.4
Description
• A string stream uses a string buffer to hold the characters in a string.
• A string stream is particularly useful when processing each line of a file that may
contain unexpected data.
The console
Temperature Analyzer
48.1 54.27
Description
• The Temperature Analyzer program opens a text file specified by the user,
reads temperature data from that text file, processes that data, and writes
output to the console.
• If the file specified by the user doesn’t exist, the program displays an error
message and prompts the user for another filename.
• When the program reads from the text file, it ignores any extra data on a line,
and it skips blank lines and lines that contain incorrect data types.
Figure 5-16 The Temperature Analyzer program (part 1 of 3)
178 Section 1 Essential skills for modern C++
The code
ftinclude <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <sstream>
ftinclude <limits>
int main()
{
cout << "Temperature Analyzer\n\n";
infile.open(filename);
if (infile) ( // if file opened successfully
break;
}
else (
cout << "Unable to open input file! Try again.\n";
}
}
cout << "Input file (■ << filename « ") opened successfully.\n\n";
// update totals
avg_total avg;
if (low < lowest)
lowest ■ low;
if (high > highest)
highest - high;
}
)
infile.close() ;
cout « endl
« lines_processed << ■ of "
« lines_read << " lines successfully processed.\n\n";
// display calculations
int coll ■ 17;
int col2 =7;
cout « left « setw(coll) << "Lowest temp: ■
« right << setw(col2) « lowest « endl
« left « setw(coll) << "Highest temp: "
« right << setw(col2) « highest « endl
« left « setw(coll) << "Avg daily temp: "
« right << setw(col2) « avg_daily « endl « endl;
)
Perspective
In this chapter, you learned more skills for working with all types of I/O
streams. That includes skills for reading data from and writing data to the
console. That includes skills for working with file input and output. And that
includes skills for working with string streams, which are often helpful when
reading input from a text file.
Although this chapter only presents the skills for working with text files,
you should know that you can also use C++ to work with other types of files
such as binary files. Beyond that, you should know that most serious applica
tions store their data in databases. However, these C++ skills are beyond the
scope of this book.
Terms
stream file I/O
input main memory (RAM)
output text file
I/O file access flag
input stream table
whitespace column
output stream row
buffer field
flush the buffer record
error bits delimiter
data validation tab-delimited file
stream manipulators string stream
significant digits string buffer
fixed-point notation
Summary
• A stream is a sequence of characters that can be used to get input or output
for your program. Input and output is commonly abbreviated as I/O or IO.
• An output stream contains character data that is being written to an object,
and an input stream contains data that is being read from an object.
• When the extraction operator reads data from an input stream, whitespace
such as spaces, tabs, or new lines is typically ignored.
• A buffer is a location in memory that temporarily stores stream data as it is
being written or read. When a buffer is flushed, all the data temporarily in
the buffer is sent to its next destination.
• All stream objects provided by C++ share a similar interface. As a result, you
can use the same operators and member functions for all types of C++ streams.
• You can use the member functions of an input stream to check the error bits
of the stream or to reset the error bits of a stream.
Chapter 5 How to work with I/O streams and files 181
• You can use a loop to continue to prompt the user until that user enters valid
data. This is known as data validation.
• You can use stream manipulators with the stream insertion (<<) operator to
format output streams.
• The setprecision() manipulator rounds floating-point numbers to the speci
fied number of significant digits, which are the digits that carry meaning
about the accuracy of a measurement.
• The fixed manipulator forces the output of floating-point numbers to be in
fixed-point notation rather than the default scientific notation.
• Data that’s stored in main memory (RAM) is lost when the program ends.
But if you save that data to a file, you can read it the next time the program
runs.
• A text file stores data as a series of characters. If you open a text file in a text
editor, it’s easy for a human to read the characters.
• The open() member function of the ofstream and fstream classes accepts two
arguments. The first is the name of the file to open, and the second is one or
more file access flags that indicate the type of stream to create.
• When storing a table of data in a file, it’s common to separate the columns
and rows of the table (also called records and fields) with characters known
as delimiters.
• A file that uses tab (\t) and newline (\n) characters to separate columns and
rows is known as a tab-delimited file.
• A string stream uses a string buffer to hold the characters in a string.
182 Section 1 Essential skills for modern C+ +
225.2 17.0
1374.8 64.5
Open the program, review the code, and test the program
1. Open the Miles Per Gallon project or solution that’s in this folder:
ex_starts\ch05_exl_mpg
2. Review the code. Note that it validates the miles and gallons entries to be sure
they’re greater than zero, rounds the MPG to two decimal places, and gets
entries for one or more trips.
3. Run the program to see how it works. Test to see what happens when you
enter 0 for the miles or gallons. Then, test to see what happens when you enter
two values for one of the entries. Finally, test to see what happens when you
enter an invalid number.
Validate and handle input errors
4. Modify the program so it discards any extra data entered by the user.
5. Modify the program so it makes sure that the user enters valid numbers. If the
input operation fails, the program should display an error message, clear the
error bits from the input stream object, and discard any remaining data in the
stream.
6. Run the program again. This time, it should ignore any extra data entered
by the user. In addition, it should display an error message and prompt for
another entry if the user enters an invalid number for miles or gallons.
Write the valid entries to a tab-delimited file
7. Modify the program so it appends valid miles and gallons entries to a
tab-delimited file named trips.txt. Both of these values should have one
decimal digit. (For this program, you can assume that the user doesn’t enter
miles or gallons with more than one decimal digit.)
8. Run the program and enter two or more valid sets of entries. Then, use a text
editor to view the text file.
9. Close the text file and run the program again. This time, enter valid and
invalid entries.
10. Display the text file again to be sure that the program appended valid entries
to the original entries and that it didn’t write any invalid entries to the file.
Chapter 5 How to work with I/O streams and files 183
1. Open the Miles Per Gallon project or solution that’s in this folder:
ex_starts/ch05_ex2_mpg
2. Modify the program so it starts by displaying the data in the trips.txt file as
well as the MPG. Be sure to set the column widths and the precision so the
values are displayed as shown above.
3. Modify the program so it displays the data in the text file and the MPG after it
writes the data to the file. To do that, you can copy the code you just wrote so
it’s executed after the data is written to the file.
4. Modify the program so it displays the total miles, total gallons, and average
MPG both when the program starts and anytime data is written to the file. Be
sure to set the column widths, alignment, and precision, and make sure the
decimals are always displayed.
184 Section 1 Essential skills for modern C+ +
The fundamental data types and their typical sizes and ranges
Type Bytes Range
char 1 Integers from 0 to 127 that map to one of the characters
in the ASCII character set.
short 2 Integers from -32.768 to 32.767.
int 4 Integers from -2.147.483.648 to 2.147.483.647.
long 4 Same as int on most systems.
long long 8 Integers from -9.223.372.036.854.775.808 to
9.223.372.O36.854.775.8O7.
float 4 Single-precision, floating-point numbers from -3.4E38 to
3.4E38 with up to 7 significant digits.
double 8 Double-precision, floating-point numbers from -1.7E308
to 1.7E3O8 with up to 16 significant digits.
long double 8 Same as double on most systems, but sometimes 10 bytes
with range of -3.4E4932 to 1.7E4832.
bool 1 A true or false value.
Description
• The American Standard Code for Information Interchange (ASCII) assigns an
integer from 0 to 127 for the individual characters on a keyboard.
• Integers are positive or negative whole numbers. Unsigned integers only allow
for positive whole numbers.
• Floating-point numbers provide for very large and very small numbers that
require decimal positions, but with a limited number of significant digits. A
single-precision number provides for numbers with up to 7 significant digits. A
double-precision number provides for numbers with up to 16 significant digits.
• A Boolean value can be true or false. The true keyword maps to a non-zero int
value, typically 1, and the false keyword maps to an int value of 0.
• The fundamental data types are also known as the built-in data types.
Technical notes
• The number of bytes a data type uses in memory can vary from system to system.
• To express the value of a floating-point number, you can use scientific notation.
For example, 2.382E+5 means 2.382 times 105, which is a value of 238,200.
Conversely, 3.25E-8 means 3.25 times IO"8, which is a value of ,(X)O(XXX)325.
How to use the auto keyword to infer the data type based on initial value
auto hours ■ 40; II compiler infers that hours is an int
auto hours ■ 40.0; // compiler infers that hours is a double
Description
• A variable stores a value in memory that can change as a program executes. In
other words, the value of a variable varies as a program executes.
• Before you can use a variable, you must define its data type and its name.
• After you define a variable, you can initialize it. To do that, you assign a value to
the variable with the assignment operator (■).
• You can define more than one variable in a single statement, as long as the
variables have the same data type. To do that, you use commas to separate the
variables. You can also initialize the variables in that same statement.
• When you define and initialize a variable in one statement, you can use the
auto keyword. Then, the compiler will infer the type based on the value used to
initialize it.
• To identify float values, you can type an/or F after the number.
• To identify long values, you can type an I or L after the number. The compiler will
be able to tell the difference between long ints and long doubles.
• To identify long long int values, you can type ll or LL after the number.
• For clarity, uppercase Ls are better because a lowercase I resembles the number 1.
When working with the fundamental data types, you usually don't need to
use the auto keyword. However, it can sometimes result in code that’s shorter
and more flexible, especially when working with more complex data types as
described later in this book. For example, chapter 10 provides several examples
of when it makes sense to use the auto keyword.
double radius;
cout << "Enter radius: ";
cin >> radius;
Description
• A constant stores a value in memory that cannot change as a program executes.
In other words, the value of a constant remains constant as a program executes.
• To define a constant, you begin the definition statement with the const keyword.
A constant must be initialized at the time it is defined.
• In other programming languages, it’s common to use all capital letters when
naming constants. This distinguishes them from variables. In C++, however, this
can cause conflicts with macros, which are named fragments of code. As a result,
many programmers recommend using lowercase letters when naming constants.
The console
Light Years Calculator
SCIENTIFIC NOTATION
Miles: 3.5274e+013
Kilometers: 5.6766e+013
Parsecs: 1.839606
FIXED NOTATION
Miles: 35274000000000.00000000
Kilometers: 56766000000000.00000000
Parsecs: 1.83960600
Bye!
The code
ftinclude <iostream>
#include <iomanip>
int main() (
cout << "Light Years Calculator\n\n" ;
double light_years;
cout << "Enter light years: ";
cin >> light_years;
cout « fixed
« ■FIXED NOTATION\n"
« ■Miles: w « setw(24) « miles << endl
« ■Kilometers: w « setw(24) « km « endl
« ■Parsecs: w « setw(24) « parsecs « endl << endl;
cout « ■Bye!\n\n-;
}
double d ■ 95.5;
int i = 86, j = 91;
double average ■ (d+i+j)/3; // promote int values to double values
// average ■ 90.8333
Description
• If you perform assignment or arithmetic operations with different data types,
C++ automatically converts or casts the types using its rules of type coercion.
• You can also explicitly convert types using the static_cast operator.
• C++ also allows you to perform a cast by coding the data type in parentheses
just before the value to be converted. However, this syntax forces the compiler
to choose from among several types of casts. Since this can lead to unexpected
results, it’s better to specify the type of cast you want to use.
C++ also allows you to perform an explicit cast by coding the data type in
parentheses just before the value to be converted as shown by the last example
in figure 6-5. However, this syntax forces the compiler to choose from among
several types of casts, including a static cast. Since this can lead to unexpected
results, it’s better to use the static_cast operator to specify that you want to use a
static cast.
When you code an explicit cast, an error may occur at runtime if C++ isn’t
able to perform the cast. As a result, you should use an explicit cast only when
you’re sure that C++ is able to perform the cast.
Although you typically cast between numeric data types, you can also cast
between the integer and char types. That’s because every char value corresponds
to an integer that identifies it in the ASCII character set. However, there is a
possible loss of data when converting integer types such as the int type to the
char type. As a result, you should be careful when doing this.
Examples
auto resultl ■ numeric._limits<int> : :min( ) ; // -2147483648
auto result2 ■ numeric..limits<int> : :max( ) ; // 2147483647
bool result3 ■ numeric._limits<double>::is_signed; // 1 (true)
bool result4 ■ numeric..1imits<short>::is_integer; // 1 (true)
bool result5 ■ numeric._limits<float>::is_exact; // 0 (false)
Description
• Data types vary from system to system. You can use the sizeofO operator to
determine the size of a type on your system. You can use the members of the
numericjimits class to determine the range of a type on your system.
• When an integer variable is assigned a value that’s too big for its type, it will
overflow, or wrap around to the type’s minimum. When an integer variable is
assigned a value that’s too small for its type, it will underflow, or wrap around to
the type’s maximum.
• When a floating-point variable is assigned a value that’s too big for its type, the
result varies depending on the system. This may cause your program to display
errors and stop, display errors and keep running, or keep running with no errors.
• To access static members of a class, you can code the name of the class, the
scope resolution operator (::), and the name of the member.
Figure 6-6 How to work with data type sizes and limits
198 Section 1 Essential skills for modern C++
Code that shows how some floating-point numbers are not exact
cout « setprecision(24) ;
cout « .0 « endl;
cout « .1 « endl;
cout « .5 « endl;
cout « .9 « endl;
The console
o
0.100000000000000005551115
0.5
0.90000000000000002220446
Description
• Because of the way floating-point numbers are stored internally, they can’t
represent the exact value of the decimal places in some numbers. This can cause
problems with equality comparisons and calculations.
• To fix problems with equality comparisons, you can avoid using equality operators
with floating-point numbers.
• To fix problems with calculations, you can use the round() function of the cmath
header to round the floating-point number to a less precise number of decimal places.
Description
• The Standard Template Library (STL) contains many data types in addition to the
fundamental data types. Many of these data types can contain multiple elements
and are often called containers.
• A vector contains a sequence of elements of the same data type. You can use the
subscript operator ([ ]) with an index to access the elements of a vector, where 0
is the index for the first element, 1 is the index for the second element, and so on.
• Out of bounds access occurs if your code specifies an index that’s outside the
range of valid indexes. Since this leads to unexpected results, you should always
write code that prevents out of bounds access.
• Vectors, as well as other items provided by the STL. are members of the std
namespace. Because of that, a program must include a using directive for that
namespace, a using declaration for the specific member of the namespace, or it
must fully qualify any references to the member with the name of the namespace.
The syntax for using an initialization list to define and initialize a vector
vector*type> vector_name [■] { valuel(, value2] ... };
How to use a for loop to display all the elements in a vector object
vector<int> scores ( 99, 87, 91, 76 };
for (int i - 0; i < scores.size(); ++1) (
int score ■ scores[i];
cout « score << *
)
The console
99 87 91 76
Description
• You can define a vector and provide initial values in a single statement by using an
initialization list. The number of values you include in the list determines the initial
size of the vector.
• You can use the size() function of a vector to get the number of elements in the vector.
• When iterating the elements of a vector, you can avoid out of bounds access by using
a range-based for loop. This kind of loop iterates over every element in the vector
without using a counter or the subscript operator.
Description
• The size() function returns an unsigned integer, but the exact data type varies.
As a result, if you need to store this unsigned integer in a variable, it’s a good
practice to use the auto keyword to define that variable.
• You’ll learn more about the vector class and other STL containers in chapter 10.
The console
The Test Scores program
Enter score: 97
Enter score: 93
Enter score: eighty two
Invalid number. Try again.
Enter score: 105
Score must be from 0 to 100. Try again.
Enter score: 83
Enter score: -1
Score count: 3
Score total: 273
Average score: 91
The code
tinclude <iostream>
tinclude <vector>
tinclude <cmath>
int main()
<
cout « "The Test Scores program\n\n" ;
vector<int> scores;
int score ■ 0;
while (score != -1) {
cout << "Enter score: ";
cin >> score;
if (cin.failO) {
cin.clearO; // clear error bits
cin.ignore(1000, '\n'); // discard input up to end of line
cout << "Invalid number. Try again.\n";
}
else if (score > 100) (
cout << "Score must be from 0 to 100. Try again.\n";
}
else if (score < -1) {
cout << "Score can't be a negative number. Try again.\n";
}
else if (score > -1) { // valid score - add to vector
scores.push_back(score);
}
When the user enters -1 and the while loop ends, the main() function
continues by checking if the vector is empty. If so, it displays a message that
indicates that the user didn’t enter any scores.
However, if the vector isn’t empty, the code loops through the scores stored
in the vector and calculates the total of all the scores. To do that, it defines an int
variable named total and initializes it to 0. Then, it uses a range-based for loop to
iterate through each score and update the total variable accordingly.
After calculating the score total, this code uses the size() function to get
the count of the scores. Then, it calculates the average score. To do that, this
code uses a static cast to convert the score total from the int type to the double
type. That way, C++ uses decimal division, which allows the average to contain
decimal places. Then, this code rounds the average to 1 decimal place. Finally,
this code displays the score count, total, and average.
Chapter 6 How to work with data types, strings, and vectors 209
>
The console
The Temperature Manager program
COMMANDS
v - View temperatures
a - Add a temperature
r - Remove a temperature
s - Save changes
x - Exit
Command: v
TEMPERATURE S
Day Low High
1 48.4 57.2
2 46.0 50.0
3 68.2 73.0
Command: a
Enter low temp: 73.2
Enter high temp: 101.5
Command: r
Enter day to remove: 3
Temps for day 3 have been removed.
Command: s
Your changes have been saved.
Command: x
Bye!
The code
#include <iostream>
ffinclude <iomanip>
ffinclude <fstream)
ffinclude <string>
#include <vector>
int main()
<
string filename - "temps.txt";
// define variables
ofstream output_file;
int day_num, index;
low_temps.push_back(low);
high_temps.pushback(high);
break;
case 'r':
int day;
cout << "Enter day to remove: ";
cin >> day;
index - day - 1;
if (index >■ 0 && index < high_temps.size()) {
high_temps.erase(high_temps.begin() + index);
low_temps.erase(low_temps.begin() + index);
}
cout << "Temps for day ■ « day << " have been removed.\n";
break;
case 's':
output_file.open(filename) ;
for (int i ■ 0; i < low_temps.size(); ++i) (
low ■ low_temps[i];
high ■ high_temps[i];
output_file « low << '\t' « high « '\n';
}
output_file.close();
cout << "Your changes have been saved.\n";
break;
case 'x':
cout << "Bye!\n\n";
break;
default:
cout << "Invalid command. Try again.\n";
break;
}
)
)
Description
• A string is a sequence of char values.
• You can use the string class that’s available from the C++ standard library to work
with strings. When you do, you can think of a string as a vector of characters.
• You can use the subscript operator ([]) to access the individual characters of a
string where 0 is the first character, 1 is the second character, and so on.
character in the string. Within the loop, a single statement converts each
character to uppercase and appends it to the string named name_upper.
If you compare the range-based for loop with the standard for loop, you’ll
see that the range-based loop is easier to code. In addition, it’s less prone to
leading to out of bounds access. As a result, you should use the range-based
for loop whenever it works for your purposes. However, if you need to use an
index to access specific characters of a string, it typically makes sense to use a
standard for loop.
if (movie_name.empty()) {
cout << "Error! Movie name is required.\n";
}
Description
• The string class provides many of the same member functions as the vector
class. You can use these functions to work with the characters of the string.
// find() function
auto index - name. find("grace") ; // index is 0
index ■ name.f ind( 'g'); // index is 0
index ■ name.f ind( •r'); // index is 1
index ■ name.f ind( •r*, 2); // index is 11
index = name.f ind( 'g'» 1); // index is -1
// rfind() function
index ■ name.rf ind(•r'); // index is 11
index ■ name.rf ind(•r', 10); // index is 1
index ■ name.rf ind('h', 5); // index is -1
index = name.rfind("grace"); // index is 0
How to get the index of the first character that’s not whitespace
string name - "\t\n bjarne stroustrup";
auto index - name.find_first_not_of(" \n\t"); // index is 6
Description
• The find() and rfind() functions accept arguments of the string or char type.
• All of these functions return a value of -1 if they can’t find what you’re searching for.
Description
• A substring is part of a string. When you use the substr() function to get a
substring, it creates a new string and doesn’t modify the original string.
Description
• You can use member functions to modify a string by inserting another string into it,
deleting part of the string, or replacing part of the string with another string.
• To replace a single character with another character, you can use a standard for loop
and the subscript operator.
If so, it uses the subscript operator to replace the dash character with the
space character. This shows that looping through the characters in a string is
sometimes the simplest and most efficient way to process a string.
Description
• C++ provides functions that you can use to get information about a character,
such as whether it is an upper- or lowercase letter, a number, punctuation, or
whitespace.
• These functions return an integer that corresponds to a true or false value. For
a true value, these functions return a non-zero number, typically 1. For a false
value, these functions return 0.
The console
The code
#include <iostream>
#include <string>
int main()
{
cout << "Create Account\n\n";
// get password
cout « "Enter password: ■;
getline(cin, password);
// make sure first name uses initial cap and then lower case
char letter ■ first_name[0];
first_name[0] ■ toupper(letter);
for (int i - 1; i < first_name.length(); ++i) (
letter - first_name[i];
first_name[i] ■ tolower(letter);
)
The console
Let's Play Word Jumble!
The code
#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
#include <ctime>
int main() (
cout « "Let's Play Word Jumble!\n"
« "\n"
« "Enter 'hint' for a hint.\n"
« "Enter 'exit' to exit.\n\n";
After displaying the hint, this code increments the variable that stores the count
of hints. That way. the next time the user enters “hint”, the program will display
another character in the word.
If the user enters “exit”, the code displays a message that indicates that the
program is ending. Then, it breaks out of the loop, which ends the program.
Finally, if the user doesn’t guess the word or enter “hint” or “exit”, the code
displays a message that indicates the guess was not correct. Then, program
execution jumps to the top of the loop, which prompts the user for another guess.
Chapter 6 How to work with data types, strings, and vectors 233
if (guess ■■ word) (
cout « "Congratulations! You guessed it.\n\n";
break;
)
else if (guess -- "hint") (
cout « "Hint: ";
for (int i - 0; i < word.length(); ++i) {
if (i < hint_count) (
cout << static_cast<char>(toupper(word(i))) << ' '
}
else <
cout « << ’ ';
}
)
cout « "\n\n";
++hint_count;
)
else if (guess ■■ "exit") (
cout « "Bye! Let's play again soon.\n\n";
break;
)
else (
cout « "Nope. Try again!\n\n";
)
}
}
Perspective
Now that you’ve completed this chapter, you should be able to work with
any of the fundamental data types you need in your programs. In addition, you
should be able to use vectors and strings whenever you need them.
In this chapter, the Temperature Manager program uses two vectors to store
two columns of data for low and high temperatures. For now. this technique is
adequate. However, this technique gets unwieldy if you needed to store many
columns of data. Fortunately, C++ provides other ways to store and manipulate
a table of data, and you’ll learn more about how to do that starting in chapter 9.
Terms
fundamental data types promote data
built-in data types demote data
integers scope resolution operator
floating-point numbers integer overflow
significant digits integer underflow
single-precision number Standard Template Library (STL)
double-precision number containers
characters subscript operator
Boolean value index
scientific notation vector
ASCII character set element
variable out of bounds access
define a variable out of range access
initialize a variable initialization list
assignment operator range-based for loop
constant push an element
casting data string
type coercion substring
Chapter 6 How to work with data types, strings, and vectors 235
Summary
• C++ provides fundamental data types to store integers, floating-point
numbers, characters, and Boolean values.
• To express the value of a floating-point number, you can use scientific
notation.
• Variables store data that varies as a program runs. Constants store data that
remains constant as a program runs. You use the assignment operator to
assign values to variables and constants.
• If you perform assignment or arithmetic operations with different data
types. C++ automatically converts or casts the types using its rules of type
coercion.
• When you use different data types in an expression. C++ automatically
promotes lower ranking data types to the next higher data type. When you
assign a value to a variable that has a lower ranking data type. C++ automati
cally demotes the value to that data type. This can result in the loss of some
data.
• When an integer variable is assigned a value that’s too big for its type, it will
overflow, or wrap around to the type’s minimum. When an integer variable
is assigned a value that’s too small for its type, it will underflow, or wrap
around to the type’s maximum.
• The Standard Template Library (STL) contains many data types in addition
to the fundamental data types. The STL types that contain collections of
other data types are often called containers.
• A vector contains a sequence of elements of the same data type. You can use
the subscript operator ([]) to access the elements of a vector.
• Out of bounds access occurs if your code specifies an index that’s outside
the range of valid indexes. Since out of bounds access leads to unexpected
results, you should always write your code to prevent it.
• A range-based for loop automatically iterates over every element in a vector
or string without having to specify a counter or use the subscript operator.
• A string contains a sequence of char values. You can think of a string as a
vector of characters.
236 Section 1 Essential skills for modern C+ +
Continue? (y/n): y
Continue? (y/n): n
Monthly Yearly
investment rate Years Future Value
100.00 5.00 10 15592.93
150.00 5.00 10 23389.39
Create Account
Hi Grace,
Thanks for creating an account!
2. Add a while loop that gets an email address until a valid address is entered.
You can pattern this loop after the loop that gets a valid password.
3. Within the while loop, start by checking that the email includes an at sign (@)
and a dot character (.).
4. If the email address includes an at sign and a dot character, check that the rest
of the characters in the address are alphanumeric, an at sign, a dot character,
an underscore (_), or a dash (-). To do that, you can use a range-based for
loop.
5. Check that there is at least one character before the at sign and the last dot
character and two or three characters after the dot character. To do that, you
can use the indexes for the at sign and the last dot character and the length of
the email address.
7
How to code functions
So far. the programs you’ve seen in this book have consisted of a single
function, the main() function. As your programs get longer and more complex,
though, you’ll need a way to break them down into manageable chunks of
reusable code. One way to do that is to code your own functions that can be
called by the main() function.
In this chapter, you’ll learn a variety of skills for declaring, defining, and
calling functions. In addition, you’ll learn how to organize your functions into
header files and namespaces. When you do that, you’ll be able to access these
functions from multiple programs just as you can the functions in the C++
standard library.
Description
• A function is a block of code that can be called by other statements.
• To define a function, code the function’s return type, name, a set of parentheses,
and a set of braces. Within the parentheses, you can code zero or more parameters,
separating parameters with commas. Within the braces, you can code one more
statements for the body of the function.
• To call, or invoke, a function, code the name of the function and a set of paren
theses. Within the parentheses, code any arguments separated by commas. The
arguments must be in the same sequence as the parameters in the function.
• If a function uses the void keyword for its return type, it doesn’t return any
data. If a function specifies a return type, you must code a return statement that
returns data of the specified type to the calling code.
Figure 7-1 How to define and call a function
242 Section 1 Essential skills for modern C++
The console
Miles Per Gallon Calculator
The code
ftinclude <iostream>
#include <cmath>
void display_title() {
cout << "Miles Per Gallon Calculator\n\n" ;
}
int main() (
display_title();
double miles;
cout « "Enter miles driven: ■;
cin >> miles;
double gallons;
cout << "Enter gallons of gas used: ■;
cin >> gallons;
return 0;
}
Description
• The main() function can call other functions that are defined before it.
int main() {
display_title() ; // call function 1
// define function 1
void display_title() {
cout « "Miles Per Gallon Calculator" « endl « endl;
}
// define function 2
double calculate_mpg(double miles, double gallons) {
double mpg ■ miles I gallons;
mpg - round(mpg * 10) / 10; // round to 1 decimal place
return mpg;
}
Description
• To declare a function, you code the function’s return type. name, and param
eters followed by a semicolon. In a declaration, the parameter names are
optional. However, they’re recommended because they improve the readability
of the code.
• A function that has been declared is also known as a function prototype.
• If you declare a function before the main() function, you can define a corre
sponding function after the main() function.
int main() {
calc_tax(85.0, .05);
cout « "Tax: ■ « tax « endl; // display global - tax is 4.25
)
int main() {
calc_tax(85.0 , .05);
cout « "Tax: ■ « tax « endl; // display global - tax is 0.0
)
Description
• A variable defined inside a function is known as a local variable. A variable
defined outside of all functions is known as a global variable.
• A local variable can only be used within the function that defines it. Global
variables can be used by all functions.
• When a local variable has the same name as a global variable, the local variable
shadows the global variable. In a function that defines the variable, all operations are
done on the local variable. Otherwise, all operations are done on the global variable.
Figure 7-4 When and how to use local and global variables
248 Section 1 Essential skills for modern C+ +
display
menu
to
fahrenheit
The console
Convert Temperatures
MENU
1. Fahrenheit to Celsius
2. Celsius to Fahrenheit
Bye!
The code
tinclude <iostream>
tinclude <cmath>
// declare functions
void display_menu();
void convert_temp();
double to_celsius(double fahrenheit);
double to_fahrenheit(double Celsius);
int main() {
display_menu();
char again - 'y';
while (again -- 'y') (
convert_temp();
// define functions
void display_menu() {
cout « "MENU\n"
« "1. Fahrenheit to Celsius\n"
« "2. Celsius to Fahrenheit\n\n";
)
When organizing your code, you can put the function definitions in any
sequence that makes sense to you. This program organizes the functions by
putting them in the sequence in which they’re called. To start, the main()
function is called first. As a result, this code places it before the definitions
for the other functions. Since the main() function calls the display_menu()
function followed by the convert_temp( ) function, that’s the sequence in which
they’re coded. And since the convert_temp( ) function calls the to_celsius() and
to_fahrenheit() functions, they appear after the convert_temp() function.
Chapter 7 How to code functions 253
double f - 0.0;
double c - 0.0;
switch (option) {
case 1:
cout << "Enter degrees Fahrenheit: ";
cin >> f;
c ■ to_celsius(f);
c ■ round(c * 10) I 10;
f ■ to_fahrenheit(c);
f ■ round(f * 10) I 10;
return future_value;
)
Description
• You can specify a default value for any argument in a function declaration or
definition by assigning a value to the argument. However, the arguments with
default values must be coded last in the function declaration or definition.
• When you call a function, any arguments that have default values are optional,
but you can override the default value for an argument by supplying that
argument.
• You can’t assign a default value in both a declaration and a definition. If you
do, you’ll get a compile-time error.
int i ■ 120;
cout < < i « ■ Fahrenheit ■ " « to_celsius(i) << " Celsius\n";
The console
120 Fahrenheit - 48.8889 Celsius
120 Fahrenheit - 48.89 Celsius
120 Fahrenheit - 49 Celsius
Description
• When you create two or more functions with the same name but different param
eter lists, the functions are overloaded.
• When you code a function, the function name plus the number of parameters
and the data type for each parameter form the signature of the function. Each
overloaded function must have a unique signature.
• When overloading functions, the parameter names aren’t part of the signature,
and the return type isn’t part of the signature. As a result, for a function signature
to be unique, the function must have a different number of parameters, or at least
one of the parameters must have a different data type.
Description
• A value variable always stores its own copy of the value. As a result, changing
the value for one value variable doesn’t change the value of any other value
variables.
• A reference variable stores a reference to the value. This allows multiple refer
ence variables to refer to the same value. As a result, changing the value for one
reference variable also changes the value for any other variables that refer to that
value.
• To define a variable as a reference variable, code the & symbol immediately after
its data type or immediately before its name.
Description
• If a parameter may contain a large amount of data, you can improve the
efficiency of the function by declaring the parameter as a reference parameter.
That way, the function doesn’t need to make a copy of the data that’s passed to
the parameter.
• If you don’t want to allow the function to change the reference parameter, you
can declare it as a constant.
• You can use the return statement to exit a function at any point in the function.
Figure 7-10 Examples that use reference parameters
262 Section 1 Essential skills for modern C++
The console
The Temperature Manager program
COMMANDS
v - View temperatures
a - Add temperatures
r - Remove temperatures
x - Exit
Command: v
TEMPERATURES
Day Low High
1 57.2 78.3
2 54.3 75.9
3 55.8 77.0
Command: a
Enter low temp: 53.1
Enter high temp: 74.7
Your temperatures have been added.
Command: r
Enter day to remove: 3
Temps for day 3 have been removed.
Command: x
Bye!
The code
#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <limits>
#include <vector>
After declaring the functions, the main() function shown in part 2 of figure
7-11 provides the code that’s executed when the user starts the program. First,
this code displays the title of the program. Then, it uses the display_menu()
function to display the menu of commands that are available to the user. Next, it
defines the vectors for the low and high temperatures and loads the temperatures
from the text file for this program into the vectors by calling the load_temps()
function and passing the vectors.
After loading the data from the file, this program begins a while loop that
continues until the user enters ‘x’ to exit the program. This loop begins by
getting a command from the user. Then, it uses a switch statement to execute
the appropriate command. If the user enters V, ‘a’, or ‘r’. the code executes the
view_temps(), add_temps(), or remove_temps() functions by passing the vectors
that store the low and high temperatures to the appropriate function. If the user
enters ‘x’, the code displays a message that says “Bye!” and exits the loop.
Otherwise, this code displays a message that indicates that the command was
invalid and uses the display_menu() function to display the list of commands
again.
After the main() function, this code defines the get_char() function that gets
a character from the user. To start, this function defines a char variable named
choice. Then, it displays the prompt argument on the console. Next, it gets the
character entered by the user and stores it in the char variable. Finally, it discards
any other data that the user may have entered and returns the character to the
calling code.
Chapter 7 How to code functions 265
display_menu();
cin.ignore(10000, '\n');
return choice;
}
void display_menu() {
cout « "COMMANDS\n"
<< "v - View temperatures\n"
<< "a - Add temperatures\n"
<< "r - Remove temperatures\n"
« "x - Exit\n\n";
}
The add_temps() function in part 4 of figure 7-11 provides the code that’s
executed when the user enters ‘a’ to select the add temperatures command.
This function accepts two vectors, one for low temperatures and one for high
temperatures. To improve efficiency, this function defines these vectors as
reference parameters. However, since this function needs to modify these vectors
by adding an element to each of them, it doesn’t declare these parameters as
constants.
Within the add_temps() function, the code begins by getting the low and high
temperatures from the user. Then, it uses the push_back() function to add these
temperatures to the end of the vectors for the low and high temperatures. Next,
it saves this data to the file by calling the save_temps() function and passing
both vectors to that function. Finally, it displays a message that indicates that the
temperatures have been added.
The remove_temps() function provides the code that’s executed when the
user enters ‘r’ to select the remove temperatures command. This function works
much like the add_temps() function. However, it includes some range checking
to prevent out of bounds access with either of the vectors. To start, this code
converts the day that’s entered by the user to an index that corresponds to an
element in the vector. To do that, it subtracts 1 from the day. Then, it checks if
the index is greater than or equal to 0 and less than the size of the vector of high
temperatures. If it is, the index is within a valid range for both vectors. As a
result, the code continues by using the erase() function to remove the tempera
ture for the specified day from both vectors. Then, it saves this data to the file
by calling the save_temps() function. However, if the index isn’t valid, this code
displays a message that indicates that the day was not valid.
As you review this code, note that it separates the code for the file I/O from
the code for the console I/O. In other words, it separates the code that works
with the data in the file from the code that works with the user interface. That’s
generally considered a good design practice because it allows you to change the
user interface without having to change the data access code. For example, if
you later need to change the user interface from a console program to a graphical
user interface (GUI), you could still use the same file I/O code.
In addition, this design leads to code that's more reusable. For example, both
the add_temps() and remove_temps() functions call the save_temps() function to
save any changes to the data immediately after the data has been changed. That
way, the user won’t lose that data even if the program crashes after executing
these functions.
Similarly, the main() function calls the display_menu() function in two
places. First, it displays the commands when the program starts. In addition,
it displays the commands if the user enters an invalid command. That way, if
a user enters an invalid command, the program shows the user the list of valid
commands to choose from.
Chapter 7 How to code functions 269
// declare functions
double to_celsius(double fahrenheit);
double to_fahrenheit(double Celsius);
int main() {
double c - to_celsius(32);
cout « "Celsius: ■ « c << endl;
Description
• You can organize functions by storing them in separate files. To do that, you
can store the function declarations in a header file, also known as an interface
file, and you can store the function definitions in an implementation file.
• To prevent the compiler from including the same header twice, headers
typically use the #ifndef. #define. and #endif preprocessor directives to define
an include guard.
• To use a header that you’ve defined, you must use an #include directive that
encloses the name of the header file in double quotes, not in angle brackets.
This indicates that the header file is in the working director)', not the include
directory that contains the header files from the C++ standard library.
namespace temperature {
double to_celsius(double fahrenheit);
double to_fahrenheit(double Celsius);
}
ttendif // MURACH_TEMPERATURE_H
int main() {
double c ■ to_celsius(32);
cout « "Celsius: " « c << endl;
}
int main() {
double c ■ temperature::to_celsius( 32);
cout « "Celsius: " « c << endl;
}
Description
• A namespace provides a way of organizing code to avoid naming conflicts.
• To define your own namespace, code the namespace keyword, a name for
the space, and a set of braces. Within the braces, code the functions for the
namespace.
• Once you’ve put functions in a namespace, you can use any of the techniques
described in chapter 2 to identify the members of the namespace.
#include <string>
#include <limits>
namespace console {
// declare general-purpose functions
double get_double(std::string prompt,
double min ■ std::numeric_limits<double>::min(),
double max ■ std::numeric_limits<double>::max());
int get_int(std::string prompt,
int min ■ std::numeric_limits<int>::min(),
int max ■ std::numeric_limits<int>::max());
char get_char(std::string prompt,
bool add_blank_line ■ true);
#endif // MURACH_CONSOLE_H
namespace console {
// declare helper functions
void discard_remaining_chars();
void handle_invalid_number();
bool check_range(double num, double min, double max);
Figure 7-14 A header for getting input from the console (part 1 of 2)
276 Section 1 Essential skills for modern C++
The get_int() function shown in part 2 of figure 7-14 works like the
get_double() function. However, it works with int values instead of double
values. Like the get_double() function, the get_int() function uses the helper
functions defined at the end of this file.
The get_char() function begins by displaying the prompt and getting the
first character that’s entered by the user. However, this function includes a
second parameter that specifies whether it displays a blank line after the user
enters the character. This parameter has a default value of true, so this function
displays a blank line after the user input, unless the calling code specifies a
second argument of false. Then, it calls the discard_remaining_chars() function
to discard all remaining characters on the current line, and it returns the first
character that the user entered.
The discard_remaining_chars() function contains a single statement.
However, this statement is a complicated statement that uses the ignoreO
function of the cin object to discard all remaining characters on the current line.
As a result, it’s easier and more consistent to call the discard_remaining_chars()
function than it is to code the complicated statement that’s contained within this
function.
The handle_invalid_number() function contains three statements. The first
displays a standard message for an invalid number, the second clears the failure
flag from the cin object, and the third calls the discard_remaining_chars()
function to discard any remaining characters on the current line.
The check_range() function returns a Boolean value that indicates whether
the specified number is within the specified range. To accomplish this, the body
of this function checks whether the specified number is between the specified
minimum and maximum values. If not, this code displays an appropriate error
message and returns a false value. Otherwise, the number is within the specified
range. In that case, this code returns a true value. Since the parameters for this
function are double types, you might think that it wouldn’t work for int values.
However, this function works equally well for int values because C++ automati
cally converts int values to double values.
As you review this code, note that these files don't use using directives or
declarations. Instead, they use the scope resolution operator (::) to access the
members of the standard and console namespace that they need. As mentioned
earlier in this chapter, that’s a best practice when coding header files.
There are several advantages to using functions like the get_double(),
get_int(), and get_char() to get valid user input. First, they make it possible to
reuse your code within a program. If. for example, your program needs to get
10 valid double values, you can call the get_double() function 10 times instead
of repeating its code in 10 different places in your program. Second, since you
aren't repeating code in multiple locations, you are following the Don’t Repeat
Yourself (DRY) principle, which makes your code easier to debug and maintain.
When you put these functions in a header file, it’s possible to reuse them in
other programs too. One way to do that is to copy the header file and its imple
mentation file into the director)' for another program. Then, you can include the
header file as described earlier in this chapter.
Chapter 7 How to code functions YT7
discard_remaining_chars ();
return choice;
)
void handle_invalid_number() {
std::cout << "Error! Invalid number. Try again.\n";
std::cin.clear(); // clear the failure flag
discard_remaining_chars ();
)
Figure 7-14 A header for getting input from the console (part 2 of 2)
278 Section 1 Essential skills for modern C++
The console
The Future Value Calculator
INPUT
Monthly Investment: $100
Error! Invalid number. Try again.
Monthly Investment: 100 dollars
Yearly Interest Rate: 36
Error! Number must be less than 30. Try again.
Yearly Interest Rate: 12.0
Years: one
Error! Invalid number. Try again.
Years: 1
OUTPUT
Monthly Investment: 100.00
Yearly Interest Rate: 12.0
Years: 1
Future Value: 1280.93
Continue? (y/n):
The code
#include <iostream>
#include <iomanip>
#include <string>
#include <limits>
#include "console.h
int main() {
cout « "The Future Value Calculator\n\n" ;
Perspective
Now that you’ve finished this chapter, you should have all the skills that
you need for defining and calling the functions that your programs require.
These are critical skills that become increasingly valuable as your programs get
longer and more complex. By applying these skills, you make your programs
easier to understand, test, debug, and maintain.
In section 3 of this book, you’ll learn how to use object-oriented program
ming, which provides another way to organize the code in your programs.
Fortunately, many of the skills for working with functions also apply to object-
oriented programming. That’s another reason this chapter is so important.
Terms
function global constant
define a function hierarchy chart
parameter hierarchy outline
call a function overloaded function
invoke a function signature of a function
return statement value variable
Don’t Repeat Yourself (DRY) reference variable
declare a function header file
function prototype interface file
global variable include guard
global scope implementation file
local variable namespace
local scope namespace scope
shadow a global variable
Summary
• A function is a block of code that can be called by other statements.
• To define a function, code the function’s return type. name, a set of paren
theses. and a set of braces.
• Within the parentheses of a function, you can code zero or more parameters
where each parameter consists of a data type and a name and multiple
parameters are separated by commas.
• Within the braces of a function, you can code one or more statements for the
body of the function. If the function returns a value, the body of the function
must use a return statement to return a value of the appropriate data type to
the calling code.
• To call. or invoke, a function, code the name of the function and a set of
parentheses. Within the parentheses, code any arguments and separate
multiple arguments with commas.
Chapter 7 How to code functions 283
• To declare a function, you code the function’s return type, name, and param
eters followed by a semicolon.
• A function declaration is also known as a function prototype.
• A variable defined inside a function is known as a local variable and is only
available within that function.
• A variable defined outside of all functions is known as a global variable and
is available to all functions in the program.
• When a local variable has the same name as a global variable, the local
variable shadows the global variable.
• A value variable always stores its own copy of a value.
• A reference variable stores a reference to a value. This allows multiple
reference variables to refer to the same value.
• You can organize functions by storing them in separate files. To do that, you
can store the function declarations in a header file, also known as an interface
file, and you can store the function definitions in an implementation file.
• To prevent the compiler from including the same header twice, headers
typically use the #ifndef, #define. and #endif preprocessor directives to
define an include guard.
• A namespace provides a way to organize code to avoid naming conflicts.
The functions defined within a namespace have namespace scope.
284 Section 1 Essential skills for modern C+ +
2. Review the code and run the program to refresh your memory on how it works.
Create a header file
3. Use your IDE to create a header file named validation.!).
4. Add the preprocessor directives to define an include guard. Use a name for
the header file that consists of your first initial and last name, followed by
“_VALIDATION_H ”.
5. Define a namespace named validation. Within this namespace, declare
two functions named is_valid_password and is_valid_email. Each of these
functions should accept a string and return a Boolean value that indicates if
the string is valid. For this to work, you’ll need to include the header file for
the string class.
Create an implementation file
6. Use your IDE to create a file named validation.cpp that will implement the
validation header file.
7. Add the code to implement the validation namespace and the two functions
it contains. To do that, you can copy code from the mainf) function for the
program and paste it into the function definitions. Then, you can adjust the
code as necessary so it works within the functions. For this to work, you’ll
need to include the header files for the iostream and string classes.
Update the file that contains the main() f unction to use the header file
8. Add a using directive for the validation namespace to the file that contains the
mainf) function.
9. Replace the code in the main() function that validates the password with a
statement that calls the is_valid_password() function and assigns the return
value to the valid_password variable.
10. Replace the code in the main() function that validates the email address with
a statement that calls the is_valid_email() function and assigns the return
value to the valid_email variable.
11. Test the program to see that it works the same as it did before. Now. though,
the mainf) function should be easier to read and understand. In addition, the
functions in the header file can be used by other programs.
8
How to test, debug,
and deploy a program
As you develop a C++ program, you need to test it to make sure that it
performs as expected. Then, if you encounter any problems, you need to debug
the program to fix the problems. Finally, when you’re done testing and debug
ging a program, you need to deploy it so your users can run it.
INPUT
Monthly Investment: 100
Yearly Interest Rate: 3
Years: 3
OUTPUT
Monthly Investment: 100.00
Yearly Interest Rate: 3.0
Years: 3
Future Value: 3662.06
Continue? (y/n):
Description
• To test a program, you run it to make sure that it works properly no matter
what combinations of valid or invalid data you enter.
• When you debug a program, you fix all of the errors (hugs') that you find when
you test the program.
The Miles Per Gallon program when it’s tested with valid data
Miles Per Gallon Calculator
The Miles Per Gallon program when it’s tested with invalid data
Miles Per Gallon Calculator
INPUT
Monthly Investment: ten
Error! Invalid number. Try again.
Monthly Investment: 0
Error! Number must be greater than 0. Try again.
Monthly Investment: 10000
Error! Number must be less than 10000. Try again.
Monthly Investment: 9999
Yearly Interest Rate:
Description
• It’s easy to find compile-time errors because the program won’t run until you fix them.
• Runtime errors and logic errors can slip through your tests if you don’t check to
make sure the results are correct, or if you don't test a wide enough range of entries.
return future_value;
}
INPUT
Monthly Investment: 100
Yearly Interest Rate: 3
Years: 3
calculate_future_value () starting...
monthly_investment : 100
monthly_rate: 0.0025
months: 36
month 1 future_value: 100.25
month 2 future_value: 200.751
month 3 future_value: 301.503
month 4 future_value: 402.506
month 5 future_value: 503.763
Description
• A simple way to trace the execution of a program is to include statements at key
points in the code that display debugging messages. These messages can indicate
what code is being executed, and they may include the values of important variables.
(Name) cokulatejuture.vaiu *
File c:\murechUpp\w\boi D
FulName cekulete.future.vatu v
(Name)
Set»/return, the nerne of the object.
Error List
Description
• A breakpoint causes program execution to stop before the line that contains the
breakpoint is executed.
• To set a breakpoint for a line of code, open the file in the code editor and click
in the bar to the left of the line number. The breakpoint is identified by a red dot
on the line where you clicked.
• To disable a breakpoint, point to the breakpoint icon with your mouse and click
the Disable breakpoint icon that appears. To re-enable it. point to the breakpoint
icon again and click on the Enable breakpoint icon that appears.
• To remove a breakpoint, click on the breakpoint icon.
• You can set and remove breakpoints either before you start debugging or while
you’re debugging. In most cases, you’ll set at least one breakpoint before you
start debugging.
• To start debugging for a project, press F5 or select the Debug->Start Debugging
menu item.
Fie Edit View Project Build Debug Team Tools Test Analyte Window Help JoelMj-azh • 3
* m 9 . . | .1 > Continue • - ■ 0 * J ? *?
O ’
Pi<x»tv (105C6]fut>4».v4lu».«x« Thread: (16536) Mam Thraad ’ Y »<F-
$t<p Mo (Fl 1)
main.cpp « X
8) futve.velue J® cWcuWe.future.valuefdoub •
(GloW Scope)
O | % « u.
double calculate_future_value(double aonthly_investaent, +
67 Diagnostics seraon 4 second* (4013 s sHected)
68 double yearly_intere. , int .) (
11.569* 11-57*
69
70 // convert yearly values to aonthly values 4 Events
71 double ■onthly_rate • >early_lnterest_ratc / 12 / 100;
II
72 int oenths • year* • 12;
73
74 // calculate future value
4 Process Memory (KB.&s'.cc
805
• Prr.ate Bytes
805
V
75 double futur-e_valuo • 0;
Summiry Events Memory Usage CPU Usage
76 for (int i • 0; i < mnthj; ♦+!) {
77
78
future_value • (futve_value t •onthlyinvestnent)
(1 « aonthly_rate);
Events
Zv
79 } •» Slxw Events (18M18J
00 V
too s. ■ < ► Memory Usage
LckU'.
1
Name Value Type (nt<e Solution ©Of iron ■ 0 Warning*
future_velue W527192976959564 double
Starch Error Int
i 5 int
meMhly^imvstment ixDooooaxococoo double ' Cede Dexnptcn Project F.le
monthly.rata double
months 36 int
yearty.Merest.raU ijxmxmxococoo double
years 3 int
Description
• When a breakpoint is reached, program execution is stopped before the line is
executed.
• The arrow in the bar at the left side of the code editor window shows the line
that will be executed next.
• The Locals window shows the values of the variables that are in scope for the
current function. To display it, click on the Locals tab.
• The Autos window shows the values of the variables in the current line of code
and the preceding line. To display it, click on the Autos tab.
main.tpp o X
TO futuro.vtlue ® cdculate.futu'e.valuddoub • 0 | TO TO u,
+
1 1 105
s.-■v-------
double ■onthly.rate • yearly.intereat.rate / 12 / 100; Cwgnostks session 3 seconds (3.444 s selected)
Int tenths a years • 12;
,1ZZ
// calculate future value
double future.value • 0;
for (Int 1 • 0; 1 < aonths; aai) (
0 w
future.value - (future.value ar ) V
(1 ♦ laonthly.rate); a CPU <% of all processors)
► Memory Usage
Description
• A stack trace is a list of the functions that have been called in the reverse order
in which they were called.
• By default, Visual Studio displays a stack trace in the Call Stack window that’s
included in the group of windows on the bottom right side of the main window.
• To jump to a line of code in the code editor that’s displayed in the stack trace,
double-click on that line in the stack trace.
Description
• A breakpoint causes program execution to stop before the line that contains the
breakpoint is executed.
• To set a breakpoint for a line of code, open the file in the code editor and click
on the line number. The breakpoint is identified by a blue arrow that’s placed
over the line number.
• To deactivate a breakpoint, click on the breakpoint icon. To reactivate, click it
again.
• To remove a breakpoint, hold down the Control button, click on the breakpoint
icon, and select the Delete Breakpoint item from the resulting menu.
• You can set and remove breakpoints either before you start debugging or while
you’re debugging. In most cases, you’ll set at least one breakpoint before you
start debugging.
• To start debugging for a project, set a breakpoint and run the project as you
normally would.
Description
• When a breakpoint is reached, program execution is stopped before the line is
executed.
• The line that will be executed next is shaded in the code editor window.
• The Auto window shows the values of the variables that you’re most likely to
be interested in. To display it, select the Auto item from the list at the bottom of
the Debug area.
• The Locals window shows the values of the local variables that are in scope
for the current function. To display it, select the Locals item from the list at the
bottom of the Debug area.
• To stop debugging and exit the program, click the Stop button that’s in the Xcode
toolbar at the top of the window, or select the Detach item from the Debug menu.
Description
• A stack trace is a list of the functions that have been called in the reverse order
in which they were called.
• By default, when you begin a debugging session. Xcode displays the Debug
navigator on the left side of the main window, and it displays a stack trace
within this navigator.
• To jump to a line of code in the code editor that’s displayed in the stack trace,
click on that line in the stack trace.
• When you end a debugging session, you can display the Project navigator again
by clicking on the Project navigator button that’s above the Debug navigator.
Description
• Since C++ compiles the source code to an executable file, deploying a program is
as easy as making the executable file available to your users.
• On Windows, if your users run the executable by double-clicking on it, the
console might close when your program exits. Then, your users might not be able
to view the output of your program. To fix this, you can add some code to the end
of your program to keep it open until the user presses the Enter key.
• If your users open a console and use it to run the executable, the console stays
open even after your program exits.
• With Xcode, you can find the location of your executable file by expanding
the Products folder for the project. Control clicking on the executable file, and
selecting the Show in Finder item from the resulting menu.
• If your program uses a file and no path is specified, the file must be located in the
working directory, which is almost always the directory that contains the executable.
Perspective
Now that you’ve completed this chapter, you should have the skills you
need to test a program to identify any bugs it may contain. Then, you should be
able to use the debugging techniques presented in this chapter to determine the
cause of those bugs.
However, you should know that the debuggers available from most IDEs
provide some additional features that you can use to test and debug your
programs. After reading this chapter, you should be able to learn more about
those features on your own.
You should also know that the testing approaches described in this chapter
are just the basics for getting you started. As you begin to develop more
complex programs, you may want to learn more about other testing approaches
such as Test Driven Development (TDD) and Behavior Driven Development
(BDD). In addition, you may want to learn about other testing technologies
such as unit testing, which is a way of creating tests for individual units of
source code such as functions to make sure they work correctly.
Terms
test a program test plan
debug a program trace program execution
bug debugger
compile-time error breakpoint
syntax error stack trace
runtime error executable file
logic error
Summary
• To test a program, you run it to make sure that it works properly no matter
what combinations of valid or invalid data you enter.
• When you debug a program, you find and fix all of the errors (hugs) that you
find when you test the program.
• Compile-time errors are caught by the compiler before you run the program.
These errors are usually syntax errors that violate the rules for how state
ments must be written.
• Runtime errors don’t violate the syntax rules, but they can cause the program
to crash or to yield unexpected results.
• Logic errors are statements that don’t cause syntax or runtime errors, but
produce the wrong results.
• A fesr plan is a table or spreadsheet that shows the data entries that you
can use for testing the program as well as the correct results for each set of
entries. That way, you can verify that the program’s results are correct.
Chapter 8 How to test, debug, and deploy a program 311
cout << "Please enter the year that movie was released: ";
cin >> movie.year;
How to read values
cout << movie.title « " was released in " « movie.year << ".\n";
Description
• You can use the struct keyword to define a structure that consists of one or more
data types. When you define a structure, the definition ends with a semicolon.
• The data types that compose a structure type are called data members, or just members.
• When you define a variable of a structure type, an object of that type is created.
• You can use dot notation to access the data members of an object that’s created
from a structure.
• You can use a vector to store one or more objects created from a structure. Then,
you can use a range-based for loop to easily access the data in each object.
Description
• When you create an instance of a structure, the members contain whatever
values happen to be at the memory location where they are created. This can
lead to unexpected results.
• Due to possible unexpected results, it’s generally considered a best practice to
initialize the members with starting data when you create an object.
• You can use an initialization list to set the starting values of an object when you
create it. When you use an initialization list, the assignment operator is optional.
• The first value in an initialization list is assigned to the first member in the
structure, the second value is assigned to the second member, and so on.
• You don't have to include a value in the list for every member. However, if you
don’t initialize a member, you can’t initialize any subsequent members.
• In C++11 and later, you can set default values in the structure definition.
The console
The Movie List program
Enter a movie...
Title: Casablanca
Year: 1942
TITLE YEAR
Casablanca 1942
Wonder Woman 2017
The code
#include <iostream>
tinclude <iomanip>
tinclude <string>
#include <vector>
int mainf) {
cout << "The Movie List program\n\n"
<< "Enter a movie...\n\n";
movie_list.push_back(movie);
When the user ends this loop by not entering ‘y’ or ‘Y’, the code in part 2 of
figure 9-3 displays all Movie objects that have been stored in the vector. To do
that, it uses the stream output manipulators to left align all data in two columns
where the first column is 30 characters wide (10*3) and the second column is
10 characters wide.
As you review this code, note that the Movie List program doesn't validate
any of the data entered by the user. That keeps the code listing short and focused
on the code that works with the Movie structure.
Chapter 9 How to work with structures and enumerations 323
struct Invoice {
int number ■ -1;
Date date;
Date due_date;
double total - 0;
);
The console
INVOICE
Number: 1
Date: 2018-1-2
Due Date: 2018-4-15
Total: 1430.72
Description
• You can nest structures within other structures. To do that, you code a structure
variable as a data member.
• You use dot notation to access the members of a nested structure.
• There is no limit to how deep you can nest structures.
Description
• When you pass a structure object to a function, all the values in its data
members are passed by value. That means a copy of each member is passed.
• If a function needs to access the same structure object as the calling code, the
object must be passed by reference. This improves performance for large objects.
• A function can’t return multiple values. However, if a function returns a structure
type, it can return multiple values as the data members of the structure object.
How to compare the data members of the Movie objects for equality
An if statement that displays a message
if (wwl.title ■■ ww2.title &&
wwl.year -- ww2.year) {
cout « "The movies are equal.\n";
} else {
cout « "The movies are NOT equal.\n";
)
The console
The movies are equal.
Description
• A structure doesn't define the equality operator (■■) by default. As a result, if
you try to use the equality operator to compare two structure variables, you’ll
get an error.
• To test whether two structure variables are equal, you can compare the values
in the data members.
Description
• In addition to members that contain data, a structure can have member functions that
operate on its data members. A member function works like a regular function.
• To call a member function from a structure object, you use the dot operator.
• As with regular functions, you can declare a function in the structure definition and
define it after the structure definition.
• To define a function outside of a structure definition, prefix the function definition with
the data type and the scope resolution operator (::). That way, the compiler knows that
this function is a member of the structure.
• You can also use member functions to add operators for your data type. To do that, you
use the operator keyword and the operator you’re adding when you define your function.
The console
The Movie List program
COMMANDS
v - View movie list
a - Add a movie
d - Delete a movie
x - Exit
Command: v
TITLE YEAR STARS
1 Casablanca 1942 5
2 Wonder Woman 2017 4
Command: a
Title: The Wizard of Oz
Year: 1939
Stars (1-5): 5
The Wizard of Oz was added.
Command: d
Number: 1
Casablanca was deleted.
Command: x
Bye!
The code
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <string>
#include <vector>
struct Movie {
string title -
unsigned int year ■ 0;
unsigned int stars ■ 0;
bool equals(Movieft); // member function declaration
);
ifstream input_file(movies_file);
if (input_file) ( // if file opened successfully...
Movie movie;
string line;
while (getline(input_file, line)) (
stringstream ss(line);
int number - 1;
for (Movie movie : movies) (
cout « setw(col_width / 2) « number
<< setw(col_width * 4) << movie.title
<< setw(col_width) << movie.year
<< setw(col_width) << movie.stars << endl;
♦♦number;
)
cout << endl;
if (already_exists) {
write_movies_to_file(movies);
cout << movie.title << ■ already exists and was updated.\n\n" ;
}
else (
movies.push_back(movie) ;
write_movies_to_file(movies);
cout « movie.title « ■ was added.\n\n";
}
As a result, the function returns it to the calling code. This causes program
execution to exit the loop and the function. Otherwise, the number is invalid. As
a result, the code displays an appropriate error message. Then, program execu
tion continues at the top of the loop, which prompts the user for another number.
Part 4 of figure 9-8 begins by defining a function that deletes a movie. This
function begins by getting a number that corresponds with a movie in the list of
movies that’s displayed on the console. Then, this code gets the index for that
movie by subtracting 1 from the number for the movie. Next, it uses the index to
get the Movie object, delete it from the vector, write the movie data in the vector
to a file, and display an appropriate message that includes the title of the movie
that was deleted.
The next function in part 4 displays a menu of commands. This function
consists of a single statement that displays the characters (v, a. d. and x). Then,
the user can enter one of these characters to execute the commands (view. add.
delete, and exit) for working with the program.
The last function is the main() function that runs when the program starts.
This function begins by displaying the name of the program. Then, it displays
the menu for the program, it reads the movie data from the text file for the
program into a vector of movies, and it begins a while loop that continues until
the user enters ‘x’ to exit. Within the loop, the code prompts the user to enter a
command. Then, a switch statement executes the appropriate code depending
on what command is entered. If the user enters V, ‘a’, or ‘d’. the function for
viewing a movie, adding a moving, or deleting a movie is called. However, if the
user enters ‘x’, the code displays a message of “Bye!”, and program execution
exits the loop, which causes the program to end. Or. if the user doesn’t enter a
valid command, the switch statement displays an appropriate error message, and
program execution continues at the top of the loop, which prompts the user for
another command.
As you review this code, note that the add_movie() and delete_movie()
functions don’t return the vector of movies after they change it. That’s because
these methods define the vector parameter as a reference variable. As a result,
the code in the main() function refers to the same vector that’s modified by the
add_movie() and delete_movie() functions.
Also note that the write_movies_to_file(), view_movies(). and
get_movie_number() functions accept the vector of movies as a reference
parameter. That means these functions also work with the vector defined in
the main() function. In addition, because these functions don’t change the
vector, the vector parameter is defined as a constant.
Chapter 9 How to work with structures and enumerations 337
void display_menu() {
cout « "COMMANDS\n"
<< "v - View movie list\n"
<< "a - Add a movie\n"
<< "d - Delete a movie\n"
« "x - Exit\n\n";
int main() {
cout « "The Movie List program\n\n";
display_menu();
vector<Movie> movies ■ read_movies_from_file();
char command - ' v ’ ;
while (command != 'x') (
cout << "Command:
cin >> command;
switch (command) (
case 'v':
view_movies(movies);
break;
case 'a':
add_movie(movies) ;
break;
case 'd':
delete_movie(movies);
break;
case 'x':
cout << "Bye!\n\n";
break;
default:
cout << "Not a valid command. Please try again.\n\n";
break;
}
)
)
Description
• An enumerated data type, or enumeration, is a user-defined type that’s composed
of constant values. Enumerations can make your code more clear.
• The constant values contained in an enumeration are called enumerators. By
default, they are stored in memory as sequential integers, starting at 0.
• To create a scoped enumeration, also known as a class enumeration, you code the
class keyword. This keyword is available for enumerations with C++11 and later.
the values together. Next, the fourth statement assigns the subtract enumerator to
the Operation variable. Finally, the fifth statement calls the calculate() function
and passes it this enumerator and the two double values. This subtracts the
second value from the first.
Description
• To assign an integer value to an enumeration variable, or an enumeration value to
an integer variable, you can use a static cast.
• Since enumerators correspond to integer values, you can use the relational operators
with them.
• By default, the enumerator values are int values that begin at 0. However, you can
specify the values of the enumerators as long as you use an integer type like int or char.
• You can also specify the integer type that the enumeration uses to store the underlying
values.
An unscoped enumeration
enum Operation { add, subtract, multiply, divide };
Description
• Prior to C++11, it was a common practice to create unscoped enumerations.
Unscoped enumerations work much like scoped enumerations, but they can lead
to several problems. As a result, you should use scoped enumerations whenever
possible.
The console
The Monthly Bonus Calculator
DEPARTMENTS
1 - Marketing
2 - IT
3 - Sales
4 - Other
The code
tinclude <iostream>
// function prototypes
void showjnenu();
Department get_department();
bool check_criteria(Department);
int main() {
cout « "The Monthly Bonus Calculator\n\n" ;
showjnenu();
return 0;
)
// function definitions
void showjnenu() {
COUt « "DEPARTMENTS\n"
<< static_cast<int>(Department ::marketing) << " - MarketingXn"
<< static_cast<int>(Department ::it) << ■ - IT\n"
<< static_cast<int>(Department ::sales) « " - Sales\n"
<< static_cast<int>(Department ::other) « " - Other\n\n";
Perspective
Now that you’ve finished this chapter, you should have all the skills that
you need for defining any structures or enumerations that your programs
require. These are critical skills that become increasingly valuable as the data
requirements for your program become more complex. By applying these
skills, you can make your programs easier to understand, test, debug, and
maintain.
When you define a structure, you define a simple object. As a result, you
can think of structures as your first step in using object-oriented programming.
In section 3 of this book, you’ll learn the rest of the skills that you’ll need for
working with object-oriented programming including how to define classes,
how to work with inheritance, and more.
Terms
structure enumeration
data member enumerated data type
initialize enumerator
initialization list scoped enumeration
nested structure class enumeration
return value optimization (RVO) unscoped enumeration
member functions
Summary
• A structure consists of one or more other data types.
• The data types that compose a structure are called the data members, or just
members, of the structure.
• It’s considered a best practice to initialize the members of a structure with
starting data when you create an object from a structure.
• You can use an initialization list to set the starting values of an object when you
create it. When you use an initialization list, the assignment operator is optional.
• You can nest structures within other structures. To do that, you code a
structure variable as a data member.
• A structure can have member functions that work with the data members of
the structure and operators that perform operations on its data members.
• An enumerated data type, or enumeration, is a user-defined type that
consists of constant values.
• The constant values contained in an enumeration are called enumerators. By
default, enumerators are stored in memory as sequential integers, starting at 0.
• With C++11 and later, it’s generally considered a best practice to use the class
keyword to create scoped enumerations, also known as class enumerations.
• Prior to C++11, it was a common practice to create unscoped enumerations.
Chapter 9 How to work with structures and enumerations 349
Name Email
Grace Hopper ghoppeir0yahoo.com
Margaret Hamilton margarethamilton&mit.edu
2. Review the code in the main.cpp file, and then run the program to see how it
works.
Add a structure and the functions that use it
3. Define a structure named Account that contains four string members for the
first name, last name, password, and email that are initialized to empty strings.
4. Define a function named read_accounts_from_file() that creates an Account
object for each account in the tab-delimited accounts.txt file, adds it to the end
of a vector of Account objects, and then returns the vector.
5. Modify the display_accounts() function so it accepts the vector of Account
objects as a parameter. Then, remove the code that gets the data from the
accounts.txt file, and modify the remaining code so it uses a loop to display
the data for each object in the vector.
6. Define a function named write_accounts_to_file() that accepts the vector of
Account objects as a parameter. This function should write the data in each
object to the accounts.txt file.
350 Section 2 More skills as you need them
Description
• The sequence containers store data sequentially by index, and the associative
containers store data by key.
• The array and vector containers store data in contiguous memory', which is a
consecutive block of memory' with no gaps.
• The list, forward list. map. set, multimap. and multiset containers store data in
non-contiguous memory, which are unrelated memory' locations that may have gaps.
• The deque (pronounced “deck”) container stores its elements in a combination
of contiguous and non-contiguous memory.
• A container adapter adapts a container to make it more specialized.
Description
• An iterator object allows you to access and traverse the elements in a container.
• The iterators returned by the begin() and end() functions define a range, or interval,
that starts at the first element and ends one memory location past the last element.
This is called a half-open interval, and can be represented by the mathematical
notation |begin:end).
• The iterator returned by end() is sometimes called an off-the-end iterator.
• The six iterator types are hierarchical, where the input and output iterators are
the lowest and the contiguous iterator is the highest.
• The iterators provide operations for working with elements. Each higher type inherits
the operations provided by the lower ones and adds one or more operations of its own.
Figure 10-2 A summary of STL iterators
356 Section 2 More skills as you need them
How to get iterators that point to the first and third elements
auto iter ■ prices.begin(); // get iterator that points to first element
With a random-access iterator
iter ■ iter + 2; // use ♦ operator to jump to third element
With an iterator that’s lower than random-access
++iter; // use ++ operator to move to second element
++iter; // use ++ operator to move to third element
cout << *b << " has " « (*b).size() << " letters"; // indirection op
cout << *b << " has " « b->size() << " letters"; // member access op
// both statements display "hello has 5 letters"
Description
• An iterator for a container has a type that's related to the container’s type. To simplify
the code for declaring an iterator variable, many programmers use the auto keyword.
• An iterator points to an element in a container. To access the value an iterator points
to, you can dereference the iterator with the indirection operator (*) or the member
access operator (->). For more information about how this works, see chapter 17.
• You should avoid dereferencing the iterator that's returned by end(), since it can
cause unexpected results from incorrect values to program crashes.
Description
• The forward_list container and the array container don’t support all of the functions
that the rest of the STL containers do. For example, neither of these containers support
the insert() or erase() functions, and forward_list doesn’t support the size() function.
• The examples shown here use vectors, but they work with other containers too.
That’s because all the examples use operations provided by lower-level iterators.
How to use a for loop and subscripting to iterate the data in a container
for (int i = 0; i < prices.size(); ++i) (
cout « prices[i] « • •;
}
Pros
• You can easily skip elements by changing loop criteria.
• You don’t need to dereference an iterator to get the value of an element.
• It uses a counter variable that can be useful for tasks such as displaying a
number for each element.
Cons
• Out of bounds access is possible.
• Subscripting is not available to all containers.
Description
• You can use a variety of techniques to iterate the elements in a container. Each of
these techniques has its pros and cons.
Examples
vector<char> greeting ( 'h',' e','1','1','o','w','o','r','1','d'};
vector<char> greeting2 - greeting; // assign all elements to new vector
// both vectors now contain helloworld
// resize greeting
greeting.resize(S);
// greeting now contains hello
// greeting.size() returns 5
Description
• Some member functions are shared by all the containers in the STL. some
are shared by the sequence containers, and some are shared by the associative
containers.
• The forward_list container and the array container don’t support all of the
functions that the rest of the sequence containers do. For example, neither of
these containers support the push_back() or pop_back() functions.
Description
• The elements in a vector are stored in contiguous memory. This allows for random
access and fast iteration.
• To maintain contiguous memory, a vector must sometimes move elements. When this
happens, any iterators previously returned by begin() or end() become invalid.
• Although a vector must sometimes move elements, its contiguous memory makes it
the best performing container for most uses.
• You can initialize a vector with an initialization list or with iterators. These
techniques work with most of the other STL containers as well.
Figure 10-7 Member functions of a vector
366 Section 2 More skills as you need them
Description
• The capacity of a vector is the amount of contiguous memory that’s been allocated
for its elements.
• When you create an empty vector, both its size and its capacity are zero. Then, as
you add elements, the capacity is increased as needed.
• You can specify the capacity of a vector by passing a value to the reserveO
function. This doesn’t create any elements. It just allocates memory for them.
The console
The Movie Rankings program
Movie Rankings
1 - Casablanca
2 - Wonder Woman
3 - The Godfather
4 - E.T.
5 - The Bridge on the River Kwai
Movie Rankings
1 - Casablanca
2 - The Godfather
3 - E.T.
4 - Wonder Woman
5 - The Bridge on the River Kwai
The code
tinclude <iostream>
tinclude <iomanip>
#include <string>
#include <vector>
int main() {
vector<string> movies { "Casablanca", "Wonder Woman", "The Godfather",
"E.T.", "The Bridge on the River Kwai" };
After checking whether the user wants to change a ranking, this code
declares a while loop that runs if the user enters ‘y’ or ‘Y’ at the prompt. Within
this loop, the code begins by getting the current ranking of the movie that the
user wants to change. Then, it gets the new ranking for that movie. To keep the
program simple, it doesn't validate the user entries for the rankings.
After getting the current and new rankings, this code passes these rankings
along with the vector of movie titles to the change_ranking() function. This
updates the vector so it stores the movies in the sequence of their rankings. Then,
this code calls the display_rankings() function to display the updated rankings.
Finally, the code uses the get_choice() function again to check whether the user
wants to change any rankings. If the user enters ‘y’ or *Y\ the loop is executed
again. Otherwise, the program ends.
The display_rankings() function starts by displaying “Movie Rankings”
followed by a line of dashes that underlines this header. Then, it uses a for loop
to iterate all the elements in the vector and display each movie’s ranking and
title. It calculates the ranking by adding 1 to the variable named i that indicates
the movie’s position in the vector, so the movie at index 0 has a ranking of 1, the
movie at index 1 has a ranking of 2. and so on.
The get_choice() function starts by defining a char variable named choice.
Then, it prints a message to the console that asks if the user wants to change
a ranking. Next, it stores the user’s response in the char variable. Finally,
it discards any remaining characters from the input stream and returns the
character entered by the user.
The change_ranking() function starts with an if statement that checks
whether the current and new rankings entered by the user are in bounds. To
do that, it uses relational and logical operators to make sure the values in the
current_ranking and new_ranking variables are each greater than zero and less
than or equal to the value returned by the size!) function of the vector of movie
titles.
If the two variables are in bounds, the code within the if statement decre
ments the rankings so they contain values that can be used as indexes for the
vector. For example, if the current_ranking variable stores a value of 1. it’s
referring to the first movie title, which has an index of 0. Then, the code uses the
subscript operator to get the movie title for the ranking the user wants to change,
and it stores this string in a variable named movie.
After storing the title in the movie variable, the code calls the erase()
function of the vector to remove the movie title from the vector. To do that, it
calls the begin() function to get an iterator that points to the first element in the
vector. Then, it uses the + operator and the current_ranking variable to advance
that iterator to the element to be removed.
Finally, the code calls the insert!) function of the vector to put the movie
title back in the vector at the correct position for its new ranking. To do that, it
again uses the begin!) function to get an iterator for the first element and the +
operator to advance the iterator to the correct position. But this time, it uses the
new_ranking variable to advance to the correct position. In addition, it passes the
value in the movie variable as the string value to insert.
Chapter 10 How to work with STL containers and iterators 371
int new_ranking ■ 0;
cout << "Enter new ranking of the movie: ";
cin >> new_ranking;
char get_choice() (
char choice;
cout « "Do you want to change any rankings? (y/n): ■;
cin >> choice;
cin.ignore(10000, *\n');
return choice;
}
// store element
string movie - movies[current_ranking];
Description
• An array contains a sequence of elements of the same data type. It stores the elements
in contiguous memory, or consecutive blocks of memory with no gaps.
• The number of elements in an array is fixed and must be set at compile time. Therefore,
you can only use a literal or constant value to specify the number of elements.
• You can use an initialization list to set the starting values for the elements of an
array. If you don’t, no starting values will be assigned.
• You can use the subscript operator ([]) to get and set elements in an array by
specifying the element’s index.
• You can use a range-based for loop, a traditional for loop, or a while loop to loop
through the elements in an array.
• An array allows out of bounds access. To avoid this, you can use a range-based for
loop or the size() member function of the array.
The console
The code
tinclude <iostream>
#include <array>
int main() {
array<int, len> scores { 0 }; // create and initialize the array
The header file for the list and forward list containers
#include <list>
finclude <forward_list>
Description
• The elements of both the list and the forward list are stored in non-contiguous
memory, with pointers linking the elements.
• The elements of a list have pointers that link to the next element and the
previous one. This is called a doubly linked list and it allows the iterator to be
bidirectional.
• The elements of a forward list only have pointers that link to the next element.
This is called a singly linked list and it only allows the iterator to move forward.
• A list never moves its elements. As a result, iterators that point to existing
elements are never invalidated unless you remove the element from the list.
This is the main advantage that lists have over vectors.
• A list can sometimes be more efficient than a vector for inserting or removing
elements in the middle of the sequence. Before you choose a list over a vector,
though, you should do performance testing to make sure the list is more efficient.
Examples
list<int> numsl { 1, 2, 3, 4, 5 };
list<int> nums2 { 5, 6, 7, 8, 9 };
list<int> nums3 { 9, 10 };
numsl.splice(numsl.end(), nums3);
// numsl now contains 123459 10, and nums3 is empty
numsl.reverse();
// numsl now contains 10 !9 5 4 3; 2 1 8 7 6
numsl.sort();
// numsl now contains 1 2 3 4 5 6 7 8 9 10
nums2.sort();
nums1.merge(nums 2);
// numsl now contains 1 2 3 4 5 5 6 7 8 9 9 10, and nums2 is empty
nums1.remove(5);
// numsl now contains 1 2 3 4 6 7 8 9 9 10
Description
• The spliceO and merge() functions don’t make copies of their elements.
Instead, they transfer elements by rearranging pointers. As a result, after these
functions run. the elements passed to them are no longer in the initial list.
The console
The Movie Rankings program
Movie Rankings
1 - Casablanca
2 - Wonder Woman
3 - The Godfather
4 - E.T.
5 - The Bridge on the River Kwai
Movie Rankings
1 - Casablanca
2 - The Godfather
3 - E.T.
4 - Wonder Woman
5 - The Bridge on the River Kwai
The code
tinclude <iostream>
#include <iomanip>
#include <string>
*include <list>
Part 2 of figure 10-14 shows the definitions of the three functions called by
the main() function. The get_choice() function, which asks the user if they want
to change the ranking for a movie, uses the same code as the Movie Rankings
1.0 program described earlier in this chapter. However, most of the code in the
other two functions is different from the code in the 1.0 version.
After it displays an underlined heading for the rankings, the
display_rankings() function uses a range-based for loop to iterate the
list instead of a traditional for loop like the 1.0 version. That’s because
you can’t use the subscript operator to access the elements in a list.
In addition, this loop uses a counter variable named i. Then, within
the body of the loop, this code uses the element value and the counter
variable to display each movie’s ranking and title.
Like the 1.0 version, the change_ranking() function starts by checking that
the current and new rankings entered by the user are in bounds. The remaining
code, though, is different from the code in the 1.0 version. To start, this code
uses the auto keyword and the begin() member function of the list to get an
iterator that points to the first element in the list. Then, it uses a for loop and the
increment operator (++) to advance the iterator so it points to the element that
contains the current ranking. It must use this operator rather than the addition
operator (♦) because the list uses a bidirectional iterator, and the addition
operator isn't available to this type of iterator.
After this code advances the iterator to the correct element, the code defines
a string variable named movie and uses the indirection operator (*) to get the
movie title for the ranking the user wants to change. Then, it stores this title in
the movie variable and calls the erase() function of the list to remove the movie
title. Since the iterator variable already points to the correct element, this code
passes that iterator to the erase() function.
At this point, the code needs to position the iterator variable so it points
to the correct location for the new ranking. To do that, the code calls the
begin() function again to move the iterator back to the first element. Then, it
uses another for loop and the increment operator to advance the iterator to the
position of the new ranking. Finally, the code calls the insert!) function of the list
to put the movie title back in the list at the correct position for its new ranking.
To do that, this code passes the iterator and the movie variable to the insert!)
function.
As you can see. quite a few changes are necessary to get this program
to work with a list instead of a vector. Most of these changes are necessary
because a list uses a lower-level iterator than a vector. However, modifying the
2.0 version of this program so it works with a vector wouldn't require as many
changes. That’s because a higher-level iterator can work with any code that a
lower iterator can.
This shows that it’s possible to write code that can work with many types
of containers. Of course, if you know that you’re going to use a vector, as you
probably would in this case, it can be much more convenient to just use the
higher-level functionality that its random-access iterator provides.
Chapter 10 How to work with STL containers and iterators 383
char get_choice() (
char choice;
cout « "Do you want to change any rankings? (y/n): ■;
cin >> choice;
cin.ignore(10000, *\n');
return choice;
}
// remove element
movies.erase(iter);
q.popO;
cout << "front ■ ■ « q. front () « ■ and back ■ ■ « q.backO << endl;
// displays "front ■ 2 and back =2"
s.pop();
cout << "top ■ ■ « S.topO « endl;
// displays "top - 1"
end()
Description
• The set and multiset store keys in ascending order in non-contiguous memory'.
• When you add or remove a key. the pointers are rearranged to maintain the
sort order but the data itself isn’t moved. Because of that, iterators that point to
existing elements aren’t invalidated.
begin()
Description
• The map and multimap store key/value pairs in ascending order by key in
non-contiguous memory.
• When you add or remove a key/value pair, the pointers are rearranged to
maintain the sort order but the data itself isn’t moved. Because of that, iterators
that point to existing elements aren’t invalidated.
• You use the pair structure to work with the keys and values of a map or
multimap. The pair structure has a data member named first that contains the
key and a data member named second that contains the value.
How to insert, read, and write values with the subscript operator
states("IA") ■ 1946; // adds key/value
states("IA") - 1846; // modifies value
cout << "Iowa entered union in ■ « states("IA"] << endl; // reads value
// states now contains AK/1959 CA/1850 FL/1845 IA/1846 IL/1818 OR/1859 WA/0
// year is 0
Description
• You can use the subscript operator, the insertO member function, or the insert_or_assign()
member function to insert a new key/value pair in a map.
• When you use the insertO function, you can manually code a new pair structure, or
you can use the make_pair() function.
• If you use the insert_or_assign() function and the key already exists, its value is changed.
• You can use the subscript operator to read or modify an existing value by key.
• When you use the subscript operator to access a value by key and the key isn’t in the
map. it’s added with a value that’s the default value of the data type. Because of that,
it’s safer to first use the find() member function to see if a key is in a map.
Figure 10-18 How to insert key/value pairs into a map and find a value by key
392 Section 2 More skills as you need them
The console
The Word Counter program
60 WORDS: it was the best of times it was the worst of times it was
the age of wisdom it was the age of foolishness it was the epoch of
belief it was the epoch of incredulity it was the season of light
it was the season of darkness it was the spring of hope it was the
winter of despair
The code
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <map>
int main() {
cout « "The Word Counter program\n\n";
Part 2 of figure 10-19 shows the definitions of the functions called by the
main() function. First, the load_words() function starts by defining a vector of
strings named words. Then, it defines an input file stream object and opens it by
passing it the filename of the text file.
If the file opens successfully, the code uses a while loop to get each word
in the text file. For each word, this code loops through each character in the
word, discarding punctuation and converting any uppercase letters to lowercase.
When that’s done, this code uses the push_back() function to add the word to the
vector.
After this code adds all of the words in the text file to the vector, it calls the
close() function of the file stream to close the connection to the file. Then, it
returns the vector of words to the calling code.
The get_word_count() function starts by defining a map of string/int pairs
named word_count. Then, it uses a range-based for loop to process all the words
in the vector.
Within this loop, the first statement passes the word from the vector as the
key to the find() function of the map. Then, it compares the iterator returned by
find() to the off-the-end iterator. If they’re equal, that word isn’t yet in the map.
As a result, the code uses the subscript operator to add the key with a count
value of 1. However, if the key is already in the map. the code uses the subscript
operator to retrieve it and increment its associated count value by one. When the
loop finishes, it returns the map of word/count pairs to the calling code.
Chapter 10 How to work with STL containers and iterators 395
if (infile) (
string word;
while(infile >> word) {
string new_word -
for (char c : word) (
if (c — || c -- ■»•) <
continue; // remove punctuation
} else if (isupper(c)) {
new_word += tolower(c); // convert to lowercase
} else (
new_word c;
}
)
words.push_back(new_word); // add word
)
infile.close();
}
return words;
}
Description
• When you nest containers, it’s a best practice to code a space between the closing angle
brackets so older compilers don’t interpret them as the stream extraction operator (»).
Perspective
Now that you’ve completed this chapter, you should have the skills you
need to work with the containers and iterators of the Standard Template
Library. In the next chapter, you’ll learn how to use the STL algorithms to
perform common tasks with the data in a container, such as searching and
sorting the elements in a container.
Terms
Standard Template Library (STL) initialize a vector
container capacity
iterator size declarator
sequence container pointer
associative container doubly linked list
contiguous memory singly linked list
non-contiguous memory first-in. first-out (FIFO) access
container adapter last-in, first-out (LIFO) access
interval key
half-open interval balanced binary search tree
off-the-end iterator key/value pair
dereference an iterator return value optimization (RVO)
indirection operator nested containers
member access operator
Summary
• The Standard Template Library (STL) includes containers that store data and
iterators that allow access to the data.
• The sequence containers store data in sequence by index, and the associative
containers store data by key.
• The array and vector containers store data in contiguous memory, which is a
consecutive block of memory with no gaps.
• The list, forward list, and associative containers store data in non-contiguous
memory, which are unrelated memory locations that may have gaps.
• An iterator allow s you to access and traverse the elements in a sequence.
• The iterators returned by a container’s begin() and end() functions define a
range that starts at the first element and ends one past the last element. This
is called a half-open interval, and can be represented by the mathematical
notation [begin:end).
• The iterator returned by a container’s end() function is sometimes called an
off-the-end iterator.
• There are six types of iterators, and they are hierarchical. Each higher
iterator inherits the operations provided by the low er ones.
Chapter 10 How to work with STL containers and iterators 399
COMMANDS
v - View movie list
a - Add a movie
d - Delete a movie
m - Modify a movie
x - Exit
Command: v
TITLE YEAR STARS
1 Casablanca 1942 5
2 Wonder Woman 2017 4
3 Wizard of Oz 1939 5
Command: m
Number: 2
Enter new number of stars (1-5) for Wonder Woman: 3
Wonder Woman was updated.
Command: v
TITLE YEAR STARS
1 Casablanca 1942 5
2 Wonder Woman 2017 3
3 Wizard of Oz 1939 5
Command: x
Bye!
Country codes: CA MX US
Continue (y/n)?: y
Continue (y/n)?: n
2. Review the main.cpp file to see that it contains the code for a function
named get_choice() as well as the starting code for two functions named
display_codes() and display_country(). In addition, the main() function
defines a map whose key/value pairs contain country codes and names.
3. Add code to the display_codes() function so it displays the country codes as
shown above.
4. Add code to the display_country() function so it gets an iterator for the
element whose key is equal to the code. If the code isn’t found, display an
appropriate message. Otherwise, display the country name as shown above.
Because the map parameter is defined as a constant, you’ll need to use the at()
member function to do that, not the subscript operator.
5. Add code to the main() function that calls the display_codes() function to
display the country codes when the program starts. In addition, add code that
calls the display_country() function to display country names as long as the
user indicates they want to continue.
402 Section 2 More skills as you need them
FILE TEXT: It was the best of times, it was the worst of times,
it was the age of wisdom, it was the age of foolishness, it was
the epoch of belief, it was the epoch of incredulity, it was the
season of Light, it was the season of Darkness, it was the spring
of hope, it was the winter of despair...
WORD COUNT: 60
Examples that use the same algorithm with five different container types
The containers
vector<char> vec { >;
array<char, 5> arr { >;
list<char> 1st ( >;
multiset<char> mst { >;
string str ■ "hello";
The algorithm that counts how many times a value appears in a container
int c;
c ■ count(vec.begin(), vec.end(), •1' ); // c is 2
c ■ count(arr.begin(), arr.end(), •1' ); // c is 2
c ■ count(1st.begin(), 1st.end(), •1' ); // c is 2
c ■ count(mst.begin(), mst.end(), •1' ); // c is 2
c ■ count(str.begin(), str.end(), •1' ); // c is 2
Description
• The STL algorithms don’t perform operations directly on a specific container.
Instead, they perform operations on a half-open, or |b:e), interval that’s defined
by iterators. This provides abstraction between the algorithms and the containers.
• The STL algorithms are commonly used with STL containers. However, they can
also be used with non-STL containers like strings, built-in arrays, and custom
objects.
• As long as an object provides iterators that define a half-open interval, and those
iterators define the operations required by the algorithm, the algorithm can
perform its task.
Description
• When you call an algorithm, you pass it the iterators that define a half open
interval as well as any other arguments the algorithm requires.
• The type of iterator an algorithm requires determines which containers can use it.
• Many algorithms accept a function as an argument. This allows you to
customize how the algorithm performs its operation.
• To pass a function as an argument, you just code the function name. If you
include parentheses and arguments, you are calling the function, not passing it.
Note
• Unless otherwise noted, this chapter presents algorithms in the algorithm header file.
Non-modifying operations
Algorithm Iterator Description
count(b, e, val) Input Returns the number of times that val occurs in
interval [b:e).
for_each(b, e, func) Input Passes each element in interval [b:e) to func.
find(b, e, val) Input Returns an iterator that points to the first occur
rence of val in interval [b:e). Returns the off-the-
end iterator if val isn’t found.
search(b, e, b2, e2) Forward Searches for the first occurrence of the interval
[b2:e2) in interval [b:e) and returns an iterator that
points to the first element. Returns the off-the-end
iterator if the interval isn’t found.
Description
• The non-modifying algorithms don’t change the values or the order of the
elements in an the interval.
• The find() and searchf) algorithms perform a linear search, which means that
they start at the first element and continue in order until they either find what
they’re looking for or reach the end of the interval.
Modifying operations
Algorithm Iterator Description
replace(b, e, val, val2) Forward Replaces the elements in [b:e) that are equal
to val with val2.
fill(b, e, val) Forward Assigns val to every element in (b:e).
copy(b, e, b2) Input Copies the values in [b:e) to the interval
beginning with b2.
remove(b, e, val) Forward Removes the elements in [b:e) that are equal
to val.
Description
• The modifying algorithms change the elements in an interval by adding,
replacing, or removing values.
• The copy() algorithm requires that the container being copied to has at least as
many elements as the interval being copied.
• The remove() algorithm moves the removed elements to the end of the interval
and returns an iterator that points to the first removed element. You can then
pass this iterator to a container’s erase() member function to remove the
elements from the container.
Minimum/maximum operations
Algorithm Iterator Description
min_element(b, e) Forward Returns an iterator that points to the smallest value
in [b:e).
max_element(b, e) Forward Returns an iterator that points to the largest value
in [b:e).
Numeric operations
Algorithm Iterator Description
iota(b, e, val) Forward Fills the interval |b:e) with values that
start with val and are incremented by 1.
accumulate(b, e, val) Input Returns the total of the values in |b:e),
with val as the initial value of the total.
accumulate(b, e, val, func) Input Returns the total of the values in |b:e),
with val as the initial value of the
total. Passes each element to func to
determine how to add it to the total.
Figure 11 -5 How to use the min and max algorithms and the numeric algorithms
414 Section 2 More skills as you need them
Description
• The sort() algorithm requires a random-access iterator, so it can’t be used with a list,
set, or map. However, the list container has a sort() member function, and the map
and set containers are sorted by key by default.
• Like the sort() algorithm, the sort() member function of the list container accepts an
optional function that lets you determine the sort criteria.
• The binary_ searchO algorithm returns a Boolean value, not an iterator to an element.
• The binary_search() algorithm performs a binary search by repeatedly selecting the
midpoint of a sorted interval and discarding the half that doesn’t contain the search value.
The console
The Number Cruncher program
11 RANDOM NUMBERS: 12 25 10 3 0 8 8 1 10 16 15
11 SORTED NUMBERS: 0 1 3 8 8 10 10 12 15 16 25
Sum ■ 108 Average ■ 9 Max ■ 25 Min ■ 0
The code
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <algorithm>
#include <numeric>
int main() (
cout « "The Number Cruncher program\n\n";
sort(numbers.begin(), numbers.end());
cout « numbers.size() « " SORTED NUMBERS: ";
for_each(numbers.begin (), numbers.end(), display_int);
cout « endl;
Description
• When you use the STL algorithms with maps and multimaps, you often need to use
the versions that accept a function argument. This provides a way for you to tell the
algorithm how to work with the data members of the pair structure.
How to count the number of elements in all the vectors in the map
int count_grades(int count, pair<string, vector<double* * p) {
return count + p.second.size();
}
Description
• When you use the STL algorithms with nested containers, you often need to use
the versions that accept a function argument. This provides a way for you to tell the
algorithm how to work with the nested container.
Description
• You can use the template keyword with the typename or class keywords to create
function templates with generic data types. This allows the functions to accept a
variety of data types.
• When you call a function template directly, you call it as you would a regular function.
• When you pass a function template as an argument to an algorithm, you need to
specify the data type(s) in angle brackets (<>).
// overload () operator
bool operator () (pair<string, double> p) const {
return p.second < limit;
}
};
Code that uses the function object with the count_if() algorithm
auto num_low ■ count_if(
products.begin(), products.end(), Under(500.0)); // num_low is 3
num_low ■ count_if(
products.begin(), products.end(), Under(200.0)); // num_low is 1
Description
• A function object, or functor, is an object that you can pass as a function argument.
This works because the object overloads the function call operator (()) to define
the function that’s called when the object is passed to a function.
• Function objects let you avoid writing multiple functions for varying search criteria.
• A function object usually has a constructor that lets you set the value of one or
more data members. You’ll learn more about constructors in chapter 14.
Description
• Lambda expressions allow you to code functions inline or store them in variables. A
lambda expression can capture external variables to use in its body.
• You can use the auto keyword to allow a lambda to accept multiple data types.
The console
Processing regional uptimes in uptimes.txt...
Central:
Avg uptime - 98.73*
Daily: 100.0* 99.9* 100.0* 98.2* 99.4* 96.9* 96.7*
Sorted: 96.7* 96.9* 98.2* 99.4* 99.9* 100.0* 100.0*
Eastern:
Avg uptime - 98.66*
Daily: 96.6* 98.5* 100.0* 100.0* 98.2*
Sorted: 96.6* 98.2* 98.5* 100.0* 100.0*
Mountain:
Avg uptime - 98.75*
Daily: 96.7* 100.0* 97.8* 99.7* 98.3* 100.0*
Sorted: 96.7* 97.8* 98.3* 99.7* 100.0* 100.0*
Western:
Avg uptime - 97.59*
Daily: 99.8* 99.3* 100.0* 89.0* 99.4* 98.9* 96.7*
Sorted: 89.0* 96.7* 98.9* 99.3* 99.4* 99.8* 100.0’
The code
#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <numeric>
int mainf) (
const string filename ■ "uptimes.txt";
cout « "Processing regional uptimes in " « filename « "...\n\n";
auto uptimes ■ load_uptimes(filename);
if (uptimes.empty()) {
cout << "No data or unable to open file.\n\n";
}
else <
process_uptimes(uptimes) ;
}
}
templatectypename T>
void display(T value) {
cout « value << ■* ■;
}
Part 2 of figure 11-13 shows the remaining two function definitions. The
load_uptimes() function returns a map of string/vector pairs. Remember, it’s
efficient to return an STL container from a function because most compilers
move the data to the calling code rather than copying it.
Within the function, the code starts by declaring the map that stores the
uptime data. Then, the function creates an input file stream object named datafile
and opens it by passing it the filename parameter. Next, this code checks whether
the file was opened successfully.
If the file was opened successfully, the code uses a while loop to get each
line from the file. Then, it uses that line to create a string stream object named
row. Next, the code stores the first value in the row in a string variable named
region, and it uses a while loop to add the rest of the values in the row to a vector
of double values named times. After this code extracts all the values in the string
stream, it uses the subscript operator to add the region and vector of uptimes to
the map. with the region as the key and the vector of uptimes as the value.
Once this code processes all the lines in the text file, it closes the input file
stream object and returns the map to the calling code. However, if the file stream
doesn’t open successfully, this code returns an empty map.
The process_uptimes() function accepts a map of string/vector pairs by
constant reference. Within this function, the code starts by using a range-based
for loop to iterate through the pairs in the map. Within the loop, the first state
ment uses the first data member of the current pair object to get the region, and
the second statement uses the second data member to get the vector of double
values. Then, the third statement uses the accumulatef) algorithm to total all the
values in the vector and store the result in a variable named sum.
After getting the total for the region, this code displays the name of the
region. Then, it calculates and displays the average uptime for that region. Next,
it uses the for_each() algorithm and the templated displayO function to display
the daily uptimes for the region. Finally, it sorts the uptimes with the sort()
algorithm and uses the for_each() algorithm and the templated displayO function
again to display the sorted uptimes for the region.
When the loop completes, the code calculates the average uptime across
all regions. To do that, it needs to calculate the sum of all the uptimes in all the
vectors in the map. and it needs to get a count of all the uptimes in all the vectors
in the map. This code uses the accumulate!) algorithm with a lambda expression
for both of these tasks.
Both lambda expressions pass an accumulator variable and a pair object to the
body of the expression. Then, the first lambda expression uses the accumulate!)
algorithm again to get the total of all the uptimes in the vector, adds this total to
the accumulator variable, and returns that variable. The second lambda expression
adds the size of the vector of uptimes to the accumulator variable and returns that
variable. Once the code calculates the total and count of all uptimes, it calculates
and displays the average of all uptimes.
Chapter 11 How to work with STL algorithms 433
if (datafile) (
string line;
while (getline(datafile, line)) { // for each line in file
istringstream row(line); // create string stream
sort(times.begin(), times.end());
cout << "\n\tSorted: ";
for_each(times.begin(), times.end(), displaysdouble>);
cout << endl;
}
Perspective
Now that you’ve completed this chapter, you should understand how to
use the STL algorithms to work with STL containers and string objects. That
includes how to pass functions to algorithms. In the next chapter, you’ll learn
how to use STL algorithms with built-in arrays. In addition, you’ll learn how
to write your own algorithms for searching and sorting built-in arrays. Finally,
in chapter 19, you’ll learn how to write your own algorithms for working with
STL containers.
Terms
Standard Template Library (STL) binary search
container function template
iterator generic data type
algorithm function object
abstraction functor
non-modifying algorithm constructor
modifying algorithm lambda expression
linear search capture specifier
Summary
• The Standard Template Library (STL) contains three types of objects: containers
that store data, iterators that allow access to the data, and algorithms that perform
tasks with the data.
• To provide abstraction between the algorithms and the containers, the algorithms
perform operations on a half-open interval rather than directly on a specific
container.
• The find() and search() algorithms perform a linear search that starts at the first
element and continues sequentially until it finds the search value or reaches the
end of the interval.
• The binary_search() algorithm performs a binary search that selects the midpoint
of a sorted interval and discards the half that doesn’t contain the search value.
This continues until the search value is found or the end of the interval is
reached.
• You can use the template keyword to create function templates with generic data
types. This allows a function argument to accept a variety of data types.
• A function object, or functor, is an object that you can pass as a function argument.
• A function object usually has a constructor that lets you set the value of one
or more data members of the object.
• A lambda expression allows you to code functions inline or store them in variables.
• A lambda expression can use a capture specifier to capture external variables
to use in its body.
Chapter 11 How to work with STL algorithms 435
60 WORDS: it was the best of times it was the worst of times it was
the age of wisdom it was the age of foolishness it was the epoch of
belief it was the epoch of incredulity it was the season of light
it was the season of darkness it was the spring of hope it was the
winter of despair
8 DUPLICATE WORDS
t t t t t t t
Element 0 Element 1 Element 2 Element 3 Element 4 Element 5 Element 6
Description
• A built-in array contains a sequence of elements of the same data type. It stores
the elements in contiguous memory, or consecutive blocks of memory with no
gaps.
• The number of elements in an array is fixed and must be set at compile time.
Therefore, you can only use a literal or constant value to specify the number of
elements.
• Each element in an array has an index, where the first element has an index of
0. the second an index of 1, and so on. To access an individual element, you use
the subscript operator ([]) to specify the element’s index.
Description
• You can use an initialization list to set the starting values of an array. When you do, both
the assignment operator and the size declarator are optional. If you don’t supply the size
declarator, the compiler sizes the array based on the number of values in the list.
• The first value in an initialization list is assigned to the first element in the array, the
second value is assigned to the second element, and so on.
• If the initialization list doesn’t specify a value for every element, the rest of the
elements are initialized to the default value for the data type.
• You can use a for loop or a while loop to iterate the elements in an array. Like a vector,
an array allows owz of bounds access. To avoid that, you can use a constant to define the
size of an array and use that constant value to prevent out of bounds access.
• You can also use a range-based for loop with an array. However, this type of loop
doesn’t work correctly with arrays that are passed to functions. As a result, it’s more
common to use traditional for loops with arrays.
display_array(nums, size);
}
The console
The array BEFORE passing to function: 20 bytes.
The array AFTER passing to function: 8 bytes.
The elements: 12345
Description
• To create a function that accepts an array, you code the subscript operator ([ ]) after
the parameter name. This subscript operator should not include a size declarator.
• An array isn’t passed to a function by value. Instead. C++ converts it to a
pointer to the first element in the array. A pointer contains the memory address
of a value, not the value itself.
• This conversion to a pointer is known as array decay, because information
about the array, including its size, is lost.
• You can still access the elements of the array with the subscript operator ((]).
However, to avoid out of bounds access, you need to know the number of elements.
That’s why it’s common to pass the number of elements as a separate argument.
• Array decay can also occur when an array variable is used in an expression.
Figure 12-3 How to pass an array to a function
446 Section 2 More skills as you need them
Array decay can also occur when you use an array in an expression. For
instance, the last example defines an array and uses it in a stream insertion
expression. Here, the array decays to a pointer. As a result, this statement
displays the memory address of the first element in the array.
if (equal) (
cout << "The arrays are equal.\n";
}
else (
cout << "The arrays are NOT equal.\n";
}
The console
The arrays are NOT equal.
Description
• Arrays don’t provide relational operators or assignment operators.
• To compare two arrays, you can use a loop to compare the values in each
element of the two arrays.
• To copy an array, you can use a loop to copy the values in the elements of one
array to the elements of another array.
The console
The Test Scores program
Enter score: 89
Enter score: 78
Enter score: 92
Enter score: 88
Enter score: -1
Score count: 4
Score total: 347
Average score: 86.8
The code
tinclude <iostream>
tinclude <cmath>
int main() {
const int capacity ■ 50;
int scores[capacity] ( 0 }; // set each element in array to 0
// initialize variables
int score_count - 0, score - 0;
Part 2 of figure 12-5 starts by defining a while loop that runs as long as two
conditions are true. The first is that the user hasn't entered -1 to end the program.
The second is that the number of scores entered is less than the capacity of the
array. This prevents the user from entering more scores than the array can hold.
Within the loop, the code extracts the score entered by the user from the
standard input stream and checks it. If the extraction failed, the code notifies
the user, clears the input stream, and jumps to the top of the loop. If the score is
within the appropriate range, the code adds it to the array, increments the score
count, and jumps to the top of the loop. If the score is outside the appropriate
range, the code notifies the user and jumps to the top of the loop.
Once the while loop completes, either because the user entered -1 or the
maximum number of scores has been entered, the code checks the score count. If
it’s zero, the user didn’t enter any valid scores. In this case, the code displays an
appropriate message and the program ends.
Otherwise, the score count is greater than zero, so the user entered one
or more valid scores. In this case, the code passes the scores array and the
score_count variable to the calculate_total() function and stores the return value
in a double variable named total. Then, the code uses this value to calculate the
average score. Finally, the code displays the score count, the score total, and the
average score on the console.
After the mainf) function, this code defines the calculate_total() function.
This function starts by defining a double variable named total and initializing
it to zero. Then, it uses the score_count parameter in a for loop that iterates the
array of scores. Within this loop, the code adds each score to the total. When the
loop completes, the function returns the total to the calling code.
As you review this code, note that the code in the main() function that calls
the calculate_total() function stores the int value that’s returned in a double
variable. This allows the statement that follows that calculates the average score
to use decimal division, not integer division.
Chapter 12 How to work with built-in arrays and C strings 451
if (cin.failO) (
cin.clearO; // clear bad input flag
cin.ignore(1000, '\n'); // discard input up to end of line
cout << "Invalid number. Try again.\n";
}
else if (score > 100) (
cout << "Score must be from 0 to 100. Try again.\n";
}
else if (score < -1) {
cout << "Score can't be a negative number. Try again.\n";
}
else if (score > -1) {
scores[score_count] ■ score; // store score in array
++score_count; // increment score count
}
}
cout « endl;
if (score_count == 0) (
cout << "No scores entered.\n\n";
}
else (
// calculate total and average scores
double total - calculate_total(scores, score_count);
double average ■ total I score_count;
average - round(average * 10) I 10;
return 0;
}
An introduction to C strings
A C string, also known as a C-style string, is a built-in array of characters
that ends with a null terminator, also known as a null character. This null termi
nator is the ASCII code 0. When working with null terminators, it’s important to
understand that the ASCII code 0 is not the same as the character literal'()', which
is ASCII code 48. Instead, to specify a null terminator, you can use the character
literal AO'.
The first example in figure 12-6 shows two ways to create a C string. First,
you can code a char array with an initialization list whose last value is the null
terminator. Second, you can code a char array and initialize it with a string literal.
Then. C++ automatically adds the null terminator to the end of the array. Either
way, the compiler infers the size of the array from the initialization list or the
string literal. In other words, you don’t need to include a size declarator.
The second example shows that it’s possible to code a partially filled C
string. This example includes a size declarator of 15 and a string literal with five
characters. The graphical representation below this example shows that this code
produces a C string with 15 characters and that all elements after the first five
characters contain the null terminator.
An empty string is a C string whose elements are all initialized with the null
terminator. This is similar to initializing the elements of an int array with the
default value for the int type, which is 0. The third example shows three ways
to code an empty C string. To do that, you can code an empty string literal, an
initialization list that contains ASCII code 0 (no single quotes), or an initializa
tion list that contains the character literal for ASCII code 0 (single quotes).
When you’re working with string literals, you don’t need to code the null
terminator value in your code. However, the fourth example shows how to make
sure it’s there. To start, this code uses the sizeofO operator to show that a C
string of 5 characters uses 6 bytes: 1 byte for each character plus 1 byte for the
null terminator. Then, this code uses a loop to display the ASCII code for each
character, including the null terminator, on the console.
The fifth example shows two char arrays that aren’t C strings. Remember, if
a char array doesn’t end with a null terminator, it’s not a C string. Here, the first
array isn’t initialized so it doesn’t contain any values, and the initialization list
for the second array doesn’t include a null terminator.
Chapter 12 How to work with built-in arrays and C strings 453
Code that shows the extra element for the null terminator
char greeting[] - "hello"; // 5 characters
int num_bytes - sizeof(greeting); // num_bytes is 6
for (int i ■ 0; i < num_bytes; ++i) {
cout << static_cast<int>(greeting[i]) << ' ’; // display ASCII code
}
cout << endl;
The console
104 101 108 108 111 0
Description
• A string is a sequence of characters. In previous chapters, you learned how to use
the string object that’s available from the C++ standard library to work with strings.
• AC string, or C-style string, is a char array that ends with a null terminator, also
called a null character.
• The null terminator is a char that contains the ASCII code 0. You can code it with
the number zero (no single quotes) or with a character literal.
• AC string contains one element for each character in the string, plus an additional
element for the null terminator.
• In C++, string literals are actually C strings, not string objects.
Description
• The stream insertion operator (<<) can write a C string to an output stream.
• The stream extraction operator (>>) can read characters from an input stream
and automatically add a null terminator to create a valid C string.
• If you know the number of characters a string will have, you should size the C
string for that number of characters, plus one for the null terminator. Otherwise,
you can size the C string for the maximum number of allowable characters,
plus one for the null terminator.
• If a user enters more characters than a C string can hold, the program can crash.
To prevent this, you can use the getlineO member function of the input stream.
• When using the getlineO member function of an input stream, the failbit is set if the
user enters more characters than specified in the second argument. However, you
can use the clear() and ignoreO functions of the input stream to reset the stream.
Description
• Like other arrays, you can’t use assignment or relational operators with C strings.
However, the cstring header provides several functions for these types of operations.
• The strncat() and strncpyO functions don’t automatically adjust to account for the null
terminator. Instead, you need to write code that manually adjusts for the null terminator.
• To use the stmcat() and strncpyO functions with Visual StudioAMSVC, you may need to
disable Security Development Lifecycle (SDL) checks. To do that, display the Property
Pages dialog (Project->Properties), expand the C/C++ group, select the General item,
and set the SDL checks property to No.
Beyond the functions presented in this figure, the cstring header also
provides functions named strcat!) and strcpy(). These functions work similarly to
stmcat() and stmcpyO functions, but they don’t accept a numeric value to limit
the number of characters in the resulting C string. This is why these functions
don’t have an ‘n’ in their names. Using these functions is dangerous because you
can end up overwriting the null terminator with a character, thus transforming
a C string into an ordinary char array. Because this can lead to problems, it’s
generally considered a better practice to use the stmcat() and stmcpyO functions.
Description
• You can use the strlen() function to determine the number of characters in a C string.
• You can use the null terminator to determine when you’ve reached the end of a C string.
• When coding a function that accepts a C string as an argument, you don’t need to
include a parameter for the string length because you can get that information from the
C string.
The console
Create Account
Hi Grace,
Thanks for creating an account!
The code
tinclude <iostream>
tinclude <cstring> // for C string utility functions
// function declarations
void get_name(char first_name[], char full_name[], int size);
void get_password(char password!], int size);
void trim_leading_whitespace(char cstring!]);
int find_space(char cstring!]);
void capitalize_initial_letter(char cstring!]);
void reset_stream();
int main()
<
cout « "Create Account\n\n";
const int size - 51; // space for 50 characters plus null terminator
char full_name[size] ■ first_name[size] ■ password[size] -
return 0;
>
Part 2 of figure 12-10 presents the first three function definitions. The
get_name() function starts with a while loop that runs until the break keyword
stops it. This technique allows the function to continue to ask users to try again if
they enter invalid data.
Within the loop, the code asks the user to enter a full name and displays
the maximum number of characters allowed. To get this maximum number of
characters, the code subtracts 1 from the size variable. This reserves space for the
null terminator.
After prompting the user, the code calls the getline() function of the cin
object and passes it the full_name parameter and the size variable. Then, this
code handles the situation that occurs if the user enters too many characters by
calling the reset_stream() function that’s defined in part 3 of this figure.
Next, the code trims any leading whitespace, such as spaces and tabs, from
the beginning of the name. To do that, it passes the full_name variable to the
trim_leading_whitespace() function.
After trimming any leading whitespace, the code gets the first name from the
full name entered by the user. To do that, it passes the full_name variable to the
find_space() function to get the index of the first space in the string. If the index
is -1, there’s no space. As a result, the code asks the user to enter a full name,
and the loop repeats.
However, if the C string contains a space, the code uses the strncpyO
function to copy the characters of the first name from the full_name variable to
the first_name variable. To do that, this code passes the index of the space after
the first name to the strncpyO function so the copy operation stops at that index.
Then, the break keyword ends the loop.
The trim_leading_whitespace() function begins by using the isspace()
function to check whether the C string it receives has leading whitespace. If
not. the function ends. However, if the C string does have leading whitespace,
this function defines an int index named i and initializes it to 0. Then, a while
loop runs as long as the character in the C string at the index is a whitespace
character. Within the body of the loop, the index is incremented. This advances
the index to the first non-whitespace character.
After getting the index of the first non-whitespace character, the code
declares a second index named j and initializes it to 0. Then, a second while loop
uses the first index to iterate through the rest of the elements of the C string.
Within the body of this loop, the first statement copies the character at the first
index to the second index. This shifts the character to the left by the number of
whitespace characters that the function skipped. Next, the code increments both
the i and j variables.
When this second loop ends, the code adds the null terminator to the end of
the cstring variable. At this point, there may be non-null characters after the null
terminator, but they don’t prevent the C string from working correctly with the
other functions for this program.
The find_space() function starts by setting a Boolean flag to false. Then, it
uses the strlen() function to loop through each character in the string. When it
finds a space, it sets the flag to true and exits the loop. Finally, the code checks
the flag to determine whether a space was found. If so, it returns the index of the
space. Otherwise, it return a value of -1 to indicate that no space was found.
Chapter 12 How to work with built-in arrays and C strings 463
trim_leading_whitespace (full_name);
Part 3 of figure 12-10 presents the last three function definitions. The
get_password() function starts with a while loop that runs until the break
keyword stops it. Within the loop, the code asks the user to enter a password
and displays the maximum number of characters allowed.
After prompting the user to enter a password, this code calls the getline()
function of the cin object and passes it the password parameter and the size
variable. Then, the code handles the situation that occurs if the user enters too
many characters by calling the reset_stream() function.
Next, the code validates the password entered by the user. To start, it uses
the strlen() function to get the number of characters in the password. Then, it
uses that value in a conditional expression that sets the a Boolean flag named
hasjength to true or false. If the password is less than 8 characters, the flag is set
to false. Otherwise, it's set to true.
The code also checks that the password contains at least one number. To do
that, it defines a Boolean flag named has_number and initializes it to false. Then,
it uses a for loop to pass each character in the C string to the isdigit() function.
If that function returns true, the code sets the has_number flag to true and exits
the loop. However, if isdigit() never returns true, the flag remains set at its initial
value of false when the loop ends.
Finally, the code checks that the password contains at least one special
character. To do that, it defines a Boolean flag named has_special_char and
initializes it to false. Then, it uses a for loop to pass each character in the C
string to the ispunct() function. If that function returns true, the code sets the
has_special_char flag to true and exits the loop. However, if ispuncfl) never
returns true, the flag remains set at its initial value of false when the loop ends.
After all three Boolean flags have been set, the code tests these flags to check
w hether the user has entered a password that is at least 8 characters long and
contains a number as well as a special character. If so. the code exits the outer
w hile loop, which exits the function. Otherwise, the code notifies the user of the
password criteria and the w hile loop repeats, w hich prompts the user to enter
another password.
The capitalize_initial_letter() function starts by using the toupper!) function
to capitalize the first character in the C string that’s passed to it. Then, it uses a
for loop to make sure the remaining characters are lower case. To do that, it uses
the strlen() function to get the number of characters in the C string, and it uses
this value to determine the number of times the for loop iterates. This for loop
starts at the index of 1. w hich is the second character in the C string. As a result,
the loop iterates through all characters in the C string, starting at the second
character. Within the for loop, the code uses the tolower!) function on each
character.
The reset_stream() function starts by using the fail!) member function of
the standard input stream to check the state of the stream. If the failbit is set,
the function resets it by calling the stream’s clear() function. Then, it uses the
stream’s ignore!) function to discard any remaining characters in the console
input stream.
Chapter 12 How to work with built-in arrays and C strings 465
void reset_stream() {
if (cin.failO) (
cin.clear();
cin.ignore(10000, '\n');
}
}
Description
• A linear search algorithm searches the elements in a sequence in ascending order
until it finds the search value.
• A binary search algorithm divides the elements in a sequence in half, determines
which half the search element is in. and discards the other half. It repeats this
process until it finds the search element. For this to work, the elements in the
sequence must be sorted.
• A binary search can be more efficient than a linear search, but there’s a cost if the
list must be sorted first. If the list is not sorted, and you’re only searching once,
use a linear search. If the data is already sorted, or if you’re going to perform
multiple searches after sorting, use a binary search.
A function that uses the bubble sort algorithm to sort an array of ints
void bubble_sort(int arr[], int size) {
bool is_swap;
int temp;
do (
is_swap false; // reset swap flag each iteration
for (int i 0; i < (size - 1); ++i) ( // stop at 2nd to last
if (arr[ij > arr[i + 1] ) ( // if current greater than next
temp - arr[i]; // swap current and next values
arr[i] - arr[i ♦ 1] ;
arr[i + 1] - temp;
is_swap - true; // set flag to show swap occurred
}
}
} while (is_swap); // if a swap occurred, continue loop
}
The swaps that occur at each iteration of a bubble sort
Initial sequence: (2 7 5 1)
First iteration: 2 swaps (2 5 7 1) then (2517)
Second iteration: 1 swap (2 1 5 7)
Third iteration: 1 swap (1 2 5 7)
Fourth iteration: no swaps - end loop
A function that uses the selection sort algorithm to sort an array of ints
void selection_sort(int arr(], int size) (
int min_index, min_value;
for (int i ■ 0; i < (size - 1); ++i) ( // stop at 2nd to last
min_index ■ i; // get index of current element
min_value - arr[i); // get value of current element
Description
• A bubble sort algorithm loops through a sequence, comparing adjacent elements and
swapping values as needed. It loops until it doesn’t need to swap any more elements.
• A selection sort algorithm loops through a sequence once, moving each element to
its final position. A selection sort can be more efficient for large sequences.
How to use pointer arithmetic to get a pointer to one past the last element
auto end - prices + size;
Description
• To use an STL algorithm, you need iterators that define a [beginzend) interval.
• With a built-in array, you can use array decay to get a pointer to use as the begin
iterator. Then, you can use pointer arithmetic to get a pointer to use as the end iterator.
Description
• A one-dimensional (ID) array holds one set of data, such as all the test scores for
a single student.
• A two-dimensional (2D) array holds multiple sets of data, such as all the test scores for
multiple students. 2D arrays can be thought of as a table of rows and columns.
• You use two subscripts to access elements in a 2D array and nested loops to iterate them.
• When you use initialization lists, it’s best to separate the sets with inner brackets.
• A 2D array of C strings is like a 1D array unless you need to access individual characters.
Figure 12-14 How to work with a two-dimensional array
474 Section 2 More skills as you need them
// function declaration
void display_scores(double scores[][cols]);
int main() {
double scores[rows][cols] - { (88,87,91,86},
(93,96,89,95),
(82,77,79,83) );
display_scores(scores);
)
// function definition
void display_scores(double scores[][cols]) {
for (int row ■ 0; row < rows; ++row) {
double total ■ 0;
cout << "Student " « (row ♦ 1) « ■;
for (int col ■ 0; col < cols; ++col) {
cout « scores[row][col] « '
total +■ scores[row][col] ;
)
cout << "\tAvg score: " « (total / cols) « endl;
)
)
The console
Student 1: 88 87 91 86 Avg score: 88
Student 2: 93 96 89 95 Avg score: 93.25
Student 3: 82 77 79 83 Avg score: 80.25
Description
• When you code a function parameter that accepts a two-dimensional array, you
need to include a size declarator for the second subscript.
• When you code a multi-dimensional array, you need to include a size declarator
for ever}' subscript except the first one.
• Any required size declarator must be known at compile time. Because of that,
it must be a literal value or a constant. If it’s a constant, it should be a global
constant so the code that defines the array and the function that accepts the
array can both use it.
The console
What kind of items are going to be in your list? cities
Amsterdam
Cologne
New York
Portland
Victoria, B.C.
The code
ftinclude <iostream>
#include <cstring>
// global constant
const int item_length ■ 51;
// function declarations
void alpha_sort(char items[][item_length], int size);
void reset_stream();
int mainf) (
// create 2D array to hold C strings from user
const int size ■ 5;
char list[size][item_length];
char itemtype[item_length] ■
cout << "What kind of items are going to be in your list? ■;
cin.getline(item_type, item_length);
reset_stream();
cout « endl;
Part 2 of figure 12-16 begins by passing the 2D array and the local size
constant to the alpha_sort() function. Then, it uses a loop to display the sorted C
strings on the console.
After the main() function, this code defines the alpha_sort() function. Like
the function’s declaration, the definition uses the global constant item_length as
the size declarator for the second subscript operator.
The body of the alpha_sort() function uses the bubble sort algorithm to sort
the C strings in the two-dimensional array named items. This is similar to the
bubble sort function presented in figure 12-12. However, since this function
works with C strings rather than integers, the temp variable that temporarily
stores elements is a C string. In addition, the code uses utility functions from the
cstring header to compare and swap the C string elements. Remember, though,
that these utility functions don't automatically adjust for the null terminator like
the getlineO function does. Because of that, this code manually adjusts for the
null terminator by subtracting 1 from the item_length global constant.
As you review this code, take a moment to look at how it swaps the
elements. To start, it uses the strcmpO function to compare the current element
to the next element. Remember, strcmpO returns 1 if the first argument is greater
than the second argument. So. this code checks if the current element is greater
than the next. If it is, the code executes the body of the if statement.
In the body of the if statement, three code statements use the stmcpyO
function to swap the current element and the next element. The first statement
copies the characters from the current element to the temp variable. The second
statement copies the characters from the next element to the current element.
And the third statement copies the characters from the temp variable to the next
element.
The definition for the reset_stream() function starts by using the fail()
function of the standard input stream to check the state of the stream. If the
failbit is set, the function clears it and discards any remaining characters in the
stream.
Chapter 12 How to work with built-in arrays and C strings 479
void reset_stream() {
if (cin.failO) (
cin.clear();
cin.ignore(10000, '\n');
}
}
Note
• To get this program to compile with Visual Studio, you may need to set the
SDL checks property for the project to No as described in figure 12-8.
Perspective
Now that you’ve completed this chapter, you should have a good idea of
how to work with the built-in array, including how to work with C strings. In
most cases, you shouldn’t need to use built-in arrays or C strings. Still, it’s good
to understand how they work in case you need to work in a resource constrained
environment, or you need to maintain legacy code.
Terms
built-in array null character
contiguous memory empty string
index algorithm
subscript operator linear search
initialization list binary search
size declarator bubble sort
out of bounds access selection sort
pointer pointer arithmetic
memory address one-dimensional (1D) array
array decay two-dimensional (2D) array
string table
C string rows
C-style string columns
null terminator multidimensional array
Summary
• A built-in array contains a sequence of elements of the same data type. It
stores the elements in contiguous memory, or consecutive blocks of memory
with no gaps.
• Each element in an array has an index, where the first element has an index
of 0, the second an index of 1. and so on.
• To access an individual element, you use the subscript operator ([ ]) to
specify the element’s index.
• You can use an initialization list to set the starting values of an array.
• When you create an array, you can use a size declarator to specify the size of
an array.
• Like a vector, an array allows out of bounds access. To avoid this, you can
use a constant to define the size of an array and use that constant to prevent
out of bounds access.
• When you pass an array to a function. C++ converts it to a pointer that points
to the memory address of the first element in the array, not to the value itself.
This is known as array decay, because information about the array is lost.
• A string is a sequence of characters.
Chapter 12 How to work with built-in arrays and C strings 481
Sales count: 4
Sales total: 14466.6
Average sales: 3616.65
2. Review the code for this program and then run it to see that it works much
like the Test Scores program.
3. Define a constant that can be used as the size declarator for an array that can
store up to twelve elements.
4. Replace the vector named sales with an array, and initialize the elements of
the array with the default value for a double type.
5. Define a variable named sales_count that can be used to count the number of
sales entries, and initialize this variable to 0.
6. Modify the while loop so it won’t accept more sales entries than can be stored
by the array.
7. Modify the code that's executed for a valid sales entry so the sales amount
is stored in the next element in the array and the sales_count variable is
increased by one.
8. Modify the code that checks if sales were entered so it works for the modified
code.
9. Modify the code that calculates the average sales and displays the number of
sales entries so it uses the sales_count variable.
Chapter 12 How to work with built-in arrays and C strings 483
2. Define two constants that can be used as the size declarators for a two-dimensional
array. Assign values to these constants so they will accommodate the data that’s
currently stored in the words vector.
3. Replace the words vector with a two-dimensional array of C strings.
4. Modify the code that gets a random word from the array so the word is stored
in a C string. To do that, you can use a loop to assign each character of the
randomly selected word from the two-dimensional array to the C string.
5. Modify the code that jumbles the word so the jumbled word is stored in a C
string. To do that, you can use the strncpyO function to copy the randomly
selected word to the jumbled word.
6. Modify the code that gets the user guesses so the guess is stored in a C string and
the strcmpO function is used to compare the guess to the other strings.
484 Section 2 More skills as you need them
Calling code that uses the return value to check for an error
double mpg - calculate_mpg(100, 0);
if (mpg — -1.0) {
cout « "Error! Unable to calculate miles per gallon\n\n";
} else {
cout « "Miles per gallon: ■ « mpg « "\n\n";
)
The console
Error! Unable to calculate miles per gallon
Description
• Before exceptions were available to C++, it was common for a function to
signal that an error had occurred by returning a value such as -1 or NULL that
represented an error code. Then, the calling code could check the return value
and proceed accordingly. Unfortunately, this practice made it easy for other
programmers to use a function incorrectly.
Description
• An exception is an object that represents an error that has occurred and contains
information about that error. In most cases, you create an exception from one of
the classes in the exception hierarchy.
• You use the throw statement to throw an exception. You can throw any object,
but it usually makes sense to throw objects created from one of the predefined
exception classes.
• The predefined exception classes all have a constructor that allows you to
specify an error message for the object that you’re creating.
• In the hierarchy of the predefined exceptions, the exception class is the parent
class of the other exception classes. These classes in turn can be referred to
as child classes of the exception class, but they can also be the parent class of
other more specific exception classes.
Description
• In a try statement, you code a try' clause that contains any statements that might
throw an exception. Then, you code a catch clause that catches and handles any
exceptions that have been thrown. This is known as exception handling.
• If you code the exception class as the exception type in the catch clause, the
catch clause catches all types of exceptions that can occur.
• When an exception occurs, C++ skips any remaining statements in the try'
clause and executes the statements in the catch clause.
The console
The code
tinclude <iostream>
tinclude <cmath>
void display_title() (
cout « "Miles Per Gallon Calculator\n\n" ;
)
int main() {
display_title();
double miles;
cout « "Enter miles driven: ";
cin >> miles;
double gallons;
cout « "Enter gallons of gas used: ";
cin >> gallons;
double mpg;
try {
mpg ■ calculatempg(miles, gallons);
cout << "Miles per gallon: ■ « mpg « "\n\n";
>
catch (const invalid_argument& e) (
cout << e.whatO « "\n\n";
|
cout « "Bye!\n\n";
return 0;
)
The console
Miles Per Gallon Calculator
Bye!
The code
ftinclude <iostream>
#include <cmath>
#include "console.h" // the console namespace from chapter 7
void display_title() {
cout << "Miles Per Gallon Calculator\n\n" ;
}
int main() (
display_title();
// exit program
cout << "Bye!\n\n";
return 0;
}
Description
• When a try statement includes multiple catch clauses, the clauses for excep
tions within the same branch of the exception hierarchy must be coded in
sequence starting with the most specific exception and ending with the least
specific exception.
char choice;
cout << "Would you like to create a new file? (y/n): ";
cin >> choice;
cout << endl;
if (choice ■■ 'y') {
cout << "OK. You can add temperatures now.\n";
}
else (
cout << "Please make sure the file exists and try again.\n"
<< "You may need to specify a full path to file.Xn";
throw;
}
}
}
Description
• In some situations, you may need or want to code a catch clause that catches
an exception, performs some processing for that exception, and rethrows the
exception.
COMMANDS
v - View temperatures
a - Add temperatures
r - Remove temperatures
x - Exit
Command:
display_menu();
Description
• To catch all exceptions, even if they aren’t part of the predefined exception
hierarchy, you can code a catch clause with three dots within the parentheses.
Description
• The call stack lists the functions that have been called in reverse order.
• When a function encounters a condition it can’t handle, it should throw an
exception. This allows other programmers to handle the exception in a way
that’s appropriate for their programs.
• When a function calls another function that may throw an exception, the
function can catch the exception and handle it, or it can allow the exception to
continue further up the stack trace.
• When an exception is thrown, the runtime system looks through the call stack
until it finds a function that catches and handles the exception.
• If an exception is thrown and isn’t caught and handled somewhere in the
program, the runtime system aborts execution of the program. This causes the
program to crash.
Perspective
In this chapter, you have learned some of the best practices for handling
exceptions in C++. As a result, you should now be able to write code that
handles exceptions so your programs don’t crash. And that’s what you need to
do in a professional program.
At this point, you’re ready to read section 3 to learn how to develop
object-oriented programs. When you’re done with that section, you’ll have a
better understanding of how an exception class defines an exception object and
how the hierarchy of exception classes works. In addition, you’ll learn how to
define a custom exception class by inheriting one of the classes in the excep
tion hierarchy. In most cases, that’s preferable to using a structure to define an
exception as shown in this chapter.
Terms
error throw an exception
error code catch an exception
exception exception handling
parent class rethrow an exception
child class call stack
Summary
• In the early days of C++, it was a common practice for a function to signal
that an error had occurred by returning an error code such as -1 or NULL.
• An exception is an object that represents an error that has occurred and contains
information about that error. In most cases, you create an exception from one of
the classes in the exception hierarchy.
• In the hierarchy of predefined exceptions, the exception class is the parent class
of the other exception classes. These classes can be referred to as child classes of
the exception class, but they can also be the parent class of other child classes.
• You can use the throw statement to throw an exception object.
• In a try statement, you code a try clause that contains any statements that might
throw an exception. Then, you code a catch clause that catches and handles any
exceptions that have been thrown. This is known as exception handling.
• Sometimes, you need to code a catch clause that catches an exception,
performs some processing for that exception, and rethrows the exception.
• The call stack lists the functions that have been called in reverse order. When an
exception is thrown, the runtime system looks through the call stack until it finds a
function that catches and handles the exception.
Chapter 13 How to work with exceptions 507
Convert Temperatures
MENU
1. Fahrenheit to Celsius
2. Celsius to Fahrenheit
Object-oriented
programming
So far in this book, you have learned how to create objects from the classes
that are available from the C++ standard library, like the string and vector
classes. In addition, you have learned how to organize your programs into a
series of functions that process the data of your program. This is known as
procedural programming.
In the next three chapters, you’ll learn how to define your own classes
and use objects created from those classes. This allows you to group the
data for your programs and the procedures for operating on that data into
an object. This is known as object-oriented programming, and it can speed
the development of large programs and make your code more reusable.
To start, chapter 14 presents the basic skills for defining and using
classes. That includes using the fundamental concept of encapsulation.
Then, chapter 15 shows you how to work with two more fundamental
concepts of object-oriented programming: inheritance and polymorphism.
Finally, chapter 16 presents some additional object-oriented programming
skills, including how to work with static members and functions and how
to overload operators.
Because chapter 14 presents the basic skills for using object-oriented
programming, it’s a prerequisite to chapters 15 and 16. How ever, chapters
15 and 16 aren't prerequisites for the chapters in section 4. If you w ant
to. then, you can go on to the chapters in section 4 after reading chapter
14. But. at some point, you’ll w ant to be sure to read chapters 15 and 16
because they present critical object-oriented programming skills.
14
An introduction to object-oriented
programming
When you use object-oriented programming (OOP), you group related
variables and functions into data structures called objects. In chapter 9. for
example, you learned how to define structures that use data members to store the
data of an object. In addition, you learned how to code member functions that
work with the data members of an object.
A Movie structure
struct Movie (
string title ■ ""; // data member
int year ■ 0; // data member
bool equals(const Movie&); // member function declaration
);
Code that creates an object from the Movie structure and uses it
Movie movie; // create Movie object
movie.title - "Roundhay Garden Scene" // set title
movie.year ■ 1776 // set year - invalid - too old
Description
• Object-oriented programming (OOP) groups related variables and functions
into data structures called objects.
• A structure can be thought of as a blueprint from which objects are created.
• The data members of a structure store the data of an object.
• The member functions of a structure define the tasks that an object can perform.
Often, these functions provide a way to work with the data members of an object.
• Once you create an object from a structure, the object has an identity (a unique
address), a state (the data that it stores), and behavior (the functions that it
provides). As a program runs, an object’s state may change.
• An object is an instance of a structure. In other words, you can create multiple
objects from a single structure. The process of creating an object from a
structure is sometimes called instantiation.
• Encapsulation allows you to hide the members of an object from other code
that uses the object. This is also known as data hiding.
• Like a structure, a class defines the data members and member functions of
an object. However, by default, the members of a class can’t be accessed from
outside the class. As a result, classes are typically used to provide encapsulation.
• Structures work fine for most purposes, but allowing direct access to members
can sometimes lead to problems. To prevent these problems, you can use
classes to provide encapsulation as shown in the next figure.
A Movie class
class Movie {
private: // data members are private
string title ■
int year ■ 0;
public: // member functions are public
void set_title(string);
string get_title();
void set_year(int);
int get_year();
bool equals(const Movieft);
};
Code that creates an object from the Movie class and uses it
Movie movie;
movie.set_title("Roundhay Garden Scene"); // set title
// movie.year - 1776; // illegal - no direct access
// movie.set_year(1776); // invalid - throws exception
movie.set_year(1888); // set year
Description
• An interface allows a programmer to use an object without understanding its
internal code.
• If the interface for an object remains the same, a programmer can change the
source code that implements the object without needing to change or recompile
any of the code that uses the object.
public:
// public members go here
[private:
// you can also code private members here]
);
Description
• To prevent the calling code from directly accessing a data member, define it in
a private section of the class. Then, if you want, you can code public member
functions that provide indirect access to the data member.
• To allow the calling code to directly access a data member, define it in a public
section of the class.
• By default, the members of a class are private until the public keyword is
coded. As a result, you don’t need to code the private keyword at the beginning
of a class. However, it’s generally considered a good practice to include this
keyword to clearly identify all private members.
A setter function
void Movie::set_title(string title_param) {
title ■ title_param;
}
Description
• When you name a function, you should start each name with a verb. It’s a common
coding practice to use the verb set for functions that set the values of data members
and to use the verb get for functions that return the values of data members.
• Functions that access the value stored in a data member are commonly referred to
as getters or accessors.
• Functions that change the value stored in a data member are commonly referred
to as setters or mutators.
• When you code a getter function, it’s considered a good practice to include the
const keyword after the function’s parentheses. You must do this in both the
declaration and the definition.
• When you code functions, you can access private data members just by coding
their names. However, if a setter function has a parameter with the same name
as a data member, you can identify the data member by prefixing it with the
this keyword and the member access operator (->).
The console
The Movie List program
Enter a movie...
TITLE YEAR
Roundhay Garden Scene 1888
Arrival of a Train 1895
The code
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
class Movie {
private:
string title;
int year;
public:
void set_title(string);
string get_title() const;
void set_year(int);
int get_year() const;
};
After the function definitions for the class, this code presents the main()
function. This function begins by displaying the title of the program and
prompting the user to enter a movie. Then, it defines a vector to store Movie
objects. Next, it enters a while loop that continues while the user enters ‘y’ or
Y’.
Within this loop, the first statement creates a Movie object. Then, it gets the
title of the movie and uses the set_title() function to store this title in the Movie
object. Next, it gets the year of the movie and uses the set_year() function to
store the year in the Movie object. Since this function will throw an exception if
the user enters an invalid year, this code catches the exception and handles it. To
keep things simple, this program handles the exception by displaying an error
message and exiting the program normally. However, a more robust program
would display an error message and give the user another chance to enter a year.
After setting the title and year in the Movie object, this code adds the movie
to the end of the vector. Then, it asks whether the user would like to enter
another movie. If so, the loop executes again. If not, the loop ends.
After the while loop, the code displays the movies in two columns where the
first column is three times as wide as the second column (30 characters and 10
characters). Here, the second statement displays column headers for TITLE and
YEAR. Then, a for loop displays the title and year for each iMovie object stored
in the vector. After this for loop, the program ends.
Chapter 14 How to define classes 523
int main() (
cout << "The Movie List program\n\n"
« "Enter a movie...\n\n";
string title;
cout << "Title: ";
getline(cin, title);
movie.set_title(title);
int year;
cout << "Year: ";
cin >> year;
try {
movie.set_year(year);
}
catch (const invalid_argument& e) {
cout << e.whatO << "\n";
cout << "Exiting program...\n\n";
return 0;
}
movies.push_back(movie) ;
The declarations for three more functions for the Movie class
private:
string to_upper(string); // helper function
public:
string get_title_upper () const;
bool iequals(const Movieft);
Description
• If you need to code a member function but you don’t want to make it available
outside of the class, you can declare it as private. Since a private function can
be used by other member functions, it’s known as a private helperfunction.
Description
• When you define a class, you should consider coding overloaded setter functions
that accept different data types.
• If a setter function accepts a parameter with a data type that’s different from the
type of the data member, you can use the functions shown above to convert the
parameter to the appropriate type.
• The to_string() function is typically used to convert a number to a string so it
can be concatenated with other strings.
Description
• A constructor is a special type of member function that’s automatically called
when you create (construct) an object.
• A constructor must use the same name and capitalization as the name of the class.
• A constructor does not include a return type.
• A default constructor has zero required parameters.
• If you provide default values for each parameter of a constructor, the
constructor can accept arguments and can also act as the default constructor.
• If you don’t code any constructors, C++ creates a default constructor for your class.
However, this default constructor might not initialize data members. As a result, the
data members might point to whatever old values happen to be in memory.
• If you code one or more constructors but don’t code a default constructor, C++
doesn’t create a default constructor for your class.
A destructor
The declaration
-Movie() ;
The definition
Movie::-Movie() {
// code that frees system resources goes here
}
Description
• A destructor is a special type of member function that’s automatically called
when an object is destroyed. C++ automatically destroys most objects when
they go out of scope.
• A destructor must use the same name and capitalization as the name of the
class and be preceded by a tilde character (-).
• A destructor can’t include a return type.
• A destructor can’t accept any arguments.
• A class can only contain one destructor.
• A common use for a destructor is to free system resources that an object is
using.
#include <string>
class Movie {
private:
std::string title;
int year;
int stars;
std::string to_upper(std::string);
public:
Movie(std::string title ■ "", int year ■ 1888, int stars - 1);
Movie set_title(std::string);
std::string get_title() const;
Movie set_year(int);
int get_year() const;
Movie set_stars(int);
int get_stars() const;
#endif // MURACH_MOVIE_H
using std::string;
using std::invalid_argument;
// private function
string Movie::to_upper(string str) {
string str_upper;
for (char c : str) {
str_upper.push_back(toupper(c));
}
return str_upper;
}
// public functions
Movie::Movie(string title, int year, int stars) {
set_title(title) ;
set_year(year);
set_stars(stars);
}
Figure 14-10 The header and source files for a Movie class (part 1 of 2)
534 Section 3 Object-oriented programming
appropriate setter functions. For example, it passes the year parameter to the
set_year() function. That way, the setter function can validate the value that’s
passed to it.
The definitions for the next six functions work as you would expect. Here,
all of the setter functions validate their parameters. This helps to make sure that a
Movie object stores valid data. For example, this code makes sure the title string
is 120 characters or less. Similarly, it makes sure that the number of stars for a
movie ranges from one to five.
The iequalsf) function provides a way to perform a case-insensitive compar
ison of two Movie objects. To do that, it uses the private to_upper() function to
convert both movie titles to uppercase before comparing them. However, this
function doesn’t include the number of stars in the comparison. As a result, the
two movies are considered equal if the titles are equal (not including case) and
the years are equal.
Chapter 14 How to define classes 535
Description
• It’s a common practice to store the interface for a class in a header file and the
source code that implements the interface in another file. This works much like
storing functions in header and source files as described in chapter 7.
Figure 14-10 The header and source files for a Movie class (part 2 of 2)
536 Section 3 Object-oriented programming
#include <string>
class Movie {
private:
std::string title;
int year;
int stars;
std::string to_upper(std::string);
public:
Movie(std::string title ■ "", int year ■ 1888, int stars =1);
void set_title(std::string);
std::string get_title() const { return title; } // inline function
void set_year(int);
int Movie::get_year() const ( return year; } // inline function
#endif // MURACH_MOVIE_H
Description
• If you code the definition of a member function in the header file, the function is
known as an inline function.
• When you code an inline function, you are giving the compiler a hint that you
would like it to optimize the executable file by using a process known as inline
expansion. Inline expansion replaces each function call with the code in the
function itself.
• When you code an inline function, there is no guarantee that the compiler will
use your hint.
• Modem compilers often use inline expansion automatically, even for functions
that aren't coded as inline functions.
The console
The Movie List program
COMMANDS
v - View movie list
a - Add a movie
d - Delete a movie
x - Exit
Command: v
TITLE YEAR STARS
1 Casablanca 1942 5
2 Wonder Woman 2017 4
Command: a
Title: The Wizard of Oz
Year: 1939
Stars (1-5): 5
The Wizard of Oz was added.
Command - d
Number: 1
Casablanca was deleted.
Command: x
Bye!
The code
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <string>
#include <vector>
#include "Movie.h"
vector<Movie> read_movies_from_file() {
vector<Movie> movies;
ifstream input_file(movies_file);
if (input_file) ( // if file opened successfully...
string line;
while (getline(input_file, line)) (
stringstream ss(line);
In part 2 of figure 14-12, the code uses a single statement to add the Movie
objects to the vector of Movie objects. To do that, this code calls the constructor
of the Movie class and passes it the title, year, and stars that have been read from
the file. Then, it passes the movie to the push_back() function of the vector.
In the write_movies_to_file() function, the code uses the getter functions to
get the data from the Movie object. This is necessary because the data members
of the Movie class are private. As a result, you can’t access them directly.
Instead, you must use these getter functions to get values from the data members.
Similarly, the view_movies() function uses the getter functions to get the values
from the data members.
In part 3. the get_movie() function creates a Movie object and stores it in a
variable named movie. To do that, it passes the title, year, and stars entered by
the user to the constructor of the Movie class. Then, the next statement returns
the Movie object. Alternately, this code could return the Movie object without
creating a variable for it by coding the return statement like this:
return Movie(title, year, stars);
In the add_movie() function, the code uses the iequalsf) function to compare
the current Movie object with every movie in the vector of Movie objects. If
the current Movie already exists in the vector, this code uses the set_stars() and
get_stars() functions to set the number of stars for the existing movie to the
number of stars for the new movie. In other words, it doesn't add a new movie
to the list since the title and year are the same. Instead, it updates the number of
stars, which might be different.
The rest of the code in this listing works mostly as described in chapter 9.
As a result, you shouldn’t have much trouble understanding how it works. The
only difference of any significance is that the add_movie() and delete_movie()
functions use the get_title() function to get the title of the movie instead of
directly accessing the data member.
As you review this code, you may wonder why it doesn’t use a try statement
to catch the exceptions that might be thrown by the constructor of the Movie
class. In the read_movies_from_file() function, this isn’t necessary if the data
in the file is valid. However, in the get_movie() function, it would be a good
practice to catch these exceptions and handle them. It would be an even better
practice to prevent these exceptions from being thrown in the first place by
validating user input so no invalid data is passed to the Movie object. To do that,
you could use functions from the console namespace described in chapter 7.
Chapter 14 How to define classes 541
int number ■ 1;
for (Movie movie : movies) {
cout << setw(col_width / 2) << number
« setw(col_width * 4) « movie.get_titie()
« setw(col_width) << movie.get_year()
« setw(col_width) << movie.get_stars() << endl;
♦♦number;
)
cout « endl;
Movie get_movie() {
string title;
cout « "Title: ■;
cin.ignore(1000, ’\n');
getline(cin, title);
int year;
cout « "Year: ";
cin >> year;
if (already_exists) (
write_movies_to_file(movies );
cout « movie.get_title() « " was updated.\n\n" ;
}
else (
movies.push_back(movie) ;
write_movies_to_file(movies);
cout « movie.get_title() « " was added.\n\n";
}
void display_menu() {
cout « "COMMANDS\n"
<< "v - View movie list\n"
<< "a - Add a movie\n"
<< "d - Delete a movie\n"
« "x - Exit\n\n";
int main() {
cout « "The Movie List program\n\n";
display_menu();
vector<Movie> movies ■ read_movies_from_file();
char command - ' v ’ ;
while (command != 'x') (
cout << "Command:
cin >> command;
switch (command) (
case 'v':
view_movies(movies);
break;
case 'a':
add_movie(movies) ;
break;
case 'd':
delete_movie(movies);
break;
case 'x':
cout << "Bye!\n\n";
break;
default:
cout << "Not a valid command. Please try again.\n\n";
break;
}
)
)
Description
• UML (Unified Modeling Language) is the industry standard used to describe
the classes and objects of an object-oriented program.
• A UML class diagram describes the data members and member functions of
one or more classes.
• The minus sign (-) marks the private members that can’t be accessed by other
classes, and the plus sign (+) marks the public members that can be accessed by
other classes.
• For each member function, the name is followed by a set of parentheses.
Within the parentheses, the class diagram specifies the parameters of the
function.
• When a member function provides a way to get data that can’t be set in the
object, it’s known as a read-only function.
Description
• After the names of the data members and function parameters, these class
diagrams specify a colon and the data type.
• After the parentheses of a function, these class diagrams specify a colon and
the return type for the function.
#include <string>
class Product {
double price;
int discount_percent;
public:
std::string name;
Product(std::string name ■ double price - 0.0,
int discount_pct ■ 0);
void set_price(double);
double get_price() const {
return price;
}
void set_discount_percent(int);
int get_discount_percent() const {
return discount_percent;
}
double get_discount_amount();
double get_discount_price ();
};
#endif // MURACH_PRODUCT_H
Part 2 of figure 14-15 shows the code that implements the header file for the
Product class. To start, this code defines the constructor for this class. This code
assigns the first parameter to the name data member by directly accessing this
data member. Then, it assigns the second parameter to the price data member by
passing it to the set_price() function. Similarly, it assigns the third parameter to
the discount_percent data member by passing it to the set_discount_percent()
function. That way, the class can validate the data being assigned to these data
functions.
As you review this code, note that it doesn't need to implement the getter
functions for the price and discount_percent data members. That’s because these
functions were defined as inline functions in the header file. As a result, the
header file contains the implementation for these functions.
Also, note that the get_discount_amount() and get_discount_price()
functions don’t get or set any data members. Instead, they use the data members
to calculate a result. Then, they return the result of that calculation. For example,
the get_discount_amount() function calculates the discount amount by multi
plying the price by the discount percent. Similarly, the get_discount_price()
function calculates the discount price by subtracting the discount amount from
the price. This is a good example of how a class can include functions that
operate on its data members.
Chapter 14 How to define classes 551
using std::string;
using std::invalid_argument;
double Product::get_discount_amount() {
double discount_amount - price * discount_percent / 100;
return round(discount_amount * 100) I 100;
}
double Product::get_discount_price() {
double discount_price ■ price - get_discount_amount();
return round(discount_price * 100) / 100;
}
The console
The Product Viewer program
PRODUCTS
1. Stanley 13 Ounce Wood Hammer
2. National Hardware 3/4" Wire Nails
3. Economy Duct Tape, 60 yds. Silver
PRODUCT DATA
Name: Stanley 13 Ounce Wood Hammer
Price: 12.99
Discount percent: 62
Discount amount: 8.05
Discount price: 4.94
The code
tinclude <iostream>
(♦include <string>
(♦include <vector>
(♦include ■Product.h"
In part 2 of figure 14-16, the main() function begins by displaying the title
of the program. Then, it creates a vector of Product objects. Note that this code
doesn't assign the Product objects it creates to variables. Instead, it calls the
constructor of the Product class to create each object, and it stores each object in
the vector without going through the intermediate step of creating a variable to
store the object.
After creating the vector of Product objects, this code passes that vector
to the show_products() function defined in part 1 of this figure. This displays
the numbered list of products. Then, this code uses a while loop to display the
information for one or more products. To do that, the code in the loop prompts
the user to enter a number that corresponds to a product. Next, it checks if
the number entered by the user corresponds to a Product object from the
vector of Product objects. If it does, the code passes that Product object to the
show_product() function. This displays the data for the product object.
Chapter 14 How to define classes 555
vector<Product> products;
product s.push_back(
Product("Stanley 13 Ounce Wood Hammer", 12.99, 62));
product s.push_back(
Product("National Hardware 3/4\" Wire Nails", 5.06, 0));
product s.push_back(
Product("Economy Duct Tape, 60 yds. Silver", 7.24, 0));
show_products(products);
A Die class
Figure 14-17 begins by showing a UML diagram for a Die class that defines
a single six-sided die. This class has a single private data member named
value that has a public getter function. In addition, the class provides a default
constructor for the class, as well as a public function named roll() that sets the
value data member to a random value from 1 to 6.
The header file for the Die class defines the private data member and
declares all of the public functions in the UML diagram. Then, the source code
file implements these functions. To start, it imports the header files that it needs
to get a random number, as well as the header file for the Die class.
The constructor for the Die class uses the srand() function to seed the rand()
function that’s used in the roll() member function. Then, the constructor initial
izes the value of the die to 1. As a result, when you first create a Die object, its
value is set to 1.
The roll() function begins by using the rand() function with the modulus
operator (%) to get a value from 0 to 5. Then, the next statement increments this
value so it’s from 1 to 6.
As you would expect, the get_value() function returns the value. This
provides a way for the calling code to get the value after the roll() function has
been called.
Chapter 14 How to define classes 557
class Die {
private:
int value;
public:
Die();
void roll();
int get_value() const;
);
Sendif // MURACH_DIE_H
Die::Die() {
srand(time(nullptr)); // seed the rand() function
value ■ 1;
)
void Die::roll() {
value ■ rand() % 6; // value is : ■ 0 and <= 5
♦♦value; // value is : = 1 and <= 6
)
A Dice class
Figure 14-18 begins by presenting a UML diagram that shows how object
composition works. More specifically, this diagram shows that a Dice object is
composed of one or more Die objects. To show this relationship, this diagram
connects the two classes with a line, and it places a diamond connector next
to the Dice class to show that it’s the object that’s composed of other objects.
It may not seem like much, but being able to define an object that’s composed
of other user-defined objects is a powerful feature that allows you to create
complex data structures that you can customize for each program that you
develop.
The header file for the Dice class starts by including the header files it
needs. This includes the header file for the Die class, which shows that the
Dice class uses Die objects. Then, the Dice header file defines a single private
data member named dice that’s initialized to an empty vector of Die objects.
Finally, it declares the public functions in the UML diagram, including a default
constructor.
The source code file for the Dice class implements these functions. To start,
it defines a default constructor. This constructor doesn’t perform any initializa
tion because the dice data member doesn’t need to be initialized. However, if
you later wanted to initialize the vector by setting its capacity, you could add
that code here.
The add_die() function adds a Die object to the vector. To do that, this
function calls the push_back() function of the Dice object to add the Die object
to the end of the vector.
The roll_all() function rolls all of the Die objects that have been added to the
vector. To do that, this code loops through the Die objects in the Dice object and
calls the roll() function for each one.
The get_dice() function is a getter function for the dice data member. It
returns the vector of Die objects that’s stored in that data member. This provides
a way for the calling code to get the value from each Die object in the Dice
object.
Chapter 14 How to define classes 559
tinclude <vector>
tinclude "Die.h"
class Dice {
private:
std::vector<Die> dice;
public:
Dice();
void add_die(Die die);
void roll_all();
std::vector<Die> get_dice() const;
ftendif // MURACH_DICE_H
Dice::Dice() {}
void Dice::roll_all() {
for (Die& die : dice) {
die.roll();
)
}
Description
• Object composition is a way to combine simple objects into more complex
ones. For example, one Dice object can store multiple Die objects.
• In a UML diagram, you can use the diamond connector to identify the object
that is composed of other objects.
The console
The Dice Roller program
The code
tinclude <iostream>
tinclude "Die.h"
tinclude "Dice.h"
int main() {
cout « "The Dice Roller program\n\n" ;
The console
Figure 14-20 begins by showing the console for the Pig Dice game. The
console begins by displaying the rules for the game. Then, it prompts the user to
roll the die or hold the die. If the user holds, the total of all the points rolled for
that turn are added to the overall total and the turn ends. If the user rolls and the
roll is a 1, though, the turn ends and the points rolled for that turn are lost. The
game continues for as many turns as it takes for the player to get to at least 20
points.
The code
The code begins by including the header file for the Die class shown earlier
in this chapter. Then, the code defines a structure for the GameState object. This
structure defines six public data members to store the state of the game, and it
initializes these data members to values that are appropriate for a new game. For
example, the turn data member is set to 1, the score data member is set to 0, and
the game_over data member is set to false. In addition, the die data member is
initialized by using the abbreviated syntax to call the default constructor of the
Die class.
At this point, you may wonder why this program doesn’t use a class instead
of a structure. The answer is that the GameState object applies to this program
and isn’t designed to be reused by other programs. It just provides a way to
combine some related data members that are needed by the program. Also, it
doesn’t need to provide encapsulation for these data members. As a result, it’s a
good candidate to be a structure instead of a class.
After defining the GameState structure, this code declares five functions
that are used to organize the code. Of these functions, the last four accept the
GameState object as a reference parameter. As a result, the code within these
functions can get any of the data members of the GameState object to check the
state of the game, and it can set any of these data members to change the state of
the game.
Chapter 14 How to define classes 563
The console
Let's Play PIG!
TURN 1
Roll or hold? (r/h): r
Die: 5
Roll or hold? (r/h): r
Die: 4
Roll or hold? (r/h): r
Die: 5
Roll or hold? (r/h): h
Score for turn: 14
Total score: 14
TURN 2
Roll or hold? (r/h): r
Die: 6
Roll or hold? (r/h): h
Score for turn: 6
Total score: 20
Game over!
The code
tinclude <iostream>
tinclude "Die.h"
// declare functions
void display_rules();
void play_game(GameState&);
void take_turn(GameState&);
void roll_die(GameState&);
void hold_tum(GameState&) ;
// define functions
void di splay_rules() {
cout « "Let's Play PIG!\n"
<< "\n"
<< ■* See how many turns it takes you to get to 20.\n"
<< ■* Turn ends when you hold or roll a l.\n"
<< ■* If you roll a 1, you lose all points for the tum.\n"
<< ■* If you hold, you save all points for the turn.\n\n";
}
if (choice ■■ 'r')
roll_die(game);
else if (choice ■■ 'h')
hold_turn(game);
else
cout << "Invalid choice. Try again.\n";
In part 3 of figure 14-20. the roll_die() function begins by getting the Die
object from the GameState object and calling its roll() function. This updates
the value that’s stored in the Die object. Next, this code displays the value that’s
stored in the Die object on the console.
After that, the roll_die() function checks if the value in the die is 1. If it is,
the code changes several data members of the GameState object. Specifically, it
sets the score_this_turn member to 0. it increments the turn member by 1. and it
sets the tum_over member to true. In other words, it discards all points for this
turn and ends the turn. Then, it displays a message on the console that indicates
that the turn is over and that there was no score for this turn. However, if the die
is not 1. this code increments the score_this_tum data member by the value of
the die.
The hold_tum() function begins by changing two data members of the
GameState object. Specifically, it increments the score data member by the
score_this_turn data member, and it sets the turn_over data member to true. In
other words, this code saves the score for the turn and ends the turn. Then, the
code displays the score for the turn and the total score on the console. Next, the
code resets the score_this_tum data member to 0 so it’s ready for the next turn.
After that, the hold_turn() function checks if the score is greater than or
equal to 20. If it is, this code sets the game_over variable to true and displays
a message that indicates how many turns it took to finish the game. Otherwise,
this code increments the turn data member by 1.
Chapter 14 How to define classes 567
if (game.score >=20) (
game.game_over = true;
cout << "You finished in " << game.turn << ■ turns!\n\n";
)
else (
game.turn += 1;
)
)
Perspective
This chapter has presented the most useful skills for working with classes
and objects. In addition, it has presented the most important principle of object-
oriented programming: encapsulation. In the next chapter, you’ll learn about two
more important principles of OOP: inheritance and polymorphism.
When you’re first getting started with OOP. it can be hard to see its advantages
over procedural programming. For now. you can focus on just two. First, dividing
the code into classes often makes it easier to reuse code. For example, the Die and
Dice classes presented in this chapter could be reused by many programs. Second,
using classes helps you separate the different layers of a program. That can
simplify the development of the program and make the program easier to maintain
and enhance later on.
Terms
object-oriented programming (OOP) getter
data structure accessor
object setter
structure mutator
data member helper function
member function constructor
identity default constructor
state destructor
behavior inline function
instance inline expansion
instantiation UML (Unified Modeling Language)
encapsulation class diagram
data hiding read-only function
class object composition
interface
Summary
• Object-oriented programming (OOP) groups related variables and functions
into data structures called objects.
• A structure or class can be thought of as a blueprint from which objects are
created.
• The data members of a structure or class store the data of an object.
• The memberfunctions of a structure or class define the tasks that an object can
perform. Often, these functions provide a way to work with the data members
of an object.
• Once you create an object from a structure or class, the object has an identity (a
unique address), a state (the data that it stores), and behavior (the functions that
it provides). As a program runs, an object’s state may change.
Chapter 14 How to define classes 569
I I
I o |
I_____ I
|o o|
i°—°i
TOTAL: 6
Description
• Inheritance lets you create a new class based on an existing class. Then, the
new class inherits the public data members and member functions of the
existing class.
• A class that another class inherits is called a base class, parent class, or
superclass.
• A class that inherits another class is called a derived class, child class, or
subclass.
• A superclass can include protected members that are only available to the
current class and its subclasses.
• A subclass can add new data members and functions to the superclass. It can
also override a function from the superclass by providing its own version of the
function.
Access modifiers
Keyword Description
private Available to the current class.
protected Available to the current class and its subclasses.
public Available to the current class, its subclasses, and all other classes.
#include <string>
class Product (
protected:
std::string name; // protected data member
private:
double price;
int discount_percent;
public:
Product(std::string name - double price - 0.0,
int discount_pct = 0);
void set_price(double);
double get_price() const { return price; }
void set_discount_percent(int);
int get_discount_percent() const { return discount_percent; }
ttendif // MURACH_PRODUCT_H
#include <string>
#include "Product.h"
#include <string>
tinclude "Product.h"
Sendif // MURACH_MOVIE_H
Note
• Because the Movie subclass and the Book subclass in the previous figure are
fully implemented in their header files, no implementation files are required.
show_product(product);
show_product(book);
show_product(movie);
}
The console
Description: Stanley 13 Ounce Wood Hammer
Discount Price: 4.94
Description
• Polymorphism is a feature of inheritance that lets you treat objects of
subclasses as if they were objects of the superclass.
• If you access a function of a superclass object and the function is overridden
in the subclasses of that class, polymorphism determines which function is
executed based on the object’s type.
The console
PRODUCTS
1. Stanley 13 Ounce Wood Hammer
2. The Big Short by Michael Lewis
3. The Wizard of Oz (1939)
PRODUCT DATA
Name: The Big Short by Michael Lewis
Price: 15.95
Discount percent: 34
Discount amount: 5.42
Discount price: 10.53
PRODUCT DATA
Name: The Wizard of Oz (1939)
Price: 14.99
Discount percent: 50
Discount amount: 7.5
Discount price: 7.49
Bye!
The code
#include <iostream>
#include <string>
#include "Product.h"
#include "Book.h"
#include "Movie.h"
void show_products() {
cout « "PRODUCTS\n"
« "1. " << pl.get_description() « endl
« "2. " << p2.get_description() « endl
« "3. " << p3.get_description() « endl « endl;
}
int main() (
cout << "The Product Viewer program\n\n" ;
show_products();
switch (number) {
case 1:
show_product(pl);
break;
case 2:
show_product(p2);
break;
case 3:
show_product(p3) ;
break;
default:
cout << "Invalid product number.\n\n";
break;
}
return 0;
}
private:
double price;
int discount_percent;
public:
// other public functions and constructors
Description
• An abstract class is a class that can be inherited by other classes but that you
can’t use to create an object.
• To create an abstract class, declare one of its virtual functions as a pure virtual
function by coding “= 0” at the end of the function declaration.
Description
• To explicitly identify a function as a function that overrides a virtual function
with the same name, you can code the override keyword at the end of the
function declaration. This is optional, but it can help prevent errors that are
hard to detect and fix.
• To prevent subclasses from overriding an inherited virtual function, you can
code the final keyword at the end of the function declaration.
• When necessary, you can use the override and final keywords in the same
function declaration.
• The override and final keywords were introduced with C++11.
struct Day {
double low_temp;
double high_temp;
);
Sendif // MURACH_DAY_H
Description
• Multiple inheritance allows a class to inherit multiple classes.
• The Day structure defines a Day object that stores the low and high tempera
tures for the day.
• The Day Reader and DayWriter classes work with a vector of Day objects.
• The DaylO class inherits the DayReader and DayWriter classes. As a result, the
load_temps() and save_temps() functions are available from a DaylO object.
^include <string>
#include <vector>
#include "Day.h" // include Day structure
class DayReader {
private:
std::string filename;
public:
DayReader(std::string filename ■ "");
std::vector<Day> load_temps();
};
#endif // MURACH_DAYREADER_H
The DayReader.cpp file
#include <fstream>
#include <vector>
#include "DayReader.h■
#include "DaylOError.h"
DayReader::DayReader(std::string filename_param) {
filename ■ filename_param;
}
vector<Day> DayReader::load_temps() {
vector<Day> days;
Day day;
ifstream input_file(filename);
if (input_file) (
while (input_file >> day.low_temp >> day.high_temp) {
days.push_back(day);
}
input_file.close();
return days;
}
else (
throw DaylOError("Unable to open file: ■ + filename);
}
tinclude <string>
•include <vector>
•include "Day.h" // include Day structure
class DayWriter (
private:
std::string filename;
public:
DayWriter(std::string filename ■ ■");
void save_temps(const std::vector<Day>& temps);
};
•endif // MURACH_DAYWRITER_H
The DayWriter.cpp file
•include <fstream>
•include <vector>
•include "DayWriter.h"
DayWriter::DayWriter(std::string filename_param) {
filename ■ filename_param;
}
#include <string>
#include "DayReader.h"
#include "DayWriter.h"
Wendif // MURACH_DAYIO_H
The DaylO.cpp file
tinclude <string>
#include "DaylO.h"
#include <iostream>
#include <string>
Wendif // MURACH_DAYIOERROR_H
Description
• To define a custom exception, you can code a class that inherits one of the
built-in exception classes. Then, you can use the custom exception as you
would any other exception.
void Dice::roll_all() {
for (int i - 0; i < size(); ++i) // calls vector size() function
Die& die - at(i); // calls vector at() function
die.rollO;
)
)
Description
• If an object is a type of another object, it typically makes sense to use inheri
tance to create the relationship between the two classes.
• If an object has a type of another object, it typically makes sense to use object
composition to create the relationship between the two classes.
Perspective
Conceptually, this is one of the most difficult chapters in this book. Although
the basic idea of inheritance is easy enough to understand, figuring out how
to use it to benefit your programs can be difficult. So if you find yourself a bit
confused right now. don’t be disheartened.
Fortunately, when you’re getting started, you typically don’t need to code
classes that inherit other classes. But, as you learn more about programming,
you’ll see more examples of how to use inheritance and your understanding of it
should grow. The good news is that you don’t have to understand every nuance
of how inheritance works to use it. For example, when you learned about the
C++ exception hierarchy in chapter 13, you didn’t need to understand that the
invalid_argument class inherits the logic_error class, which inherits the excep
tion class. But now that you realize that these classes use inheritance, you should
understand how they work a little better.
If you continue with programming, you’ll learn how to inherit other classes
to create more complex programs. For example, you’ll use inheritance to create
graphical user interfaces (GUIs), web programs, and mobile apps. In addition,
as you learn more about object-oriented design, it will become clearer when it
makes sense to use inheritance.
Terms
inheritance
superclass
subclass
base class
derived class
parent class
child class
access modifier
protected access
virtual function
override a function
redefine a function
polymorphism
dynamic binding
static binding
late binding
early binding
abstract class
pure virtual function
multiple inheritance
Chapter 15 How to work with inheritance 603
Summary
• Inheritance lets you create a new class based on an existing class. Then, the
new class inherits the public data members and member functions of the
existing class.
• A class that another class inherits is called a base class, parent class, or
superclass.
• A class that inherits another class is called a derived class, child class, or
subclass.
• A class can use access modifiers to specify whether members of a superclass
are accessible to subclasses. A protected member of a class is available to
the current class and its subclasses, but not to any code that’s outside of
these classes.
• A subclass can add new data members and functions that aren’t in the
superclass.
• A subclass can override a virtual function from the superclass by providing
its own version of that function.
• A subclass can redefine a non-virtual function from the superclass by
providing its own version of that function.
• Polymorphism is a feature of inheritance that lets you treat objects of
subclasses as if they were objects of the superclass.
• Overriding a virtual function allows polymorphism to work because it
allows C++ to determine which function to call at runtime. This is known as
dynamic binding or late binding.
• Redefining a function that isn’t virtual prevents polymorphism from working
because it forces C++ to determine which function to call at compile time.
This is known as static binding or early binding.
• An abstract class is a class that objects can’t be created from but that can be
inherited by other classes.
• To create an abstract class, you declare one of its virtual functions as a pure
virtual function by coding “= 0” at the end of the function declaration.
• Multiple inheritance allows a class to inherit multiple classes.
• If an object is a type of another object, it makes sense to use inheritance to
create the relationship between the two classes.
• If an object has a type of another object, it makes sense to use object compo
sition to create the relationship between the two classes.
• Encapsulation, inheritance, and polymorphism are three of the fundamental
concepts of object-oriented programming (OOP).
604 Section 3 Object-oriented programming
PRODUCT DATA
Name: Rubber Soul (The Beatles) - CD
Price: 9.99
Discount percent: 10
Discount amount: 1
Discount price: 8.99
10. Modify the Movie and Book classes so they include and inherit the Media class,
not the Product class. This should create a class hierarchy that looks like this:
Product
Media
Movie
Book
11. Delete the format data member from the Movie and Book classes.
12. Modify the constructors of the Movie and Book classes so the format argument
is passed to the constructor of the Media class. In other words, these classes
should set the format data member by calling the constructor of the Media class.
Note how this reduces code duplication.
13. Run the program to make sure it still works correctly.
Add an Album class
14. Add a class named Album that inherits the Media class. The Album class should
work much like the Book class, but it should include a data member for storing
the artist, not a data member for storing the author.
15. In the main.cpp file, modify the code that creates the objects so it creates an
Album object in addition to a Product, Book, and iMovie object. This object
should store the data for a music album that you like.
16. Modify the show_products() function so the program displays the description
for the Album object when it starts.
17. Modify the main() function so it passes the Album object to the show_product()
function if the user enters the correct number for the album. This should display
the Album object as shown at the beginning of this exercise.
4. Attempt to run the program. This should display an error message that
indicates that you can’t create an object from the abstract Product class.
5. Open the main.cpp file. Then, modify the code that attempts to create the
Product object so it creates a Book object instead. For data, you can use the
name and author of one of your favorite books.
6. Run the program to make sure it works correctly.
Use the override and final keywords
7. In the Movie and Book classes, add the override and final keywords to the
get_description() function. This shows that this function overrides the pure
virtual function of the Product class and that this function can’t be overridden
by other subclasses.
8. Create the header file for a new class named UsedBook that inherits the Book
class.
9. Code a constructor that accepts the same four arguments as the constructor
for the Book class and passes those arguments to the constructor of the Book
class. The body of this constructor doesn’t need to contain any code.
10. Code a get_description() function that attempts to override the get_description()
function of the Book class. This function should append “(Used)” to the end of
the description.
11. In the main.cpp file, modify the code that creates the first object so it creates a
UsedBook object instead of a Book object.
12. Attempt to run the program. This should display an error message that
indicates that the get_description() function can’t be overridden because it is
final.
13. In the Book class, remove the final keyword from the get_description()
function.
14. Run the program. This time, it should work correctly.
16
More skills for object-
oriented programming
The last two chapters presented the fundamental skills and concepts for
working with object-oriented programming (OOP). Now, this chapter presents
a few more skills for working with object-oriented programming. In particular,
it shows how to work with static members, how to work with friend members,
and how to overload operators so they work correctly with objects that you
define.
The Product class with a static data member and a static function
The Product.h file
ftifndef MURACH_PRODUCT_H
#define MURACH_PRODUCT_H
#include <string>
class Product {
private:
double price;
int discount_percent;
static int object_count; // static data member
public:
Product(std::string name - double price - 0.0,
int discount_percent - 0);
std::string name;
void set_price(double);
double get_price() const { return price; }
void set_discount_percent(int);
int get_discount_percent() const { return discount_percent; }
double get_discount_amount() const;
double get_discount_price () const;
# end if // MURACH_PRODUCT_H
Code in the Product.cpp file that works with the static data member
int Product::object_count ■ 0; // initialize static data member
Description
• You can use the static keyword to code static data members and static functions
that belong to the class, not to an object created from the class.
• Static data members and static member functions are sometimes called class
data members and class member functions.
• When you code a static function, you can only use static data members and
local variables that are defined in the function. You can’t use regular data
members in a static function because they belong to an object created from the
class, not to the class itself.
members. For small classes, grouping members like this isn’t critical. However,
as your classes get larger, grouping the members of a class in a logical way
makes your code easier to read and maintain.
Description
• You can use the scope resolution operator (::) to access a static data member
or function directly from the class without creating an object.
• You can use the dot operator (.) to access a static data member or function
from an object created from a class.
tinclude <string>
tinclude <limits>
class Console {
public:
Console() (}; // define default constructor
ttendif // MURACH_CONSOLE_H
The console.cpp file
#include <iostream>
#include <string>
#include <limits>
#include ■console.h"
Code that calls static functions directly from the Console class
double mi ■ Console::get_double("Monthly Investment: ", 0, 10000);
int years ■ Console::get_int("Years: ", 0, 100);
char choice ■ Console::get_char("Continue? (y/n): ■)
Description
• If a function doesn't use the data members of a class, you may want to make
the function static. That way, it can be called directly from the class or from an
object created from the class.
A FuelTank class
The FuelTank.h file
#ifndef MURACH_FUEL_TANK_H
#define MURACH_FUEL_TANK_H
#include <iostream>
class FuelTank {
private:
double gallons;
public:
FuelTank(double gallons_param =0) { gallons - gallons_param; }
void set_gallons(double gallons_param) { gallons - gallons_param; }
double get_gallons() const ( return gallons; }
double get_liters() const;
};
#endif // MURACH_FUEL_TANK_H
The FuelTank.cpp file
tinclude <iostream>
#include "FuelTank.h"
Description
• The FuelTank class can be used to store the amount of fuel in the tank.
Internally, this class stores the amount of fuel in gallons, but it allows the user
to get the amount of fuel in gallons or liters.
The header file for a FuelCan class that declares a friend function
ttifndef MURACH_FUEL_CAN_H
ttdefine MURACH_FUEL_CAN_H
class FuelCan (
private:
double gallons - 0;
public:
FuelCan(double gallons_param =0) ( gallons - gallons_param; }
double get_gallons() ( return gallons; }
friend void pour(FuelTank& tank, FuelCan& can);
);
ttendif // MURACH_FUEL_CAN_H
The header file for a FuelTank class that declares a friend function
ttifndef MURACH_FUEL_TANK_H
ttdefine MURACH_FUEL_TANK_H
#include "FuelCan.h"
class FuelTank {
private:
double gallons;
public:
FuelTank(double gallons_param =0) { gallons ■ gallons_param; }
void set_gallons(double gallons_param) { gallons ■ gallons_param; }
double get_gallons() const { return gallons; }
double get_liters() const;
friend void pour(FuelTank& tank, FuelCan& can);
);
ttendif // MURACH_FUEL_TANK_H
Description
• You can use the friend keyword to declare friend functions that exist outside of
a class. These functions can access the private members of a class.
• A forward declaration declares a class or function so it can be defined and
compiled later.
Description
• An operator function is a function that defines the behavior of an operator for
an object.
• The arithmetic binary operators should create and return a new object because
they shouldn’t modify the state of the objects used in the operation.
Description
• You can use the this keyword to get a pointer to the location of an object in
memory. You can use the indirection operator (*) to access the object.
• The postfix overload uses a dummy parameter to identify the overload as the postfix
overload rather than the prefix overload.
• A postfix operator should return a copy of the object before the increment operation.
• A prefix operator should return a reference to the object after the increment operation.
• Because the prefix operator doesn’t need to make a copy of an object, it is more
efficient than the postfix operator.
• The techniques shown in this figure also work with the decrement operator (—).
Description
• The relational operators return a Boolean value that indicates the result of the
comparison. Since these operators should not modify their parameters, you can
define them as constant reference parameters.
TANK 5
Gallons: 500
Liters: 1892.7
Description
• An operator that accepts and returns an object by reference allows you to chain
calls to the operator together within a single statement.
Perspective
Now that you’ve finished this chapter, you have a solid foundation in
object-oriented programming. At this point, you have most of the object-
oriented skills you need for understanding how various C++ libraries such as
the Standard Template Library (STL) work. In fact, the next three chapters
show how to use the skills you learned in chapter 14 to define classes that
implement custom containers that work much like the vector and list containers
that are available from the STL. As you read the remaining chapters of this
book, you’ll reinforce your existing knowledge of object-oriented program
ming, and you’ll learn a few more object-oriented skills.
Terms
static data member
static member function
class data member
class member function
forward declaration
friend function
operator function
dummy parameter
chaining
Summary
• Within a class, you can use the static keyword to code static data members
and static member functions that belong to the class, not the object that’s
created from the class.
• Static data members and static functions are sometimes called class data
members and class member functions.
• A forward declaration declares a class or function so it can be defined and
compiled later.
• Within a class, you can use the friend keyword to declare friend functions
that are defined outside of the class. These friend functions can accept
objects created from the class that they can use to access the private
members of the class.
• An operator function is a function that defines the behavior of an operator
for an object.
• To overload the postfix operator, you code a dummy parameter that isn’t
used by the function except to create a different signature that identifies the
overload as the postfix overload, not the prefix overload.
• An operator that accepts and returns an object by reference allows you to
chain calls to the operator together within a single statement.
Chapter 16 More skills for object-oriented programming 629
CIRCLE 1:
radius=20|diameter=40|circumferences125.664|area=1256.64
CIRCLE 2:
radius=10|diameter=20|circumferences62.8318|area=314.159
8. In the main.cpp file, add code that uses the prefix increment operator to incre
ment the fourth Circle object. Then, use the displayO function to display that
object on the console as shown above. At this point, the console should look
like the console shown above.
Add the insertion operator
9. In the header file for the Circle class, add a declaration for the insertion
operator.
10. In the implementation file, add a definition for this operator. To do that, you
can copy the code that displays the Circle object from the display!) function
in the main.cpp file. For this to work, you need to include the iostream header,
so this definition can work with the std::ostream class.
11. In the main.cpp file, modify the code so it uses the insertion operator instead
of the displayO function to display the Circle objects on the console. To make
sure this code is no longer using the display!) function, comment out the
displayO function when you’re done.
Section 4
Description
• The physical memory of a computer is a sequence of bytes.
• Each byte is a physical location and is represented by a number, also called a
memory address. On most systems, the numbers range from zero to one less than
the memory size.
• The notation for memory addresses varies by system, but it’s common for systems
to use hexadecimal, or hex, notation. Hex notation is a base 16 numbering system
that uses sixteen distinct symbols, typically 0-9 for zero to nine and A-F for ten to
fifteen.
• You can use the address of operator (&) to get the memory address of an object,
which can be thought of as anything that’s stored in memory. This returns the
address of the first byte of the location of the object in memory.
Code that defines an int variable and a pointer to that int object
int num - 200; // defines and initializes int variable named num
int* ptr ■ fcnum; // defines and initializes pointer to int named ptr
0x28ff28 0x28ff2c
Code that defines a string variable and a pointer to the string object
string s ■ "hello"; // defines and initializes string variable
string* sptr ■ &s; // defines and initializes pointer to string vari
able
Description
• A pointer variable, or pointer, is an object that stores the memory address of
another object. In other words, the pointer points to the other object.
• The value of a pointer is a memory address, but a pointer is also an object in its
own right with its own memory address.
• You can use the address of operator (&) to get an address that you can use to
initialize a pointer variable.
• A pointer variable stores a memory address and has a data type that depends
on the data type it points to. For example, a pointer to an int data type is of the
pointer to int type. This is referred to as a compound type.
• You can use the indirection operator (*) to dereference a pointer. This allows
you to access the value of the object that a pointer points to.
• You can use the member access operator (->) to access public data members
and functions of the object that a pointer points to.
An error that can occur when changing the object a pointer points to
iptr - π // ERROR: cannot convert from double* to int*
Description
• When defining a pointer, you can code the asterisk immediately after the data
type, immediately before the variable name, or with spaces before and after the
asterisk.
• It’s recommended that you initialize a pointer when you define it, since uninitial
ized pointers are a common cause of errors.
• When defining multiple pointers on one line, make sure each variable has an
asterisk.
• With C++11 and later, it’s a best practice to define a null pointer by assigning it
the nullptr keyword. Prior to C++11, you can define a null pointer by assigning it
a value of NULL or 0.
• Once you define a null pointer, you shouldn’t try to dereference it.
pointer would still think it pointed to an object that was 4 bytes. Because this
could lead to problems, the compiler displays an error if you try to do this.
Description
• In general, pointer variables are more difficult to work with than reference
variables. As a result, it’s generally considered a best practice to use reference
variables unless you need the functionality of a pointer variable.
Description
• To pass an argument to a function by reference, you can use a reference parameter
or a pointer parameter.
• In general, reference parameters are easier to work with. As a result, you should use
reference parameters unless you’ve got a good reason to use a pointer parameter.
Calculators square_result() (
this->result *■ this->result; // this-> is optional
return *this; // return a self-reference
}
int get_result() {
return this->result; // this-> is optional
}
};
Description
• Every object has a pointer named this whose value is its own memory address.
• When you define member functions in a class or structure, you can use the
pointer named this to access the members of the current object.
• When coding member functions, you can use the member access operator (->)
of the pointer named this to access other members of the object. However,
unless you need to do this to prevent a naming conflict, this technique is
optional.
• When coding member functions, you can return a reference to the current
object. This is known as a self-reference, and it’s useful for creating member
functions that can be chained together.
• To return a self-reference, use the indirection operator (*) to return the under
lying value of the pointer named this.
Either way, if the steps pointer is not null, the function loops through the
elements in the array and adds them to the accumulator variable. After the loop
finishes, the function returns this variable. Note that if the steps pointer is null,
the loop doesn’t run and the function returns the initial accumulator value of
zero, which is probably what you want.
Chapter 17 How to work with memory and pointers 649
The console
The code
#include<iostream>
int main() (
const int num_days =7;
char days[] - "MTWTFSS"; // built-in array for the days argument
int steps[num_days] - ( 0 }; // built-in array for the steps argument
std::cout « "Total steps for the week: " << total « '\n'
« "Average daily steps: " << avg << "\n\n";
}
The console
Welcome to the Weekly Step Counter program!
The code
#include<iostream>
# include <vec tor>
class Counter {
private:
std::vector<int> steps;
public:
std::vector<char> days ■ ( *M’, ’T*,'W',*T*,• F’,'S', 'S' };
Counters get_weekly_steps();
double get_total();
};
int mainf) (
std::cout « "Welcome to the Weekly Step Counter program!\n\n"
« "Please enter your steps for the week:\n";
Counter steps;
double total ■ steps.get_weekly_steps().get_total(); // chaining
double avg ■ total I steps.days.size();
std::cout « "Total steps for the week: " << total « '\n'
« "Average daily steps: " << avg << "\n\n";
}
Counters Counter::get_weekly_steps() {
int daily_steps;
for (char day : this->days) (
std::cout << day « ": ";
std::cin >> daily_steps;
this->steps.push_back(daily_steps); // this-> is optional
}
std::cout « '\n';
return ‘this; // return a self-reference
}
double Counter::get_total() (
double total ■ 0;
for (int s : this->steps) ( // this-> is optional
total += s;
}
return total;
}
Description
• When a program starts, the system sets aside memory for the program code,
global and local variables, and functions. It also sets aside memory' that can be
allocated as the program runs.
• Dynamic memory is memory' that’s allocated at runtime. The free store is a type
of dynamic memory.
• Unlike stack memory, free store memory is not automatically deallocated when
the code goes out of scope. Instead, the program must manually deallocate it.
Description
• You can use the new keyword to allocate free store memory. This keyword
returns a pointer to the newly allocated memory.
• You can use the delete keyword to deallocate a single object, and you can use
the delete keyword followed by square brackets ([]) to deallocate an array.
// destructor
-MyContainer() (
deleted elements; // deallocate memory
cout << "memory deallocated for MyContainer object\n";
}
};
The console
mainf) function starting...
memory allocated for MyContainer object
mainf) function ending...
memory deallocated for MyContainer object
Description
• When you create an object on the stack, the object’s constructor runs when the
object is created and its destructor runs when it goes out of scope.
• RAII (Resource Acquisition Is Instantiation') is a programming pattern that
takes advantage of the way the stack creates and destroys objects to allocate
and deallocate free store memory automatically.
• For RAH to work, the object must be created on the stack. If the object is
created on the heap, its destructor won’t be called when it goes out of scope.
• RAII can also be used to work with other system resources such as file handles.
• Some C++ compilers don't call destructors (sometimes called unwinding
the stack) when an unhandled exception is thrown. To account for this, you
can code a try/catch statement around all code in the main() function of your
program that handles all exceptions.
// destructor
-MyContainer() {
deleted elements;
)
// copy constructor
MyContainer(const MyContainerft tocopy) {
elements ■ new int[size];
for (int i ■ 0; i < size; ++i) { // deep copy
elements[i] ■ tocopy.elements[i];
)
)
Description
• When you define a class, the compiler creates default copy constructor and copy
assignment operator functions. In classes that use pointers, these functions copy
the pointers, not the underlying objects. This is known as making a shallow copy.
• When you use RAIL you must define a constructor and destructor. With the
Rule of Three, if you define (1) a destructor, you should also define (2) a copy
constructor and (3) a copy assignment operator. With RAII. the copy constructor
and assignment operator must copy the underlying objects, not the pointers. This
is known as making a deep copy.
// move constructor
MyContainer(MyContainer&& tomove) {
elements - tomove.elements; // assign pointer to data
tomove.elements - nullptr; // remove pointer to data
}
Description
• C++11 and later provide move semantics that enable you to move existing data.
This is more efficient than copying existing data.
• The rvalue reference declarator (&&) indicates that the data of an object can be
moved.
• To implement move semantics in your classes, you need to code a move
constructor and a move assignment operator.
• When you use RAIL you must define a constructor and destructor. With the
Rule of Five, if you define (1) a destructor, you should also define (2) a copy
constructor, (3) a move constructor, (4) a copy assignment operator, and (5) a
move assignment operator.
Description
• With C++ 11 and later, you can use smart pointers to manage memory automatically.
• The weak_ptr type is often used with the shared_ptr type to avoid circular references.
• There is also an auto_ptr type, but it was deprecated in C++11.
The console
The Sensor Analysis program
Enter the number of days you'd like to analyze: 5
class HeapArray {
private:
int array_size;
int* arr ■ nullptr;
public:
// constructors
HeapArray(const int);
HeapArray(const HeapArray& tocopy); // copy constructor
HeapArray(HeapArray&& tomove); // move constructor
// destructor
-HeapArray();
// assignment operators
HeapArray& operator® (const HeapArray& tocopy); // copy assignment
HeapArray& operator® (HeapArray&& tomove); // move assignment
// subscript operator
int& operator[] (int i);
// Member functions
int size() const;
int* begin();
int* end();
};
#endif /* MURACH_HEAPARRAY_H */
//---------CONSTRUCTORS-----------//
HeapArray::HeapArray(const int size) {
array_size ■ size; // set array size property
arr ■ new int[array_size]{0}; // allocate memory on heap
}
array in the private data member named arr. In addition, this code initializes all
the elements of the array to a value of zero.
Part 2 of figure 17-15 presents the rest of the source file for the HeapArray
class. It starts with the definition for the copy constructor function. This
constructor accepts a constant HeapArray object by reference. Then, it uses the
array_size value of the HeapArray parameter to set its own array_size value.
Next, it uses that array_size value to allocate an array of integers on the free
store and stores the pointer to the array in the arr data member.
Unlike the previous constructor, the copy constructor doesn’t use an initial
ization list to initialize the array. Instead, it initializes the array by looping
through the elements in the HeapArray parameter and copying each value to its
own array. This works because you can use the subscript operator with a pointer
to an array to access that array’s elements.
The definition for the move constructor function accepts a HeapArray rvalue
reference. As with the copy constructor, it starts by using the array_size value of
the parameter object to set its own array_size value. Then, it assigns the param
eter arr member to its own arr member. Next, to make sure that only one pointer
points to that array, it assigns a null pointer to the parameter arr data member
and sets the parameter's array_size value to zero. This shows that the HeapArray
parameter object has no elements after the move operation is completed.
The definition for the destructor function deallocates the free store memory
that the arr data member points to. This works even when the arr member has
been set to null by a move constructor. That’s because you can call delete on a
null pointer without causing any problems.
The definition for the copy assignment operator works similarly to the copy
constructor. It accepts a constant HeapArray object by reference. Then, it uses
this HeapArray parameter to set its own array_size data member, to allocate an
array on the free store, and to copy the elements of the parameter array to the
new array. The difference is that the copy assignment operator uses a temporary
pointer variable named new_arr to provide for exception safety.
After the elements are copied, the function deallocates the free store memory
pointed to by the arr data member. Then, it assigns the pointer to the newly
allocated memory to the arr member. Finally, it returns a self-reference.
The definition for the move assignment operator accepts a HeapArray rvalue
reference. This function starts by making sure it doesn’t try to perform a move
operation on itself. To do that, this code checks to make sure the HeapArray
parameter is not equal to the pointer to the current object. Then, it uses the
array_size value of the parameter object to set its own array_size value, it
deallocates the memory pointed to by its own arr data member, and it assigns the
array pointer from the parameter object to its own arr member. Next, it empties
the HeapArray parameter by setting its arr member to null and its array_size
member to zero. Finally, it returns a self-reference.
The last four definitions in the source file should be easy for you to
understand. The subscript operator accepts an index value and returns the
array element at that index. The size() function returns the value of the private
array_size data member. The begin() function returns the private arr data
member, which is a pointer to the first element. And the end() function returns a
pointer that points one past the last element. To do that, this function uses pointer
arithmetic, which is presented in chapter 12 and reviewed later in this chapter.
Chapter 17 How to work with memory and pointers 667
II -------- DESTRUCTOR---------- //
HeapArray::-HeapArray() {
delete[] arr; II deallocate memory
)
Part 3 of figure 17-15 presents the code for the main.cpp file for the Sensor
Analysis program. To start, this code includes the header files it needs. That
includes the cstdlib and ctime headers it needs to generate random numbers to
simulate the sensor data, as well as the HeapArray header file that it needs to
work with HeapArray objects.
After a using directive for the std namespace, this code declares a function
named load_sensor_data(). This function accepts a HeapArray object by refer
ence and doesn’t return any data. From this, you can infer that this function
updates any HeapArray objects that are passed to it.
The main() function starts by displaying a welcome message on the console.
Then, it gets the number of days to analyze from the user. Next, it defines a
constant that stores the number of seconds in a 24-hour day (86400). and it
calculates the total number of seconds for the number of days entered by the user.
The code then creates a HeapArray object named data and passes the total
number of seconds to its constructor. This creates a dynamic array on the free
store with one element for each second. Then, the code passes the HeapArray
object to the load_sensor_data() function. This loads the sensor reading for each
second into the HeapArray object.
Next, the code uses STL algorithms to analyze the sensor readings. First, it
calls the accumulateO algorithm to get the total of all the readings. Second, it
calls the min_element() algorithm to get the lowest reading. Third, it calls the
max_element() algorithm to get the highest reading. Each of these algorithms
uses the pointers returned by the begin() and end() functions of the HeapArray
object to do its work.
The main() function ends by displaying the results of the sensor analysis. It
uses the size() function of the HeapArray object to display the number of sensor
readings and also to calculate the average of the readings. In addition, it derefer
ences the iterators returned by the min_element() and max_element() algorithms
to display the lowest and highest readings.
The definition of the load_sensor_data() function starts by seeding the
random number. Then, it defines an int variable named adjust. This variable is
initialized to a random number between 10 and 70 and is used just to make the
simulated readings more interesting. Without it. the low reading is almost always
1. the high is almost always 100. and the average is always approximately 50.
Finally, this function ends by looping through each element in the
HeapArray parameter that’s received by reference. Within the loop, it generates
a random number between 1 and 1 (X) that’s stored in an int variable named num.
Then, it uses the conditional operator to adjust that random number and assign
it to the current array element. It adjusts the number by checking if it’s less than
the value in the adjust variable. If it is. the function adds the adjust value to the
random number.
As you review this function, note that the loop uses the size() function
and subscript operator of the HeapArray object. Specifically, it uses the size()
function to determine when to end. and it uses the subscript operator to update
each element. Also, note that you could easily update this loop to read numbers
from a file or other data source without having to change any of the code in the
main() function.
Chapter 17 How to work with memory and pointers 669
int main() (
cout << "The Sensor Analysis program\n\n";
int num_days ■ 0;
cout << "Enter the number of days you’d like to analyze: ";
cin >> num_days;
cout « endl;
HeapArray data(total_seconds);
load_sensor_data(data);
int num ■ 0;
for (int i ■ 0; i < data.sizeO; ++i) (
num ■ rand() % 100 + 1; // number between 1 - 100
data[i] - (num < adjust) ? num + adjust : num; // adjust number
}
}
cout << (pl ■■ p2); // displays 1 - pointers point to the same object
cout « (pl ■■ s); // displays 0 - values match but objects are different
cout << (*pl ■■ *s); // displays 1 - values match
Description
• You can use the equality operator («■) to compare pointers of the same data
type. This checks whether the pointers point to the same memory address, not
whether the objects store the same value.
• Pointer arithmetic allows you to use arithmetic operators to move a pointer
forward or backward along a sequence of bytes to point to a different memory
address. You usually do this only with pointers that point to arrays.
• A void pointer, also known as a generic pointer, can point to an object of any data
type. In most cases, you should avoid using void pointers because they often lead to
errors. However, you may encounter them when working with legacy C++ or C code.
• You can compare void pointers, pass them to a function, and return them from
a function.
• You can’t dereference void pointers or use pointer arithmetic with them.
However, if you want to perform these operations on a void pointer, you can
cast it to a pointer to a specific data type.
Description
• When working with inheritance, a pointer to a superclass can store the memory
address of a subclass object.
• When working with a pointer to a superclass, you can only call members of the
superclass, not members of the subclass. However, if you call a virtual member
of the superclass, C++ uses polymorphism to call the overridden function of the
subclass.
Description
• You can combine pointers, references, and constants to create complex
compound types.
• It can be easier to understand a complex compound type if you read it from
right to left.
Perspective
Now that you’ve completed this chapter, you should have a good under
standing of how pointers work. However, there’s more to learn about pointers
than what’s presented in this chapter. For example, this chapter didn’t present
a common pattern called PIMPL (Pointer IMPLementation) that’s often used
with interfaces and libraries. However, this chapter should provide a solid
foundation for you to research these more advanced concepts on your own. It
should also prepare you for chapter 19. which shows you how to create your
own custom containers, iterators, and algorithms.
Terms
memory address free store
hexadecimal (hex) notation heap memory (heap)
address of operator memory leaks
pointer variable (pointer) heap corruption
indirection operator RAII (Resource Acquisition Is
dereference a pointer Instantiation)
compound type unwinding the stack
member access operator shallow copy
alias deep copy
self-reference Rule of Three
function chaining move semantics
dynamic memory rvalue reference
storage rvalue reference declarator
code storage Rule of Five
static storage smart pointers
automatic storage raw pointer
stack storage pointer arithmetic
stack memory void pointer
call stack (stack) generic pointer
stack overflow complex compound type
Summary
• Each byte of memory is a physical location that’s represented by a number,
also called a memory address.
• The notation for memory addresses varies by system, but it’s common for
systems to use hexadecimal, or hex, notation.
• You can use the address of operator (&) to get the memory address of an
object. This returns the address of the first byte of the location of the object
in memory.
• A pointer variable, or pointer, is an object that contains the memory address
of another object. In other words, a pointer points to another object.
Chapter 17 How to work with memory and pointers &7T
• You can use the indirection operator (*) to dereference a pointer. This allows
you to access the value of the object that a pointer points to.
• You can use the member access operator (->) to access public data members
and functions of the object that a pointer points to.
• A reference is another name, or alias, for the object it refers to.
• Everj' object has a pointer named this whose value is its own memory
address.
• When coding member functions of a class, you can return a reference to the
current object. This is known as a self-reference, and it’s useful for creating
member functions that can be chained together.
• When a program starts, the system sets aside memory, also known as
storage, for that program.
• Code storage is memory that’s allocated for the program code itself.
• Static storage is memory that’s allocated for global variables.
• Automatic storage is memory that’s allocated for local variables and
functions. This memory uses a last-in-first-out (LIFO) stack to manage
local variables and functions. As a result, it’s also called stack storage, stack
memory, the call stack, or the stack.
• Stack overflow occurs when a program runs out of stack memory.
• Dynamic memory is memory that’s allocated and deallocated manually
at runtime. Free store storage, also known as the free store, is a type of
dynamic memory. This memory is also called heap memory or the heap.
• Failure to properly deallocate free store memory can lead to memory leaks
or heap corruption.
• RAII (Resource Acquisition Is Instantiation) is a programming pattern that
takes advantage of the way the stack creates and destroys objects to allocate
and deallocate free store memory automatically.
• Some C++ compilers don’t call destructors (sometimes called unwinding
the stack) when an unhandled exception is thrown. To account for this, you
can code a try/catch statement around all code in the main() function of your
program that handles all exceptions.
• A shallow copy copies a pointer to an object. A deep copy copies the object
that a pointer points to.
• The Rule of Three states that if you need to code (1) a destructor. (2) a copy
constructor, or (3) a copy assignment operator, you should code all three.
• C++11 and later provide move semantics that enable you to move existing
data. This is more efficient than copying existing data.
• An rvalue reference identifies an object that can be moved and is declared
with the rvalue reference declarator (&&).
678 Section 4 Skills for legacy and generic programming
• The Rule of Five states that if you need to code (1) a destructor (2) a copy
constructor. (3) a move constructor, (4) a copy assignment operator, or (5) a
move assignment operator, you should code all five.
• With C++ 11 and later, you can use smart pointers to manage memory
safely.
• Pointer arithmetic allows you to use arithmetic operators to move a pointer
forward or backward along a sequence of bytes to point to a different
memory address. You usually do this only with pointers that point to arrays.
• A void pointer, also known as a generic pointer, can point at an object of any
data type.
• You can combine pointers, references, and constants to create complex
compound types.
2. Review the main.cpp file to see that it contains the required header files and
a using statement for the std namespace. In addition, the main() function
defines a constant for the number of months and a built-in array of strings
for the month name abbreviations. It also contains the code that displays the
messages on the console when the program starts.
Chapter 17 How to work with memory and pointers 679
Define the weights array and the functions that work with it
3. Define another built-in array named weights that can store a value for each
month, and initialize the values to 0.0.
4. Declare a function named get_monthly_weights() that accepts a pointer to the
weights array, a pointer to the months array, and the number of months and
doesn’t return a value.
5. Declare another function named get_total() that accepts a pointer to the
weights array and the number of months and returns a double type.
6. Code the definition for the get_monthly_weights() function. This function
should prompt the user for a weight for each month by displaying the month
abbreviation in the months array, and then store the values that are entered in
the weights array. Be sure to check that neither the months or weights array
contains a null pointer before using it.
7. Code the definition for the get_total() function. This function should calcu
late the total of all the values in the weights array. Be sure to check that the
weights array doesn’t contain a null pointer before using it.
Modify the main() function to use the weights array
8. Add code to the main() function that calls the get_monthly_weights()
function.
9. Add code to the main() function that calls the get_total() function.
10. Add code to the main() function that calculates and displays the average
weight.
11. Use the max_element() and min_element() algorithms to get pointers to
the elements in the weights array that contain the maximum and minimum
weights. Then, display the values of those elements.
12. Modify the mainf) function so it uses the accumulateO algorithm to get the
total of the weights instead of calling the get_total() function.
3. Review the HeapArray.cpp file to see that its constructor and destructor use
RAII to allocate and deallocate memory for an array on the heap. Notice also
that it includes a subscript operator and size(), begin(), and end() member
functions that are used by the Step Counter program.
4. Run the program and enter the number of steps for each day of the week.
When you do. an error will occur because the default copy operation for the
HeapArray object copies the pointer to the array, not the array itself.
Implement the Rule of Three
5. Open the HeapArray.h file and add the declaration for a copy constructor that
accepts a constant reference to a HeapArray object.
6. Also in the HeapArray.h file, add the declaration for a copy assignment
operator that accepts a constant reference to a HeapArray object and returns a
reference to a HeapArray object.
7. Open the HeapArray.cpp file and add the definition for the copy constructor.
This constructor should create a HeapArray object with a pointer to an array
that's a copy of the array in the HeapArray object that’s passed to it.
8. Also in the HeapArray.cpp file, add the definition for the copy assignment
operator. This should work like the copy constructor, but it should store the
new array in a temporary variable to provide for exception safety and it should
return a self-reference. When you code this function, be sure to deallocate the
old memory for the array before you assign a pointer to the new memory.
9. Run the program again. This time, it should work correctly.
18
How to work
with templates
In chapters 10 and 11, you learned how to use the containers and algorithms
of the Standard Template Library (STL). These containers and algorithms use
a feature know as templates to allow them to be flexible enough to work with
different data types. That's why it’s called the Standard Template Library. In
this chapter, you’ll learn how to use templates to code your own containers and
algorithms that can work with multiple data types.
Some of the examples in this chapter use built-in arrays as described in
chapter 12 and pointers as described in chapter 17. As a result, you should
read those chapters before reading this chapter. Otherwise, you may find it
difficult to understand parts of this chapter.
An overloaded function
In chapter 12, you learned how to code a linear_search() function like
the first one shown in figure 18-1. This function implements the linear search
algorithm to find a specified int in an array of int values. Unfortunately, this
function only works with the int type. However, you could overload this function
to get it to work correctly with different data types. For example, this figure
shows how to overload this function so it also works with the char type.
When you overload a function, the compiler must compile all of the function
overloads. That’s true even if one or more overloads aren't used by the program.
For example, if you included both overloads of the linear_search() function
shown in this figure in a header file but a program only worked with an array
of int values, the program would still compile the overload for working with an
array of char values. Obviously, this isn’t as efficient as it could be. Instead of
using function overloads, then, you can use function templates to provide for
multiple data types.
Chapter 18 How to work with templates 683
Description
• You can overload a function to get it to work correctly with different data
types.
• When you overload a function, you must code one function overload for each
data type.
• When you overload a function, the compiler must compile all of those
functions, even if they aren’t used by the program.
A function template
Figure 18-2 begins by showing the linear_search() function presented in
the previous figure after it has been converted to a function template. A function
template allows a function to work correctly with any data type that supports
the operators and functions that are used by the template. In this case, the
linear_search() function uses the equality operator (■■), so this function will
work with any data type that supports this operator. As a result, this function
works correctly with most data types, including the built-in data types and
classes.
If you review the code for the function template, you’ll notice that the
function is prefixed by a line of code that defines the function as a template with
a type parameter named T. Then, the function uses this type parameter to specify
the type for the elements of the array and the value to find. Other than that,
the code for the linear search is the same as the code shown in the overloaded
functions in the previous figure. However, the code doesn’t have to be duplicated
for each data type.
After the function template, this figure shows code that calls the
linear_search() function template three times, each time using a different
data type. With each call, the compiler generates a function with the
appropriate data types and compiles that function. Specifically, the first
call generates the first overloaded function presented in the previous
figure that works with the int type. The second call generates the second
overloaded function presented in the previous figure that works with the
char type. And the third call generates another function that works with
the double type.
Since the function in the previous figure didn’t provide an overload for
working with the double type, that function isn’t capable of working with the
double type. As a result, this clearly shows that using a function template is more
flexible than overloading a function.
In addition, because the compiler only generates the functions that are used
by the program, function templates can result in smaller executable files. As a
result, using a function template is typically more efficient than overloading a
function. The compiler also generates these functions at compile time, not at
runtime, so there’s no performance penalty for using templates.
As you gain experience with C++, you’ll find that function templates are
often used to allow algorithms like the linear search algorithm to work with
multiple data types instead of just a single data type. In general, templates
provide a way to convert a function that uses a specific type such as the int type
to a function that uses a generic type. As a result, working with templates is also
referred to as generic programming or generics.
Chapter 18 How to work with templates 685
A function template
templat®<typename T>
int linear_search(const T arr[], int counter, T value_to_find) (
for (int i ■ 0; i < counter; ++i) (
if (arr[i) ■■ value_to_find) (
return i; // value found - return index
}
)
return -1; // value not found - return -1
)
Description
• You can code a single function template to get a function to work correctly with all
data types that support the operators and functions used by the function template.
• When the compiler encounters a call to a function template, it generates a
function with the appropriate data types and compiles that function.
• Function templates are often used to allow algorithms to work with multiple
data types instead of just a single data type.
• Function templates are typically more flexible than function overloading.
• Function templates often reduce code duplication when compared to function
overloading.
• Since the compiler generates functions from a template at compile time, not at
runtime, there’s no performance penalty for using templates.
• Since the compiler only generates functions that are called by the program, function
templates often result in smaller executable files than function overloading.
• Templates provide a way to convert a function that uses a specific type to a
function that uses a generic type. As a result, working with templates is also
referred to as generic programming or generics.
How to explicitly specify the type when you call a function template
display<int>(1);
display<string>("Hammer");
display<double>(19.99);
Description
• To create a function template, you code a template prefix before the function. This
prefix consists of the template keyword followed by a pair of angle brackets (<>).
• Within the angle brackets, you specify a type parameter that the function
template can use anywhere in its declaration or definition.
• The typename and class keywords that you can use to specify a type parameter
are synonymous.
• It’s a common convention to use a single capital letter such as T for type for the
name of a type parameter.
• The compiler can typically deduce the correct data type when you call a
function template. However, you can also explicitly specify the data type in
angle brackets.
Figure 18-3 How to code a function template with one type parameter
688 Section 4 Skills for legacy and generic programming
Description
• You can code a template prefix that defines multiple type parameters. To do
that, use commas to separate the parameters.
Figure 18-4 How to code a function template with multiple type parameters
690 Section 4 Skills for legacy and generic programming
Description
• You can use a class template to get a class to work with different type parameters.
• To code a class template, code the template prefix before the class definition.
Then, you can use the type parameter in your class definition.
templatectypename T>
class HeapArray {
private:
int array_size;
T* arr;
public:
// declarations
HeapArray(const int); // constructor
HeapArray(const HeapArray& tocopy); // copy constructor
HeapArray(HeapArray&& tomove); // move constructor
-HeapArray (); // destructor
HeapArray& operators(const HeapArray& tocopy); // copy assignment
HeapArrayft operators(HeapArray&& tomove); // move assignment
// inline definitions
T& operator[](int i) { return arr[i]; } // subscript operator
int size() const { return array_size; } // member functions
T* begin() ( return arr; }
T* end() ( return arr + array_size; }
//---------CONSTRUCTORS-----------//
templatectypename T>
HeapArray<T>::HeapArray(const int size) {
array_size ■ size; // set array size property
arr - new T[array_size]; // allocate memory on heap
for (int i ■ 0; i < array_size; i++) ( // initialize array values
arr[i] - T();
}
templatectypename T>
HeapArraycT>::HeapArray(const HeapArrayft tocopy) { // COPY
array_size ■ tocopy.array_size; // copy size
arr - new T[array_size]; // allocate memory on heap
for (int i ■ 0; i c array_size; i++) ( // copy array values
arr[i] ■ tocopy.arr(i);
}
templatectypename T>
HeapArraycT>::HeapArray(HeapArray&& tomove) { // MOVE
array_size ■ tomove.array_size; // copy size
arr ■ tomove.arr; // reset array pointer
tomove.array_size =0; // empty passed in container
tomove.arr ■ nullptr;
}
Except for the template prefix and the HeapArray<T> qualification, the code
for the destructor and the move assignment operator is also the same as the code
from the previous chapter. However, the copy assignment operator uses the type
parameter named T to specify the type of the new array that it creates.
Chapter 18 How to work with templates 695
//---------ASSIGNMENT OPERATORS-----------//
templatectypename T>
HeapArray<T>& HeapArray<T>::operator= (const HeapArrayft tocopy) <
// COPY
array_size = tocopy.array_size; // copy size
T* new_arr ■ new T[array_size]; // allocate memory on heap
for (int i ■ 0; i < array_size; ++i) ( // copy array values
new_arr[i] ■ tocopy.arr(i);
}
delete[] arr; // deallocate old memory
arr ■ new_arr; // assign new memory
return *this; // return a self-reference
templatectypename T>
HeapArray<T>& HeapArray<T>::operator= (HeapArray&& tomove) { // MOVE
if (this !■ &tomove) { // don't move if passed self
array_size ■ tomove.array_size; // copy size
deleter) arr; // deallocate old memory
arr - tomove.arr; // reset array pointer
tomove.array_size ■ 0; // empty passed in container
tomove.arr ■ nullptr;
#endif // MURACH_HEAP_ARRAY_H
Description
• Some compilers require a class template to be declared and defined within a
single compilation unit. As a result, it’s generally considered a best practice to
put all code for the class template in a header file.
• You can use function templates to define the member functions.
• You can often improve efficiency by coding member functions inline.
int main() {
HeapArray<int> nums(ll);
for (int i ■ 0; i < nums.size(); ++i) {
nums[i] ■ (i ♦ 1) * 10;
}
cout << "First number: ■ << nums[0] « endl;
cout << "Second number: " « nums[l] << endl;
HeapArray<string* words(3) ;
words[0] ■ "hello";
words[1] ■ "good";
words[2] ■ "buddy";
cout << "First word: ■ « words[0] << endl;
cout << "Second word: " « words[1] « endl;
}
The console
First number: 10
Second number: 20
First word: hello
Second word: good
Description
• When you create an object from a class template, you must code angle brackets
(<>) after the name of the class. Within those brackets, you must code the data
types required by the class template.
• The process of generating classes from a class template is called specialization.
For example. Heap Arrayci nt> and HeapArray<string> are specializations of
HeapArray. Similarly, vector<char> and vector<Movie> are specializations of
vector.
// declaration
templatectypename T>
void display (HeapArrayc*T>&) ;
int main() {
HeapArray<int> nums(11);
for (int i ■ 0; i < nums.size(); ++i) (
nums(i) ■ (i ♦ 1) * 10;
)
display(nums); // call function template
// definition
templatectypename T>
void display(HeapArraycT>& arr) (
for (T elem : arr) {
cout cc elem cc •
|
cout cc endl;
|
The console
10 20 30 40 50 60 70 80 90 100 110
hello good buddy
Description
• You can use a function template to work with a class template. To do that, the
function template can accept arguments of a class template object or return an
object created from a class template.
Figure 18-8 How to code a function template that works with a class template
700 Section 4 Skills for legacy and generic programming
The console
The Sensor Analysis program
The code
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <algorithm>
#include <numeric>
#include "HeapArray.h
int main() {
cout << "The Sensor Analysis program\n\n";
int num_days ■ 0, total_seconds ■ 0;
cout << "Enter the number of days you'd like to analyze: ";
cin >> num_days;
cout < < endl;
Perspective
Now that you’ve completed this chapter, you should understand how to
work with templates. In particular, you should understand how to define a class
template for a container or a function template for an algorithm. In the next
chapter, you’ll learn how to use templates to define other custom classes and
algorithms. In particular, you’ll learn how to define your own class templates
for a custom vector and a custom list, and you’ll learn how to define your own
algorithms that work with those containers or the STL containers.
Terms
overload a function
function template
generic programming
generics
template prefix
type parameter
class template
specialization
Summary
• You can overload a function to get it to work correctly with different data
types.
• You can code a function template to get a function to work correctly with
all data types that support the operators and functions used by the template.
This is more flexible than overloading a function.
• Templates provide a way to convert a function that uses a specific type such
as the int type to a function that uses a generic type. As a result, working
with templates is also referred to as generic programming or generics.
• To create a function template, you code a template prefix before the function.
This prefix consists of the template keyword followed by a pair of angle
brackets (<>).
• Within the angle brackets of a template prefix, you can specify a type
parameter that the function template can use in its declaration or definition.
• You can use a class template to get a class to work correctly with different
type parameters.
• The process of generating classes from a class template is called
specialization.
Chapter 18 How to work with templates 703
Temperatures: 75 64 92 88 57
Sorted temperatures: 57 64 75 88 92
2. Open the main.cpp file and review its code. Note that this program includes
the bubble_sort() function from chapter 12 that sorts an array of int values.
Also note that this function uses the greater than operator (>).
3. Run the program to see that it displays the temperatures, the sorted tempera
tures, and the prices as shown above.
4. Convert the bubble_sort() function to a function template. In addition to
adding a type parameter, be sure to change the name of the variable that
refers to an individual element to something more generic.
5. Modify the code in the main() function so it uses the bubble_sort() function
to sort the array of prices. Then, print the sorted array as shown above. This
shows that the bubble_sort() function can now be used with the double data
type, since is supports the greater than operator.
3. Add the declaration for a function named linear_search() that accepts the
value to be found for any type that supports the equality operator.
4. Add the definition for the linear_search() function. Because this function will
use the type parameter, be sure to include the template prefix. Also be sure to
qualify the name of the function with the name of the class.
5. Add code to the linear search function that loops through the array in the
elements data member using the array_size data member, returns the index of
the value if it’s found, and returns -1 if it isn't found.
6. Open the main.cpp file and add code to the main() function that uses
the linear_search() function to get the index of the first element with the
minimum sensor reading and the index of the first element with the maximum
sensor reading.
7. Modify the code that displays the minimum and maximum values so it also
displays the second where these values first occur.
Numbers array: 10 20 30 40 50 60
The number 40 was found at index 3
How to create an alias with the using keyword (C++11 and later)
using size_type - unsigned int;
Description
• The STL containers define several common types, sometimes known as member
types. Each type of container provides its own member types. These types make
it possible to write generic code that can work with any container type.
• With C++11 and later, you can create an alias for a member type with the using
keyword. Prior to C++11. it was common to use the typedef keyword to create
an alias for a member type.
// Constructors
MyVector() ( // default - inline
elem ■ nullptr;
array_size ■ 0;
space =0;
}
MyVector(const MyVectorfc tobecopied); // copy constructor
MyVector(MyVector&fc tobemoved); // move constructor
MyVector(int initial_size); // set initial size
MyVector(std::initializer_list<T> list); // initialization list
// Destructor
-MyVector();
// Assignment operator
MyVectorfc operators(const MyVectorfc tobecopied); // copy assignment
MyVectorfc operators(MyVector&ft tobemoved); // move assignment
// Subscript operator
reference operator[](int i) ( return elem[i]; } // inline
const_reference operator[](int i) const { return elem[i]; }// inline
// Member functions
size_type size() const { return array_size; } // inline
size_type capacity() const { return space; } // inline
// move constructor
template <typename T>
MyVector<T>::MyVector(MyVector&ft tobemoved) {
array_size ■ tobemoved.array_size; // copy size
space - tobemoved.space; // copy capacity
elem ■ tobemoved.elem; // point to existing elements
// destructor
template <typename T>
MyVector<T>::-MyVector() {
deleted elem; // OK even if elem is nullptr
}
// return a self-reference
return *this;
)
// move assignment
template <typename T>
MyVector<T>& MyVector<T>::operators(MyVector&& tobemoved) {
if (this !■ &tobemoved) (
array_size - tobemoved.array_size; // copy size
space ■ tobemoved.space; // copy capacity
// return a self-reference
return *this;
)
// reserve()
template ctypename T>
void MyVector<T>::reserve(int new size) {
if (new_size <■ space) return; // never decrease allocation
T* new_elem ■ new T[new_size]; // allocate new space
for (int i ■ 0; i < array_size; ♦♦i) ( // copy old elements
new_elem[i] ■ elem[i);
}
deleted elem; // deallocate old space
elem ■ new_elem; // assign new space
space ■ new_size; // set new capacity
}
11 push_back()
template ctypename T>
void MyVectorcT>::push_back(T item) {
if (space ■■ 0) // if array empty,
reserved); // start with capacity of 1
else if (array_size ■■ space) // if array full,
reserve(2 * space); // add more capacity (doubling is common)
// resized
template ctypename T>
void MyVectorcT>::resize(int new_size) {
reserve(new_size); // set new capacity (won't make smaller)
for (int i ■ array_size; i < new_size; ++i) { // init new elements
elem[i] ■ T();
}
The console
The code
ftinclude <iostream>
#include <string>
#include <algorithm>
#include "MyVector.h"
int mainf) (
cout << "The Task Manager program\n";
cout << "Enter tasks and enter 'x* when done.\n\n";
MyVector<string> tasks;
string task -
while (true) (
cout << "Enter task: ";
getlinefcin, task);
if (task — "x") (
break ;
}
else {
tasks.push_back(task);
}
}
cout « endl;
sort(tasks.begin(), tasks.end());
Description
• This Task Manager program uses the custom MyVector object to store tasks
entered by the user. Because MyVector is STL compliant, this program can use
it with the STL sort() algorithm.
The five iterator traits needed for an iterator class to be STL compliant
Trait Description
iterator_category A tag that indicates the functionality the iterator provides.
value_type The data type of an element.
difference_type The type for the difference between iterators.
reference The reference type of an element.
pointer The pointer type of an element.
// rest of class
};
// rest of class
};
Description
• When you create your own container, you can use pointers as iterators as shown earlier
in this chapter, or you can define a custom iterator class as shown in this figure.
• If you want your custom iterator class to be able to be used by STL algorithms,
you need to implement five iterator traits.
• You can create a customer iterator by inheriting the std::iterator class. To do
that, you pass the five iterator traits as part of the class template, with the
last three being optional. This approach is convenient but leads to code that’s
difficult to read and maintain. As a result, it was deprecated in C++17.
Description
• The Link structure represents an element in a list container that can link to the
previous and next elements.
template<typename T>
class Mylterator {
private:
Link<T>* current ■ nullptr;
public:
// iterator traits
using iterator_category ■ std::bidirectional_iterator_tag;
using value_type ■ T;
using difference_type ■ std::ptrdiff_t;
using reference ■ T&;
using pointer ■ T*;
// constructor
Mylterator(Link<T>* curr) {
current ■ curr;
}
// helper function
void initialize() {
head - nullptr;
tail - nullptr;
list_size - 0;
)
public:
// Member types
using size_type - std::size_t;
using value_type ■ T;
using reference = T& ;
using iterator - MyIterator<T>;
// Constructor
MyList() ( initialize(); } // inline
// Destructor
-MyListO;
// Member functions
size_type size() const ( return list_size; } // inline
void push_back(T);
void pop_back();
reference at(int index);
void remove(T);
Description
• The MyList class stores pointers to Link objects that represent the first and last
elements in the list.
• The iterator defined by MyList is of type MyIterator<T>. As a result, the
begin() and end() functions return MyIterator<T> objects.
• To save space and keep the focus on working with iterators, the MyList class
shown here doesn’t implement the Rule of Five. As a result, you can’t pass a
MyList object by value. To fix this, you should implement the Rule of Five, or
at least the Rule of Three.
if (lhead) { // if empty
head ■ new_link;
tail ■ new_link;
}
else ( // if elements exist
auto curr_tail ■ tail; // store current tail link
// pop_back()
template <typename T>
void MyList<T>::pop_back() {
if (tail) { // if elements exist
if (head ■■ tail) { // if one element
delete head;
initialize();
}
else { // if multiple elements
Link<T>* todelete ■ tail;
tail ■ tail->prev;
tail->next ■ nullptr;
delete todelete;
--list_size;
}
}
}
The at() function accepts an int argument that specifies the index of the
element, and it returns the reference member type of the MyList<T> object. To
inform the compiler that the return type is a type, the function definition begins
with the typename keyword. Otherwise, the compiler might not recognize the
reference member type as a type.
In the body of this function, the code begins by checking whether the index
parameter is out of bounds. If so. the function throws an out_of_range exception.
Otherwise, the function returns a reference to the element at that index.
Since the elements of MyList aren't stored in an array, the code can’t use
the subscript operator to get the element at the specified index. Instead, the code
defines a pointer to a Link object named iter. Then, it uses a for loop to iterate
through the elements in the list to the one specified by the index. Each time the
loop executes, the code modifies the iter variable so it points to the next Link
object in the list. When the iter variable points to the element at the specified
index, the loop ends. Then, the function returns the value of the element that’s
pointed to by the iter variable.
The remove() member function accepts an element of type T that might be in
the list. Then, the function removes all elements in the list that match the element
specified by the parameter. To do that, the body of the function begins by defining
a pointer to a Link object named iter and initializing it to the first element in the
list. Then, it uses a while loop to iterate through all elements in the list.
Within the loop, the code begins by checking whether the value of the iter
pointer is equal to the value of the val parameter. If it isn’t, the code simply
moves to the next link. If it is, however, the code removes the Link object that’s
pointed to by the iter pointer.
To do that, the code begins by checking whether there’s only one Link object
in the list. In that case, the code deallocates the memory for that Link object.
Then, it calls the initialize!) helper function to empty the list. Next, it uses the
break statement to end the loop since there are no more objects in the list.
However, if there are multiple links, the code does some more checking to
determine whether the element is stored in the first link of the list, the last link of
the list, or somewhere in the middle of the list. If it’s stored in the first link, this
code changes the head data member so it points to the next link of the list. Then,
it sets the prev pointer of the head link to nullptr. since this link is now the first
link in the list.
The code that’s executed if the element is stored in the last link of the list is
similar. It changes the tail data member so it points to the previous link of the
list. Then, it sets the next pointer of the tail link to nullptr.
If the element is stored in a link between the first and last links, this code
must change the previous and next links so they point to each other rather than to
the current link. To do that, the first two statements get iterators that point to the
links before and after the current link. Then, the third statement changes the next
pointer of the before link so it points to the after link, and the fourth statement
changes the prev pointer of the after link so it points to the before link.
After the pointers are rearranged, the code stores a pointer to the next link in
a temporary Link object named next. Then, it deallocates the memory for the Link
object pointed to by the iter variable, it assigns next to iter, and it decrements the
size of the list.
Chapter 19 How to code custom containers, iterators, and algorithms 729
out of range");
>
return iter->value; II return value of link
>
}
II if multiple links
rearrange connecting pointers
(iter ■■ head) ( II if first of multiple links
head ■ iter->next;
head->prev ■ nullptr;
}
else if (iter ■■ tail) { II if last of multiple links
tail ■ iter->prev;
tail->next ■ nullptr;
}
else { II if between other links
// get the links before and after the current link
auto before_iter ■ iter->prev;
auto after_iter ■ iter->next;
The console
The code
tinclude <iostream>
#include <string>
#include "MyList.h"
int main() {
cout « "The Task Manager program\n";
cout « "Enter tasks and enter 'x' when done.\n\n";
MyList<string> tasks;
string task - ■";
while (true) (
cout << "Enter task: ";
getline(cin, task);
if (task — -x") (
break;
}
else {
tasks.push_back(task);
}
)
cout « endl;
Description
• This Task Manager program uses the MyList object to store tasks, and it uses
operations provided by the Mylterator class to display the elements.
Description
• In the template for an algorithm, you can use the type name to indicate the kind
of iterator your algorithm expects. This makes it easier for other programmers to
understand how to use the algorithm.
The console
The Number Cruncher program
11 RANDOM NUMBERS: 1 1 10 26 5 29 2 17 28 26 21
11 SORTED NUMBERS: 1 1 2 5 10 17 21 26 26 28 29
Sum ■ 166 Average ■ 15
Min ■ 1 Max ■ 29
Midpoint ■ 17 Median - 17
The code
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "MyVector.h"
ftinclude <algorithm>
#include <numeric>
#include "find_midpoint.h"
int main() (
cout « "The Number Cruncher program\n\n";
sort(numbers.begin(), numbers.end());
cout « numbers.size() « " SORTED NUMBERS: ";
for_each(numbers.begin (), numbers.end(), display_int);
cout « endl;
// calculate median
double median;
if (numbers.size() % 2 !■ 0) ( // odd - median is same as midpoint
median ■ *mid_iter;
}
else { // even - median is sum of midpoint and prev divided by 2
double mid_val - *mid_iter;
double prev_val - *(--miditer);
median ■ (mid_val + prev_val) I 2;
}
cout « "Median ■ ■ « median << "\n\n";
}
Description
• The Number Cruncher program sorts the numbers in a sequence so that they’re
in ascending order. Then, it uses the find_midpoint() custom algorithm to find
the number at the midpoint of the sequence.
• When the number of elements is odd. the midpoint is the same as the median.
When the number of elements is even, the program calculates the median by
adding the midpoint to the value of the previous element and dividing by two.
Terms
member types
alias
iterator traits
Summary
• The STL containers define several commonly used types, sometimes known
as member types. These types make it possible to write generic code that can
work with any container type.
• With C++11 and later, you can create an alias for a member type with the
using keyword. Prior to C++11, it was common to use the typedef keyword
to create an alias for a member type.
• If you want to use your custom iterator class with STL algorithms, you need
to implement five iterator traits.
Chapter 19 How to code custom containers, iterators, and algorithms 739
TESTING
Init: Go to store|Feed cats|Check email|
capacity(): 3
push_back(): Go to store|Feed cats|Check email|Brush teeth|
capacity(): 6
size(): 4
at(O): Go to store
at(size-1): Brush teeth
resize(20)
capacity(): 20
size(): 20
Optional
3 Just-In-Time debugger
3 \C*e 2017*141 toolset C<86.rf4)
QC“ profiling tools
□ Wmdows 10 SOK (100.16299.0) far Desktop C+... ]
location
Description
• Visual Studio Community is a free IDE that you can use to create C++ programs. It
runs on Windows and macOS but is typically used for Windows.
• When you install Visual Studio, the Microsoft Visual C++ (MSVC) compiler is also
installed. This is the compiler that Visual Studio uses by default to compile C++
programs.
• The procedure above installs the current version of Visual Studio, which was Visual
Studio 2017 at the time of this printing. However, this book will work equally well
with later versions of Visual Studio.
• For more information about installing and using Visual Studio, you can refer to
the Visual Studio website. Chapter 1 also presents an introduction to using Visual
Studio for Windows development.
The subfolders
Folder Description
book_apps The applications that are presented throughout this book.
ex_starts The starting points for the exercises at the end of each chapter.
ex_solutions The solutions to the exercises.
Figure A-2 How to install the source code for this book
Appendix B
How to set up macOS
for this book
This appendix shows how to install the Xcode IDE that we recommend for
developing C++ applications for the macOS operating system. Then, this
appendix shows how to install the source code for this book. In addition, it
describes how this source code includes some extra code that isn’t shown in
the book that makes it easier to use Xcode to work with files that store data.
As you read this appendix, please remember that most websites are
continually updated. As a result, some of the procedures in this appendix
may have changed since this book was published. Nevertheless, these
procedures should still be good guides to installing the software. And
if there are significant changes to these setup instructions, we will post
updates on our website (www.murach.com).
Description
• Xcode Community is a free IDE that you can use to create C++ programs. It runs
on macOS and is typically used to develop applications for Apple platforms.
• When you install Xcode. the Clang compiler is also installed. This is the compiler
that Xcode uses by default to compile C++ programs.
• For more information about installing and using Xcode. you can refer to the Xcode
website.
The subdirectories
Directory Description
book_apps The applications that are presented throughout this book.
ex_starts The starting points for the exercises at the end of each chapter.
ex_solutions The solutions to the exercises.
Figure B-2 How to install the source code for this book
754 Appendix B How to set up inacOSfor this book
The directory that contains the files for most programs in this book
/Users/useraame/Documents/murach/cpp/files
The subdirectories
Directory Description
temp_manager The temps.txt file for all Temperature Manager programs.
temp_analyzer The temps.txt file for all Temperature Analyzer programs.
Description
• By default, Xcode sets the working directory for a project to a subdirectory that’s
different from the director)' for the project’s source code. As a result, if an Xcode
project uses a file to store its data, it must specify a full path to that file, not just the
filename of the file.
• In the download for this book, every Xcode project that works with data in a file includes
code that attempts to automatically set a full path to that file. If this code can’t set a full
path automatically, you can manually edit the path so it’s correct for your system.
• The code that attempts to automatically set a full path to the data file varies from
project to project, but the general idea is always the same.
• If you don’t want to use the code that automatically sets the full path to the file, you
can change the working director)' for a project by opening the project and selecting
the Product->Scheme->Edit Scheme item. Then, you can select the Run category,
check the Use Custom Working Directory box, and specify the working directory.
Figure B-3 How the source code makes it easier for Xcode projects to store data in files
Addition operator 757
| | operator. 92-93
Index - (destructor). 530-531
+ operator
addition. 49
concatenation. 66-67
- operator positive sign. 114-115
negative sign. 114-115 overloading. 620-621
sublraetion. 49 with iterators. 356-357
— operator. 114-115 +♦ operator. 114-115
with iterators. 356-357 overloading. 622-623
! operator. 92-93 with iterators. 356-357
! ■ operator. 84-85 +■ operator
#define preproeessor directive, 270-271 appending. 66-67
#endif preprocessor directive. 270-271 compound addition. 117
Wifndcf preprocessor directive. 270-271 < operator. 84-85
#include preprocessor directive. 10-11. 50-51 overloading. 624-625
header file, 270-271 « operator. 10-11. 52-53
% operator. 48-49 overloading. 626-627
%= operator. 117 with C strings. 454-455
& operator. 636-637 <■ operator. 84-85
changing the object a pointer points to. 640-641 -■ operator. 117
for passing a pointer to a function. 644-645 - operator. 189
&& (rvalue reference declarator), 660-661 »■ operator. 84-85
&& operator. 92-93 overloading. 624-625
() operator. 426-427 -> operator. 356-357
*. for defining a pointer. 638-641 for accessing a data member. 518-519
* operator. 49 lambda expression. 428-429
* indirection operator. 622-623 with pointers. 638-639
with iterators. 356-357 with the this keyword. 646-647
with pointers. 638-641 > operator. 84-85
with the this pointer. 646-647 overloading. 624-625
*■ operator. 117 >■ operator. 84-85
. operator. 70-71 » operator. 10-11. 54-55
with static members. 610-615 overloading. 626-627
with structures. 316-317 with C strings. 454-455
I operator. 49 ID array. 472-473
/ = operator. 117 2D array. 472-473
:: operator. 60-61
for accessing a static member from a class.
610-615
A
for calling a function of a superclass. 578-579 Abstract class. 588-589
for implementing a static member. 608-609 Abstraction. 404-405
with enumerations. 338-339 Access modifiers. 576-577
with getter and setter functions. 518-519 Accessor. 518-519
with namespaces. 272-273 accumulateO algorithm. 412-413
with static class members. 196-197 with built-in arrays. 470-471
with structure member functions. 330-331 with key/value pairs. 420-421
(] operator, see subscript operator Addition compound assignment operator. 116-117
| operator. 164-165 Addition operator. 49
overloading. 620-621
758 Address of operator Bug
J M
Java, 4-5 Machine language. 12-13
Join strings. 66-67 Macro. 190-191
Justify columns. 154-155 Main memory (RAM), 162
main() function. 10-11.42-44
make_pair() function (utility header file), 390-391
K make_unique() function. 663-664
Key. 386-387 mallee keyword. 654
Key/value pair. 352-353.420-421 Map. 388-395. 420-421
map. 388-389 initializing. 388-391
Keyword. 44-45 map container. 352-353
map header tile. 388-389
Map of vectors. 396-397
L max() member function (limits), 146-147. 196-197
Lambda expression. 428-430 max_element() algorithm. 412-413
Last-in. first-out (LIFO) access (stack), 384-385. Member, see data member
652-653 Member access operator. 356-357
Last-in. first-out (LIFO) container. 352-353 for accessing a data member. 518-519
Late binding. 582 with pointers. 638-639
left manipulator (iostream). 153-155 with the this keyword. 646-647
length!) member function (string), 216-217 Member function
Less than operator. 84-85 calling, 70-71
overloading. 624-625 class. 524-525
Less than or equal operator. 84-85 static. 608-615
LIFO access (stack). 384-385. 652-653 structure. 330-331.512-513
LIFO container. 352-353 Member operator (structure), 330-331
Light Years Calculator program (constants), 192- Member types (STL). 706-707
193 Memory
limits header tile. 146-147 contiguous. 352-353,440-441
Linear search. 409.414 dynamic, 652-655
Linear search algorithm (built-in array), 466-467 non-contiguous. 352-353
Link structure. 720-721 physical
Linker. 12-13 Memory address. 444-445. 636-637
List. 376-383 memory header file. 663-664
initializing, 376-377 Memory leak. 652-655
list container. 352-353. 720-721 avoiding. 656-669
list header file. 376-377 mergeO member function (list). 378-379
Literal value. 46. 47 Microsoft Visual C++ (MSVC) compiler. 14-15
Locals window installing. 744-745
Visual Studio. 298-299 Miles Per Gallon program
Xcode. 305 that prevents exceptions. 494-495
Logic error. 76-77. 288-289 with exception handling. 492-493
logic_error class. 488-489 with functions. 242-243
Logical operators. 92-93 min() member function (limits). 196-197
with if statements. 94-95 min_clemcnt() algorithm. 412-413
long data type. 186-187 with built-in arrays, 470-471
long double data type. 186-187 MinGW, 14-15
long long data type. 186-187 Minimalist GNU for Windows (MinGW). 14-15
Low-level language. 12-13 Modify a string, 222-223
lvalue. 52
Modulus operator Operators 767
Subscript operator (cont.) this pointer, returning the current object from a
with maps. 388-391 member function. 646-647
with strings. 214-215. 223-224 Throw an exception. 488-489
with two-dimensional arrays. 472-473 custom. 598-599
with vectors. 200-201, 364-365 throw' statement. 488-489
substrf) member function (string), 220-221 for rethrowing an exception. 498-499
Substring. 220-221 Tilde character (destructor). 530-531
Subtraction compound assignment operator. 117 time() function (clime). 64-65
Subtraction operator. 49 to_string() function. 526-527
Superclass, 574-575 Token. 70
defining. 576-577 tolowcrO function. 84-85
swap() member function (STL container). 358-359 Top Five program (C strings and 2D array). 476-
switch statement. 104-109 479
Syntax error. 20-21. 30-31. 76-77. 288-289 top() member function (stack). 384-385
toupper() function. 84-85
common. 290-291
Trace code execution. 294-295
try statement. 490-491
T with multiple catch clauses. 496-497
Tab-delimited file. 168-169
Two-dimensional (2D) array. 472-473
passing to a function. 474-475
Table. 168-169.472-473
Type coercion. 194-195
targetver.h file. 24-25
Type conversion. 194-195
Task Manager 1.0 program (MyVector class), 716-
717 Type parameter
Task Manager 2.0 program (MyList class), 730- class template. 690-691
731 function template. 684-687
Temperature Analyzer program (string stream), typedef keyword
176-179 iterator trait. 718
Temperature Manager program member type. 706-707
with exception handling. 500-501 typename keyword
with file I/O. 170-173 class template. 690-691
with functions. 262-269 function template. 424-425. 686-687
with vectors. 210-213
Template. 681-701
class, 690-701
u
function. 424-425. 682-689 UML. 544-545
template keyword. 424-425. 686-687. 690-691 UML diagram. 544-549
Template prefix with inheritance. 574-575
class template. 690-691 with multiple inheritance. 592-593
function template. 686-689 with object composition. 558-559
Terminal. 8 Unary operators. 114-115
Ternary operator. 102-103 Underflow (integer), 196-197
Test a program. 76-77. 288-289 underflow_error class. 489
Test phases. 288-289 Unified Modeling Language (UML), 544-545
Test plan. 292-293 uniquc_ptr. 663-664
Test Scores program Unscoped enumeration. 342-343
with a built-in array. 448-451 unsigned int data type. 187
with a vector. 206-209 Unsigned integers. 187
with a while loop. 126-127 unsigned long data type. 187
with an array, 374-375 unsigned long long data type. 187
Text file. 162-163 unsigned short data type. 187
this keyword Unwinding the stack. 657
for accessing a data member. 518-519 Uptime Percentage program (template function
for referring to the current object. 622-623 and algorithms), 430-433
using declaration Xcode IDE 773
learning C++.
k______ ______ J
www.murach.com facebook.com/murachbooks
twitter.com/MurachBooks
1-800-221-5528
(Weekdays, 8 am to 4 pm Pacific Time) linkedin.com/company/
mike-murach-&-associates
[email protected] instagram.com/murachbooks
What software you need for this book
• The Visual Studio Community IDE or the Xcode Community IDE.
• The Microsoft Visual C++ (MSVC) compiler, which is installed with the Visual
Studio IDE. or the Clang compiler, which is installed with the Xcode IDE.
You can download this software for free and install it as described in appendix A (Windows)
or appendix B (macOS).
www.murach.com