The only remaining feature we need to understand before solving our bookstore problem is how to define a data structure to represent our transaction data. In C++ we define our own data structures by defining a class. A class defines a type along with a collection of operations that are related to that type. The class mechanism is one of the most important features in C++. In fact, a primary focus of the design of C++ is to make it possible to define class types that behave as naturally as the built-in types.
In this section, we’ll describe a simple class that we can use in writing our bookstore program. We’ll implement this class in later chapters as we learn more about types, expressions, statements, and functions.
To use a class we need to know three things:
• What is its name?
• Where is it defined?
• What operations does it support?
For our bookstore problem, we’ll assume that the class is named Sales_item
and that it is already defined in a header named Sales_item.h
.
As we’ve seen, to use a library facility, we must include the associated header. Similarly, we use headers to access classes defined for our own applications. Conventionally, header file names are derived from the name of a class defined in that header. Header files that we write usually have a suffix of .h
, but some programmers use .H
, .hpp
, or .hxx
. The standard library headers typically have no suffix at all. Compilers usually don’t care about the form of header file names, but IDEs sometimes do.
Sales_item
ClassThe purpose of the Sales_item
class is to represent the total revenue, number of copies sold, and average sales price for a book. How these data are stored or computed is not our concern. To use a class, we need not care about how it is implemented. Instead, what we need to know is what operations objects of that type can perform.
Every class defines a type. The type name is the same as the name of the class. Hence, our Sales_item
class defines a type named Sales_item
. As with the built-in types, we can define a variable of a class type. When we write
Sales_item item;
we are saying that item
is an object of type Sales_item
. We often contract the phrase “an object of type Sales_item
” to “a Sales_item
object” or even more simply to “a Sales_item
.”
In addition to being able to define variables of type Sales_item
, we can:
• Call a function named
isbn
to fetch the ISBN from aSales_item
object.
• Use the input (
>>
) and output (<<
) operators to read and write objects of typeSales_item
.
• Use the assignment operator (
=
) to assign oneSales_item
object to another.
• Use the addition operator (
+
) to add twoSales_item
objects. The two objects must refer to the same ISBN. The result is a newSales_item
object whose ISBN is that of its operands and whose number sold and revenue are the sum of the corresponding values in its operands.
• Use the compound assignment operator (
+=
) to add oneSales_item
object into another.
The important thing to keep in mind when you read these programs is that the author of the
Sales_item
class defines all the actions that can be performed by objects of this class. That is, theSales_item
class defines what happens when aSales_item
object is created and what happens when the assignment, addition, or the input and output operators are applied toSales_item
s.In general, the class author determines all the operations that can be used on objects of the class type. For now, the only operations we know we can perform on
Sales_item
objects are the ones listed in this section.
Sales_item
sNow that we know what operations we can use with Sales_item
objects, we can write programs that use the class. For example, the following program reads data from the standard input into a Sales_item
object and writes that Sales_item
back onto the standard output:
#include <iostream>
#include "Sales_item.h"
int main()
{
Sales_item book;
// read ISBN, number of copies sold, and sales price
std::cin >> book;
// write ISBN, number of copies sold, total revenue, and average price
std::cout << book << std::endl;
return 0;
}
If the input to this program is
0-201-70353-X 4 24.99
then the output will be
0-201-70353-X 4 99.96 24.99
Our input says that we sold four copies of the book at $24.99 each, and the output indicates that the total sold was four, the total revenue was $99.96, and the average price per book was $24.99.
This program starts with two #include
directives, one of which uses a new form. Headers from the standard library are enclosed in angle brackets (< >
). Those that are not part of the library are enclosed in double quotes (" "
).
Inside main
we define an object, named book
, that we’ll use to hold the data that we read from the standard input. The next statement reads into that object, and the third statement prints it to the standard output followed by printing endl
.
Sales_item
sA more interesting example adds two Sales_item
objects:
#include <iostream>
#include "Sales_item.h"
int main()
{
Sales_item item1, item2;
std::cin >> item1 >> item2; // read a pair of transactions
std::cout << item1 + item2 << std::endl; // print their sum
return 0;
}
If we give this program the following input
0-201-78345-X 3 20.00
0-201-78345-X 2 25.00
our output is
0-201-78345-X 5 110 22
This program starts by including the Sales_item
and iostream
headers. Next we define two Sales_item
objects to hold the transactions. We read data into these objects from the standard input. The output expression does the addition and prints the result.
It’s worth noting how similar this program looks to the one on page 6: We read two inputs and write their sum. What makes this similarity noteworthy is that instead of reading and printing the sum of two integers, we’re reading and printing the sum of two Sales_item
objects. Moreover, the whole idea of “sum” is different. In the case of int
s we are generating a conventional sum—the result of adding two numeric values. In the case of Sales_item
objects we use a conceptually new meaning for sum—the result of adding the components of two Sales_item
objects.
It can be tedious to repeatedly type these transactions as input to the programs you are testing. Most operating systems support file redirection, which lets us associate a named file with the standard input and the standard output:
$ addItems <infile >outfile
Assuming
$
is the system prompt and our addition program has been compiled into an executable file namedaddItems.exe
(oraddItems
on UNIX systems), this command will read transactions from a file namedinfile
and write its output to a file namedoutfile
in the current directory.
Exercises Section 1.5.1
Exercise 1.20: http://www.informit.com/title/032174113 contains a copy of
Sales_item.h
in the Chapter 1 code directory. Copy that file to your working directory. Use it to write a program that reads a set of book sales transactions, writing each transaction to the standard output.Exercise 1.21: Write a program that reads two
Sales_item
objects that have the same ISBN and produces their sum.Exercise 1.22: Write a program that reads several transactions for the same ISBN. Write the sum of all the transactions that were read.
Our program that adds two Sales_item
s should check whether the objects have the same ISBN. We’ll do so as follows:
#include <iostream>
#include "Sales_item.h"
int main()
{
Sales_item item1, item2;
std::cin >> item1 >> item2;
// first check that item1 and item2 represent the same book
if (item1.isbn() == item2.isbn()) {
std::cout << item1 + item2 << std::endl;
return 0; // indicate success
} else {
std::cerr << "Data must refer to same ISBN"
<< std::endl;
return -1; // indicate failure
}
}
The difference between this program and the previous version is the if
and its associated else
branch. Even without understanding the if
condition, we know what this program does. If the condition succeeds, then we write the same output as before and return 0
, indicating success. If the condition fails, we execute the block following the else
, which prints a message and returns an error indicator.
The if
condition
item1.isbn() == item2.isbn()
calls a member function named isbn
. A member function is a function that is defined as part of a class. Member functions are sometimes referred to as methods.
Ordinarily, we call a member function on behalf of an object. For example, the first part of the left-hand operand of the equality expression
item1.isbn
uses the dot operator (the “.
” operator) to say that we want “the isbn
member of the object named item1
.” The dot operator applies only to objects of class type. The left-hand operand must be an object of class type, and the right-hand operand must name a member of that type. The result of the dot operator is the member named by the right-hand operand.
When we use the dot operator to access a member function, we usually do so to call that function. We call a function using the call operator (the ()
operator). The call operator is a pair of parentheses that enclose a (possibly empty) list of arguments. The isbn
member function does not take an argument. Thus,
item1.isbn()
calls the isbn
function that is a member of the object named item1
. This function returns the ISBN stored in item1
.
The right-hand operand of the equality operator executes in the same way—it returns the ISBN stored in item2
. If the ISBNs are the same, the condition is true; otherwise it is false.
Exercises Section 1.5.2
Exercise 1.23: Write a program that reads several transactions and counts how many transactions occur for each ISBN.
Exercise 1.24: Test the previous program by giving multiple transactions representing multiple ISBNs. The records for each ISBN should be grouped together.