Standard C++ With Object-Oriented Programming BOOK CH 2
Standard C++ With Object-Oriented Programming BOOK CH 2
Contents
TWO C++ Primer Part II: Object-Based programming 51
2.1 Data Abstraction and Encapsulation 51
2.11 Summary 77
Exercises 78
CHAPTER TWO I
C++ Primer
Part II: Object-Based
programming
One of the most central features of OOP is the division of the whole program
into smaller autonomous entities, called objects, with well-defined interactions.
This feature significantly reduces overall complexity and enhances the quality
of the program in many different ways. An object organizes data and related
operations into a black box, which hides the internal data structures, represen-
tations, and procedures from outside view. A data structure is concrete when
its exact details are fixed and exposed. Traditional programming approaches
depend heavily on concrete data. OOP, on the other hand, emphasizes data ab-
straction and encourages hiding the details of data by presenting only the data's
behavior. For example, if you do not know the details of car construction, you
can still drive one effectively if you know behavior such as "steering clockwise
makes the car turn right." This leaves the implementation of steering to the
black box, which can use one of several alternatives: regular, power-assisted,
rack-and-pinion, and so on. In addition to structures, the object also contains
mechanisms, or procedures, that are necessary to operate the structures. These
procedures are attached to the structures to form one inseparable unit. This
technique is called encapsulation.
The C++ class construct supports data abstraction and encapsulation. A class
describes the construction of an object and serves as a blueprint to build objects.
It specifies the internal workings as well as the external interface of an object. A
class has a name, or tag, and specifies members belonging to the class that may
be data, functions, or objects. Once a class is defined, the class name becomes
a new data type and is used to declare variables of that type. A class-type
variable is an instance of a class and is called an object of that class.
Tobegin, let's consider a simplified class representing bank accounts:
class Account
public:
Account() ; II constructor (1)
Account (unsigned n, double b); II constructor (2)
void deposit (double amt); II deposit amt into this account
bool withdraw(double amt); II withdraw amt from this account
double balance(); II balance inquiry
unsigned id(); II get account number
1* other public members *1
private:
unsigned acct_no; II account number
double acct_bal; II current balance
1* other private members *1
Class names should appear as capitalized nouns, which is the style recom-
mended and followed in this book. Here the class Account is declared following
the general form:
class Name
{
If you are new to C++, you may sometimes forget the final semicolon in a class
declaration. This can cause many unpleasant errors. One way to remember the
semicolon is to type in the form class Name { } i before entering the class
body, which in itself can be very involved.
The class body consists of declarations for data members, function mem-
bers, or both. Member declarations are supplied in the usual way-namely,
with declarations and function prototypes. However, no initializers are allowed
for data member declarations. Except for overloading, all members must have
distinct names. The class Account contains the data members
unsigned acct_noi II account number
double acct_bali II current balance
which are the identification number and current balance of a bank account.
Function prototypes in a class definition specify member functions of the class.
For example,
void deposit(double amt) i
declares deposi t to be a member function. The actual code for member func-
tions usually resides in a separate implementation file.
Once a class is declared, objects of the class can be created. A class is a blueprint
to build objects of a particular type (Figure 2.1), and the objects are known as
instances of the class. The class name becomes a type name and can be used to
declare variables. A class-type variable is an object of that class.
For example, objects of type Account can be defined as follows:
Account susan(5551234, 600.0) i II object susan (3)
Account jack; II object jack (4)
The variable susan is an Account object with the given account number and a
beginning balance of 600. The initialization of this object is through the Account
constructor.
In class Account, the Account member (line 1) is special. In general, a
member function with the same name as the class itself is called a constructor
~
class --------;> Account
Account objects
which provides the desired initialization of the data members acct_no and
acct_bal. No return type of any kind is allowed in declaring a constructor,
which should never return a value. Note that the constructor function name
Account is qualified by Account: : to attach the name to the Account class as a
member. More will be said on this presently.
The variable jack is an object initialized by the constructor that takes no
arguments.
An object can be thought of as an independent computing agent (a tiny com-
puter) with its own storage and instruction set. The data members define the
storage, and the function members provide the instruction set.
The C++ class construct also specifies access control to its members. In
the class definition, members are sectioned into groups to achieve information
hiding:
Message
~
other than those specifically provided by the Account class can be performed on
these private quantities. In designing a class, you, the 00 programmer, must
design the public/private grouping to support effective use and to maximize
information hiding.
We already know that not every member of an object is accessible from the
outside. When permitted, the members of an object are accessed with the dot
( .), or member-of operator. For example,
Pointers to class objects are also very useful. For example, the pointer variable
acnt-ptr
is initialized to point to the Account object jack. But it can also be assigned
another value:
When you are dealing with a pointer to an object, the members can be accessed
directly through the pointer with the -> operator:
t
State
;"f
\
Host Object A7
is wrong because the result depends on which of the two arguments is evalu-
ated first. You should use instead something like this:
ittj
power(i-l, i)i
Parameters in a function header are formal in the sense that any name
can be used for them without changing the meaning of the function. The same
situation is found in mathematics where the notations f (x) = x2 and f (y) = y2
define the same function.
Formal parameters are local to a function. When a function call is made, a
copy of the value of the actual argument is passed to the corresponding formal
parameter. In other words, arguments are passed by value. With pass by value,
a function can work only on copies of arguments, not on the actual arguments
themselves. Therefore, the actual arguments have the same values before and
after the function call.
When necessary, it is possible to modify data in the calling function. One
way is by passing pointers as actual arguments. Recall that a pointer is the
memory location, or address, of an object or a piece of data. Once the called
function gets the address, it can proceed to modify information stored at that
address. As a result, data in the calling function are altered indirectly.
Unlike basic data types and objects, there is no automatic copying of the
elements in an array when it is passed as an argument. Instead, the address
of its first element is passed. (This is the value of the array name.) Therefore,
the formal array parameter becomes another name by which the same array
elements can be accessed.
Pass by value can be expensive if the arguments represent large data objects.
Pass by reference is a way to pass arguments without copying. C++ supports
reference formal parameters for functions. A reference parameter is an alias for
the actual argument passed (without copying) in a function call. An assignment
to a reference parameter assigns a value to the corresponding actual argument.
A reference parameter is used most often for the following purposes:
may look a little suspicious at first because tmpis of type int but b is of type
int&. It simply means that tmpgets the value of the actual argument represented
by the int reference b. Thus, the following code works:
int r = 7, s = 11;
swap(r, s); / / now r is 11 and s is 7
The values are switched because a and b are reference parameters that become
aliases for rand s, respectively, when swapis called. In other words, the effect
of the call swap(r , s) is
int tmp = s;
s = r;
r = tmp;
Also recall the std: :cin. get (c) usage. It works because std: :cin. get takes a
reference parameter (char& c). A reference parameter is one form of the C++
reference, a feature explained in Section 3.8.
The C++ coding style sometimes calls for defining many small functions that
are very simple. For such small functions, function call overhead (i.e., the
argument-passing and the value-returning activities associated with a func-
tion call) becomes significant compared to the amount of time spent in the
called function. To reduce this overhead and to increase code efficiency,C++
allows you to declare functions inline. For example,
inline int MAX(inta, int b)
{ return (a > b ? a : b); }
inline double ABS(double a}
{ return (a > 0 ? a : -a); }
A call to an inline function is expanded by the compiler so that the effective
code is substituted at the place of call, and run-time function call overhead
is avoided. An inline specifier advises the compiler to make an inline ex-
pansion if possible. An implementation may expand only inline functions
containing straight-line code with only a few statements. Usually, no condi-
tionals (if), loops (while, for), or other branching statements are allowed. In
most situations, these limitations coincide with good programming practices.
Furthermore, a class member function completely defined inside the class dec-
laration (Section 2.6) is automatically declared inline.
To expand a function inline, the compiler must have seen the function
definition when compiling a file. This is usually done by including a header
file where the definition of the inline function has been placed.
The main function is special in C++ because it marks the starting point for pro-
gram execution and is not called by other functions in the program. However,
it is possible to supply arguments to a C++ program when it is invoked. The
command-line arguments are passed as character strings to the function main.
A main function expecting arguments is normally declared as follows:
char *argv [ ]
declares the formal array parameter argv as having elements of type char *
(character pointer). In other words, each ofthe arguments argv [0], argv [l], ... ,
argv[argc-lj is a character pointer. The meanings of the formal arguments
argc and argv are as follows:
argc The number of command-line arguments, including command name
argv [n] A pointer to the nth command-line argument as a character string
then
argc Is 3
argv[O] Points to the command name coo
argv[l] Points to the string argl
argv[2] Points to the string arg2
argv[3] Is 0 (NULL)
The parameters for the function main can be omitted if they are not needed.
Now let's consider a program that receives command-line arguments. To
keep it simple, all the program does is echo the command-line arguments to
standard output:
The program displays each entry of argv except argv [0]. To separate the
strings, the program displays a SPACE after each argv [i] , and the last argument
is followed by the proper end of line.
Note that main is declared to return an int, and the last statement of main is
The return value of main indicates, to the invoker of the program, whether
the program executed successfully and terminated normally. This value is re-
ferred to as the exit status. For example, on UNIX systems, a zero exit status
indicates successful or normal execution of the program, whereas a nonzero
(usually positive) exit status indicates abnormal termination. Thus, it is ad-
visable always to use a return statement in the main program, even though it
works without one.
The parameters argc and argv of a main program reference the explicit argu-
ments given on the command line (Section 2.3). Every time a program runs,
another array of strings representing the user environment, called the environ-
ment list, is also passed to the program. This provides a way, in addition to the
command-line arguments, to pass information to a program.
The environment list is always available in the system-defined global
variable
HOME=/users/fac/pwang (UNIX)
PATH=/usr/local/bin:/usr/local:/usr/ucb:/bin:/usr/bin:. (UNIX)
TERM=vt200 (UNIX)
PATH=C:\iC:\NETMANAGiC:\JDK1.2\BINiC:\TOOLS\VIM46W32\VIM-4.6(Windows)
The first three examples are from UNIX and the fourth is from Windows. The
final element of the environ array is a zero pointer (NULL)
to mark the end.
To access environ, include the header <stdlib. c>.Although direct search
of environ is possible, it is simpler to access environment values with the
Standard Library function getenv:
char *getenv(char * varName)
This function searches the environment list for a string whose name part
matches the varNamegiven and returns a pointer to the value part. If no match
is found, then varName is not an environment variable and NULL is returned.
The function getenv makes it easy to retrieve environmental values. For
example
#include <stdlib.h>
The str ingMatch function looks for str in 1ine and returns the starting position
in line if a match is found. Otherwise, -1 is returned. It receives string objects
as references (line I), checks if str is empty (line 2), obtains the length of the
given strings (line 3), and compares characters (lines 4 and 5) to do the job.
You can use subscripts to access individual characters in a string object.
For example, the function
where sl is a string object and s2 is a string object or C-style string. The string
s3 is s2 appended to sl while sl and s2 remain unmodified. Tomodify sl, use
length exceeds str .max_size (), the read operation fails (Section 2.7).
Section 2.8 contains a complete example that uses stringMatch ().
class Vector2D
{ public:
Vector2D() {} II no-args constructor
Vector2D(float a, float b) I I inline (1)
{ x = a; y = b; }
Vector2D difference (Vector2D&a);
float inner(Vector2D& a);
bool isPerpendicular(Vector2D& a);
bool nonzero() II inline (2)
{ return ( x ! = O. 0 II y ! = O. 0 ); }
void display();
1* other members not shown *1
private:
float x, y;
Let's turn our attention to the other member functions of Vector2D.The mem-
ber function inner
float inner(Vector2D a) ;
receives a Vector2Dobject argument and returns a float value. The actual code
for inner is defined in the file Vector2D. c:
111/1111 Vector2D.C IIIIIIIIII
#include <iostream>
#include "Vector2D.h"
bool Vector2D::isPerpendicular(Vector2D& v)
return ( nonzero() &&v.nonzero()
&& ABS(inner(v)) < 0.00000001 ) i
It makes sure both vectors are nonzero and their inner product is zero, up to a
tolerance. Note that both difference and isPerpendicular take an argument
passed by reference.
Now that we have the Vector2D. hand Vector2D. C files in place, we can con-
struct a solution for the rectangle problem that makes use of Vector2D objects.
The approach is simple:
IIIIIII rectangle.C
#include <iostream>
#include nVector2D.hn
int main()
{ std: :cout « "Enter vertices 0,1,2,3 "
« std: :endl;
Vector2D p[4] ; II vector array (A)
for ( int i = 0; i < 4; itt) II input all four points
p[i] = getVec(i);
Vector2D u = prO] .difference(p[3]);
Vector2D v;
for (int i = 0; i < 3; itt) II process all sides
{ v = p[it1] .difference(p[i]); II vector difference (C)
if ( ! u. isPerpendicular (v) ) II check perpendicularity
{ std::cout« "No, not a rectangle." «std::endl;
return 1;
}
std: :cout « "Yes, a rectangle." « std: :endl;
return 0;
After the coordinates for the four vertices are read (in sequence), four vectors
are in the array p [4] whose declaration (line A) invokes the Vector2Ddefault
constructor four times. A 20 vector u representing one side of the quadrilateral
is then calculated by vector subtraction (line B).A second Vector2Dobject v is
made for an adjacent side (line C). The perpendicularity of u and v is checked.
After all sides are checked, the right conclusion can be made.
Assuming the file Vector2D. 0 has already been produced, compile
rectangle. Cwith Vector2D. 0 and run the program.
The ability to work with vectors that correspond to real geometric objects
allows the solution to be stated simply and elegantly with geometric concepts
and also makes it much easier to explain and understand. More important, the
Vector2Dclass can help in many other situations in plane geometry. Hence, the
class has potential for reuse.
Furthermore, the object-based solution is easily adaptable to changes in
the problem specification. For instance, determining whether ABC D is a par-
allelogram involves almost no change to the program. Youjust add a member
iSParallel (Vector2D&b) to the Vector2Dclass if it is not already there.
Section 1.7 mentioned cin, cout, and cerr-three ready-made objects for I/O
in each program. These objects are instances of the I/O stream classes that are
part of the C++ Standard Library. Toavoid global name conflicts, the Standard
Library classes and objects are placed in a separate namespace std, which is
the reason we use the prefix in std: :coutoWehave also seen the use of cin. get
and cout.put for character I/O.
While these standard objects take care of terminal I/O, there are occasions
when you want direct I/O from or to a specific file. This can be done by setting
up new I/O objects connected to the desired files. The declarations
#include <iostream>
#include <fstream>
out.close() j
The main program should first check the arguments supplied on the command
line for correctness. If the arguments are unacceptable, a clear message should
be displayed stating the nature of the error and its cause (if known). Use
the object cerr for sending error messages to ensure that they appear on the
terminal immediately without buffering. A conditional statement such as
if (argc != 3)
{ std: :cerr « argv[O] « ": expects 2 arguments but was given"
« argc-l « std: :endl;
std: :cerr « "Usage" « argv[Oj « " input-file output-file";
« s td: :end1;
checks the number of command-line arguments supplied. Note that the value
of argc is, by definition, the number of command-line arguments plus 1. Al-
ways identify the program unit or subunit displaying the error message. The
command name identifies which program is announcing the error. When ap-
propriate, a function name further narrows down the error location. In this
example, the program refers to its own name as argv [0], which is better than
assuming a specific file name.
After displaying an error message, a program may continue to execute,
return a particular value not produced normally, or elect to abort. The Standard
Library function exit is called to terminate the execution of a program:
The ifstream class (Section 6.7) member function fail () returns true if the
file failed to open. The same usage applies for an of stream object.
Let's put the stringMatch function (Section1.15)to use together with appropri-
ate 1/0 and error handling. The intention is to define a stringSearch command
that works with standard I/O when given no arguments or with specific I/O
files.
1111111 stringSearch.C
#include <iostream>
#include <string>
#include <fstream>
#include <stdlib.h>
void match(std: :string& str, std: :istream& in, std: :ostream& out)
{ if ( str.empty() ) II str is empty
{ std: :cerr « "match: empty match string"
« std: :endl;
}
string line;
while ( std: :getline(in, line, '\n') ) I I (a)
{ if ( stringMatch(str, line) > 0 ) II (b)
out « line « std::endl; II (c)
}
out. flush () ;
The function match searches (line b) for str in each line from the given in-
put stream in (line a) and outputs any matching lines to the output stream
out (line c). Note that reference parameters are used. Failure to use reference
parameters for I/O objects can prove unwise in most situations.
int main (int argc, char* argv[])
{ if ( argc < 2 I I argc > 4 )
std: :cerr « "Usage:" « argv[O]
« " str [infile [outfile]]" « std: :endl;
}
std: :string str(argv[l]);
if ( argc == 2 ) II use standard 1/0
match(str, std: :cin, std::cout);
else II use file
{ ifstream infile(argv[2]);
if (infile.fail() ) II (2)
std: :cerr « argv[O] « ":can't open input file"
« argv[2] « std: :endl;
return 1;
}
if ( argc == 3 )
match(str, infile, std::cout);
else II argc == 4
{ of stream ofile(argv[3]);
if ( ofile.fail() ) II (3)
{ std: :cerr « argv[O] « ":can't open output file"
« argv[3] « std: :endl;
exit (1) ;
}
match(str, infile, ofile);
Here are some basic programming tips and things to remember to improve
your C++ programs:
Use comments to document the purpose and the effects of the function,
the meaning of the arguments, and the value returned. Format the function
body as follows:
Use names in all caps for symbolic constants, preferring canst over #define
(Sections 14.4 and 3.9). Give functions and variables meaningful names (in all
lowercase), using the underscore C) or capitalization to connect multiple words
when appropriate.
1. Use capitalized nouns for dass names. Join multiple words, and
abbreviate if necessary, while capitalizing each word, as in Vector2D
and GroupLeader.
2. Put each class definition in a separate header file, and be sure to use
the once-only header feature (Section 3.15). Put member function
definitions in a corresponding. C (or. CPp) file, which uses #include to
include its own header file.
3. In a class definition, put public members first and carefully document
public member functions with comments. Specify the meaning of
arguments.
4. Give only extremely simple member functions inside the class
definition.
5. If a class has any constructor, provide a default constructor.
1. Consider the Account class in Section 2.1. Add a member function void
display () to this class.
2. Add a member function transfer () for the Account class that transfers a given
amount from another account to the host account. Also implement the same
function as a nonmember.
4. Consider the Account class in Section 2.1. Are the following declarations cor-
rect? Possible? Explain why.
Account paul;
Account marYi
6. Suppose sally is already declared as an Account object with an account iden-
tification and initial balance. Is the call sally. Account (new_id, new_balance)
possible? Why?
7. How do you convert a C-style string to a string object? And vice versa? Is it
possible to pass a C-style string to a function parameter declared as a string?
Assign a C-style string to a string variable? Why?
8. Apply the library function get line in a program that counts the number of
lines in any text file specified on the command line.
9. Write a reverse-echo program that takes all words on the command line and
displays them backward, character by character.
10. Let words in a file be character strings separated by one or more white-space
characters (SPACE, TAB, NEWLINE). Write a program to count the number of
words in the input file (cin).
12. Add the '==' operator to the Vector2D class. Add the member function
is-parallel (Vector2D&)to test whether a vector is parallel to the host vector
object. Given any four points A, B,C, and D, use Vector2Dobjects to determine
if ABCD is a parallelogram.
14. NIM is a game in which two players alternate in drawing counters, pennies, or
the like from a set of 12arranged in three rows of three, four, and five counters,
respectively. With each move, a player is allowed to draw either one, two, or
three counters. The player who draws the last counter loses. Write a program
to play NIM with one person or with two. (Hint: Consider a NIM board object.)