Anda di halaman 1dari 149

CONTENTS

Chapter 1 - Analysis and Design Methods/Introduction to UML

Purpose of Methods
Algorithmic Decomposition
Object-Oriented Decomposition
How the UML Came to Be
The Components of the UML
Unification of methods
Model
What, then, is a model? Simply put,
Principle of Modeling
Object-Oriented Modeling
An Overview of the UML
The UML Is a Language
The UML Is a Language for Visualizing
The UML Is a Language for Specifying
The UML Is a Language for Constructing
The UML Is a Language for Documenting
Where Can the UML Be Used?
A Conceptual Model of the UML
Building Blocks of the UML
Meta-Model

Chapter 2 - Object Oriented Approach

The application
Conventional approach to software design:
For the mail client we have the following abstract data types
Object Orientation
The OO Approach
Object Oriented Approach
Improves maintainability
Generalization through abstraction
What is an Object?
State, Behavior, and Identity
Object Persistence
Persistence Closure

Chapter 3 - Managing Console I/O Operations

INTRODUCTION
C++ STREAMS
C++ STREAM CLASSES
UNFORMATTED I/O OPERATIONS
Overloaded Operators >> and <<
Put() and get() Functions
Getline() and wrlte() Functions
FORMATTED CONSOLE I/O OPERATIONS

i.exe
Defining Field Width: width()
Setting Precision: precision()
Filling and Padding: fill()
FormattIng Flags, Bit-fields and setf()
Displaying Trailing Zeros and Plus Sign
MANAGING OUTPUT WITH MANIPULATORS
DESIGNING OUR OWN MANIPULATORS

Chapter 4 - WORKING WITH FILES

Introduction
Classes for file stream operations
Opening and closing a file
Detecting end of file
More about open() : file modes
File pointers and their manipulations
Sequential input and output operations
Command line arguments

Chapter 5- Advanced Features in C++

Classes within classes


Overloading << and >>
One more use of friend function
A word of caution
Smart pointers
More smart pointers
Pointers to members
The explicit keyword
The mutable keyword
Namespaces
Using a namespace
RTTI
When to use RTTI
Typecasting in C++

Chapter 6 - Templates

Function templates
A template based Quicksort
Class templates
A linked list class template
Tips about Templates

Chapter 7 - Exception Handling

Checking function return value


Setjmp() and longjmp()
Exception handling in C++
Specifying the Exception class
Throwing an exception
The try block
The Exception Handler (catch block)
How the whole thing works
A more practical example
Exception with arguments
Chapter 1 - Analysis and Design Methods/Introduction to UML

Objectives

At the end of this chapter you will be able to understand:

The need for Unification of Different Methods


How Visual Modelling impacts on reducing complexity.

Purpose of Methods

Methods have evolved in response to the growing complexity of software systems. In the early days of
computing, one simply did not write large programs, because the capabilities of our machines were greatly
limited. The dominant constraints in building systems were then largely due to hardware: machines had small
amounts of main memory, programs had to content with considerable latency within secondary storage devices
such as magnetic drums, and processors had cycle times measured in the hundreds of microseconds. In the
1960s and 1970s the economics of computing began to change dramatically as a hardware costs plummeted
and computer capabilities rose. As a result, it was more desirable and now finally economical to automate more
and more applications of increasing complexity. High-order programming languages entered the scene as
important tools. Such languages improved the productivity of the individual developer and of the development
team as a whole, thus ironically pressuring us to create software systems of even greater complexity.

Many design methods were proposed during the 1960s and 1970s to address this growing complexity. The
most influential of them was top-down structured design, also known as composite design. This method was
directly influenced by the topology of traditional high-order programming languages, such as FORTRAN and
COBOL. In these languages, the fundamental unit of decomposition is the subprogram, and the resulting
program takes the shape of a tree in which subprograms perform their work by calling other subprograms. This
is exactly the approach taken by top-down structured design: one applies algorithmic decomposition to break a
large problem down into smaller steps.

Since the 1960s and 1970s, computers of vastly greater capabilities have evolved. The value of structured
design has not changed, Structured programming appears to fall apart when applications exceed 100,000 lines
or so of code. More recently, dozens of design methods have been proposed, many of them invented to deal
with the perceived shortcomings of top-down structured design. Methods can be categorized as one of three
kinds:
a) Top-down structured design
b) Data-driven design
c) Object-oriented design

Top-down structured design is exemplified by the work of Yourdon and Constantine, Myers and Page-Jones.
The foundation of the method derive from the work of Writhe And Dahl, Dijkstra, and Hoare an important
variation on structured design method of Mills, Linger, and Hevner. Each of these variations applies algorithmic
decomposition. More software has probably been written using these design methods than with any other.
Nevertheless, structured design does it provide an adequate means of dealing with concurrency. Structured
design does not scale up well for extremely complex systems, and this method is largely inappropriate for use
with object-based and object-oriented programming languages.

It involves direct relationships between the inputs and outputs of the system, but require little concern for
time-critical events.

i.exe
Object-oriented analysis and design is the method we introduce in this book. Its underlying concept is that one
should model software systems as collection of co-operating objects, treating individual objects as instances of
a class within a hierarchy of classes. Object-oriented analysis and design directly reflects the topology of more
recent high-order programming languages such as Smalltalk, Object Pascal, C++, the Common Lisp Object
System (CLOS), Java, and Ada.

Algorithmic Decomposition

Most of uses have been formally trained in the dogma of top-down structured design, and so we approach
decomposition as a simple matter of algorithmic decomposition, wherein each module in the system denotes a
major step in some overall process. Figure 1-2 is an example of one of the products of structured design, a
structure chart that shows the relationships among various functional elements of the solution. This particular
structure chart illustrates part of the design of a program that updates the content of a master file. It was
automatically generated from a data flow diagram by an expert system tool that embodies the rules of
structured design.

Object-Oriented Decomposition

We suggest that there is an alternate decomposition possible for the same problem. In Figure 1-3, we have
decomposed the system according to the key abstractions in the problem domain. Rather than decomposing
the problem into steps such as Get formatted update and Add check sum, we have identified objects such as
Master File and Check Sum, which derive directly from the vocabulary of the problem domain.

Although both designs solve the same problem, they do so in quite different ways. In this second
decomposition, we view the world as a set of autonomous agents that collaborate to perform some higher-level
behavior. Get formatted update thus does not exist as an independent algorithm; rather, it is an operation
associated with the object File of Updates. Calling this operation creates another object, Update to Card. In this
manner, each object in our solution embodies its own unique behavior, and each one model some object in the
real world. From this perspective, an object is simply a tangible entity, which exhibits some well-defined
behavior. Objects do things, and we ask then to perform what they do by sending them messages. Because our
decomposition is based upon objects and not algorithms, we call this an object-oriented decomposition.

How the UML Came to Be

The UML is the brainchild of Grady Booch, James Rumbaugh, and Ivar Jacobson. Recently dubbed “the Three
Amigos.” These gentlemen worked in separate organizations through the ‘80s and early ‘90s.each devising his
own methodology for object-oriented analysis and design. Their methodologies achieved preeminence over
those of numerous competitors. By the mid ‘90s, they began to borrow ideas from each other, so they decided
to evolve their work together. In 1994, Rumbaugh joined Rational Software Corporation, where Booch was
already working. Jacobson enlisted at Rational a year later.

The rest, as they say, is history. Draft versions of the UML began to circulate throughout the software industry
and the resulting feedback brought substantial changes. As many corporations felt the UML would serve their
strategic purposes, a UML consortium sprang up.

Members included DEC, Hewlett-Packard, Intellicorp, Microsoft, Oracle, Texas Instruments, Rational, and
others. In 1997, the consortium produced version 1.0 of the UML and submitted it to the Object Management
Group (OMG) in response to OMG’s request for a proposal for a standard modeling language. The consortium
expands, generated version1.1, and submitted it to the OMG, who adopted it in late 1997. The OMG took over
the maintenance of the UML and produced dons two more revisions in 1998. The UML has become a de fact o
standard in the software industry, and it continues to evolve.
The Components of the UML

The UML consists of a number of graphical elements that combine to form diagrams. Because it’s a language,
the UML has rules for combining these elements. Rather than
telling you about these elements and rules, let’s jump right into the diagrams because they’re what you’ll use to
do system analysis.

a) Unification of methods

i. Across historical methods and notations. Combines commonly accepted concepts from many OO Methods,
selecting clear definition for each concept, notation and terminology.
ii. Across the development life cycle. Seamless from analysis to deployment. No need to translate from one
stage to another. Critical for iterative and incremental development.
iii. Across application domains.
iv. Across implementation languages and platforms.
v. Across development processes, though specially suitable in iterative incremental development.
vi. Across internal concepts. UML meta-model represents underlying relationships among various concepts in
a broad way applicable to many known and unknown situations.

Model

Modeling is not just a part of the building industry. It would be inconceivable to deploy a new aircraft or an
automobile without first building models—from computer models to physical wind tunnel

models to full-scale prototypes. New electrical devices, from microprocessors to telephone switching systems
require some degree of modeling in order to better understand the system and to communicate those ideas to
others. In the motion picture industry, storyboarding, which is a form of modeling, is central to any production.
In the fields of sociology, economic, and business management, we build models that we can validate our
theories or try out new ones with minimal risk and cost.

What, then, is a model? Simply put,

A model provides the blueprints of a system. Models may encompass detailed plans, as well as more general
plans that give a 30,000-foot view of the system under consideration. A good mode includes those elements
that have broad effect and omits those minor elements that are not relevant to the given level abstraction.
Every system may be described from different aspects using different models, and each model is therefore a
semantically closed abstraction of the system. A model may be structural, emphasizing the organization of the
system, or it may be behavioral, emphasizing the dynamics of the system.

Why do we model? There is one fundamental reason.

We build models so that we can better understand the system we are developing.

Through modeling, we achieve four aims.

a) Models help us to visualize a system as it is or as we want it to be.


b) Models permit us to specify the structured or behavior of a system.
c) Models give us a template that guides us in constructing a system.
d) Models document the decisions we have made.

We build models of complex systems because we cannot comprehend such a system in its entirely.
There are limits to the human ability to understand complexity. Through modeling, we narrow the problem we
are studying by

focusing on only one aspect at a time. Attack a hard problem by dividing it into a series of smaller problems
that you can solve. Furthermore, through modeling, we amplify the human intellect. A model properly chosen
can enable the modeler to work at higher levels of abstraction.

Saying that one ought to model does not necessarily make it so. In fact, a number of studies suggest that most
software organizations do little if any formal modeling. Plot the use of modeling against the complexity of a
project, and you’ll find that the simpler the project, the less likely it is that formal modeling will be used.

A developer might sketch out an idea on a blackboard or a scrap of paper in order to visualize a part of a
system, or the team might use CRC cards to work through a scenario of the design of a mechanism. There’s
nothing wrong with any of these models. Just as there exists a common language of blueprints for the
construction industry, a common language for electrical engineering, and a common language for mathematical
modeling, so too cans a development organization benefit by using a common language for software modeling.

Every project can benefit from some modeling. The more complex your project, the more likely it is that you
will fail or that you will build the wrong thing if you do no modeling at all. All interesting and useful systems
have a natural tendency to become more complex over time. So, although you might think you don’t need to
model today, as your system evolves you will regret that decision, after it is too late.

Principle of Modeling

The use of modeling has a rich history in all the engineering disciplines. That experience suggests four basic
principles of modeling.

a) The choice of what models to create has a profound influence on how a problem is
attacked and how a solution is shaped.

b) Every model may be expressed at different levels of precision.


c) The best models are connected to reality.
d) No single model is sufficient. Every nontrivial system is best approached through a small set of nearly
independent models.

The same is true of object-oriented software systems. To understand the architecture of such a system, you
need several complementary and interlock views: a use case view (exposing the requirements of the system), a
design view (capturing the vocabulary of the problem space and the solution space), process view (modeling
the distribution of the system’s processes and threads an implementation view (addressing the physical
realization of the system and a deployment view (focusing on system engineering issues). Each of these views
may have structural, as well as behavioral, aspects. Together, these views represent the blueprints of software.

Depending on the nature of the system, some models may be more important than others. For example, in
data-intensive systems, models addressing design views will dominate. In GUI-intensive systems, static and
dynamic case views are quite important. In hard real time systems, dynamic process views tend to be more
important. Finally, in distributed systems, such as finds in Web-intensive applications, implementation and
deployment m are the most important.

Object-Oriented Modeling
Civil engineers build many kinds of models. Most commonly, there are structural models that help people
visualize and specify parts of systems and way those parts relate to one another. Depending on the most
important ness or engineering concerns, engineers might also build dynamic model instance, to help them to
study the behavior of a structure in the presence of earthquake. Each kind of model is organized differently,
and each has it focus.

In software, there are several ways to approach a model. The two most ways are from an algorithmic
perspective and from an object-oriented perspective.

The traditional view of software development takes an algorithmic perspective. In this approach, the main
building block of all software is the procedure or function. This view leads developers to focus on issues of
control and the decomposition of larger algorithms into smaller ones. There’s nothing inherently evil about such
a point of view except that it tends to yield brittle systems. As requirements change (and they will) and the
system grows (and it will), systems built with an algorithmic focus turn out to be very hard to maintain.

The contemporary view of software development takes an object-oriented perspective. In this approach, the
main building block of all software systems is the object or class. Simply put, an object is a thing, generally
drawn from the vocabulary of the problem space or the solution space; a class is a description of a set of
common objects. Every object has identity (you can name it or otherwise distinguish it from other objects),
state (there’s generally some data associated with it), and behavior (you can do things to the object, and it can
do things to other objects, as well).

For example, consider a simple three-tier architecture for a billing system, involving a user interface, middle
ware, and a database. In the user interface, you will find concrete objects, such as buttons, menus, and dialog
boxes. In the database, you will find concrete objects, such as tables representing entities from the problem
domain, including customers, products, and orders. In the middle layer, you will find objects such as
transactions and business rules, as well as higher-level views of problem entities, such as customers, products,
and orders.

The object-oriented approach to software development is decidedly a part of the mainstream simply because it
has proven to be of value in building systems all sorts of problem domains and encompassing all degrees of
size and complexity. Furthermore, most contemporary languages, operating systems, and tools are object-
oriented in some fashion, giving greater cause to view the world in terms of objects.

A number of consequence flow from the choice of viewing the world in an object-oriented fashion: What’s the
structure of a good object-oriented architecture? What artifacts should the project create? Who should create
them? How should they be measured?

Visualizing, specifying, constructing, and documenting object-oriented systems is exactly the purpose of the
Unified Modeling Language.

An Overview of the UML

The UML is a language for:

a) Visualizing
b) Specifying
c) Constructing
d) Documenting

The artifacts of a software-intensive system.


The UML Is a Language

A language provides a vocabulary and the rules for combining words in that vocabulary for the purpose of
communication. A modeling language is a language whose vocabulary and rules focus on the conceptual and
physical representation of a system. A modeling language such as the UML is thus a standard language for
software blueprints.

Modeling yields an understanding of a system. No one model is ever sufficient. Rather, you often need multiple
models that are connected to one another in order to understand anything but the most trivial system. For
software-intensive systems, this requires a language that addresses the different views of a system’s
architecture as it evolves throughout the software development life cycle.

The vocabulary and rules of a language such as the UML tell you how to create and read well-formed models,
but they don’t tell you what models you should create and when you should create them. That’s the role of the
software development process. A well-defined process will guide you in deciding what artifacts to produce,
what activities and what workers to use to create them and manage them, and how to use those artifacts to
measure and control the project as a whole.

a) The UML Is a Language for Visualizing

For many programmers, the distance between thinking of an implementation and then pounding it out in code
is close to zero. You think it, you code it. In fact, some things are best cast directly in code. Text is a
wonderfully minimal and direct way to write expressions and algorithms.

In such cases, the programmer is still doing some modeling, albeit entirely mentally. He or she may even
sketch out a few ideas on a white board or on a napkin. However, there are several problems with this. First,
communicating those conceptual models to others is error-prone unless everyone involved speaks the same
language. Typically, projects and organizations develop their own language, and it is difficult to understand
what’s going on if you are an outsider or new to the group. Second, there are some things about a software
system you can’t understand unless you build models that transcend the textual programming language. For
example, the meaning of a class hierarchy can be inferred, but not directly grasped, by staring at the code for
all the classes in the hierarchy. Similarly, the physical distribution and possible migration of4tie objects in a
Web-based system can be inferred, but not directly grasped, by studying the system’s code. Third, if the
developer who cut the code never wrote down the models that are in his or her head, that information would
be lost forever or, at best, only partially recreatable from the implementation, once that developer moved on.

Writing models in the UML addresses the third issue: An explicit model facilitates communication.

Some things are best modeled textually; others are best modeled graphically. in all interesting systems, there
are structures that transcend what can be represented in a programming language. The UML is such a
graphical language. This addresses the second problem described earlier.

The UML is more than just a bunch of graphical symbols. Rather, behind each symbol in the UML notation is a
well-defined semantics. In this manner, one developer can write a model in the UML, and another developer, or
even another tool, can interpret that model unambiguously. This addresses the first issue described earlier.

b) The UML Is a Language for Specifying


In this context, specifying means building models that are precise, unambiguous, and complete. In particular,
the UML addresses the specification of all the important analysis, design, and implementation decisions that
must be made in developing and deploying a software-intensive system.

c) The UML Is a Language for Constructing

The UML is not a visual programming language, but its models can be directly connected to a variety of
programming languages. This means that it is possible to map from a model in the UML to a programming
language such as Java C++, or Visual Basic, or even to tables in a relational database or the persistent store of
an object-oriented database. Things that are best expressed graphically are done so graphically in the UML,
whereas things that are best expressed textually are done so in the programming language.

This mapping permits forward engineering: The generation of code from a UML model into a programming
language. The reverse is also possible: You can reconstruct a model from an implementation back into the
UML.

Reverse engineering is not magic. Unless you encode that information in the implementation, information is lost
when moving forward from models to code. Reverse engineering thus requires tool support with human
intervention. Combining these two paths of forward code generation and reverse engineering yields round-trip
engineering, meaning the ability to work in either a graphical
or a textual view, while tools keep the two views consistent.

d) The UML Is a Language for Documenting

A healthy software organization produces all sorts of artifacts in addition to raw executable code. These
artifacts include (but are not limited to)

Requirements
i. Architecture
ii. Design
iii. Source code
iv. Project plans
v. Tests
vi. Prototypes
vii. Releases

Depending on the development culture, some of these artifacts are treated more or less formally than others.
Such artifacts are not only the deliverables of a project, they are also critical in controlling, measuring, and
communicating about a system during its development and after its deployment.

The UML addresses the documentation of a system’s architecture and all of its details. The UML also provides a
language for expressing requirements and for tests. Finally, the UML provides a language for modeling the
activities of project planning and release management.

Where Can the UML Be Used?

The UML is intended primarily for software-intensive systems. It has been used effectively for such domains as:

a) Enterprise information systems


b) Banking and financial services
c) Telecommunications.
d) Transportation
e) Defense/aerospace
f) Retail
g) Medical electronics
h) Scientific
i) Distributed Web-based services

The UML is not limited to modeling software. In fact, it is expressive enough to model non-software systems,
such as workflow in the legal

system, the structure and behavior of a patient healthcare system, and the design of hardware.

A Conceptual Model of the UML

To understand the UML, you need to form a conceptual model of the language, and this requires learning three
major elements: the UML’s basic building blocks, the rules that dictate how those building blocks may be put
together, and some common mechanisms that apply throughout the UML. Once you have grasped these ideas,
you will be able to read UML models and create some basic ones. As you gain more experience in applying the
UML, you can build on this conceptual model, using more advanced features of the language.

Building Blocks of the UML

The vocabulary of the UML encompasses three kinds of building blocks:

a) Things
b) Relationships
c) Diagrams

Things are the abstractions that are first-class citizens in a model; relationships tie these things together;
diagrams group-interesting collections of things.

Meta-Model

A metamodel describes the constituents of a model and their relationships.

The purpose of a metamodel include:

a) Description of semantics of UML’s modeling concepts.


b) Description of UML modeling constructs and corresponding notation

So that vendors can implement tools to support it.


c) Basis for establishing UML as a standard approach to OOAD.
Chapter 2 - Object Oriented Approach

Objectives

At the end of this chapter you will be able to understand:

The need for Object-Oriented Technology

The application

To design a mail front end to handle (i.e. send, receive, save, reply etc.) mail in different modalities and types.

Notes: Below are more details about the application. The types of email includes:

a) Textual email from and to outside,


b) Mails and memos from within the institute,
c) Faxes,
d) Voice mail.

Assume that each mail type is queued up separately. The following operations are available:

a) Select type of mail


b) Select mail item
c) Move up and down the queue
d) Send, delete, reply, save
e) Quit

Conventional approach to software design:

Progressive top-down functional decomposition of the requirement until we reach simple procedures.

If we adopt this approach and refine things further we get:

a) Initialization
b) Mail reception
c) User command interpretation
d) Termination after the first level of decomposition.

Each node will be expanded until we reach the simplest procedures.

i.exe
Notes: Carrying the process a little further gives the following picture:

Mail-handler

Mail-reception User-command
initialization Interpretation Termination

Queue-navigation
Reply Read Quit
Select Mail-type
Select item
In
selected mail-type
should know
about Mail-item
Figure: Progressive top-down functional decomposition

Example of 'read' :

void read(Mailitem) {

switch(mailitem->type)
{
case email : read_email(mailitem);
break;
case memos : . . . ;
case fax : . . . ;
}
. . .

Notes: The point to note is the way we have to do a case analysis for different mail types. This will be
necessary for each operation.

The result of the top-down functional design process is:

a) The details of the original specification are distributed across the entire program. (Note that similar to read,
all other routines will have to know about the various kinds of mail items.
b) There is practically no code reuse. Most routines will be handling the basic data structures e.g. the queuing
up of the mail items.
c) A change in functionality (e.g. video mail) leads to changes at a large number of places in the program.
d) Not suitable for highly interactive programs where functionality rapidly.
For the mail client we have the following abstract data types:

a) Queues
b) Mail items (which package the data along with functions defined on it, like read, save etc.)

Object Orientation

a) Object orientation brings in the concept of inheritance.


b) This overcomes the limitations of the object-based approaches.

Notes: The inheritance mechanism addresses the limitation of modules to implement slightly variant
behavior without any change in the code of the modules.

The OO Approach

a) The class definition feature of 00 languages gives us the facility to define objects, which behave like
abstract data types.
b) The inheritance mechanism allows us to create minor variants without changing the original code.

We can define prioritized queues as:

class Prioritized_Queue{
public:
Prioritized_Queue( );

Prioritized_Queue( );
void Add_to_Queue (Queueitem* q);
Queueitem Remove_from_Queue( );

// other operations on
// ‘Prioritized_Queue

...
...

Private:
// Implementation can be an array
// or a doubly linked list.
}

Notes: In the modular approach we have to define afresh a data type cal1ed ‘Prioritized_Queue’, which
behaves largely like a queue except that ‘Add_to_Queue’ will not just add to the back of the queue, but
may insert in-between. Note that in all other respects a prioritized queue behaves exactly like a queue. This
is wasteful and inelegant.

This problem is solved through inheritance.

Object Oriented Approach

Definition of a prioritized queue using inheritance.

class Prioritized_Queue: public Queue {


public
Prioritized\_Queue ( );
Prioritized_Queue ( );
void Add_to\_Queue (Queueitem* q);
...
// Priority is an enumerated
// type for the range of types.
Priority Priority_of_Frontitem( );
// The function above implements
// behavior which is special
// to ‘Prioritized_Queue’ and not
// meaningful for ordinary queues.
Private
// implementation
}

Notes: Through inheritance a prioritized queue defines and implements only those parts, which are
different from a standard queue. All behaviors of a queue, which are not explicitly overridden, remain as
they are. This has two advantages:

a) Working code, which has been tested, gets used without changes. It need not be copied from one place to
another. New code changes are restricted to the minimum necessary.
b) Code is getting reused in a basic way. The higher up in an inheritance hierarchy a function is the more
potential there is for reuse.

Improves maintainability

a) Another major gain due to inheritance is the introduction of abstraction, which allow us to add new
functionality or change it with a few localized changes to the code.

Generalization through abstraction

a) This is possible by defining an abstract class called Mail.


b) Mail generalizes the various types of mail e.g. email, memo etc.

Generalization through abstraction is a powerful tool. It makes extension to existing applications manageable.

Notes: The Mail class has all the operations which were valid for specific types of mail like Memo, Email,
Fax, Voicemail etc. i.e. select, read, reply etc. Each mail type is a a kind of Mail. The hierarchy looks like
this:
Each operation in Mail is inherited by each subclass and implemented suitably for its kind of mail.

Mail

E-mail Memo Voice-Mail Video-Mail

Figure: Operation in Mail

What is an Object?

An OBJECT IS a representation of an entity, either real world or conceptual. An object can represent something
concrete, such as Joe’s truck or my computer, or a concept such as a chemical process, a bank transaction, a
purchase order, Mary’s credit history, or an interest rate.

An object is a concept, abstraction, or thing with well-defined boundaries and meaning for an application. Each
object in a system has three characteristics: state, behavior, and identity.

State, Behavior, AND Identity.

THE STATE OF an object is one of the possible conditions in which it may exist. The state of an object typically
changes over time, and is defined by a set of properties (called attributes), with the values of the properties,
plus the relationships the object may have with other objects. For example, a course offering object in the
registration system may be in one of two states: open and closed. If the number of students registered for a
course offering is less than 10, the state of the course offering is open. When the tenth student registers for
the course offering, the state becomes closed.

Behavior determines how an object responds to requests from other objects and typifies everything the object
can do. Behavior is implemented by the set of operations for the object. In the registration system, a course
offering could have the behaviors add a student and delete a student.

Identity means that each object is unique—even if its state is identical to that of another object. For example,
Algebra 101 Section 1, and Algebra 101, Section 2 are two objects in the Course Registration System. Although
they are both course offerings, they each have a unique identity.

Object Persistence

a) Objects can be either transient or persistent. Most business objects are persistent.
b) Persistent objects need a persistent storage (mostly database).
c) Database can be relational database or object-oriented database.

Persistence Closure

When a storage mechanism stores an object, it must store with it the dependents of that object. Whenever a
retrieval mechanism retrieves a previously stored object, it must also retrieve any dependent of that object that
has not yet been retrieved.
Chapter 3 - Managing Console I/O Operations

Objectives

At the end of this session you will be able to understand:

C++ Streams and Stream Classes


Unformatted I/O Operations
Formatted console I/O operations
Formatting Flags, Bit-fields and setf()
Managing output with manipulators

Topics

INTRODUCTION
C++ STREAMS
C++ STREAM CLASSES
UNFORMATTED I/O OPERATIONS
Overloaded Operators >> and <<
Put() and get() Functions
Getline() and wrlte() Functions
FORMATTED CONSOLE I/O OPERATIONS
Defining Field Width: width()
Setting Precision: precision()
Filling and Padding: fill()
FormattIng Flags, Bit-fields and setf()
Displaying Trailing Zeros and Plus Sign
MANAGING OUTPUT WITH MANIPULATORS
DESIGNING OUR OWN MANIPULATORS

MANAGING CONSOLE I/O OPERATIONS

INTRODUCTION

Every program takes some data as input and generates the processed data as output following the familiar
input-process-output cycle. It is, therefore, essential to know how to provide the input data and how to present
the results in a desired form. We have, in the earlier chapters, used cin and cout with the operators» and «for
the input and output operations. But we have not so far discussed as to how to control the way the output is
printed. C++ supports a rich set of I/O functions and operations to do this. Since these functions use the
advanced features of C++ (such as classes, derived classes and virtual functions), we need to know a kit about
them before really implementing the C++ I/O operations.

Remember, C++ supports all of C’s rich set of I/O functions. We can use any of them in the C++ programs.
But we restrained from using them due to two reasons. First, I/O methods in C++ support the concepts of OOP
and secondly, I/O methods in C cannot handle the user-defined data types such as class objects.

C++ uses the concept of stream and stream classes to implement its 1/0 operations with the console and disk
files. We will discuss in this chapter, how stream classes support the console oriented input-output operations.
File-oriented I/O operations will be discussed in the next chapter.

C++ STREAMS
The I/O system in C++ is designed to work with a wide variety of devices including terminals, disks, and tape
drives. Although each device is very different, the I/O system supplies an interface to the programmer that is
independent of the actual device being accessed. This interface is known as stream.

A stream is a sequence of bytes. It acts either as a source from which the input data can be obtained or as a
destination to which the output data can be sent. The source stream that provides data to the program is called
the input stream and the destination stream that receives output from the program is called the output stream.
In other words, a program extracts the bytes from an input stream and inserts bytes into an output stream as
illustrated in Figure belowThe data in the input stream can come from the keyboard or any other storage
device. Similarly, the data in the output stream can go to the screen or any other storage device. As mentioned
earlier, a streamacts as an interface between the program and the input/output device. Therefore, a C++
program handles data (input or output) independent of the devices used.

Input Stream
Extraction from
input stream
Input
device

program
Output stream
Input Inserting into
device output stream

C++ contains several pre-defined streams that are automatically opened when a program begins its execution.
These include cin and cout which have been used very often in our programs. We know that cm represents the
input stream connected to the standard input device (usually the keyboard) and cout represents the output
stream connected to the standard output device (usually the screen). Note that the keyboard and the screen
are default options. We can redirect streams to other devices or files, if necessary.

C++ STREAM CLASSES

The C++ I/O system contains a hierarchy of classes that are used’ to define various streams to deal with both
the console and disk files. These classes are called stream classes. Figure below shows the hierarchy of the
stream classes used for input and output operations with the console unit. These classes are declared in the
header file iostream.h. This file should be included in all the programs that communicate with the console unit.
ios

pointer
Istream ostream
streambuf

input
output

iostream

Istream_withassign Iostream_withassign Ostream_withassign

Figure stream classes for console IO operations

As seen in Figure, ios is the base class for istream (input stream) and ostream (output stream) which are, in
turn, base classes for iostream (input/output- stream). The class ios is declared as the virtual base class so that
only one copy of its members are inherited by the iostream.

The class ics provides the basic support for formatted and unformatted I/O operations. The class istream
provides the facilities for formatted and unformatted input while the class ostream (through inheritance)
provides the facilities for formatted output. The class iostream provides the facilities for handling both input and
output streams. Three classes, namely, istreamwithassign, ostream withassign, and iostream_withassign add
assignment operators to these classes. Table 10.1 gives the details of these classes.

Table Stream classes for console operations

Class name Contents


Ios - contains basic facilities that are used by all other
(general input/output stream class) input and output classes.
- It also contains a pointer to a buffer object
(stream buf object)
- Declares constants and functions that are
necessary for handling formatted input and output
operations.
Istream Inherits the properties of ios
(input stream) Declares input functions such as get() getline() and
read()
Contains overloaded extraction operator >>.
Ostream Inherits the properties of ios
(output stream) Declares output functions such as put() and write()
Contains overloaded insertion operator <<
Iostream Inherits the properties of ios istream and ostream
(input/output stream) through multiple inheritance and thus contains all the
input and output functions.
Streambuf Provides an interface to physical devices through
buffers.
It acts as a base for filebuf class used in files.

UNFORMATTED I/O OPERATIONS

Overloaded Operators >> and <<

We have used the objects cm and cout (pre-defined in the iostream.h file) for the input and output of data of
various types. This has been made possible by overloading the operators» and «to recognize all the basic C++
types. The » operator is overloaded in the istream class and « is overloaded in the ostream class. The following
is the general format for reading data from the keyboard:

Cln >> variable 1 >> variable2 »....» variableN;

Variable1, variable2, ... are valid C++ variable names that have been declared already. This statement will
cause the computer to stop the execution and look for input data from the keyboard. The input data for this
statement would be:
Data1 data2 …. dataN

The input data are separated by white spaces and should match the type of variable in the cm list. Spaces,
newlines and tabs will be skipped. The operator » reads the data character by character and assigns it to the
indicated location. The reading for a variable will be terminated at the encounter of a white space or a
character that does not match the destination type. For example, consider the following code:

Int code;
Cin >> code;

Suppose the following data is given as input:

The operator will read the characters up to 8 and the value 4258 is assigned to code. The character D remains
in the input stream and will be input to the next c.in statement.
The general form for displaying data on the screen is:

cout << item 1 «item2 «....«itemN;

The items item] through itemN may be variables or constants of any basic type. We have used such statements
in a number of examples illustrated in previous chapters.

Put() and get() Functions

The classes istream and ostream define two member functions get() and put() respectively to handle the
single character input/output operations. There are two types of get() functions. We can use both get(char *)
and get(void) prototypes to fetch a character including the blank space, tab and the newline character. The
get(char *) version assigns the input character to its argument and the get(void) version returns the input
character.
Since these functions are members of the input/output stream classes, we must invoke them using an
appropriate object.

Example:

Char c;
Cin.get(c) // get a character from keyboard
// and assign it to c
while (c != '\n')
{
cout << c; // display the character on screen
cin.get(c); // get another character

This code reads and displays a line of text (terminated by a newline character). Remember, the operator » can
also be used to read a character but it will skip the white spaces and newline character. The above while loop
will not work properly if the statement

cin >> c

is used in place of

cln.get(c);

Try using both of them and compare the results.

The get(void) version is used as follows:

char c;

C = cln.get(); II cin.get(c); replaced

The value returned by the function get( ) is assigned to the variable c. The function put(), a membet of
ostream class, can be used to ouput a line of text, character by character. For example,

cout.pt(x’);

displays the character x and

cout.put(ch);

displays the value of variable ch.

The variable ch must contain a character value. We can also use a number as an argument to the function put(
). For example,

cout.put(68);

displays the character D.This statement will convert the int value 68 to a char value and display the character
whose ASCII value is 68.
The following segment of a program reads a line of text from the keyboard and displays it on the screen.

char c;
cin.get(c); II read a character

while(c != '\n')
{
cout.put(c); // display the character on screen
cin.get(c);
}

////// [ character i/o with get() and put() ] /////

#include <iostream.h>
main()
{
int count = 0;
char c;
cout << "input text \n";
cin.get(c);
while (c != '\n');
{
cout.put(c);
count++;
cin.get(c);
}

input programming in c is fun


output programming in c is fun
number of characters = 24

When we type a line of input, the text is sent to the program as soon as we press the RETURN key. The
program then reads one character at a time using the statement cin.get(c); and displays it using the statement
cout.put(c);. The process is terminated when the newline character is encountered.

Getline() and wrlte() Functions

We can read and display a line of text more efficiently using the line.oriented input/output functions getline( )
and write(). The getline() function reads a whole line of text that ends with a newline character (transmitted by
the RETURN key). This function can be invoked by using the object cm as follows:

cin.getllne(line, size);

This function call invokes the function getline() which reads character input into the variable line. The reading is
terminated as soon as either the newline character ‘\n’ is encountered or size—I characters are read (whichever
occurs first). The newline character is read but not saved. instead, it is replaced by the null character. For
example, consider the following code:

char name(20];
cln.getllne(name, 20);

Assume that we have given the following input through the keyboard:

Bjarne Stroustrup <press RETURN> .

This input will be read correctly and assigned to the character array name. Let us suppose the input is as
follows:

In this case, the input will be terminated after reading the following 19 characters:

Object Oriented Pro

Remember, the two blank spaces contained in the string are also taken into account. We can also read strings
using the operator» as follows:

Cin >> name;

But remember cin can read strings that do not contain white spaces. This means that cm can read just one
word and not a series of words such as “Bjame Stroustrup”. But it can read the following string correctly:

Bjarne_Stroustrup

After reading ihe string, cm automatically adds the terminating null character to the character array.

Program 10.2 demonstrates the use of» and getline( ) for reading the strings.

///// ( READING STRINGS WITH getline() ////////


#include <iostream.h>

main()
{
int size = 20;
char city [20];
cout << "enter city name : \n";
cout << "city name :" << city << "\n\n";
cout << "enter city name again \n";
cin.getline(city, size);
cout << "city name now" << city << "\n\n";
cout << "enter another city name \n";
cin.getline(city, size);
cout << "new city name: " << city << "\n\n";
}
The write( ) function displays an entire line and has the following form:

cout.write(Iine, size);

The first argument line represents the name of the string to be displayed and the second argument size
indicates the number of characters to display. Note that it does not stop displaying the characters automatically
when the null character is encountered. If the size is greater than the length of line, then it displays beyond the
bounds of line. Program below illustrates how write( ) method displays a string.

#include <iostream.h>
#include <string.h>

main()

{
char * string1 = "C++";
char * string2 = "Programming";
int m = strlen(string1);
int n = strlen(string2);
for(int I = 1; I < n; I++)
{
cout.write(string2, I); cout << "\n";
}
for ( I = n; I > 0; I --)
{
cout.write(string2, I) ; cout << "\n";
}

cout.write(string1, m) .write(string2, n);


cout << "\n";
cout.write(string1, 10);
}

cout.write(string1, m).write(string2, n);

is equivalent to the following two statements:

cout.write(string1, n ) ;
cout.write(string2, n);

FORMATTED CONSOLE I/O OPERATIONS

C++ supports a number of features that could be used for formatting the output. These features include:

• jos class functions and flags.


• Manipulators.
• User-defined output functions.

The ios class contains a large number of member functions that would help us to format the output in a
number of ways. The most important ones among them are listed in Table below..

Table os format functions

Function Task

Width() To specify the required field size for displaying an


output value.
Precision() To specify the number of digits to be displayed after
the decimal point of a float value.

Fill() To specify a character that is used to fill the unused


portion of a field.
Setf() To specify format flags that can control the form of
output display such as left justification and right
justification.

Unsetf() To clear the flags

Manipulators are special functions that can be included in the I/O statements to alter the format parameters of
a stream. Table 10.3 shows some important manipulator functions that are frequently used. To access these
manipulators, the file iomanip.h should be included in the program.

Table Manipulators

Manipulators Equivalent los function


Setw() Widht()
Setprecision() Precision()
Setfill() Fill()
Setlosflags() Setf()

Resetlosflags() Unsetf()

In addition to these functions supported by the C++ library, we can create our own manipulator functions to
provide any special output formats. The following sections will provide details of how to use the pre-defined
formatting functions and how to create new ones.

Defining Field Width: width()

We can use the width( ) function to define the width of a field necessary for the output of an item. Since, it is a
member function, we have to use an object to invoke it, as shown below:

cout.wldth (w);

where w ts the field width (number of columns). The output will be printed in a field of w characters wide at
the right end of the field. The width( ) function can specify the field width for only one item (the item that
follows immediately). After printing one item (as per the specifications) it will revert back to the default. For
example, the statements -

cout.width(5);
cout «543 « 12 «

will produce the following output:

5 4 3 1 2
The value 543 is printed right-justified in the first five columns. The specification width(5) does not retain the
setting for printing the number 12. This can be improved as follows:

cout.width(5);
cout « 543;
cout.width(5);
cout « 12 «“\n”

This produces the following output:

5 4 3 1 2

Remember that the field width should be specified for each item separately. C++ never truncates the values
and therefore, if the specified field width is smaller than the size of the value to be printed, C++ expands the
field to fit the value. Program demonstrates how the function width() works.

///////// SPECIFYING FIELD SIZE WITH width() ]////////////

#include <iostream.h>
main()
{
int items[4] = [10, 8, 12, 15];
int cost[4] = [75, 100, 60, 99];
cout.width(5);
cout << "items";
cout.width(8);
cout << "cost";
cout.width(15);
cout << "total value " << "\n";
int sum =0;
for(int I =0; I < 4; I++)
{
cout.width(5); cout << items[I];
cout.wdith(8); cout << cost[I];
int value = items[I] * cost[I];
cout.width(15); cout << value << "\n";
sum = sum + value;
}
cout << "\n Grand total = ";
cout.width(2); cout << sum << "\n";
}

The output of Program 10.4 would be:

ITEMS COST TOTAL VALUE


10 75 750
8 100 800
12 60 720
15 99 1485
Grand Total = 3755

Note that a field of width two has been used for printing the value of sum and the result is not truncated. A
good gesture of C++!

Setting Precision: precision()

By default, the floating numbers are printed with six digits after the decimal point. However, we can specify the
number of digits to be displayed after the decimal point while printing the floating-point numbers. This can be
done by using the precision( ) member function as follows:

cout.precision(d);

where d is the number of digits to the right of the decimal point. For example, the statements

cout.precision(3);
cout << sqrt(2) << "\n";
cout << 3.14159 << "\n";
cout << 2.50032 << "\n";

will produce the following output:

1.141 (truncated)
3.142 (rounded to the nearest cent)
2.5 (no trailing zeros)

Note that, unlike the function width( ), precision( ) retains the setting in effect until it is reset. That is why we
have declared only one statement for the precision setting which is used by all the three outputs. We can set
different values to different precision as follows:

We can set different values to different precision as follows

Cout.precision(3);
Cout << sqrt(2) << "\n";
Cout.precision(5);
Cout << 3.14159 << "\n"

We also combine the field specification with the precision setting Example

Cout.precision(2);
Cout.width(5);
Cout << 1.2345;

The first two statements instruct: “print two digits after the decimal point in a field of five character width”.
Thus, the output will be:

Program shows how the functions width( ) and precision( ) are jointly used to control the output format,

#include <iostream.h>
#include (math.h>
main ()

{
cout << "precision set to 3 digits \n\n";
cout.precision(3);
cout.width(10);
cout << "value";
cout.width(15);
cout << "sqrt of value " << "\n";
for (int n = 1; n <=5; n++)
{
cout.width(8);
cout << n;
cout.width(13);
cout << sqrt (n) << "\n";
}
cout << "\n Precision set to 5 digits \n \n ";

cout.precision(5); // precision parameter changed


cout << "sqrt (10) = " << sqrt(10) << "(default setting ) \n ";
}

cout << "sqrt(10) = " << sqrt(10) << "\n\n";


cout.precision(0); // precision set to default
cout << "sqrt(10) = "<< sqrt(10) << "default setting \n";
}

Here is the output of Program 10.5

Precision set to 3 digits

VALUE SORT_OF_VALUE
1 1
2 1.414
3 1.732
4 2
5 2.236

Precision set to 5 digits

sqrt(10) = 3.16228
sqrt(10) = 3.162278 (default setting)

Observe the following from the output:

1. The output is rounded to the nearest cent (i.e., 1.6666 will be 1.67 for two digit precision but 1.3333
will be 1.33).
2. Trailing zeros are truncated.
3. Precision setting stays in effect until it is reset.
4. Default precision is 6 digits.
Filling and Padding: fill()

We have been printing the values using much larger field widths than required by the values. The unused
positions of the field are filled with white spaces, by default. However, we can use the fill( ) function to fill the
unused positions by any desired character. It is used in the following form:

cout.fIll (ch);

Where ch represents the character which is used for filling the unused positions.

Example:

cout.fiII('*');
cout.width(10);
cout « 5250 «“\n”;

The output would be:

* * * * * * 5 2 5 0

Financial institutions and banks use this kind of padding while printing cheques so that no one can change the
amount easily. like precision( ), fill( ) stays in effect till we change it. See Program below and its output.

PADDING WITH fill() 1//////////////

#include <iostream.h>
main()
{
cout.fill('<');
cout.precision(3);
for(int n = 1; n <= 6; n++);
{
cout.width(5);
cout << n;
cout.width(10);
cout << 1.0 / float(n) << "\n";
if(n == 3) cout.fill ('>');
}
cout << "\n padding changed \n\n";
cout.fill('#'); // fill() reset
cout.width(15);
cout << 12.345678 << "\n";
}

FormattIng Flags, Bit-fields and setf()

We have seen that when the function width( ) is used, the value (whether text or number) is printed right-
justified in the field width created. But, it is a usual practice to print the text left-justified. How do we get a
value printed left-justified’? Or, how do we get a floating-point number printed in the scientific notation? The
setf(), a member function of the ios class, can provide answers to these and many other formatting questions.
The setf( ) (setf stands for set flags) function can be used as follows:
cout.setf(arg 1, arg2);

The arg1 is one of the formatting flags defined in the class jos. The formatting flag specifies the format action
required for the output. Another ios constant, arg2, known as bit field specifies the group to which the
formatting flag belongs. Table shows the bit fields, flags and their format actions. There are three bit fields and
each has a group of format flags which are mutually exclusive. Examples:

cout.setf(ios: :left, Os: :adjustfield);


cout.setf(ios: :scientific, ios::floaifield);

Note that the first argument should be one of the group members of the second argument.

Table Flags and bit fields for setf() function

Format required Flag (arg1) Bit-field (arg2)


Left justified output Ios:: left Ios::adjustfield
Right justified output Ios::right Ios::adjustfield
Padding after sign or base indicator ( Ios::internal Ios::adjustfield
like +##20)

Scientific Notation Ios::scientific Ios::floatfield


Fixed point notation Ios:: fixed Ios::floatfield

Decimal base Ios::dec Ios::basefield


Octal base Ios::oct Ios::basefield
Hexadecimal base Ios::hex Ios::basefield

Note that the trailing zeros in the second and third items have been truncated. Certain situations, such as a list
of prices of items or the salary statement of employees, require trailing zeros to be shown. The above output
would look better if they are printed as follows:

10.75
25.00
15.50

The setf( ) can be used with the flag ios::showpoint as a single argument to achieve this form of output. For
example,

Cout.setf(ios::showpoint); //display trailing zeros

would cause cout to display trailing zeros and trailing decimal point. Under default precision, the value 3.25 will
be displayed as 3.250000. Remember, the default precision assumes a precision of six digits.
Similarly, a plus sign can be printed before a positive number using the following statement:

cout.setf(ios::showpos); //show + sign

For example, the statements

cout.setf(ios: :showpoint);
cout.setf(ios: :showpos);
cout.precision(3);
cout.setf(ios::fixed, ios::floatfield);
cout.setf(ios: :internal, ios::adjustfield);
cout.width(10);
cout « 275.5 « "\n";

will produce the following output:

+ 2 7 5 . 5 0 0

The flags such as showpoint and showpos do not have any bit fields and therefore are used as single
arguments in setf(). This is possible because the setf( ) has been declared as an overloaded function in the
class jos. Table 10.5 lists the flags that do not possess a named bit field. These flags are not mutually exclusive
and therefore can be set or cleared independently.

Table Flags that do not have bit fields

Flag Meaning

Ios::showbase Use base indicator on output


Ios::showpos Print + before positive numbers
Ios::showpoint Show trailing decimal point and zeroes
Ios::uppercase Use uppercase letters for hex output

Ios::skipus Skip white space on input

Ios::unitbuf Flush all streams after insertion


Ios:: stdio Flush stdout and stderr after insertion

I
Program demonstrates the setting of various formatting flags using the overloaded setfO function.

//// FORMATTING WITH FLAGS IN setf() ]///////


/ #include (iostream.h)
#include <math.h)

main()
{
cout.fill('*');
cout.setf(ios::left, ios::adjustfield);
cout.width(10);
cout << "value";
cout.setf(ios::right, ios::adjustfield);
cout.width(15);
cout<< "sqrt of value " << "\n";
cout.fill('.');
cout.precision(4);
cout.setf(ios::showpoint);
cout.setf(ios::showpos);
cout.setf(ios::fixed, ios::floatfield);
for(int n = 1; n <= 10; n++)
{
cout.setf(ios::internal, ios::adjustfield);
cout.width(5);
cout << n;
cout.setf(ios::right, ios::adjustfield);
cout.width(20);
cout << sqrt(n) << "\n";

}
// floatfield changed
cout.setf(ios::scientific, ios::floatfield);
cout << "\nsqrt(100) = " << sqrt(100) << "\n";
}

Note the following:

1. The flags set by setf( ) remain effective until they are reset or unset.
2. A format flag can be reset any number of times in a program.
3. We can apply more than one format controls jointly on an output value.
4. The setf( ) sets the specified flags and leaves others unchanged.

MANAGING OUTPUT WITH MANIPULATORS

The header file iomanip.h provides a set of functions called manipulators which can be used to manipulate the
output formats. They provide the same features as that of the ios member functions and flags. Some
manipulators are more convenient to use than their counterparts in the class ios. For example, two or more
manipulators can be used as a chain in one statement as shown below:

cout « manipi « manip2 « manip3 « item;


cout « manipi « itemi « manip2 « item2;

This kind of concatenation is useful when we want to display several columns of output.
The most commonly used manipulators are shown in Table 10.6. The table also gives their meaning and
equivalents. To access these manipulators, we must include the file iomanip.h in the program.

Table Manipulators and their meanings

Manipulator Meaning Equivalent

Setw(int w) Set the field width with w Width()

Setprecision(int d) Set the floating point precision to d Precision()

Setfill(int c) Set the fill character to c Fill()

Setiosflags(long f) Set the format flag f Set()

Resetiosflags(long f) Clear the flag specified by f Unset()

Endf Insert new line and flush stream '\n'

Some examples of manipulators are given below:

cout « setw(10) « 12345;

This statement prints the value 12345 right-justified in a field width of 10 characters. The output can be made
left-justified by modifying the statement as follows:

cout « setw(10) <.c setiosflags(ios::left) < 12345;

One statement can be used to format output for two or more values. For example, the statement

cout «setw(5) «setprecision(2) <.< 1.2345


.cz<setw(10) « setprecision(4) «sqrt(2)
«setw(15) «setiosflags(ios::scientific) «sqrt(3)
«endl;

will print all the three values in one line with the field sizes of 5, 10, and 15 respectively. Note that each output
is controlled by different set of format specifications. We can jointly use the manipulators and the los functions
in a program. The following segment of code is valid:

cout.setf(ios: :showpoint);
cout.setf(ios::showpos);
cout « setprecision(4);
cout « setiosflags(ios::scientific);
cout « setw(10) « 123.45678;

There is a major difference in the way the manipulators are implemented as compared to the ios member
functions. The jos member function returns the previous format state which can be used later, if necessary. But
the manipulator does not return the previous format state. In case, we need to save the old format states, we
must use the jos member functions rather than the manipulators. Example:
cout.precision(2); //previous state
int p = cout.precision(4); //current state

When these statements are executed, p will hold the value of 2 (previous state) and the new format state will
be 4. We can restore the previous format state as follows:

cout.precision(p); //p = 2

Program 10.8 illustrates the formatting of the output values using both manipulators and ios functions.

#include <iostream.h>
#include <iomanip.h>
main()
{
cout.serf(ios::showpoint);
cout << setw(5) << "n"
<< setw(15) << "Inverse_of_n"
<< setw(15) << "sum_of_terms \n\n";
double term , sum = 0;
for(int n = 1; n< 10; n++)
{
term = 1.0 / float(n);
sum = sum + term;
cout << setw95) << n
<< setw(14) << setprecision94)
<< setiosflags(ios::scientific) << term
<< setw913) << resetiosflags(ios::scientific)
<< sum
<< endl;
}
}

The output of Program 10.8 would be:

n Inverse of n Sum of terms


1 1.OOOOe+OO 1.0000
2 SOQOQe—Ol 1.5000
3 3.3333e—01 1.8333
4 2.5000e—01 2.0833
5 2.0000e—01 2.2833
6 1 .6667e—01 2.4500
7 1.4286e—01 2.5929
8 1.2500e—01 2.7179
9 lillie—Ol 2.8290
10 1.OOOOe—01 2.9290

DESIGNING OUR OWN MANIPULATORS


We can design our own manipulators for certain special purposes. The general form for creating a manipulator
without any arguments is:

Ostream & manipulator(ostream & output)


{
…………….
…………(code)
…………….
Return output;
}

Here, the manipulator is the name of the manipulator under creation. The following function defines a
manipulator called unit that displays “inches”:

ostream & unit (ostream & output)

output « “inches”;
return output;
}
The statement
cout « 36 « unit;
will produce the following output

36 inches

We can also create manipulators that could represent a sequence of operations.


Example:

ostream & show(ostream & output)


output.setf(ios: :showpoint);
output.setf(ios: :showpos);
output « setw(10);
return output;
}

This function defines a manipulator called show that turns on the flags showpoint and showpos declared in the
class ios and sets the field width to 10.

Program illustrates the creation and use of the user-defined manipulators. The program creates two
manipulators called currency and form which are used in the main program. Note that form represents a
complex set of format functions and manipulators.

//////I///////// USER—DEFINED MANIPULATORS J////////////////


#include <iostream.h>
#include <iomanip.h>
// user defined manipulators
ostream & currency(ostream & output)
{
output << "Rs";
return output;
}

ostream & form(ostream & output)


{
output.setf(ios::showpos);
output.setf(ios::showpoint);
output.fill("*");
output.precision(2);
output.<< setiosflags(ios::fixed) << setw(10);
return output;
}

main()
{
cout << currency << form << 7864.5;
}
Chapter 4 - WORKING WITH FILES

Objectives

At the end of this chapter you will be able to understand

File Manipulations
File pointers
File stream classes
Command line arguments

Topics

Introduction
Classes for file stream operations
Opening and closing a file
Detecting end of file
More about open() : file modes
File pointers and their manipulations
Sequential input and output operations
Command line arguments

INTRODUCTION

Many real-life problems handle large volumes of data and, in such situations, we need to use some devices
such as floppy disk or hard disk to store the data, The data is stored in these devices using the concept of files.
A file is a collection of related data stored in a particular area on the disk. Programs can be designed to perform
the read and write operations on these files.

A program typically involves either or both of the following kinds of data communication:

I. Data transfer between the console unit and the program.


2. Data transfer between the program and a disk file.

I.exe
External Memory

Data Files
Program file
Read data interaction
Write data to from
files files

Internal Memory

Program + Data

Console Unit

cin >> Console


(get data from Cout << program
(put data
keyboard Screen to screen)
interaction

Keyboard
Input stream

Data
input

Disk files
Program

Data
Output stream output
Write data

Figure illustrating File Input and output streams


Figure stream classes for file operations contained in fstream.h file

ios

Iostream.h file

istream Streambuf ostream

iostream

ifstream fstream ofstream filebuf


Fstream.h file

Fstream base
We have already discussed the technique of handling data communication between the console unit and the
program. In this chapter, we will discuss various methods available for storing and retrieving the data from
files.

The I/O system of C++ handles file operations which are very much similar to the console input and output
operations. It uses file streams as an interface between the programs and the files. The stream that supplies
data to the program is known as input stream and the one that receives data from the program is known as
output stream. In other words, the input stream extracts (or reads) data from the file and the output stream
inserts (or writes) data to the file. The input operation involves the creation of an input stream and linking it
with the program and the input file. Similarly, the output operation involves establishing an output stream with
the necessary links with the program and the output file.

CLASSES FOR FILE STREAM OPERATIONS

The I/O system of C++ contains a set of classes that define the file handling methods. These include ifstream,
ofstream and fstream. These classes are derived from fstreambase and from the corresponding iostream.h
class as shown in Fig. 11.3. These classes, designed to manage the disk files, are declared in fstream.h and
therefore we must include this file in any program that uses files. Table 11.1 shows the details of file operation
classes. Note that these classes contain many more features. For more details, refer to the manual.

Table Details of tile stream classes

Class Contents

filebuf Its purpose is to set the file buffers to read and write.
Contains openprot constant used in the open() of file
stream classes. Also contains close() and open() as
members.
Fstreambase Provides operations common to the file streams. Serves
as a base for fstream , ifstream and ofstream classes .
contains open() and close() functions.
Ifstream Provides input operations. Contains open() with default
input mode inherits the functions get(), getline(),
read(), seekg() and tellg() functions from istream.
Ofstream Provides output operations. Contains open() with
default output mode. Inherits put(), seekp(), tellp(),
and write() functions from ostream.
Fstream Provides support for simultaneous input and output
operations. Contains open() with default input mode.
Inherits all the functions from istream and ostream
classes through iostream.

OPENING AND CLOSING A FILE

If we want to use a disk file, we need to decide the following things about the file and its intended use:

1. Suitable name for the file.


2. Data type and structure.
3. Purpose.
4. Opening method.

The filename is a string of characters that make up a valid filename for the operating system. It may contain
two parts, a primary name and an optional period with extension.
Examples:

lnput.data
Test.doc
INVENTORY
student
salary
OUTPUT

As stated earlier, for opening a file, we must first create a file stream and then link it to the filename. A file
stream can be defined using the classes ifstream, ofstream, and fstream that are contained in the header file
.htream.l,. The class to be used depends upon the purpose, whether we want to read data from the file or
write data to it. A file can be opened in two ways:

I. Using the constructor function of the class.


2. Using the member function open() of the class.
The first method is useful when we use only one file in the stream. The second method is used when
we want to manage multiple files using one stream.

Opening Files Using Constructor

We know that a constructor is used to initialize an object while it is being created. Here, a filename is used to
initialize the file stream object. This involves the following steps:

1. Create a file stream object to manage the stream using the appropriate class. That is, the class
ofstream is used to create the output stream and the class ifstream to create the input stream.
2. Initialize the file object with the desired filename.
For example. the following statement opens a file named “results” for output:

ofstream outfile(“results”); //output only

This creates outfile as an ofstream object that manages the output stream. This object can be any valid C++
name such as o_tile, myfile or fout. This statement also opens the file results and attaches it to the output
stream outfile.

Similarly, the following statement declares infile as an ifstream object and attaches it to the file data for reading
(input).

ifstream intiIe(”data”); //input only

The program may contain statements like:

outfile << ‘TOTAL’;


outfile << sum;
infile » number;
infile » string;
We can also use the same file for both reading and writing data The programs would contain the following
statements:

Program 1
………….
……
ofstream outfile(“salary”) ; // creates outfile and connects
// salary to it

Program 2

…………………
…………………
ifstream infile (“salary”) ; // creates infile and connects salary to it.

ofstream outfile(”salary’); I/creates outfile and connects


//salary” to it

Program2

ifstream infile(”salary”); // creates infile and connects salary to it

The connection with a file is closed automatically when the stream object expires (when the program
terminates). That is. when the program] is terminated, the salary file is disconnected from the outfile stream.
Similar action takes place when the program2 terminates. Instead of using two programs, one for writing data
(output) and another for reading data (input), we can use a single program to do both the operations on a file.

Example.

outfile.close(); // Disconnect salary from outfile


ifstream infile(”salary”); // and connect to infile
infile.close(); // Disconnect salary from infile

Although we have used a single program, we created two tile stream objects, outfile (to put data to the file)
and infile (to get data from the file). Note that the use of a statement like

outfile.close();

disconnects the file salary from the output stream outfile. Remember, the object outfile still exists and the
salary file may again be connected to outfile later or to any other stream. In this example, we are connecting
the salary file to infile stream to read data.

Program uses a single file for both writing and reading the data. First, it takes data from the keyboard and
writes it to the file. After the writing is completed, the file is closed. The program again opens the same file,
reads the information already written to it and displays the same on the screen.
/////////////// [WORKING WITH SINGLE FILE ]//////////////////

#include <fstream.h>
main()
{
ofstream outf(“item”); // connect item file to outf
cout << “Enter item name :”;
char name[30];
cin >> name ; // get name from key board and write to file item
outf << name << “\n”;
cout << “enter item cost “;
float cost;
cin >> cost;
outf << cost << “\n”;
outf.close();
ifstream inf (“item”);
inf >> name;
inf >> cost;
cout << “\n”;
cout << “item name :” << name << “\n”;
cout << “item cost :” << cout << “\n”;

inf.close();

Caution – when a file is opened for writing only a new file is created if there is no file of that name. If a file by
that same name exists already then its contents are deleted and the file is presented as a clean file. We shall
discuss later how to open an existing file for updating it without losing its original contents.

Opening Files Using open()

As stated earlier, the function open() can be used to open multiple files that use the same stream object. For
example, we may want to process a set of files sequentially. In such cases, we may create a single stream
object and use it to open each file in turn. This is done as follows:

file-stream-class stream-object;
stream-object .open (“filename”);

Example:

Ofstream outfile ; // create stream for output


Outfile.open(“data1”); // connect stream to data1
……
…….
Outfile.close(); // disconnect stream from data1
Outfile.open(“data2”); // connect stream to data2
………
……….
Outfile.close(); // disconnect stream from data2
………..
………..

The above program segment opens two files in sequence for writing the data. Note that the first file is closed
before opening the second one. This is necessary because a stream can be connected to only one file at a time.

////// [ working with multiple files ] ///////////


# include <fstream.h>
main ()
{
ofstream fout; // create output stream
fout.open(“country”); // connect “country” to it
fout << “united states of america \n”;
fout << “united kingdom \n”;
fout << “south korea \n”;

fout.close(); // disconnect “country” and connect captial


fout.open(“captial”); //
fout << “washington\n”;
fout << “london\n”;
fout << “seoul \n”;
fout.close(); // disconnect “capital”

// reading the files


const int N = 80; // size of line
char line [N];
ifstream fin; // create input stream
fin.open(“country”); // connect “country” to it
cout << “contents of country file \n”;
while (fin) // check end of file
{
fin.getline(line, N); // read a lline
cout << line; // display it
}

fin.close(); // disconnect “country” and connect captial


fin.open(“capital”);
cout << “\nContents of captial file \n”;
while (fin)
{
fin.getline(line, N);
cout << line;
}
fin.close();
}

At times we may require to use two or more files simultaneously For example, we may require to merge two
sorted files into a third sorted file, This means, both thc sorted files have to be kept open for reading and the
third one kept open for writing. In such cases, we need to create two separate input streams for handling the
two input files and one output stream for handling the output file.

/// [ reading from two files simultaneously ] //////////

#include <fstream.h>
#include <stdlib.h> // for exit() function
main()
{
const int size = 80;
char line [size];
ifstream fin1, fin2; // create two input streams
fin1.open(“country”); // connect country to fin1
fin2.open(“captial”); // connect capital to fin2

for (int I = 1; I < 10; I++)


{
if (fin1.eof() != 0)
{
cout << “Exit from country \n”;
exit(1);
}
fin1.getline(line, size);
cout << “captial of “ << line ;
if(fin2.eof() !=0)
{
cout << “Exit from captial\n”;
exit(1);
}
fin2.getline(line, size);
cout << line “\n”;
}
}

DETECTING END-OF-FILE

Detection of the end-of-file condition is necessary for preventing any further attempt to read data from the file.
This was illustrated in Program by using the statement

while(fin)

An ifstream object, such as fin, returns a value of 0 if any error occurs in the file operation including the end-
of-file condition. Thus, the while loop terminates when fin returns a value of zero on reaching the end-of-file
condition. Remember, this loop may terminate due to other failures as well. (We will discuss other error
conditions later.)

There is another approach to detect the end-of-file condition. Note that we have used the following statement

if(fin1.eof() != 0) {exit(1);}
eof( ) is a member function of ios class. It returns a non-zero value if the end-of-file(EOF) condition is
encountered, and a zero, otherwise. Therefore, the above statement terminates the program on reaching the
end of the file.

MORE ABOUT OPEN() FILE MODES

We have used ifstream and ofstream constructors and the function opent to create new files as well as to open
the existing files. Remember, in both these methods, we used only one argument that was the filename.
However, these functions can take two arguments, the second one for specifying the file mode. The general
form of the function open( ) with two arguments is:

stream-object.open(“filename”,, mode):

The second argument mode (called file mode parameter) specifies the purpose for which the file is opened.
How did we then open the files without providing the second argument in the previous examples?

The prototype of these class member functions contain default values for the second argument and therefore
they use the default values in the absence of the actual values. The default values are as follows:

ios::in for ifstream functions meaning open for reading only.


ios::out for ofstream functions meaning open for writing only.

The tile mode parameter can take one (or more) of such constants defined in the class jos. Table lists the file
mode parameters and their meaning.

Table File mode parameters

Parameter Meaning

Ios::app Append to end of file

Ios::ate Go to end of file on opening

Ios::binary Binary file

Ios::in Open file for reading only

Ios::nocreate Open fails if the file does not exist

Ios::noreplace Open fails if the file already exists

Ios::out Open file for writing only

Ios::trunc Delete contents of the file if it exists


Note

1. Opening a file in ios::out mode also opens it in the ios::trunc mode by default.
2. Both ios::app and ios::ate take us to the end of the file when it is opened. But the difference between
the two parameters is that the ios::app allows us to add data to the end of the file only, while ios;:ate
mode permits us to add data onto modify the existing data anywhere in the file. In both the cases, a
file is created by the specified name, if it does not exist.
The parameter ios::app can be used only with the files capable of output.
4 Creating a stream using ifstream implies input and creating a stream using ofstream implies output. So
in these cases it is not necessary to provide the mode parameters.
5 The fstream class does not provide a mode by default and therefore, we must provide the mode
explicitly when using an object of fstream class.
6. The mode can combine two or more parameters using the bitwise OR operator (symbol I) as shown
below:

fout.open(’data”, ios::app jos:: nocreate)

This opens the file in the append mode but fails to open the file if it does not exist.

FILE POINTERS AND THEIR MANIPULATIONS

Each file has two associated pointers known as the file pointers. One of them is called the input pointer (or get
pointer) and the other is called the output pointer (or put pointer). We can use these pointers to move through
the files while reading or writing. The input pointer is used for reading the contents of a given file location and
the output pointer is used for writing to a given file location, Each time an input or output operation takes
place, the appropriate pointer is automatically advanced.

Default Actions

When we open a file in read-only mode, the input pointer is automatically set at the beginning so that we can
read the file from the start. Similarly. when we open a file write-only mode, the existing contents are deleted
and the output pointer is set at the beginning. This enables us to

Hello file

Open for reading only


H E L L O W O R L D

Input pointer

Open in append mode


H E L L O W O R L D

Output
pointer
(for writing more data)
Open for writing only

Output
pointer

Fig. Action on file pointers while opening a file

write to the file from the start. In case, we want to open an existing file to add more data, thc file is opened in
append’ mode. This moves the output pointer to the end of the file (i.e. the end of the existing contents).

Functions for Manipulation of File Pointers

All the actions on the file pointers take place automatically by default. How do we then move a file pointer to
any other desired position inside the tile? This is possible only if we can take control of the movement of the
file pointers ourselves. The file stream classes support the following functions to manage such situations:

seekg () Moves get pointer (input) to a specified location.


seekp () Moves put pointer (output) to a specified location.
tellg () Gives the current position of the get pointer.
Tellp () Gives the current position of the put pointer.

For example, the statement

infile.seekg(10);

moves the file pointer to the byte number 10. Remember, the bytes in a file are numbered beginning from
zero. Therefore, the pointer will be pointing to the 11th byte in the file.

Consider the following statements:

ofstream fileout;
fileout.open(”hello”, ios::app);
int p = fiIeout.teIIp();

On execution of these statements, the output pointer is moved to the end of the file “hello” and the value of p
will represent the number of bytes in the file.

Specifying the Offset

We have just now seen how to move a file pointer to a desired location using the ‘seek’ functions. The
argument to these functions represent the absolute position in the file.

file

start end

Outfile.seekp(m);
Figure Action of a single argument seek function

seek functions seekg( ) and seekp( ) can also be used with two arguments as follows:

seekg (offset, refposition);


seekp (offset, refposition);

The parameter offtet represents the number of bytes the file pointer is to be moved from the location specified
by the parameter refposition. The refposition takes one of the following three constants defined in the jos
class:

ios::beg start of the file


ios::cur current position of the pointer
ios::end End of the file

The seekg( ) function moves the associated file’s ‘get’ pointer while the seekp( )function moves the associated
file’s ‘put’ pointer. Table lists some sample pointer offset calls and their actions. fout is an ofstream object.
Table Pointer offset calls

Seek call Action

Fout.seekg(o, ios::beg); Go to the start

Fout.seekg(o, ios::cur); Stay at the current position

Fout.seekg(o, ios::end); Go to the end of the file

Fout.seekg(o, ios::beg); Move to (m+1) th byte in the file

Fout.seekg(o, ios::cur); Go forward by m bytes from the current position

Fout.seekg(o, ios::cur); Go backward by m bytes from the current position

Fout.seekg(o, ios::end); Go backward by m bytes from the end

SEQUENTIAL INPUT AND OUTPUT OPERATIONS

The file stream classes support a number of member functions for performing the input and output operations
on files. one pair of functions, put( ) and gef( ), are designed for handling a single character at a time. Another
pair of functions, write( ) and read( ), are designed to write and read blocks of binary data.

Put() and get()Functions

The function put() writes a single character to the associated stream. Similarly, the function get() reads a single
character from the associated stream. Program illustrates how these functions work on a file, The program
requests for a string. On receiving the string, the program writes it, character by character, to the file using the
put() function in a for loop. Note that the length of the string is used to terminate the for loop.

The program then displays the contents of the file on the screen. It uses the function get( ) to fetch a character
from the file and continues to do so until the end-of-file condition is reached. The character read from the file is
displayed on the screen using the operator «.

////// [ I/O operations on characters ] ///////////

# include <fstream.h>
#include <string.h>
main()
{
char string[80];
cout << “enter a string \n”;
cin >> string;
int len = strlen(string);
fstream file;
file.open(“text”, ios::in | ios::out);
for(int I =0 ; I< len ; I++)
file.put(string[I]); // put a character to file
file.seekg(0); // go to the start
char ch;
while (file)
{
file.get(ch); // get a character from file
cout << ch; // display it on screen
}
}

Note that we have used an fstream object to open the file. Since an fstream object can handle both the input
and output simultaneously, we have opened the file in ios::in ios::out mode. After writing the file, we want to
read the entire file and display its contents. Since the file pointer has already moved to the end of the file, we
must bring it back to the start of the file. This is done by the statement

file.seekg(0);

write() and read() Functions

The functions write( ) and read(), unlike the functions put( ) and get( ), handle the data in binary form. This
means that the values are stored in the disk file in the same format in which they are stored in the internal
memory. Figure 11.9 shows how an int value 2094 is stored in the binary and character formats. An int takes
two bytes to store its value in the binary form, irrespective of its size. But a 4-digit int will take four bytes to
store it in the character form.

The binary format is more accurate for storing the numbers as they are stored in the exact internal
representation. There are no conversions while saving the data and therefore saving is much faster. The binary
input and output functions takes the following form:

main

infile.read((char *) &v, sizeof(v));


outfile.write((char *) &v, sizeof(v));

These functions take two arguments The first is the address of the variable 1’, and the second is the length of
that variable in bytes. The address of the variable must be cast tp type char* (i.e. pointer to character type).
Program illustrates how these two functions are used to save an array of foal numbers and then recover them
for display on the screen.

//// [ I/O operations on binary files ] ////////

#include <fstream.h>
#include <iomainp.h>
const char * filename = “BINARY”;
main()
{
float height[4] = {175.5, 153.0, 167.25, 160.70} ;
ofstream outfile(filename);
outfile.write((char *) & height , sizeof(height));
outfile.close(); // close file for reading
for (int I = 0; I < 4; I ++) // clear array from memory
height[I] = 0;
ifstream infile(filename);
infile.read(char *) & height , sizeof(height);
for (I=0; I < 4; I++)
{
cout.setf(ios::showpoint);
cout << setw(10) << setprecision (2) << height[I];
}
infile.close();

Reading and Writing a Class Object

We mentioned earlier that one of the shortcomings of the I/O system of C is that it cannot handle user-defined
data types such as class objects. Since the class objects are the central elements of C++ programming, it is
quite natural that the language supports features for writing to and reading from the disk files objects directly.
The binary input and output functions read() and write() are designed to do exactly this job. These functions
handle the entire structure of an object as a single unit, using the computer’s internal representation of data.
For instance, the function write() copies a class object from memory byte by byte with no conversion. One
important point to remember is that only data members are written to the disk file and the member functions
are not.

Program illustrates how class objects can be written to and read from the disk files. The length of the object is
obtained using the sizeof operator. This length represents the sum total of lengths of all data members of the
object.

/// READING AND WRITING CLASS OBJECTS ///

#include <fstream.h>
#include <iomanip.h>

class INVENTORY
char name[10]; //item name
int code; //item code
float cost; //cost of each item
public:
void readdata (void);
void writedata(void)
1;

void INVENTORY :: readdata(void) //read from keyboard

cout « “Enter name:” ; cin >> name;


cout <C “Enter code: “ ; cin >> code;
cout << “Enter cost: “; cin ) >> cost;
void INVENTORY :: writedata(void) //formatted display on
// screen
{
cout << setiosflags(ios::left) << setw(10) <<name
<< setiosflags(ios::right) << setw(10) <<code
<< setprecision(2) << setw(10) << cost
<< endl;

main ()
{
inventory item[3] ; // declare array of 3 objects
fstream file; // input and output file
file.open(“stock.dat”, ios::in | ios::out);
cout << “enter details for three items \n”;
for (int I =0; I < 3; I++)
{
item[I].readdata();
file.write((char *) & item[I], sizeof(item[I]));
}
file.seekg(0); // reset to start
cout << “\noutput\n\n”);
for(I =o; I<3; I++)
{
file.read((char*) & item[I], sizeof(item[I]));
item[I].writedata();
}
file.close();
}

ENTER DETAILS FOR THREE ITEMS


Enter name: BASIC
Enter code: 99
Enter cost: 84
Enter name: FORTRAN
Enter code: 100
Enter cost: 98
Enter name: C
Enter code: 120
Enter cost: 125

OUTPUT
BASIC 99 84

The program uses for loop for reading and writing objects. This is possible because we know the exact number
of objects in the file. In case, the length of the file is not known, we can determine the file-size in terms of
objects with the help of the file pointer functions and use it in the for loop or we may use while(file) test
approach to decide the end of the file. These techniques are discussed in the next section.
COMMAND-LINE ARGUMENTS

Like C, C++ too supports a feature that facilitates the supply of arguments to the main() function. These
arguments are supplied at the time of invoking the program. They are typically used to pass the names of data
files. Example:

C > exam data results

Here, exam is the. name of the file containing the program to be executed, and data and results are the
filenames passed to the program as command-line arguments. The command-line arguments are typed by the
user and are delimited by a space. The first argument is always the filename (command name) and contains
the program to be executed. How do these arguments get into the program? The main() functions which we
have been using up to now without any arguments can take two arguments as shown below:

main(Int argc, char * argv[])

The first argument argc (known as argument counter) represents the number of arguments in the command
line. The second argument argv (known as argument vector) is an array of char type pointers that point to the
command line arguments. The size of this array will be equal to the value of argc. For instance, for the
command line

C >exam data results

the value of argc would be 3 and the argv would be an array of three pointers to strings as shown below:

argv[0] -> exam


argv[1] -> data
argv[2] -> results

Note that argv[0] always represents the command name that invokes the program. The character pointers
argv[1] and argv[2] can be used as file names in the file opening statements as shown below:

infile.open(argv[1]); // open data file for reading

outfile.open(argv[2]); // open results file for writing

Program illustrates the use of the command-line arguments for supplying the file names. The command line is

test ODD EVEN

The program creates two files called ODD and EVEN using the command-line arguments and a set of numbers
stored in an array are written to these files. Note that the odd numbers are written to the file ODD and the
even numbers are written to the file EVEN. The program then displays the contents of the files.

/////// [ COMMAND LINE ARGUMENTS ] ////////


#include (fstream.h)
#include (stdlib.h>
main (int argc, char * argv[])
{
int number[9] = {11, 22, 33, 44, 55, 66, 77, 88, 99};
if(argc != 3)

{
cout << “argc = “ << argc << “\n”;
cout << “error in arguments \n”;
exit (1);
}
ofstream fout1, fout2;
fout1.open(argv[1])
if (fout1.fail())
{
cout << “could not open the file “
<< argv[1] << “\n”;
exit(1);
}
fout2.open(argv[2]);
if (fout2.fail())
{
cout << “could not open the file “
<< argv[2] << “\n”;
exit (1);
}
for (int I = 0; I < 9; I++)
{
if (number[I] % 2 == 0)
fout2 << number[I] << “ “ ; // write to even file
else
fout1 << number[I] << “” ; // write to odd file
}
fout1.close();
fout2.close();
ifstream fin;
char ch;
for ( I = 1; I < argc; I++)
{
fin.open(argv[I]);
cout << “contents of “ << argv[I] << “\n”;
do
{
fin.get(ch); // read a value
cout << ch; display it
}
while (fin);
cout << “\n\n”;
fin.close();
}
}

Figure streams working on multiple files


Disk Connect one file to fout

fout
program
Country
file

Capital
file

fin

program

Connect one file to


fin
Figure Two single file streams working on one file

Program 1

Put
data

outfile
Salary file

Program 2

Get data

infile
Single file streams working on separate files

Ouptut stream

Results
file
outfile
Program

Input stream
Data
file
infile
Chapter 5- Advanced Features in C++

Objectives

At the end of this chapter you will be able to understand:

Classes within classes


Overloading operators
Pointers
Namespaces
RTTI

Topics

Classes within classes


Overloading << and >>
One more use of friend function
A word of caution
Smart pointers
More smart pointers
Pointers to members
The explicit keyword
The mutable keyword
Namespaces
Using a namespace
RTTI
When to use RTTI
Typecasting in C++

Classes Within Classes

If two classes are related with one another there can be two types of relationships between them: a kind of
relationship or a has a relationship. The kind of relationship is supported by inheritance, whereas, the has a
relationship is supported by composition or containership.

In inheritance if a class Y is derived from class X, we can say that the new class Y is like that old class X. Or in
other words “Y is a kind of X’. This is because the class Y has all the characteristics of X, and in addition some
of its own. It is like saying a car is a kind of automobile: a car has the characteristics shared by all automobiles
(has wheels and engine, runs on fuel etc.) but has some distinctive characteristics of its own (such as a four
stroke engine and a steering wheel). For this reason inheritance is often called a kind of relationship.

In a has a relationship you simply create objects of your existing class inside the new class. For example, if
there is a class called carburettor we can create an object of this class in the new class car as shown below.

class carburettor
{
};
class car
{
carburettor C;

};

Actually, we have been using composition in almost every class designed so far. We were composing classes
using built-in types. Using composition with user-defined types is equally straightforward, as you would realise
in the following program.

#include lostreamh>
#include <string.h>

class carburettor
{
private:

char type;
float cost;
char mfr[30];

public:

void setdata ( chart, float c, char *m)


{
type =t;
cost = c;
strcpy ( mfr, m)
}
void displaydata()
{

cout « endl «type «endl «cost « endl « mfr;


}
};
class car
{
private:
char model[25];
char drivetype[20];

public:
void setdata (char *m char *d)
{
strcpy ( model, in);
strcpy ( drivetype, d);
}

void displaydata()
{
cout « endl « model « endl << drivetype;
}

carburettor c; // embedded object


};

void main()
{
car mycar;
mycar.c.setdata (‘A’, 8500.00, “Mico”);
mycar.setdata (“sports”, “4-wheel”);

mycar.c.displaydata();
mycar.displaydata();
}

Since the data members of the carburettor class are private it is completely safe to embed an object of type
carburettor as a public object in the new class car. To access the member functions of the carburettor class we
simple have to use the ‘.‘ operator twice.

In composition it is more common to make the embedded objects private. This permits us to make a change in
the implementation of the embedded object, if required. For example, in the following program the function
carburettor: :displaydata() is retained as it is in the car class’s interface, but the functions setcarbudata() and
getcost() have been used within member functions of car, thereby changing the interface.

#include <iostream.h>
#include <string.h>

class carburettor
{
private:

char type;
float cost;
char mfr[30];

public:

void setcarbudata ( char t, float c, char *m)


{
type =t;
cost = c;
strcpy ( mfr, in)
}

void displaydata()
{
cout < endl << typeendl <<cost<< endl << mfr;
}

float getcost()
{
return cost;
}
};
class car

private:

char model[25];
char drivetype[20];

carburettor cc; // embedded object

public

void setdata ( chart, float c, char *mf char * char *d)


{
strcpy ( model, m);
strcpy ( drivetype, d);
cc.setcarbudata( t, c, mf);
}

void displaydata()
{
cout < endl < model << endl “drivetype;
}

float getcost(
{
return 2 * cc.getcost(
}
};

void main()
{
car mycar;
mycar.setdata (‘A’, 8500.00, “Mico”, “sports”, “4-wheel”);

mycar.displaydata();
cout « endl « mycar.getcost();
}

Which One to Use

If we are to do code reuse in C we have two ways:

(a) Copy code and change it


(b) Use good libraries

Both the approaches haven’t worked very well. C++ has much more sophisticated mechanisms for code reuse
in the form of composition and inheritance. Both permit reuse of existing classes without changing their
existing code. Composition is useful when classes act like a data type. Then an object of that type can be used
in a class in almost the same way as a basic type like int would be. Other situations would demand a more
close examination of the problem. Often inheritance is simpler to implement and doesn’t blur the clear-cut class
boundaries.

Observe the declaration at the beginning of the program:

class two;

This declaration is necessary since a class can’t be referred to until it has been declared. Class two is being
referred to in the declaration of the function accessboth() in class one. So two must be declared before one.
This declaration tells the compiler that the class two is defined later.

Overloading << and>>

In the last chapter we said that we can overload >> and << to perform I/O for user-detined types. For
example, if we have an object c l of class complex, then we can display it through the statement

cout << cl;

Or we can read it by saying,


cin >> c1;

This makes the user-defined types’ handling similar to that of basic data types. The following program shows
how this overloading can be achieved.

#include <iostream.h>
class complex
{
private

double real, imag;

public:

complex()
{
}

complex (double r, double i)


{
real = r;
imag = i;
}
friend ostream& operator << (ostream& s, complex& c);
friend istream& operator>> (istream& s, complex& c)
};
ostream& operator” (ostream& s, complex& c)
{
s << “( “<< c.real “, “>> “c.imag <<”)”
return s;
}
istream& operator>> (istream& s, complex& c)
{
s » creal >> c.imag;
return s;
}

void main()
{
complex c1 (1.5, 2.5), c2 ( 3.5, 4.5), c3;
cout<< endl<< “ci = “<<c1= “<<endl<< “c2 = “ << c2;
cout << endl << “Enter a complex number:”;
cin >> c3;
cout << “c3 = “<<c3;
}

You may note that the statements

cout << endl << “c1 = “<<c1<<endl << “c2 = “ <<c2;


cout<< endl<< “Enter a complex number”;
cin>> c3;
cout<< ‘c3= “<<c3;

are much more expressive and are similar to the way we would perform I/O with standard data types.

Here we have defined two friend functions operator << () and operator >>(). Since the friend declaration of
these functions occurs only in the complex class and not in istream or ostream classes these functions can
access only the private data of the complex class.

The operator functions are not members of the class complex. Hence the statement cm >> c3 doesn ‘t get
converted into the form cm operator>> (c3)

The object on either side of >> gets passed to the operator function. Both are collected as references. This
prevents creation of copies of these objects. The complex object accesses the private data of the complex
class. The function returns the istream object by reference to permit cascading, as in

cin >> c4 >> c5;

Exactly same argument applies to the operator « () function.

The crux of the program lies in the fact that even though fun 1() and Jun 2() are not members of the
smaripointer class, the smart pointer mechanism calls those functions for sample * that is returned by the
operator-> () function. The compiler performs all the checking to ensure that the function call works properly.
Note that the overloaded smart pointer operator must be a member function. Also it must return either an
object or its address.

Although the underlying working of the smart pointer is complex, it provides a convenient syntax, which
perhaps outweighs the complexity.

Pointers to Members
We know that to access a structure member we use a ‘.‘ or a >>’ operator. Also, to dereference a pointer we
use the * operator. Following example shows this at work.

inti;
int *ptr
struct emp
{
char name;
int age;
};

emp e, *empptr;
ptr =&i;

cout < *ptr; // dereferencing


cout << ename; // access
cout « empptr -> name; // access

If we want we can set up pointers to particular members of a structure. Since the elements of a structure are
laid out in contiguous memory locations, the address of any structure element is really an offset from the
starting address of the structure. Now to access the structure element through this pointer we need ‘.‘ or ‘->‘ to
reach the element and ‘*’ to dereference the pointer. To carry out the access and the dereferencing
simultaneously, C++ provides two new operators: ‘,*‘ and ‘->*‘. These are known as pointer to member
operators. The following program shows their usage.

#include <iostream.h>

struct sample
{
inta
float b;
};

void main()
{
int sample::*pl= &sample::a;
float sample::*p2 = &sample::b;

sample so= { 10, 3.14 };


cout<< endl << so.*p1 <<endl<< so*p2;

sample *Sp;
sp= &so;
cout<< endl << sp->”pl <<endl << sp->*p2;

// we can even assign new values


so.*pl = 20;
sp->*p2 = 6.28;
cout<< endl <<so.,*pl<<endl << so.*p2;
cout<< endl << sp->*p1 <<endl << sp->*p2;
sample soarr[] = {
{30,9.22},
{40, 7.33 },
{60, 8.88}
};

for ( int 1=0 ; i<= 2; ++)


cout << endl << soarrt[i].*pl <<endl<< soarr[il.*p2;
}’

Note the definition of the pointers p1 and p2:

Int sample ::p1 = &sampie::a;


float sample::*p2 = &sample::b

Consider the part before the assignment operator. The stars indicate that p1 and p2 are pointers, sample:.’
indicates they are pointers to an int and a float within sample. We have also initialised these pointers while
declaring them, with addresses of a and b respectively.

Really speaking there is no “address of” sample.::a because we are referring to a class and not to an object of
that class. &sample.::a merely produces an offset into the class. The actual address would be produced when
we combine that offset with the starting address of a particular object.

Hence & sample:: a is nothing more than the syntax of pointer to member. If we use p1 and p2 with one
object we would get one set of values, if we use it with another we would get another set of values. This is
what is shown towards the end of the program, where we have built an array of objects and accessed all
objects’ elements using p1 and p2. Moral is that the pointers to members are not tied with any specific object.

On the left hand side of ‘,*‘ there would always be a structure variable (object) or a reference and on the left
hand side of ‘->*“ there would always be a pointer to a structure (object).

Go through the following program carefully. I think it would help you fix your ideas about pointers to members.

#include <iostream.h>
#include conio.h

struct sample
{
inta;
float b;
int *c;
float *d
int **e;
float **f;
};
void main()
{
int sample::*pl= &sample::a;
float sample::*p2 = &sample::b;

int * sample::*p3 = &sample::c;


float * sample::*p4 = &sample::d;

int ** sample: :*p5 = &sample::e;


float ** sample::*p6 = &sample::f;

sample so = { 10, 3.14, &so,a, &so.b, &so.c, &so.d };


sample *sp;

sp = &so;

clrscr();

cout << end1<<so.*pl <<end1 << so.*p2;


cout << endl <<* ( so.*p3) <<endl <<*(so.*p4);
cout << endi << ** ( so.*p5 ) <<endi << ** ( so.*p6);

cout << end1 << sp->*pl <<endl << sp->*p2;


cout << end1 << * ( 5p->*p3 ) <<endl << (sp->*p4);
cout << end1 << ** ( sp->*p5 ) << endl<<** ( sp>*p6);

// store new values


*(so*p3) =20;
** ( sp- >*p6 ) = 6.28;

//output changed values through p1 and p2


cout << end1 << so.*pl <<end1 << so.*p2;
}

You may have understood the concept of pointers to members, but two questions still remain unanswered:

(a) Why should we use them?


(b) If we use them with classes then won’t we be required to make our data public?

Let me answer the second one first. We would certainly be required to make the data public. And this would
violate the rules of encapsulation. Hence pointer to members are more often used with member functions
(which are usually public) rather than the data members of a class.

Now the answer to the first one. By using pointers to members we can have the flexibility of choosing a
member function to be called, at run time. This permits us to select or change the behaviour at run time.
Sounds abstract? Well, you would soon understand it. For that firstly we will have to understand a pointer to a
function. The following program shows how it works.

#include <iostream.h>

void main()
{
void fun (int, float);
void (*p) ( int, float);
p= fun;
(*p) (10,3,14);
}
void fun ( int a, float b)
{
cout << end1<< a << endl <<b;

Here p is a pointer to a function that receives an int and afloat and returns a void. Note that the parentheses
around *p are necessary. In their absence p would become a function that receives an int and afloat and return
a void *

We have initialised p to address of the function funt(). Mentioning the function name without a pair of
parentheses gives its address in memory. If we want, we can still use the & operator to take the address:

p = &fun;

The syntax for callingfun() using p is as follows:

(*p)(10, 3, 14)

If we want we can have even an array of pointers to functions and then call each function in turn using this
array. The following code snippet shows this.

void (*p[3]) (int, float) = { fun 1, fun2, fun3 };


for ( int i = 0; i <= 2 ; i++)
(*p[ i]) (14 + i, 5.54 + i);

One basic condition for this code to work is that the prototypes of functions funl(),fun2() andfun3() must be
same, otherwise we cannot gather their addresses in the array p[].

Let’s now go a step further. Let’s make funl( ),fun2() and fun3() public members of class sample and then try
to call them in a manner similar to the one shown above.

#include <iostream.h>

class sample
{
public:
void funl()
{
cout « end <<” In Fun1’;
}

void fun2()
{
cout<<end1<<”In Fun2”;
}

void fun3()
{
cout << endl <<“ In Fun3”;
}

void main()
{
sample so;
void ( *p[3J)() = { fun 1, fun2, fun3 };

for (i = 0; i <= 2 ;i++)


(so,*p[i])();
}
}

This program would not clear even the compilation hurdle. An error would be reported in the declaration of p[
]. This is to be expected since we cannot refer to the member functions directly. The following two declarations
would also be wrong.

void (*p[3])() = {funl, fun2, fun3 };


void (*p[3])() = { so.funl, so.fun2, so.fun3 };

The only way we can make the declaration work is:

void (sample::*p[3])() = { &sample::funl, &sample::fun2, &sample::fun3 };

Now if we are to call the member functions using p[] the only way to do so is by using the pointer to member
syntax. The following program shows how this can be done.

#include <iostream.h>

class sample
{
public:

void funl()
{
cout << endl <<this << ‘ In fun1”;
}
void fun2(0
{
cout << endl <<this <<‘ In fun2’;
}
void fun 3()
{
cout << endl << this << In fun3;
}

};
void main()
{
sample so[4];
void (sample::*p[3] )( )={&sample::funl, &sample::fun2, &sample::fun3};

for ( intj = 0 ; j < 3 ;j++)


{
for (i = 0~ i < 2; i++)
( so[ j ]*p[ i] )( );
}
}
Using the ‘.* “‘ syntax now we can call different member functions using different objects. While doing so we
have also printed the address of each object with reference to whom the member function is being called. The
output looks like this...

Ox1feffff2 in fun1
Ox1feffff2 in fun2
Ox1feffff2 in fun3
Ox1feffff3 in fun1
Ox1feffff3 in fun2
Ox1feffff3 in fun3
Oxlfeffff4 In fun 1
Oxlfeffff4 ln fun2
Oxlfeffff4 ln fun3
Ox1feffff5 In fun1
Oxlfeffff5 ln fun2
Oxlfeffff5 ln fun3

The explicit Keyword

In Chapter 6 we saw that data conversion from standard type to user-defined type is possible through
conversion operator and the class’s constructor. However, there may be some conversion which you want
should not take place. It’s easy to prevent conversion performed by the conversion operator: just don’t define
the operator. However, the same doesn’t apply to the constructor. We may want it for building the object. At
the same time you may want that it should not get used for carrying out conversions. Through the explicit
keyword we can prevent such unwanted conversions. This keyword is available in Standard C++ and as on
date may not be supported by some compilers. The following program shows the use of this keyword.

#include <iostream,h>

class complex
{
private:

float real, imag;

public:

complex()
{
real = imag = 0.0;
}
explicit complex (float r, float i = 0.0)
{
real =r;
imag = i;
}

complex operator + (complex c)


{
complex t;
t.real = real + c.real;
t.imag = imag + c.imag;
return t;
}
void display()
{
cout<< endl << real <<“ “<< imag;
}
};
void main()
{
complex c1 (1.5, 3.5), c2;
c2=cl + 1.25;
c2.display();
}

In the statement

c2=cl +1.25

the compiler would find that here the overloaded operator + () functions should get called. When it finds that
there is a wrong type on the right hand side of + it would look for a conversion function which can convert
afloat to complex. The two-argument constructor can meet this requirement. Hence the compiler would decide
to call it. This is an implicit conversion, one that you may not have intended to make possible. We can prevent
such implicit conversions by declaring the constructor as explicit. With this keyword in place now the compiler
would report that it could not do the conversion.

There is one small disadvantage, however. We can no longer create objects by saying,

complex c3 = 2.25;

Note that the explicit keyword works only with the constructors.

The mutable Keyword

When we create a const object none of its data members can change. In a rare situation, however, you may
want some data member should be allowed to change despite the object being const. This can be achieved by
the mutable keyword as shown in the following program.

#include <iostream.h>
#include <stnng.h>

class car
{
private:

char model[20];
mutable char owner[30];
int yrofmfg;
char regno[O];

public:

car ( char *m, char *0int y, char *r)

{
strcpy ( model, m);
strcpy ( owner, 0);
yrofmfg =y;
strcpy(regno, r);
}
void changeowner ( char *o ) const
{
strcpy ( owner, o);
}
void changemodel (char *m)
{
strcpy ( model, m);
}

void display( ) const


{
cout<< end1 << model< < endl
<< owner<< end1
<<yrofmfg <<end1
<<regno;
}
};
void main()
{
const car c 1 (“VX” “Fundu, 2000, ‘MH31-G6175)
c l.display()

c1 changeowner ( “mahafundu”);
c1 .display();

// c1 changemodel (“AX”);
cl.display();
}

When the car is sold its owner would change, rest of its attributes would remain same. Since the object ci is
declared as const none of its data members can change. An exception is however made in case of owner since
its declaration is preceded by the keyword mutable. The change is brought about through the function
changeowner(). Try removing the comment in main( ). This would result in an error as the model data member
has not been declared as mutable and hence cannot be changed. Had ci been a non-const object we would
have been allowed to change the owner as well as the model.

Namespaces

Trivial it may seem, but creating names is one of the most basic activities in programming. Variable names,
function names, structure names, class names, union names, enumeration names, all fall under one general
category: names. While writing big programs involving several programmers things are likely to go out of hand
if proper control is not exercised over visibility of these names. For example, consider the following two header
files.

// mylib.h
char fun1();
void display();
class bignumbers { ... }

// somelib,h
class bignumbers {... };
void display();

If both these header files are included in a program; there would be a clash between the two bignmuber
classes and the two display() functions. One solution to this could be to create long names such that there is a
less likelihood of a clash. But then you are required to type these long names. Moreover, it is a compromise
solution invented by programmers and not a language supported solution. C++ offers a better solution to such
problems through a keyword called namespace.

C++ provides a single global name space. We can subdivide the global name space into more manageable
pieces using the namespace feature of C++. The following code shows how.

// mylib.h
namespace myheader
{
char funl();
void display();
class bignumbers { .. };.
}

//somelib.h
namespace someLib
{
class bignumbers { ... };
void display();
}

Now the class names will not clash because they become mylib: :bignumbers and somelib: :bignumbers,
respectively. Same thing would happen to the function names. They would become mylib: :display() and
somelib: :display(), thereby avoiding a clash.
Thus it is now possible to use the same name in separate namespaces without conflict. As long as they appear
in separate namespaces, each name will be unique because of the addition of the namespace identifier.

Now a few points worth noting about the namespaces:

(a) The syntax for creation of a namespace is similar to that of a class except for the semicolon beyond the
closing brace.

(b) Declarations that fall outside all namespaces are still members of the global namespace.

(c) A namespace definition can be continued over multiple header files as shown below:

// mylib.h
namespace mylib
{
char funl();
void display();
}

// mylib1 .h
namespace mylib
{
extern int var 1;
void display();
}

When a namespace is continued in this manner, after its initial definition, the continuation is called an
extensionnamespace-definition.

(d) We can give an alternative name for a namespace. This is often called a namespace-alias. This
prevents typing of unwieldy names as shown below:

namespace hard_and_soft_library
{
class hwitem
{
};
class switem
{
};
}

namespace hwsw = hard_and_soft_library;

(e) A global namespace-name cannot be the same as any other global entity name in a given program.

(f) Members of a namespace may be defined within that namespace. For example:

namespace a
{
void f()
{
// some code here
}
}

(g) Members of a named namespace can be defined outside the namespace in which they are declared. In
this ease it is necessary to explicitly qualify the name being defined as shown below:

namespace mine
{
void funl();
}

void mine::funl()
{
}

(h) If a name in a namespace is being defined outside it, then the definition must appear after the point of
declaration. For example:

void mine::fun2() // error, fun2() is not yet a member of mine


{
}
namespace mine
{
void fun2();
}
(i) A namespace definition can only appear at the global scope. Thus the following code would cause an
error.

void main()
{
namespace local //error:not at global scope
{
}
}

(j) A namespace definition can be nested within another namespace definition. For example:

namespace outer
{
intn=6;
intfun2();
namespace Inner
{
floata= 3.14;
}
}

Using A Namespace

There are two ways to refer to a name within a namespace:

(a) Using the scope resolution operator


(b) Through the using keyword

Let us understand these methods one by one.

Using Scope Resolution


We can specify any name in a namespace using the scope resolution operator, as shown below.

#include <iostream.h>
#include <stringh>

namespace mine
{
class myclass
{
private:

int yr;

public:

void changeyear();
};

class yourclass;
void funl();
}

void mine::myclass::changeyear()
{
yr = 2000;
cout<< “years don’t change”;
}

class mine::yourclass
{
public:

yourclass();
void show();
};

mine::yourclass::yourclass()
{
cout << end1 << “Reached yourclass’s zero-argument constructor”;
}
void mine :: yourclass::show()
{
cout<< endl << “Do it. Then don’t think about it” ;
}

void mine::funl()
{
cout << endl << “Be impulsive. Exercise Caution”;
}

void main()
{

mine::myclass m;
m.changeyear();

mine::funl();
mine::yourclass y;
y.show();
}

To reach a name in a namespace using the scope resolution operator can get tedious soon. Better solution
comes in the form of the using keyword.

The using Keyword

Instead of being required to use the scope resolution operator before every name, the using keyword allows
you to import an entire namespace at once. Let us write the previous program on these lines and appreciate
the convenience of this method.

#include <iostream.h>
#include <stnng.h>

namespace mine
{
class myclass
{
private:

intyr;

public:

void changeyear();
};

class yourclass;
void fun1();
}
void mine::myclass::changeyear()
{
yr = 2000;
cout << “years don’t change”;
}

class mine::yourclass
{
public:

yourclass();
void show();
};

mine::yourclass::yourclass()
{
cout << endl <<“Reached yourclass’s zero-argument constructor”;
}

void mine::yourclass::show()
{
cout << endl << “Do it. Then don’t think about it”;
}

void mine::funl()
{
cout << endl << “Be impulsive. Exercise Caution”;
}

void main()
{
using namespace mine;

myclass m;
m.changeyear();
fun1();
yourclass y;
y.show();
}

The using keyword declares all the names in the namespace to be in the current scope. So we can use the
names without any qualifiers.

RTTI

RTTI stands for Run Time Type Identification. In an inheritance hierarchy, using RTTI we can find the exact
type of the object using a pointer or reference to the base class. The idea behind virtual functions is to upcast
the derived class object’s address into a pointer to a base class object and then let the virtual function
mechanism implement the correct behaviour for that type. Does this mean that an attempt to know the type of
the derived class object from the base class pointer (RTTI) a step backward? No. At times it is useful to know
the exact type of the object from the base class pointer. You may require this information to perform some
specific operation more efficiently.

There is also a practical reason for providing RTTI as a language feature. Most class libraries were using RTTI
of some form internally. So if RTTI is made a language feature you would have a consistent syntax for each
library and would not be required to worry whether it is built into a new library that you intend to use.

Knowing Types at Runtime

Let us take that classical example of virtual functions where we have a base class called shape and two
classes—circle and rectangle—derived from it. Suppose that in an array of pointers to shapes we store the
addresses of some circle and rectangle objects. Now using this array at runtime we want to determine whether
the pointers in the array point to circle or rectangle objects. It means we want to identify the type at runtime.
The following program shows an elementary way to do so.

#include <iostream.h>
#include <stdlib.h>
#include <time.h>

class shape
{
public:

virtual char typeof( ) = 0;


};

class circle: public shape


{
private:

int xc, yc, radius;

public:

circle(intx, inty, intr)


{
xc = x;
yc = y;
radius = r;
}

char typeof()
{
return ‘C’;
}
};
class rectangle: public shape
{
private:

intxl, yl, x2, y2;


public:

rectangle(intxxl, intyyl, intxx2, intyy2)


{
xl =xxl;
y1 =yyl;
x2= xx2;
y2 = yy2;
}

char typeof()
{
return ‘R’;
}
};

void main()
{
shape *s[5];
int i, num

randomize();
for (i = 0; i <= 4; i++)
{
num = random ( 2);
if(num==0)
s[i] = new circle (10, 20,15);
else
s[i]=newrectangle( 11,12,15,70);
}
for ( i = 0 ; i <= 4 ; i++)
cout << endl<< s[i]->typeof();
}

To determine the type of object at runtime we have declared a pure virtual function called typeof( ) in the
shape class and provided its implementation in the derived classes. Depending upon which typeof() gets called
either a ‘C’ or a ‘R’ is returned.

Now let us understand when would we like to know the type at runtime. In context of our program we may
want to change the colors of all circles, then through a loop we can ask ‘if you are a circle, tell me so, I would
then change your color’.

In different class libraries different function names may be present instead of typeof(,). Instead, if we use the
C++ RTTL feature the syntax of our program would remain consistent irrespective of the class library that we
use. Let us understand the C++ RTTI support now.

C++ RTTI Support

C++ provides two ways to obtain information about the object’ class at runtime. These are:

(a) Using typeid() operator


(b) Using the dynamic cast operator

Let us explore them one by one.

typeid()

This operator takes an object, a reference or a pointer and returns a reference to a global const object of the
type typeinfo. The following program shows how to use it.

#include <typeinfo.h>
#include <iostream.h>

class base
{
public:

virtual void funl()


{
}
};
class myclass : public base
{
};
class yourclass : public base
{
};

void main()
{
base *bl
cout << endl << typeid ( b1 ).name();

myclass m;
bi = &m;
cout << endl << typeid (*bl ).name();

base *b2.;

yourclass y;
b2 = &y;
cout << endl << typeid (*b2 ).name();

if ( typeid (*bl ) == typeid (*b2))


cout << endl << Equal”;
else
cout << endl << ‘Unequal”;

cout << endl << typeid (45 ).name();


cout << endl << typeid (4’ ).name();
cout << endl << typeid (4.5 ).name();
}
Here is the output of the program...

base
myclass
yourclass
unequal
int
char
double

Here we have a base class called base. We have derived two classes from it: tnyclass and yourclass. Then we
have created two pointers to the base class objects: hi and b2. We can ask for the name of the type using the
function name(). It returns a string indicating the typename. When we pass it on a base * it returns the name
“base”. If we want the exact type it is pointing to we must dereference the pointer as in,

cout « endi < typeid (*bl ).name()

The return value of typeid() can also be compared using== and operators!= as show below:

if ( typeid (*bl ) == typeid (*b2))


cout << endl << ‘Equal’;
else
cout << endl << “Unequal”;

For the sake of consistency, we can use typeid() with built-in types as well. Hence towards the end of the
program we could write statements like:

cout << endl<<‘typeid (45 ).name();

RTTI should be used only with polymorphic classes i.e. those, which have a virtual function in the base class. In
absence of polymorphism the static type information is used.

Also runtime type identification doesn’t work with void pointers, because a void * truly means no type
information at all.

I ran the program given above in VC++ compiler version 5.0. It gave strange and misleading errors. That’s
because I had not enabled the RTTI option. To do so perform the following steps.

(a) Select ‘Settings’ from the ‘Project’ menu and click the ‘C/C++’ tab.
(b) From the category listbox, select ‘C++ language’.
(c) Click the checkbox named ‘Enable Run-time Type Information’.

Type Checking with dynamic cast

Another way to obtain type information at runtime is by using the dynamic cast operator. Its usage is shown in
the following program.

#include <typeinfo.h>
#include <iostream.h>
class base
{
public:

virtual void funl()


{
}
};
class myclass : public base
{
};
class yourclass : public base
{
};
void main()
{
base *b;
myclass m, *mp;
b = & m;

if ( mp = dynamic.cast <myclass *> ( b))


cout << endl<< “Of type myclass”;
else
cout << endl << “Not of type myclass”;

yourclass y;
b = &y;

if ( mp = dynamicsast<myclass”> ( b))
cout << endl << “Of type myclass”;
else
cout << end 1<< “Not of type myclass”;

The dynamic_cast operator attempts to convert the pointer b (which can contain either the address of a
myclass object or the address of yourclass object into a pointer to myclass. If the result is non-zero then b was
indeed pointing at myclass. If the result is zero it means it pointed to something else.

Although here we have used the dynamic_cast and typeid with pointers they work equally well with references.

When to Use RTTI

As far as possible you should use virtual functions in your code. You should resort to RTTI only when you must.
While using virtual functions you must have an access to the base class source code. If the base class is part of
a library and doesn’t contain the virtual function that you need, you are stuck up. At such times you should use
RTTI. You can derive a new class from the base class and add your extra member function to it. Then you can
detect your particular type (using RTTI) and call the member function.

Sometimes even when we have access to the source code of the base class and want to add a new feature to it
we may not do so by adding a virtual function to it. This is because for the benefit of one particular class other
classes derived from that base class require some meaningless stub of a virtual function. Instead, if we use
RTTI we can place the function that we want to add in a particular class where it is appropriate.

Typecasting in C++

Explicit type conversion is known as typecasting. Typecasting is often overused and a major source of errors.
Whenever we use typecasting we are trying to tell the compiler that even though we know the object is of one
type we are going to pretend that it is of a different type.

The C style casts looked like this

(type) expression

Unfortunately, when this style of casting is used each cast looks different since each may use a different type.
So if a program isn’t working correctly and you wish to examine all the casts there is no guarantee that you
have located all the casts. To eliminate this difficulty C++ provides a new casting syntax using four reserved
words: dynamic_cast static_cast const_cast and reinterpret cast. By using these casts you can easily search for
all the casts in any program. This will improve the chances of locating and removing bugs in a program. We
have already discussed dynamic cast in the previous section on RTTI. Let us now understand the other three.

static_cast

A static_cast is used for conversions that are well-defined. These include:


(a) castless conversions
(b) narrowing conversions
(c) conversions from void *
(d) implicit type conversions

These are shown in the following program. Read the comments in the program carefully.

#include <iostream.h>

class base
{
};

class derived :public base


{
public:

operator into
{
return 1;
}
};
class sample

{
};
void main()
{
inti 10;
long I;
float f;

// castless conversion — safe


I=i;
f=i;

cout << end1 << I<< f;

// explicit conversion – safe


I = static_cast <long> ( i);
f = static_cast <float> ( i);

cout << end1<< I << f;

// narrowing conversions
i=I;
cout << end1<<i;
i=f
cout << endl <<i;

// better way of doing narrowing conversions


i= static_cast<int>(l);
i= static_cast <int> (f);

void *vptr;
float *fptr;

// dangerous conversion through a void pointer


vptr =&i;
fptr = (float *) vptr;
fptr = static_cast <float *> (vptr);

derived d;
base *baseptr;

// upcasting - safe
baseptr =&d;
// explicit upcasting - safe
baseptr = static_cast <base*> ( &d);

int x;

// conversion through a conversion function


x=d

// more explicit conversion through a conversion function


x=static_cast<int>(d);
// sample *sptr = static_cast <sample> (baseptr) ; // error
sample *sptr = (sample *) baseptr;
}

Most of the program is self-explanatory. We would concentrate only on the last two statements of the program.
When we try to cast a pointer to base into a pointer to sample using static_cast an error is flashed. This means
that static_cast won’t allow us to cast out of the hierarchy. However, the traditional cast would permit this. This
means static_cast is safer than traditional casting.

const_cast

const_cast permits you to convert a const to a non-const as shown in the following program.

#include <iostream.h>

void main()
{
const int a = 0;
int*ptr=(int*)&a; //one way
ptr = const_cast <int *> ( &a); // better way

//long *lptr = const cast <long *> (&a); // error


}
class sample
{
private:

int data;

public:

void fun() const


{
(const_cast <sample *> (this) ) -> data = 99
}
};

If we are to assign address of a const object (a in the above program) to a pointer to a non-const (ptr’ in the
above program) we should use the const_cast.

Another place where we can use a const_cast is when we wish ‘to change a class member inside a const
member function. However, this is a workable way. A better way is to define data as mutable. This way in the
class definition itself it would be clear the data member might change in a const member function.

interpret_cast

This casting mechanism is the least safe and more often than not a source of bugs. If for some unusual reason
you need to assign one kind of pointer type to another, you can use reinterpret cast

The reinterpret_cast can also be used to convert pointers to integers or vice versa as shown in the following
program.
#include <iostream.h>

void main()
{
inta= 65000;

int*iptr= reinterpret_cast<int*>(a);
cout << end1 << iptr;

iptr++; //increases by four


cout<<endl<<iptr;

a = reinterpret_ cast <int> ( iptr);


cout << end1 <<a;

a++; //increases by 1
cout << end1 << a;
}

The use of reinterpret_cast is not recommended, but sometimes it’s the only way out.

Whenever you feel the need to use the explicit type conversion you should take time to reconsider it. You
would find that in many situations it could be completely avoided. In others it can be localised to a few routines
within the program. Always remember that whenever you are using a cast you are breaking the type system.
And that is fraught with dangers.

Chapter 6 - Templates

Objectives

At the end of this chapter you will be able to understand:

Function templates
Class templates with linked list class template

Topics

Function templates
A template based Quicksort
Class templates
A linked list class template
Tips about Templates

Templates

Templates are a mechanism that make it possible to use one function or class to handle many different data
types. By using templates, we can design a single class/function that operates on data of many types, instead
of having to create a separate class/function for each type. When used with functions they are known as
function templates, whereas when used with classes they are called class templates. We would first look at
function templates and then go on to class templates.
Function Templates

Suppose you want to write a function that returns the minimum of two numbers. Ordinarily this function would
be written for a particular data type. For example,

Int min(inta, intb)


{
return (a < b) ? a: b;
}

Here the function is defined to take arguments of type int and return a value of the same type. What if we
want to find the minimum of two long ints—-we would be required to write a completely new function.
Similarly, to find minimum of two floats or two doubles or two chars we would be required to write separate
versions of the same function. You would agree this is a suitable case for overloaded functions. These are given
below:

// min for ints


int min(inta, intb)
{
return ( a < b) ? a; b;
}

// min for longs


long min (float a, float b)
{
return ( a< b) ? a : b;
}

// min for chars


char min (char a, char b)
{
return ( a< b) ? a : b;
}
// etc...

Have we gained anything by writing these overloaded functions’? Not much, because we still have to write a
separate definition for each type. This results into three disadvantages:

(a) Rewriting the same function body over and over for different types is time consuming.
(b) The program consumes more disk space.
(c) If we locate any error in one such function, we need to remember to correct it in each function body.

Won’t it be nice if we could write such a function just once, and make it work for many different data types.
This is exactly what function templates do for us.

The following program shows how to write the min() function as a template, so that it will work with any
standard type. We have invoked this function from main() for different data types.

#include <iostream.h>
template <class T>
T min(Ta,Tb)
{
return ( a < b) ? a: b;
}

void main()
{
inti= 10,j=20;
cout<<endl<<min(i,j);
float a = 3.14, b = -6.28;
cout << ends << mm ( a, b);

char oh = ‘A’, dh = ‘Z’;


cout << end1 << min (ch, dh);

doubled=l.1,e=l.ll;
cout << end1 << min (d, e);
}

Here’s the output of the program:

10
-6.28
A
1.1

As you can see, the min() function now works with different data types that we use as arguments. It will even
work on user-defined data types, provided the less-than operator (<) is appropriately overloaded in the class
for the user-defined type.

Isn’t this code reuse? Yes, but of a different type. Inheritance and composition provide a way to reuse object
code. Templates provide a way to reuse the source code. Templates can significantly reduce source code size
and increase code flexibility without reducing type safety.

Let us now understand what grants the templated function the flexibility to work with different data types. Here
is the definition of the min() function:

template <class T>


T mm (T a, T b)
{
return(a<b)?a:b;
}

This entire syntax is called a function template. In a function template a data type can be represented by a
name (T in our case) that can stand for any type. There’s nothing special about the name T. We can use any
other name like type, mytype, etc. T is known as a template argument. Throughout the definition of the
function, wherever a specific data type like int would ordinarily be written, we substitute the template
argument, T.
What Happens at Compile Time

Just seeing the function template doesn’t swing the compiler into any real action, except for memorizing it for
future use. The compiler cannot generate any code as yet because it doesn’t know as yet what data type the
function will be working with. The code generation takes place when the function is actually called from within
the program through statements like:

cout<< endl << min (i, j);

When the compiler sees such a function call, it knows that the type to use is int, because that’s the type of the
arguments i and]. Now it generates a specific version of the min() for type int, replacing every T with an int.
This process is often known as instantiating the function template. The compiler also generates a call to the
newly instantiated function, and inserts it into the code where min (i,j)is.

Similarly, the expression mm ( a, b ) causes the compiler to generate a version of min() that operates on type
float and a call to this function; while the mm (d, e) call generates a function that works on type double. Note
that the compiler generates only one version of min() for each data type irrespective of the number of calls that
have been made for that type.

Do templates help us save memory? Not really—because, even when we use templates the four functions (for
int, float char and double) do get generated. The advantage is we are not required to type them out. The
compiler creates them from the generic version that we pass on to it. This makes the listing shorter and easier
to understand. Another advantage is, if we are to modify the function we need to make the changes at only
one place in the listing instead of four places.

Here is another function template to help you fix your ideas. This one swaps the contents of two variables.

#include <iostream.h>

template <class T>


Tswap(T&a,T&b)
{
Tc;

c=a
a=b;
b=c;
}

void main()
{
inti= 10,j=20;
swap(i,j);
cout << endl<< i<< “\t” <<j;

char ch = ‘A’, dh = ‘Z’;


swap(ch,dh);
cout << endl <<ch << “\t” << dh;
}
This code defines a function template called swap(). From this template the compiler generates functions that
will swap ints and chars.

Note that standard type conversions are not applied to function templates. When a call is encountered the
compiler first looks into the existing instantiations for an “exact match” for the parameters supplied. If this fails,
it tries to create a new instantiation to create an ‘exact match’. If this fails, the compiler generates an error.

Function Template Override

What if we want the function to behave in one way for all data types except one? In such a case we can
override the function template for that specific type. For this we simply need to provide a non-templated
function for that type. For example:

void swap ( double a, double b)


{
//some code here
}

This definition enables you to define a different function for double variables. Like other non-templated
functions, standard type conversions (such as promoting a variable of type float to double) are now applicable.

Multiple Argument Types

We can as well write a function template that takes different types of arguments during one call. The following
code shows such a function template.

#include <iostream.h
template <class T, class S, class Z> void fun (T a, S b, Z c)
{
cout <<a << endl << b << end1<<c;
}

void main()
{
inti= 10;
floatj = 3.14;
charch = ‘A’

fun ( i, j, ch);
}

You must have noticed a small syntax variation in the function template of this program. We have put the
template keyword and the function declarator on the same line:

template <class T, class 5, class Z> void fun (T a, S b, Z c)

This has got nothing to do with the multiple types of arguments that are being passed to the function template.
We could as well have adopted the multi-line approach of earlier programs:

template <class T, class 5, class Z>


void fun (T a, S b, Z c)
Templates Versus Macros

In many ways, templates work like preprocessor macros, replacing the template argument with the given type.
However, there are many differences between a macro like this:

# define min(i,j) ( ( i ) < ( j ) ? ( i ) ( j ) )

and a parallel template:


template <class T>
Tmin(Ti,Tj)
{
return ( i < j ) ? i :j);
}

The macro given above also performs a simple text substitution and can thus work with any type. However it
suffers from several limitations:

(a) The macro is expanded without any type checking.

(b) The type of the value returned isn’t specified, so the compiler can’t tell if we are assigning it to an
incompatible variable.

(c) In the macro the parameters i and] are evaluated twice. If either parameter has a post-incremented
variable, the increment would take place twice.

(d) When the preprocessor expands the macros, the compiler error messages would refer to the expanded
macro, rather than the macro definition itself. This makes bug hunting difficult.

To put it one line: macros are no match for templates.

I do not intend to discuss here the actual working of the quick sort algorithm. This topic has been dealt with
quite thoroughly in all the standard books on Data Structures. What you need to concentrate here is how to
write function template that can work for standard as well as user-defined data types.

Class Templates

The concept of templates can be extended even to classes. Class templates are usually used for data storage
(container) classes. Stacks and linked lists, which we encountered in previous chapters, are examples of
container classes. However, the examples of these classes that we presented could store data of only a single
basic type, say an integer. If we were to store data of type float in a stack we would be required to define a
completely new class. It follows that for every new data type that we wish to store, a new stack class would
have to be created. Won’t it be nice if we are able to write a single class specification that would work for
variables of all types, instead of a single basic type. Enter class templates. Here is a program with class
template in action.

#include <iostream.h>

constintMAX= 10;
template <class T>
class stack
{
private:

T stk[MAX];
int top;

public:

stack()
{
top= -1;
}

void push ( T data)


{
if (top == MAX - 1)
cout << end1<< “stack is full”;
else
{
top++;
stk[top] = data;
}
}
T pop ()
{
if(top==-1)
{
cout<<end1<<”stack is empty”;
return NULL;
}
else
{
T data = stk [top];
top—;
return data;
}
}
};
class complex
{
private:
float real, imag;

public:

complex (float r = 0.0, float i = 0.0)


{
real = r;
imag = i;
}

friend ostream& operator << (ostream &o, complex &c);


};
ostream& operator << (ostream &o, complex &c)
{
o << creal << “\t”<< c .imag;
return o;
}

void main()
{
stack<int>sl
s1.push(10);
s1.push(20);
s1.push(30);

cout << endl << s1.pop();


cout<< endl <<s1.pop();
cout<< endl <<s1.pop();

stack <float> s2;


s2.push(3.14);
s2.push (6.28);
s2.push(8.98);

cout << endl << s2.pop();


cout << endl <<s2.pop();
cout << endl << s2.pop();

complex c 1 (1.5, 2.5), c2 (3.5, 4.5), c3 (-1.5, -0.6);


stack <complex> s3;

s3.push(cl);
s3.push(c2);
s3.push(c3);

cout << endl << s3.pop();


cout << endl <<s3.pop();
cout << endi <<s3.pop();
}

We have created three stacks here: si, s2 and s3 and pushed three values on each one. Then we have popped
the values from the three stacks and displayed them on the screen. Here’s the output of the program...

30
20
10
8.98
628
3.14
-1.5 -0.6
3.5 4.5
1.5 2.5
You can observe that the order in which the elements are popped from the stack is exactly reverse of the order
in which they were pushed on the stack.

The way to build a class template is similar to the one used for building a function template. The template
keyword and <class I> signal that the entire class will be a template.

template <class T> class stack


{
// data and member functions using template argument T
};

The template argument T is then used at every place in the class specification where there is a reference to the
type of the array stk. There are three such places: the definition of stk, the argument type of the push()
function, and the return type of the pop() function.

We have also declared a class called complex and then pushed! popped complex objects to/from stack. This
proves that we can create stacks of user-defined objects too from the class template. To be able to display the
complex objects through cout we have overloaded the « operator. The working of such an overloaded operator
has already been discussed in Chapter 10.

We saw that in function templates the instantiation takes place when a function call is encountered. As against
this, classes are instantiated by defining an object using the template arguments. For example,

stack<irit>s 1;

creates an object, s 1, a stack that can store numbers of type mt. The compiler reserves space in memory for
this object’s data, using type mt wherever the template argument T appears in the class specification. It also
reserves space for the member functions (if these have not already been placed in memory by another object
of type stack <int>). These member functions also operate exclusively on type int.

When we create a stack object that stores objects of a different type, say float, space is now created for data,
as well as a new set of member functions that operate on type float.

As with normal classes can we not define the member functions of a class template outside the class? We can,
but it needs a different syntax as shown below:

template <class T>


void stack<Th:: push (T data)
(
if (top MAX - 1)
cout << endl <<”stack is full”;
else
{
top++;
stk[top] = data;
}
}
Note that the expression template <class Th must precede not only the class definition, but each externally
defined member function as well. The name stack<T> is used to identify the class of which push() is a
member.
CHAPTER 7 - Exception Handling

Objectives

At the end of this chapter you will be able to understand

Exception handling in C++


Exception handling with arguments

Topics

Checking function return value


Setjmp() and longjmp()
Exception handling in C++
Specifying the Exception class
Throwing an exception
The try block
The Exception Handler (catch block)
How the whole thing works
A more practical example
Exception with arguments

Exceptions are errors that occur at run time. The reasons why exceptions occur are numerous. Some of the
more common ones are:

(a) Falling short of memory


(b) Inability to open a file
(c) Exceeding the bounds of an array
(d) Attempting to initialize an object to an impossible value

When such exceptions occur, the programmer has to decide a strategy according to which he would handle the
exceptions. The strategies could be, displaying the error messages on the screen, or displaying a dialog box in
case of a GUI environment, or requesting the user to supply better data or simply terminating the program
execution.

Usually C programmers deal with exceptions in two ways:

(a) Following the function calls with error checks on return


values to find whether the function did its job properly or not.
(b) Using the setjrnp and longjmp mechanism. This approach is intended to intercept and handle
conditions that do not require immediate program termination. For example, if a recursive descent
parser detects an error, it should report it and continue with further processing.

Let us look at these methods more closely.

Checking Function Return Value

In C programs a function usually returns an error value if an error occurs during execution of that function. For
example, file-opening functions return a NULL indicating their inability to open a file successfully. Hence, each
time we call these functions we can check for the return value. This is shown for some fictitious functions fun
J~, fun2() and fun3() in the following cod
if( funcl()= = ERROR_VALUE)
// handle the error
else
// do normal things

if(func2() == NULL)
// handle the error
else
//do normal things

if(func3()==-1)
//handle the error
else
// do normal things

There are three problems with this approach:

(a) Every time we call a function we must check its return value through a pair of if and else. Easier said
than done! Surrounding every function call with a pair of if and else results in increase in code size.
Also, too many if - elses make the listing lose its readability.

(b) This approach cannot be used to report errors in the constructor of a class as the constructor cannot
return a value.

(c) It becomes difficult to monitor the return values in case of deeply nested function calls. Especially so if
the functions belong to a third-party library.

setjmp() and longjmp()

C does not provide an easy way to transfer control out of a function except by returning to the expression that
called the function. For the vast majority of function calls, this is a desirable limitation. You want the discipline
of nested function calls and returns to help you understand the flow of control through a program.
Nevertheless, on some occasions that discipline is too restrictive. The program is sometimes easier to write,
and to understand, if you can jump out of one or more function invocations at a single stroke. You want to
bypass the normal function returns and transfer control to somewhere in an earlier function invocation.

For example, you may want to return to execute some code for error recovery no matter where an error is
detected in your application. The setlmp() and the ion gmp() functions provide the tools to accomplish this. The
setjmp() function saves the state or the context of the process and the ion gjmp( } uses the saved context to
revert to a previous point in the program. What is the context of the process? In general, the context of a
process refers to information that enables you to reconstruct exactly the way the process was at a particular
point in its flow of execution. In a C program the relevant information includes quantities such as values of SP,
SS, FLAGS, CS, IP, BP, DI, ES, SI and DS registers. This information is saved in a structure called imp buf
defined in the header file ‘setjmp.h’. The jmp_buf structure is a system-dependent data type because different
systems might require different amounts of information to capture the context of a process.

To understand the mechanics of setjrnp() and ion g/inp( ), look at the following code fragment.

#include <iostream.h>
#include <setjmp.h>

class sample
{
public:

sample()
{
cout << endl << “Reached constructor”;
}
—sample()

{
cout<< endl << “Reached destructor”
}
};
jmp_buf buf;

void main()
{
void process();
void handle error()

if(setjmp(buf)==0)
process()
else
handle_error(); // executed when longjmp is called
}

void process()
{
int flag - 0;
sample s;

// some processing is done here


//if an error occurs during processing flag is set up
flag = 1;
if (flag)
longjmp ( buf, 1);
}

void handle error()


{
cout << endl<< “Error has occurred” ;
}

When setjmp() is called from main() it stores all the relevant information about the current processor state in
jmp_buf and returns zero. In this case, the if statement is satisfied and the process() function is called.

If something goes wrong in process( ) (indicated by the flag variable), we call longjmp() with two arguments:
the first is the buffer that contains the context to which we will return. When the stack reverts back to this
saved state, and the return statement in long jmp() is executed, it will be as if we were returning from the call
to set]mp(), which originally saved the buffer buf The second argument to longjmp() specifies the return value
to be used during this return. It should be other than zero so that in the if statement we can tell whether the
return is induced by a longjmp().
The set]mp( )/longjmp( ) combination enables us to jump unconditionally from one C function to another
without using the conventional return statements. Essentially, setjmp() marks the destination of the jump and
longjmp() is a non-local goto that executes the jump.

However, this approach is not appropriate for an object-oriented environment because it does not properly
handle the destruction of objects. In our program when longjmp( ) returned the destructor of the sample class
did not get called. Hence the object could not get properly cleaned up. This is the critical reason why a better
alternative should be thought of for handling exceptions in C++.

Exception Handling in C++

C++ provides a systematic, object-oriented approach to handling run-time errors generated by C++ classes.
The exception mechanism of C++ uses three new keywords: throw, catch, and try. Also, we need to create a
new kind of entity called an exception class.

Suppose we have an application that works with objects of a certain class. If during the course of execution of.
a member function of this class an error occurs, then this member function informs the application that an error
has occurred. This process of informing is called throwing an exception In the application we have to create a
separate section of code to tackle the error. This section of code is called an exception handler or a catch
block. Any code in the application that uses objects of the class is enclosed in a try block. Errors generated in
the try block are caught in the catch block. Code that doesn’t interact with the class need not be in a try block.
The following code shows the organisation of these blocks. It is not a working program, but it clearly shows
how and where the various elements of the exception mechanism are placed.

class sample
{
public:

// exception class
class errorclass
{
};
void fun()
{
if ( some error occurs)
throw errorclass(); //throws exception
}
};
//application

void main()
{
//try block
try
{
sample s;
s.fun();
}
catch (sampel :: errorclass) // exception handler or catch block
{
//do something about the error
}
}

Here sample is any class in which errors might occur. An exception class called errorclass, is specified in the
public part of sample. In main() we have enclosed part of the program that uses sample in a try block. If an
error occurs in sample. .fun() we throw an exception, using the keyword throw followed by the constructor for
the errorclass:

throw errorclass();

When an exception is thrown control goes to the catch block that immediately follows the try block.

A More Practical Example

Let’s now try to use exception handling in a more practical situation. We would try to implement a queue data
structure in a mariner similar to what we saw in Chapter 7. We would use exception handling to report errors in
two situations:

When the program attempts to store more objects in the queue than what it can accommodate. When the
program tries to remove an object from the empty queue.

Here is the program that uses exceptions to handle these two errors.

#include <iostream.h

#define MAX 4

class queue
{
private:

int arr[MAX];
int front, rear;

public:

// exception class for queue full. Note the empty class body
class qfull
{
};
// exception class for queue empty. Note the empty class body
class qempty
{
};

queue()
{
front=-1
rear= -1;
}
void addq (int item)

{
// if queue is full, throw exception
if (rear == MAX - 1)
throw qfull();

rear++;
arr[rear] = item;

if(front== —1)
front = 0;
}

intdelq()
{
int data;

//if queue is empty, throw exception


if(front-1 )
throw qempty();

data = arr[front];
if (front == rear)
front = rear = -1;
else
front++;

return data;
}
};
void main()
{
queue a;

try
{
a.addq (11);

a.addq(12);
a.addq(13);
a.addq( 14);
a.addq (15); // oops, queue is full
}
catch (queue::qfull)
{
cout << endl << “Queue is full”;
}

inti;
try
{
i=a.delq();
cout << endl << “Item deleted = “<<i;

i=a.delq();
cout << endl << “Item deleted =”<<i;

i= a.defq();
cout << endl << “Item deleted = “<<i;

i=adelq();
cout << endl << “Item deleted = “<<i;

i=a.delq(); // oops, queue is empty right now


cout << endl << “Item deleted = “<<i;
}
catch (queue::qempty)
{
cout << endl<< “Queue is empty”;
}
}

We have purposefully kept the capacity of the queue small so that it’s easier to trigger an exception by adding
too many items to the queue.

There are four parts involved in the exception handling mechanism. These are as under:

(a) Specifying the Exception Class

There are two exception classes in our program. They have been specified in the public part of the queue class:

class queue
{
//private data

public;

// exception class for queue full. Note the empty class body
class qfull
{
};
// exception class for queue empty. Note the empty class body
class qempty
{
};
//rest of the class specification
};
Here the body of the two classes qfull and qempty is empty. Hence objects of these classes would have no data
and no member functions. The names of these classes are used to connect a throw statement with an
appropriate catch block.

(b) Throwing an Exception

In our application an exception can occur in two situations: when the queue becomes full and we try to store
another integer in it, or when we try to remove an integer from an empty queue. Using if statements we check
whether any of these situations has arisen. If so then we throw an exception as shown below:

if(rear== MAX-1)
throw qfull();

if(front==-1 )
throw qempty();

When the queue becomes full and we throw an exception the constructor (implicit, zero-argument) for the qfull
class gets called (which creates an object of this class) and control is transferred to the exception handler.

Similarly, when the queue falls empty the constructor of qempty class creates an object and the control is
transferred to the exception handler.

(c) The try Block

The statements in main () that might cause the two exceptions have been enclosed in a pair of braces and
preceded by the try keyword:

try
{
a.addq(11 );
a.addq(12);
a.addq(13);
a.addq (14);
a.addq (15); // oops, queue is full

try
{

i=a.delq();
cout << end1 << “Item deleted = “<<i;

i=a.delq();
cout << endl << “Item deleted = “<<i;

i=a.delq();
cout << endl << “Item deleted =“ <<i;

i=a.delq();
cout << endl << “Item deleted = “<<i;

i=a.delq(); // oops, queue is empty right now


cout << end1 << “Item deleted =“ <<i;
}

This code is the application’s normal code. We would have written it even if we weren’t using exceptions. All
the code in the program need not be in a try block; just the code that interacts with the queue class.

(d) The Exception Handler (catch Block)

The code that handles an exception is enclosed in braces, preceded by the catch keyword, with the exception
class name in parentheses.

catch (queue::qfull)
{
cout << endl << “Queue is full”;
}

catch ( queue::qempty)
{
cout << endl << “Queue is empty”;
}

In our application there might be another class, say dqueue, which may also throw an exception called qfull.
When such an exception is thrown the compiler would not be able to decide which catch block the control
should be transferred to. This would be an ambiguous situation. Hence to avoid confusion, in the catch block
the exception class name must include the class in which it is located. In our case it is queue. qfull and
queue::qempty. Had there been a dqueue class it could have been dqueue: :qernpty.

The catch block is often called an exception handler. It must immediately follow the try block. In our program
the exception handler simply prints an error message.

Once the catch block has been executed the control jumps to the first statement after the catch block so you
can continue processing at that point. If you don’t want this you may terminate the execution in the catch block
(using exit( )), or transfer the control elsewhere.

How The Whole Thing Works

Let’s summarize the events that take place when an exception occurs:

(a) Code is executing normally outside a try block.


(b) Control enters the try block.
(c) A statement in the try block causes an error in a member function.
(d) The member function throws an exception.
(e) Control transfers to the exception handler (catch block) following the try block.
You can appreciate how clean is this code. Just about any statement in the try block can cause an exception,
but we don’t need to worry about checking a return value for each one. The try-throw-catch arrangement
handles it all for us, automatically.

In our program we have purposefully created two statements that cause exceptions. The first,

a.addq (15);// oops, queue is full

causes the qfull exception to be thrown resulting in the message ‘Queue is full’ being displayed.

Similarly the statement,

i=a.deiq(); // oops, queue is empty right now

causes the exception qempty to be thrown, resulting in the message ‘Queue is empty’ being displayed.

Exceptions with Arguments

In the previous program our exception classes were empty. Hence while throwing the exceptions we didn’t pass
anything to the constructor of the exception class. If the situation demands we can have data members within
the exception class. We can set these members through the constructor when we build the exception class
object. The following program shows how this can be achieved. This program too maintains a queue but
instead of two, it uses only one exception class. Through the same class it manages to report the errors of
queue being full or empty.

#include <iostream.h>
#include <stnng.h>

#define MAX 4

class queue
{
private:

int arr[MAX];
int front, rear;

public;

class fullorempty
{
public:

char str[100];

fullorempty ( char *s)


{
strcpy (str, s)
}
};
class qempty
{
};
queue()
{
front = -1;
rear=-1
}
void addq (int item)
{

if(rear== MAX.. 1)
throw fullorempty (“Queue is full”);
rear++;
arr[rear] = item;

if(front==-1 )
front = 0
}

int delq()
{
int data;

if(front == -1)
throw fullorempty (“Queue is empty’);

data = arr[front];
if (front == rear)
front = rear = -1;
else
front++;

return data;
}
};

void main ()
{
queue a;

try
{
a.addq (11);
a.addq( 12);
a.addq(13);
a.addq(14);
a.addq(15); // oops, queue is full
}
catch ( queue::fullorempty fe)
{
cout << endl << fe.str;
}
int i;
try
{
i= a.delq();
cout << endl << “Item deleted = “<<i;
i=a.delq();
cout << endl << “Item deleted = “ <<i;
i=a.delq();
cout << endl << “Item deleted = “<< i;
i= a.delq();
cout << endl << “Item deleted = “<<i;
i=a.delq(); // oops, queue is empty
cout << endl << “Item deleted = “ <<i;
}
catch ( queue::fullorempty fe)
{
cout << endl <<fe.str;
}
}

When the exceptions are thrown an exception class object gets created through the constructor of the
exception class (in this case the fullorempty class). When the constructor is called we pass it a message (char
*). It copies this message into a public member (str) of the exception class. The catch blocks access this public
member and print the relevant message.

Introducing the Standard Template Library.

CONGRATULATIONS! If you have worked your way through the preceding chapters of this book, you can
definitely call yourself a C++ programmer. In this, the final chapter of the book, we will explore one of C++’s
most exciting—and most advanced—features: the Standard Template Library.
The inclusion of the Standard Template Library, or STL, was one of the major efforts that took place during the
standardization of C++ The STL was not part of the original specification for C++ but was added during the
standardization proceedings. The STL provides general-purpose, templatized classes and functions that
implement many popular and commonly used algorithms and data structures. For example, it includes support
for vectors, lists, queues, and stacks. It also defines various routines that access them. Because the STL is
constructed from template classes, the algorithms and data structures can be applied to nearly any type of
data.

It must be stated at the outset that the STL is a complex piece of software engineering that uses some of
C++’s most sophisticated features. To understand and use the STL you must be comfortable with all of the
material in the preceding chapters. Specifically, you must feel at home with templates. The template syntax
that describes the STL can seem quite intimidating—although it looks more complicated than it actually is.
While there is nothing in this chapter that is any more difficult than the material in the rest of this book, don’t
be surprised or dismayed if you find the STL confusing at first. Just be patient, study the examples, and don’t
let the unfamiliar syntax distract you from the STL’s basic simplicity.

The STL is a large library, and not all of its features can be described in this chapter. In fact, a full description
of the Sm and all of its features, nuances, and programming techniques would fill a large book. The overview
presented here is intended to familiarize you with its basic operation, design philosophy, and programming
fundamentals. After working through this chapter, you will be able to easily explore the remainder of the STL
on your own.

This chapter also describes one of C++’s most important new classes: string. The string class defines a string
data type that allows you to work with character strings much as you do with other data types, using
operators.

Skills Check

Before proceeding, you should be able to correctly answer the following questions and do the exercises.

1. Explain why namespace was added to C++.


2. How do you specify a const member function?
3. The mutable modifier allows a library function to be changed by the user of your program. True or
false?
4. Given this class,
class X {
int a, b;
public:
X(int i, int j) { a = i, b = j; }
// create conversion to int here.
};

create an integer conversion function that returns the sum of a and b.

5. A static member variable can be used before an object of its class exists. True or false?

6. Given this class,


class Demo {
int a;
public:
explicit Demo(int i) { a = i; }
int geta() { return a; }
};

is the following declaration legal?

Demo 0 = 10;

AN OVERVIEW OF THE STANDARD


TEMPLATE LIBRARY

Although the Standard Template Library is large and its syntax is, at times, rather intimidating, it is actually
quite easy to use once you understand how it is constructed and what elements it employs. Therefore, before
looking at any code examples, an overview of the STL is warranted.

At the core of the Standard Template Library are three foundational items: containers, algorithms, and
iterators. These items work in conjunction with one another to provide off-the-shelf solutions to a variety of
programming problems.

Containers are objects that hold other objects. There are several different types of containers. For example, the
vector class defines a dynamic array, queue creates a queue, and provides a linear list. In addition to the
basic containers, the STL also defines associative came containers, which allow efficient retrieval of values
based on keys. For example, the map class defines a map that provides access to values with unique keys.
Thus, a map stores a key/value pair and allows a value to be retrieved when its key is given.

Each container class defines a set of functions that can be applied to the container. For example, a list
container includes functions that insert, delete, and merge elements. A stack includes functions that push and
pop values.

Algorithms act on containers. Some of the services algorithms perform are initializing, sorting, searching, and
transforming the contents of containers. Many algorithms operate on a sequence, which is a linear list of
elements within a container.

Iterators are objects that are, more or less, pointers. They give you the ability to cycle through the contents of
a container in much the same way that you would use a pointer to cycle through an array. The five types of
iterators are described in the following table.

Iterator Access Allowed

Random access Stores and retrieves values. Elements can be accessed randomly.
Bidirectional Stores and retrieves values. Forward and
backward moving.
Forward Stores and retrieves values. Forward moving only.

Iterator Access Allowed

Input Retrieves but does not store values. Forward moving only.
Output Stores but does not retrieve values. Forward moving only.

In general, an iterator that has greater access capabilities can he used in place of one that has lesser
capabilities. For example, a forward iterator can he used in place of an input iterator.

Iterators are handled just like pointers. You can increment and decrement them. You can apply the * operator
to them. Iterators are declared using the iterator type defined by the various containers. The STL also
supports reverse iterators. Reverse iterators are either bi-directional or random-access iterators that move
through a sequence in reverse direction. Thus, if a reverse iterator points to the end of a sequence,
incrementing that iterator will cause it to point to one element before the end.

When referring to the various iterator types in template descriptions, this book will use the terms listed in the
following table:

Term Iterator Type

Bilter Bidirectional iterator


Foriter Forward iterator
InIter Input iterator
Outlter Output iterator
Randlter Random-access iterator

In addition to containers, algorithms, and iterators, the STL relies upon several other standard components for
support. Chief among these are allocators, predicates, and comparison functions.
Each container has an allocator defined for it. Allocators manage memory allocation for containers. The default
allocator is an object of class allocator, but you can define your own allocators if you need them for
specialized applications. For tnost uses, the default allocator is sufficient.

Several of the algorithms and containers use a special type of function called a predicate. There are two
variations of predicates:
unary and binary. A unary predicate takes one argument, and a binary predicate has two arguments. These
functions return true or false; the precise conditions that make them return true or false are defined by the
programmer. In this chapter, when a unary predicate function is used, it will be notated with the type
Un!Pred. When a binary predicate is used, it will be of type BinPred. In a binary predicate, the arguments are
always in the order of first second . For both unary and binary predicates, the arguments will contain values of
the same type as the objects being stored by the container.

Some algorithms and classes use a special type of binary predicate that compares two elements. Called a
comparison function, this type of predicate returns true if its first argument is less than its second. Comparison
functions will be notated by the type Comp.

In addition to the headers required by the various STL classes, the C++ standard library includes the
<utility> and <functional> -headers, which provide support for the STL. For example, <utility> contains
the definition of the template class pair, which can hold a pair of values. We will make use of pair later in this
chapter.

The templates in <functional> help you construct objects that define operator( ). These are called function
objects, and they can be used in place of function pointers in many places. There are several predefined
function objects declared within <functional>. Some are shown in the following table.

Plus minus multiplies divides modulus


negate equal_to not....equaLto greater greaterequal
less Iessequal LogicaLand logical_or logicaLnot

Perhaps the most widely used function object is less, which determines whether the value of one object is less
than the value of another. Function objects can be used in place of actual function pointers in the STL
algorithms described later. Using function objects rather than function pointers allows the STL to generate
more efficient code. However, for the purposes of this chapter, function objects are not needed and we won’t
be using them directly. Although function objects are not difficult, a detailed discussion of function objects is
quite lengthy and is beyond the scope of this book. They are something that you will need, however, to get
maximum efficiency from the STL.

1. As they relate to the STL, what are containers, algorithms, and iterators?
2. What are the two types of predicates?
3. What are the five types of iterators?

THE CONTAINER CLASSES

As explained, containers are the STL objects that actually store data. The containers defined by the STL are
shown in Table 14-1. Also shown are the headers you must include to use each container. The string class,
which manages character strings, is also a container, but it is discussed later in this chapter.

Container Description Required Header


Bitset A set of bits <bitset>

Deque A double-ended queue <deque>


List A linear list <list>
Map Stores key/value pairs in which <map>
each key is associated with only
one value

Multimap Stores key/value pairs in which <map>


one key can be associated with
two or more values

Multiset A set in which each element is <set>


not necessarily unique

Priority_queue A priority queue <queue>

Queue A queue <queue>


Set A set in which each element is <set>
unique

Stack A stack <stack>


Vector A dynamic array <vector>

Because the names of the placeholder types in a template class declaration are arbitrary, the container classes
declare typedefed versions of these types. This makes the type names concrete. Some of the most common
typedef names are shown in the following table.

Typedf name Description


Size_type An integral type equivalent to size_t
Reference A reference to an element
Const_reference A const reference to an element
Iterator An iterator
Const_iterator A const iterator
Reverse_iterator A reverse iterator
Const_reverse_iterator A const reverse iterator

Value_type The type of a value stored in a container


Allocator_type The type of the allocator
Key_type The type of a key
Key_compare The type of a function that compares two keys
Value_compare The type of a function that compares two values

Although it is not possible to examine each container in this chapter, the next sections explore three
representatives: vector, list, and map. Once you understand how these containers work, you will have no
trouble using the others.

VECTORS

Perhaps the most general-purpose of the containers is the vector. The vector class supports a dynamic array.
This is an array that can grow as needed. As you know, in C++ the size of an array is fixed at compile time.
Although this is by far the most efficient way to implement arrays, it is also the most restrictive, because the
size of the array cannot be adjusted at run time to accommodate changing program conditions. A vector solves
this problem by allocating memory as needed. Although a vector is dynamic, you can still use the standard
array subscript flotation to access its elements.

The template specification for vector is shown here:

template <class T, class Allocator allocator<T» class vector

Here T is the type of data being stored and Allocator specifies the allocator, which defaults to the standard
allocator, vector has the following constructors:

explicit vector(const Allocator &a = Allocator( ) );

explicit vector(sizejype num, const T & val = T ( ),


const Allocator &a Allocator( ) );

vector(const vector<T, Al locator> &ob);

template <class Inlter> vector(lnlter start, Inlter end,


const Allocator &a = Allocator( ) );

The first form constructs an empty vector. The second form constructs a vector that has HW71 elements with
the value val. The value of val can be allowed to default. The third form constructs a vector that contains the
same elements as ob. The fourth form constructs a vector that contains the elements in the range specified by
the iterators start and end.

Any object that will be stored in a victor must define a default constructor. It must also define the < and = =
operations. Some compilers might require that other (:t)mparison operators he defined. (Because
implementations vary, consult your compilers docun,ntation for precise information.) All of the built-in types
automatically satisfy these requirements.

Although the template syntax looks rather complex, there is nothing difficult about declaring a vector. Here are
some examples:

vector<int> iv; // creates a zero— length int vector


vector<char> cv(5); // creates a 5-elementchar vector
vector<char>cv(5, ‘x’ ) ; // creates an int vector from an int vector vector<int> iv2 (iv); // creates am int
vector from an int vector

The following comparison operators are defined for vector:

==,< <=, !=, >, >=

The subscripting operator [ ] is a.so defined for vector. This allows you to access the elements of a vc tor
using standard array subscripting notation.

The member functions defined by vector are shown in Table 14-2. (Again, it is important not to be put off by
the syntax.) Some of the most important member functions are size( ), begin( ), end(), push back(),
insert( ), and erase( ). The size() function returns the current size of the vector. This function is quite useful
because it allows you to determine the size of a vector at run time. Remember, vectors will increase in size as
needed, so the size of a vector must be determined during execution, not during compilation.

The begin( ) function returns an iterator to the start of the vector. The end() function returns an iterator to
the end of the vector. As explained, iterators are similar to pointers, and it is through the use of the begin( )
and end() functions that you obtain an iterator to the beginning and end of a vector.

Member Function Description

template <class Inlter> Assigns the vector the sequence defined by start
void assign(lnlter start, Inlter end); and end.

template <class Size, class T> void assign(Size Assigns the vector num elements of value val.
num,
const T &val= T());
reference at(sizejype ,); Returns a reference to an element specified by i
const_reference at(size_ type I) const;
reference back(); Returns a reference to the last element in the
const_reference back() const; vector.
iterator begin( ); Returns an iterator to the first element in the
const_iterator begin() const; vector.
Size_type capacity() const; Returns the current capacity of the vector. This is
the number of elements it can hold before it will
need to allocate more memory.
void clear( ); Removes all elements from the vector.
Bool empty() const; Returns true if the invoking vector is empty and
false otherwise.
iterator end(); Returns an iterator to the end of the vector.
coust_iterator end() const;

Member Function Description


iterator erase(iterator i); Removes the element pointed to by i. Returns an
iterator to the element after the one removed.
iterator erase(iterator start, iterator end); Removes the elements in the range start to end.
Returns an iterator to the element after the last
element removed.
reference front (); Returns a reference to the first element in the
const_reference front() const; vector.

allocator_type get_allocator() const; Returns the vector’s allocator.


iterator insert(iterator i, Inserts val immediately before the element
const T & val = T ( ) ); specified by i. An iterator to the element is
returned.
void insert(iterator i, size_type num, Inserts num copies of vat immediately before the
const T &val) element specified by i.

template <class Inlter> Inserts the sequence defined by start and end
void insert(iterator I, Inlter start, immediately before the element specified by i.
Initer end);
Size_type maxsize() const; Returns the maximum number of elements that the
vector can hold.
reference operator[ ] (size_type i) const; Returns a reference to the element specified by i.
const_reference operator[] (size_type i)
const;
void pop back (); Removes the last element in the vector.

void push_back(const T &val); Adds an element with the value specified by val to
the end of the vector.
reverse_iterator rbegin(); Returns a reverse iterator to the end of the vector.
const_reverse_iterator rbegin() const;

reverse_iterator rend(); Returns a reverse iterator to the start of the vector.


const_reverse_iterator rend() const;
void reserve(size_type num); Sets the capacity of the vector so that it is equal to
at least num.
void resize(size_type num, T val= T ()); Changes the size of the vector to that specified by
num. If the vector must be lengthened, elements
with the value specified by val are added to the
end.
size_type size () const; Returns the number of elements currently in the
vector.
Void swap(vector<T,Allocator>&ob) Exchanges the elements stored in the invoking
vector with those in ob.

The push back() function puts a value onto the end of the vector. If necessary, the vector is increased in
length to accommodate the new element. You can also add elements to the middle using insert( ). A vector
can also be initialized. In any event, once a vector contains elements, you can use array subscripting to access
or modify those elements. You can remove elements from a vector using erase().

EXAMPLES
1. Here is a short example that illustrates the basic operation of a vector.

// Vector basics.
#include <lostream>
#include <vector>
using namespace std;

int main ()
{
vector<int> v; // create zero-length vector
int i;

// display original size of v


cout « Size = “<< v.size() « endi;

/* pus values onto end of vector


vector will grow as needed */
for(i<0; i<10; i++) v.push_back(i);

// display current size of v


cou~ « ‘Size now =“ « v.size() « end1;

// display contents of vector


cout << “Current consents:\n”;
for(i-0; i<v.size(); i++) cout << v [i] << “ “;

/* put more values onto end of vector - -


again, vector will grow as needed */
for (i=0; i<10; i++) v.push_bark(i+l0);

//display current size of v


cout << “ Size now = “<< v. size () end 1;

II display contents of vector


cout « “Current contents:\n”;
for(i=0; i<v.size (); i++) cout « v[i] « “ “;
cout « end1;

// change contents of vector


for(i=0; i<v.size (); i++) v[i] = v[i] + v[i];

// display contents of vector


cout « ‘Contents doubled:\n’;
for(i=0; i<v.size(); i++) cout << v[i] « “ “;
cout << end1;

return 0;
}

The output of this program is shown here:

Size = 0
Size now= 10
Current contents:
0123456789
Size now = 20
Current contents:
01 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Contents doubled:
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38

Let’s look atthis program carefully. In maine, an integer vector called v is created. Since no initialization is used,
it is an empty vector with an initial capacity of zero. That is, it is a zero-length vector. The program confirms
this by calling the size() member function. Next, ten elements are added to the end of v with the member
function pushback. This causes v to grow in order to accommodate the new elements. As the output shows, its
size after these additions is 10. Next, the contents of v are displayed. Notice that the standard array
subscripting notation is employed. Next, ten more elements are added and v is automatically increased in size
to handle them. Finally, the values of v’s elements are altered using standard subscripting notation.

There is one other point of interest in this program. Notice that the loops that display the contents of v use as
their target v.size(). One of the advantages that vectors have over arrays is that it is possible to find the
current size of a vector. As you can imagine, this is quite useful in a variety of situations.
2. As you know, arrays and pointers are tightly linked in C++. An array can be accessed either through
subscripting or through a pointer. The parallel to this in the STL is the link between vectors and
iterators. You can access the members of a vector by using subscripting or by using an iterator. The
following example shows both of these approaches.

II Access a vector using an iterator.


#include <iostream>
#include <vector>
using namespace std;

int main()
{
vector<int> v; // create zero-lengtb vector
int i;

// put values into a vector


for(i=0; 1<10; i++) v.push_back(i);

// can access vector contents using subscripting


for(i=0; i<10; i++) cout « v[i] « “ “;
cout « end1;

// access via iterator


vector<int>::iterator p= v.begin();
while(p ! v.end ()) {
cout « *p « “ “;
p+ +;
}
return 0 ;
}

The output from this program is:

0123456789
0123456789

In this program, the vector is initially created with zero length. The push back() member function puts values
onto the end of the vector, expanding its size as needed.

Notice how the iterator p is declared. The type iterator is defined by the container classes. Thus, to obtain an
iterator for a particular container, you will use a declaration similar to that shown in the example: simply qualify
iterator with the name of the container. In the program, p is initialized to point to the start of the vector by
using the begin( ) member function. This function returns an iterator to the start of the vector. This iterator
can then be used to access the vector an element at a time by incrementing it as needed. This process is
directly parallel to the way a pointer can be used to access the elements of an array. To determine when the
end of the vector has been reached, the end() member function is employed. This function returns an
iterator to the location that is one past the last element in the vector. Thus, when p equals v.end~, the end of
the vector has been reached.
3. In addition to putting new values on the end of a vector, you can insert elements into the middle using
the insert() function. You can also remove elements using erase( ). The following program
demonstrates insert() and erase ().

II Demonstrate insert and erase.


#include <iostream>
#include <vector>
using namespace std;

int main()
{
vector<int> v(5, 1); // create 5-element vector of is
int i;

// display original contents of vector


cout « ‘Size = “ « v.saze() « end1;
cout « “Original contents:\n”;
for(i=0; i<v,size(); i++) cout <<v[i] << “;
cout « endi « end1;

vector<int>:;iterator p = v.begin ();


p += 2; // point to 3rd element

II insert 10 elements with value 9


v.insert(p, 10, 9);

// display contents after insertion


cout « “Size after insert = “ « v.size() « endl;
cout « “Contents after insert:\n’;
for(i=0; i<v.size (); i++) cout « v[il « “ “;
cout « endl « endl;

II remove those elements


p = v.begin ();
p += 2; // point to 3rd element
v.erase(p, p+10); // remove next 10 elements

II display contents after deletion


cout << “Size after erase = “ « v.size() « endl;
cout « “Contents after erase:\n’;
for(i=0; i<v.size (); i ++) cout << v[i] << “ “;
cout « endl;

return 0;
}

This program produces the following output.

Size = 5
Original contents:
11111
Size after insert = 15
Contents after insert:
119999999999111

Size after erase = 5


Contents after erase:
11111

4. Here is an example that uses a vector to store objects of a


programmer-defined class. Notice that the class defines the default constructor and that overloaded
versions of < and = = are provided. Remember, depending upon how your compiler implements the
STL, other comparison operators might need to be dbfined.

// Store a class object in a vector.


#include <instream>
# include <vector>
using namespace std;
class Demo ()
double= d;
public:
Demo() { d = 0.0; }
Demo(double x) { d = x;}

Demo &operator= (double x) {


d = x; return *this;
};

double getd() { return d; };


};
bool operator<(Demo a, Demo b)
{
return a.getd() < b.getd ();
}
bool operator== (Demo a, Demo b)
return a.getd() == b.getd ();
}
int main()
{
vector<Demo> v;
int i;

for(i=0; i<l0; i++)


v.pushback(Demo(i/3.0));

for(i=0; i<size (); i++)


cout <<v[i]. Getd () << “ “;
cout « endl;

for(i=0; i<v.size(); i++)


v[i] =v [i] .getd () “ 2.1;

for(i=0: i<v.size(); i++)


cout << v[i] .getd() << “ “;

return 0;
}

The output from this program is shown here.


0 0.333333 0.666667 1 1.33333 1.66667 2 2.33333 2.66667 3
0 0.7 1.4 2.1 2.8 3.5 4.2 4.9 5.6 6.3

EXERCISES

1. Try the examples just shown, making small modifications and observing their effects.
2. In Example 4, both a default (i.e., parameterless) and a parameterized constructor were defined for
Demo. Can you
explain why this is important?
3. Here is a simple Coord class. Write a program that stores objects of type Coord in a vector. (Hint:
Remember to define
the < and = = operators relative to Coord.)

class Coord {
public:
int x, y;
Coord() { x = y = 0; }
Coord(int a, int b) { x = a; y = b; }
};

LISTS

The list class supports a bidirectional, linear list. Unlike a vector, which supports random access, a list can be
accessed sequentially only. Because lists are bidirectional, they can be accessed front to back or back to front.
The list class has this template specification:

template <class T, class Allocator = allocator<T>> class list

Here T is the type of data stored in the list. The allocator is specified by Allocator, which defaults to the
standard allocator. This class has the following constructors:

explicit list(const Allocator &a = Allocator());

explicit list(size_type num, const T &val= T ().


const Allocator &a = Allocator ());

list(const list<T, Allocator> &ob);

template <class Inlter> list(lnlter start, Inlter end,


const Allocator &a = Allocator());

The first form constructs an empty list. The second form constructs a list that has unm elements with the value
val, which can be allowd to default. The third form constructs a list that contains the same elements as ob. The
fourth form constructs a list that contains the elements in the range specified by the iterators start and end
The following comparison operators are defined for list:
==, <, <=, !==,>, >=

The member functions defined for list are shown in Table 14-3. Like a vector, a list can have elements put into
it with the push back() function. You can put elements on the front of the list by using push front( ), and
you can insert an element into the middle of a list by using insert( ). You can use splice( ) to join two lists,
and you can merge one list into another by using merge().

Any data type that will be held in a list must define a default constructor. It must also define the various
comparison operators. At the time of this writing, the precise requirements for an object that will be stored in a
list vary from compiler to compiler and are subject to change, so you will need to check your compiler’s
documentation.

Member Function Description

template <class Inlter> Assigns the list the sequence defined by start and
void assign(lnlter stan; Inlter end); end.
template <class Size, class T> void assign(Size Assigns the list num elements of value val.
num,
const T &va/= T());
reference back (); const_reference back() const; Returns a reference to the last element in the list.
iterator begin (); const_iterator begin() const; Returns an iterator to the first element in the list.
void clear(); Removes all elements from the list.
bool empty() const; Returns true if the invoking list is empty and false
otherwise.
Iterator end (); const_iterator end () const Returns an iterator to the end of the list
Iterator erase(iterator i); Removes the element pointed to by i. Returns an
iterator to the element after the one removed.
Iterator erase(iterator stan; iterator end); Removes the elements in the range start to end.
Returns an iterator to the element after the last
element removed.
Reference front (); Returns a reference to the first element in the list.
Const_reference front() const;
Allocator_type get.allocator() const; Returns the list’s allocator.
Iterator insert(iterator i, Inserts val immediately before the element
Const T &val= T ()); specified by i. An iterator to the element is
returned.
void insert(iterator i size_type num, const T &val) Inserts num copies of va/immediately before the
element specified by i.
Template <class Inlter> Inserts the sequence defined by start and end
void insert(iterator i immediately before the element specified by i.
Inlter start, Inlter end);
Size_type max_size() const; Returns the maximum number of elements that the
list can hold.

Member Function Description

void merge(list<T, Allocator> &ob); template Merges the ordered list contained in ob with the
<class Comp> invoking ordered list The result is ordered. After
void merge(<list<T, Allocator> &ob, Comp cmpfn); the merge, the list contained in ob is empty. In the
second form, a comparison function can be
specified to determine whether the value of one
element is less than that of another.
void pop_back (); Removes the last element in the list.
void pop_front (); Removes the first element in the list.
void push_back(const T &vaI); Adds an element with the value specified by val to
the end of the list.
void push jront(const T &val); Adds an element with the value specified by val to
the front of the list.
reverse_iterator rbegin(); Returns a reverse iterator to the end of the list.
const_reverse_iterator rbegin() const;
void remove(const T &val ); Removes elements with the value va/from the list.
template <class UnPred> Removes elements for which the unary predicate pr
void remove_if(UnPred pr); is true.

reverse_iterator rend (); Returns a reverse iterator to the start of the list.

const_reverse_iterator rend() const; void Changes the size of the list to that specified by
resize(size_type num, T va/= T ()); num. If the list must be lengthened, elements with
the value specified by va/are added to the end.

void reverse (); Reverses the invoking list.

Size_type size() const; Returns the number of elements currently in the


list
void sort(); Sorts the list The second form sorts the list using
template <class Comp> the comparison function cmpfn to determine
void sort(Comp cmpfn); whether the value of one element is less than that
of another.

Void splice(iterator I, Inserts the contents of ob into the invoking list at


List<T, Allocator> &ob); the location pointed to by i. After the operation, ob
is empty.
void splice(iterator i, Removes the element pointed to by e/from the list
list<T, Allocator> &ob, ob and stores it in the invoking list at the location
iterator e/); pointed to by i.

void splice(iterator i, Removes the range defined by start and endfrom


list<T, Allocator> &ob, ob and stores it in the invoking list beginning at the
iterator start iterator end); location pointed to by i.
void swap(list<T, Allocator> &ob) Exchanges the elements stored in the invoking list
with those in ob.
void unique(); Removes duplicate elements from the invoking list.
template <class BinPred> void unique(BinPred pr); The second form uses pr to determine uniqueness.

EXAMPLES
1. Here is a simple example of a list.

// List basics.
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<char> 1st; // create an empty list
int i;

for(i=0; i<10; i++) 1st.push_back(’A’+ i);


cout « ‘Size « 1st.size() « end1;
lisc<char>: :iterator p;

cout « ‘Contents: “;
while(!1st.empty()) {
p = 1st.beqin ();
couc «*p;
1st.pop_front());
}
return 0;
}

The output produced by this program is shown here.

Size = 10
Contents: ABCDEFGHIJ

This program creates a list of characters. First, an empty list object is created. Next, ten characters, the letters
A through J, are put into the list. This is accomplished with the pushback() function, which puts each new
value on the end of the existing list. Next, the size of the list is displayed. Then, the contents of the list are
output by repeatedly obtaining, displaying, and then removing the first element in the list. This process stops
when the list is empty.

2. In the previous example, the list was emptied as it was traversed This is, of course, not necessary. For
example, the loop that displays the list could be recoded as shown here.

list<char>::iterator p = 1st.begin ();

while(p != 1st.end ())


cout « *p ;
p++;
}
Here the iterator p is initialized to point to the start of the list. Each time Thfough the loop, p is incremented,
causing it to point to the next element. The loop ends when p points to the end of the list.

3. Because lists are bidirectional, elements can be put on a list either at the front or at the back. For
example, the following program creates two lists, with the first being the reverse of the second.

// Elements can be put on the front or end of a list.


#include <iostream>
#include <list>
using namespace std;

int main()
{
list<char> 1st;
list<char> revlst;
int i;

for(i=0; i<l0; i++) lst.push_back(’A’+i);

cout « “Size of 1st “ « lst.size() « endl;


cout « ‘Original contents: “;

list<char>: :iterator p;

/* Remove elements from 1st and put them


into revlst in reverse order. */
while (! 1 st , empty ();
p = lst.begin ();
cout « p
lst.pop....front();
revlst.push_front(*p);
}
cout « endl <<endl;

cout « Size of revlst = “;


cout « revlst.size() « endl;
cout « Reversed contents: “;
p = revlst.begin ();
while(p !=revlst.end ()) {
cout « *p;
p++;
}
return 0;
}

This program produces the following output.

Size of 1st = 10
Original contents: ABCDEFGHIJ

Size of revlst 10
Reversed contents: JIHGFEDCBA

In the program, removing elements from the start of 1st and pushing them onto the front of revist reverses the
list. This causes the elements to be stored in reverse order in revist.

4. You can sort a list by calling the sort() member function. The following program creates a list of
random characters and then puts the list into sorted order.

// Sort a list.
#include <iostream>
#include <list>
#include <cstdlib>
using namespace std;

int main()
{
list<char> 1st;
int i;

// create a list of random characters


for(i=0; i<l0; i++)
lst.push_back(’A’+ (rand()%26));

cout « “Original contents: “;


list<char>::iterator p = lst.begin ();
while(p != lst.end ()) {
cout « *p;
p++;
}
cout « endl « endl;

II sort the list


1st. sort ();

cout « ‘Sorted contents: “;


p = lst.begin();
while(p lst.end()) {
cout << *p;
p++;
}
return 0;
}

Here is sample output produced by the program.

Original contents: PHQGhHMEAY


Sorted contents: AEGHHMPQUY

5. One ordered list can be merged with another. The result is an ordered list that contains the contents of
the two original lists. The new list is left in the invoking list and the second list is left empty. This
example merges two lists. The first contains the letters ACEGI and the second BDFHJ. These lists are
then merged to produce the sequence ABCDEFGHIJ.

// Merge two lists.


#include <iostream>
#include <list>
using namespace std;

int main()
{
list<char> lst 1, lst2;
int i;

for(i=0; i<l0; i-4-=2) 1 st 1.push_back(’A’+i);


for(i=1; i<11; i+=2) lst2.push_back(’A’+i);
cout « ‘Contents of 1 st 1: “;
list<char>::iterator p = 1 st 1.begin ();
while (p ! = 1 st 1. end ()) {
cout « *p;
p+ +;
}
cout « endl « endl;

cout « “Contents of lst2: “;


p = lst2.begin ();
while(p != lst2.end()) {
cout « *p;
}
cout « endl « endl;

// now, merge the two lists


lstl.merge(lst2) ;
if(lst2.empty())
cout « “lst2 is now empty\n”;

cout « “Contents of lstl. after merge:\n”;


p = lstl.begin();
while (p != 1 st 1. End ()) {
cout << *p;
p+ +;
}
return 0;
}

The output produced by this program is shown here.

Contents of lstl: ACEGI

Contents of lst2: BDFHJ

lst2 is now empty


Contents of lstl after merge:
ABCDEFGHTJ

6. Here is an example that uses a list to store objects of type Project, which is a class that helps manage
software projects. Notice that the <, >, I = and = = operators are overloaded for objects of type
Project. These are the operators that were required by Microsoft’s Visual C++ 5 (the compiler used to
test the STL examples in this chapter). Other compilers might require you to overload additional
operators. The STL uses these functions to determine the ordering and equality of objects in a
container. Even though a list is not an ordered container, it still needs a way to compare elements
when searching, sorting, or merging.

#include <iostream>
#include <list>
#include <cstring>
using namespace std;
class Project {
public:
char name[40];
int days...to completion;
Project() {
strcpy(name, “ “ );
days_to_compietion = 0;
}
Project(char *n, int d) {
strcpy(name, n);
days_to_completion = d;
}

void add....days(int i) {
days_ to_completion += i;
}

void sub..days(int i) {
days to completion == i;
}
bool completed() { return !days_to_completion; }

void report() {
cout « name << “ : “;
cout « days to completion;
cout « “ days left.\n”;
}
};

bool operator<(const Project &a, const Project &b)


{
return a.days_to_completion < b.days_to_completion;
}
bool operator>(const Project &a, const Project &b)
{
return a.days to completion > b.days_to_completion;
}
bool operator==(const Project &a, const Project &b)
{
return a.days_to_completion== b.days_to_completion;
}
bool operator!=(const Project &a, const Project &b)
{
return a.days_to_completion b.days_to_completion;
}
int main()
{
list<Project> proj;

proj .push_back(Project( ‘Compiler’, 35));


Proj .push_back(Project( “Spreadshec:’ 190’ )
proj push_back ( Project (“ STL implementation, ,:000 ) )

list<Project>::iterator p = proj.begin ();

/* display projects */
while(p != proj.end ()) {
p->report();
p++;
}

// add 10 days to 1st project


p = proj.begin();
p->adddays(l0);

// move 1st project to completion


do {
p->sub_days(5);
p->report ();
} while (!p->completed() );

return 0;
}

The output from this program is shown here.

Compiler: 35 days left.


Spreadsheet: 190 days left.
STL Implementation: 1000 days left.
Compiler: 40 days left.
Compiler: 35 days left.
Compiler: 30 days left.
Compiler: 25 days left.
Compiler: 20 days left.
Compiler: 15 days left.
Compiler: 10 days left.
Compiler: 5 days left.
Compiler: 0 days left.

1. Experiment with the examples, trying minor variations.


2. In Example 1, the list was emptied in the process of displaying it. In Example 2, you saw another way
to traverse a list that does
not destroy it. Can you think of another way to traverse a list without emptying it? Show that your
solution works by substituting it into the program in Example 1.
3. Expand Example 6 by creating another list of projects that consists of the following:

Project Days to Completio

Database 780
Mail merge 50
COM objects 300
Next, sort both lists and then merge them together. Display the final result.

Maps

The map class supports an associative container in which unique keys are mapped with values. In essence, a
key is simply a name that you give to a value. Once a value has been stored, you can retrieve it by using its
key. Thus, in its most general sense a map is a list of key/value pairs. The power of a map is that you can look
up a value given its key. For example, you could define a map that uses a person’s name as its key and stores
that person’s telephone number as its value. Associative containers are becoming more popular in
programming.

As mentioned, a map can hold only unique keys. Duplicate keys are not allowed. To create a map that allows
nonunique keys, use multimap.
The map container has the following template specification:

template <class Key, class T, class Comp = less<Key>,


class Allocator = allocator<T» class map

Here Key is the data type of the keys, T is the data type of the values being stored (mapped), and Comp is a
function that compares two keys. This defaults to the standard less() utility function object.

Allocator is the allocator (which defaults to allocator).


The map class has the following constructors:
explicit map(const Comp &cmpfn = Comp (),
const Allocator &a Allocator ());

map(const map<Key, T, Comp, Allocator> &ob);

template <class Inlter> mapflJnlter start Inlter end,


const Comp &cmpfn = Comp(). const Allocator &a = Allocator());

The first form constructs an empty map. The second form constructs a map that contains the same elements
as ob. The third form constructs a map that contains the elements inthe range specified by the iterators start
and end. The function specified by cmpfn, if present, determines the ordering of the map.
In general, any object used as a key must define a default constructor and overload any necessary comparison
operators.

The following comparison operators are defined for map.

==, <, <=, !=. >, >=

The member functions defined by map are shown in Table 14-4. In the descriptions, key_type is the type of the
key and value_type represents pair < Key, T>.

Member Function Description


Iterator begin (0; Returns an iterator to the first element in the map.
Const_iterator begin() const;
Void clear (); Remove all elements from the map.
Size_type count (const key_type&k) const. Returns the number of times k occurs in the map
(1 or 0).
Bool empty () const; Returns true if the invoking map is empty and false
otherwise
Iterator end (); Returns an iterator to the end of the map.
Const_iterator end() const;
Pair<iterator, iterator> Returns a pair of iterators that point to the first aid
Equa_range(const key_type &k); last elements in the map that contain the specified
pair<const_iterator, const_iterator> key.
Equal_range(const key_type &k) const;
void erase(iterator i); Removes the element pointed to by I.

void erase(iterator start; iterator end); Removes the elements in the range start to end
Size_type erase(const key_type &k) Removes elements that have keys with the value k

iterator find(const key_type &k); const_iterator Returns an iterator to the specified key. If the key
find(const key_type &k) is not found, an iterator to the end of the map is.
const; Returned.
allocator type get allocator() const; Returns the map’s allocator.
iterator insert(iterator i, Inserts val at or after the element specified by i. An
const value_type &val); iterator to the element is returned.

template <class inlter> Inserts a range of elements.


void insert(lnlter start, Inlter end)
Pair(iterator, bool> Inserts val into the invoking map. An iterator to the
Insert(const value_type &val); element is returned. The element is inserted only if
it does not already exist. If the element was
inserted, pair<ltemtor, true> is returned.
Otherwise, pair<iterator, false> is returned.
Key_compare key_comp() const; Returns the function object that compares keys.

iterator lower_bound(const key_type &k); Returns an iterator to the first element in the map
const_iterator with the key equal to or greater than k
lower bound(const key_type &k) const;
Size_type max_size() const; Returns the maximum number of elements that the
map can hold.
reference operator[ ](const key_type &i) Returns a reference to the element specified by i. If
this element does not exist, it is inserted.
reverse_iterator rbegin(); Returns a reverse iterator to the end of the map.
const_reverse_iterator rbegin() const;
reverse_iterator rend(); Returns a reverse iterator to the start of the map.
const_reverse_iterator rend() const;
Size_type size() const; Returns the number of elements currently in the
map.
void swap(map<Key, T, Comp, Exchanges the elements stored in the invoking map
Allocator> &ob) with those in ob.
iterator upper_bound (const key_type &k); Returns an iterator to the first element in the map
const_iterator upperbound(const keyjype &k) with the key greater than k
const;
Valuecompare value_comp() const; Returns the function object that compares values.

Key/ value pairs are stored in a map as objects of type pair, which has this template specification:
template <class Ktype, class Vtype> struct pair {
typedef Ktype first type; // type of key
typedef Vtype second_type; // type of value
Ktype first; // contains the key
Vtype second; II contains the value

// constructors
pair ();
pair(const Ktype &k, const Vtype &v);
template<class A, class B> pair(const<A, B> &ob);

As the comments suggest, the value in first contains the key and the value in second contains the value
associated with that key.

You can construct a pair using either one of pair’s constructors or by using make pair( ), which constructs a
pair object based upon the types of the data used as parameters. makepair() is a generic function that has this
prototype:

template <class Ktype, class Vtype>


pair<Ktype, Vtype> make_pair(const Ktype &k, const Vtype &v);

As you can see, it returns a pair object consisting of values of the types specified by Ktype and Vtype. The
advantage of make_pair() is that it allows the types of the objects being stored to be determined automatically
by the compiler rather than being explicitly specified by you.

EXAMPLES

1. The following program illustrates the basics of using a map. It


stores ten key/value pairs. The key is a character and the value is an integer. The key/value pairs
stored are
A 0
B 1
C 2

and so on. Once the pairs have been stored, the user is prompted for a key (i.e., a letter from A through J),
and the value associated with that key is displayed.

// A simple map demonstration.


#include <iastream>
#include <map>
using namespace std;

int main()
{
map<char, int> m;
int i;

// put pairs into map


for(i=0; i<10; i++) {
m.insert(pair<char, int>(’A’+i,. i));
}
char ch;
cout « ‘Enter key: “;
cin » ch;

map<char, int>::iterator p;

II find value given key


p = m.find(ch);
if(p 1= m.end () )
cout « p->second;
else
cout « ‘Key not in map.\n”;

return 0;
}

Notice the use of the pair template class to construct the key/value pairs. The data types specified by pair must
match those of the map into which the pairs are being inserted.

Once the map has been initialized with keys and values, you can search for a value given its key by using the
find() function. find() returns an iterator to the matching element or to the end of the map if the key is not
found. When a match is found, the value associated with the key is contained in the
second member of pair.

2. In the preceding example, key/value pairs were constructed explicitly, using pair < char, int>. Although
there is nothing wrong with this approach, it is often easier to use make_pair(), which constructs a pair
object based upon the types of the data used as parameters. For example, assuming the previous
program, this line of code will also insert key/value pairs into m:

m.lnsert(make_pair((char)(’A’+i), i));

Here the cast to char is needed to override the automatic conversion to int when i is added to ‘A’. Otherwise,
the type determination is automatic.

3. Like all of the containers, maps can be used to store objects of types that you create. For example, the
program shown here creates a map of words with their opposites. To do this it creates two classes
called word and opposite. Since a map maintains a sorted list of keys, the program also defines the <
operator for objects of type word. In general, you must define the < operator for any classes that you
will use as keys. (Some compilers might require that additional comparison operators be defined.)

II A map of opposites.
#include <iostream>
#include <map>
#include <cstring>
using namespace std;

class word {
char str[20];
public:
word() { strcpy(str, “); }
word(char *s) { strcpy(str, s); }
char *get() { return str; }
};
II must define less than relative to word objects
bool operator< (word a, word b)
{
return strcmp(a.get(), b.get()) 0.
}

class opposite {
char str[20];
public:
opposite() { strcmp(str, “‘1); }
opposite(char *s) { strcpy(str, s); }
char *get() { return str; };
};

int inain()
{
map<word, opposite> m;

II put words and opposites into map


m. insert (pair<word,
opposite>(word(’yes”), opposite(no)));
m. insert (pair<word,
opposite>(word(good), opposite(bad)));
m. insert (pair<word,
opposite>(word(left’), opposite(right’)));
m. insert (pair<word,
opposite>(word(’up), opposite(’down)));

// given a word, find opposite


char str[80];
cout « ‘Enter word:
cin » str;
map<word, opposite>::iterator p;

p = m.find(word(str));
if(p != m.end());
cout « ‘Opposite: ‘ « p->second.get();
else
cout « ‘Word not in map.\n’;

return 0;
}

In this example, each entry in the map is a character array that holds a null-terminated string. Later in this
chapter, you will see an easier way to write this program that uses the standard string type.

1. Experiment with the examples, trying small variations.


2. Create a map that contains names and telephone numbers. Allow names and numbers to be entered,
and set up your program so that a number can be found when a name is given. (Hint: Use Example 3
as a model.)
3. Do you need to define the < operator for objects used as keys in a map?

ALGORITHMS

As explained, algorithms act on containers. Although each container provides support for its own basic
operations, the standard algorithms provide more extended or complex actions. They also allow you to work
with two different types of containers at the same time. To have access to the STL algorithms, you must
include <algorithm> in your program.

The STL defines a large number of algorithms, which are summarized in Table 14-5. All of the algorithms are
template functions. This means that they can be applied to any type of container. The examples that follow
demonstrate a representative sample.

Algorithm Purpose
adjacent_find
Searches for adjacent matching elements within a
sequence and returns an iterator to the first match.
Binary_search Performs a binary search on an ordered sequence.
copy Copies a sequence.
Copy_backward Same as copy() except that it moves the elements
from the end of the sequence first.
Count Returns the number of elements in the sequence.
Count_if Returns the number of elements in the sequence
that satisfy some predicate.
Equal Determines whether two ranges are the same.
Equal_range Returns a range in which an element can be
inserted into a sequence without disrupting the
ordering of the sequence.
Fill Fill_n Fills a range with the specified value.
Find Searches a range for a value and returns an
iterator to the first occurrence of the element.
Find_end Searches a range for a subsequence. This function
returns an iterator to the end of the subsequenoe
within the range.
Find_first_of Finds the first element within a sequence that
matches an element within a range.
Find_if Searches a range for an element for which a user-
defined unary predicate returns true.

For_each Applies a function to a range of elements.


Generate Assigns to elements in a range the values returned
Generate_n by a generator function.
Includes Determines whether one sequence includes all of
the elements in another sequence.
Inplace_merge Merges a range with another range. Both ranges
must be sorted in increasing order. The resulting
sequence is sorted.
Iter_swap Exchanges the values pointed to by its two iterator
arguments.
Lexicographical_compare Alphabetically compares one sequence with
another.
Lower_bound Finds the first point in the sequence that is not less
than a specified value.
Make_heap Constructs a heap from a sequence.
Max Returns the maximum of two values.
Max_element Returns an iterator to the maximum element within
a range.
Merage Merges two ordered sequences, placing the result
into a third sequence.
Min Returns the minimum of two values.
Min_element Returns an iterator to the minimum element within
a range.

Mismatch Finds the first mismatch between the elements in


two sequences. Iterators to the two elements are
returned.
Next_permutation Constructs the next permutation of a sequence.
Nth_element Arranges a sequence such that all elements less
than a specified element Ecome before that
element and all elements greater than Ecome after
it.
Partial_sort Sorts a range.
Partial_sort_copy Sorts a range and then copies as many elements as
will fit into a result sequence.
Partitiion Arranges a sequence such that all elements for
which a predicate returns true come before those
for which the predicate returns false.
Pop_heap Exchanges the first and last-1 elements and then
rebuilds the heap.
Prev_permutation Constructs the previous permutation of a sequence.
Push_heap Pushes an element onto the end of a heap.
Random_shuffle Randomizes a sequence.
Remove remove_if Removes elements from a specified range.
remove_copy remove_copy_if
Replace replace_copy Replaces elements within a range.
replace_if replace_copy_if
Reverse
reverse-copy Reverses the order of a range.
Rotate Left-rotates the elements in a range.
Rotate_copy
Search Searches for a subsequence within a sequence.
Serach_n Searches for a sequence of a specified number of
similar elements.
Set_difference Produces a sequence that contains the difference
between two ordered sets.
Set_intersection Produces a sequence that contains the intersection
of two ordered sets.
Set_symmetric_difference Produces a sequence that contains the symmetric
difference between two ordered sets.
Set_union Produces a sequence that contains the union of
two ordered sets.
Sort Sorts a range.
Sort_heap Sorts a heap within a specified range.
Stable_partition Arranges a sequence such that all elements for
which a predicate returns true come before those
for which the predicate returns false The
partitioning is stable; the relative ordering of the
sequence is preserved.
Stable_sort Sorts a range. The sort is stable; equal elements
are not rearranged.
Swap Exchanges two values.
Swap_ranges Exchanges elements in a range.
Transform Applies a function to a range of elements and
stores the outcome in a new sequence.
Unique Eliminates duplicate elements from a range.
Unique_copy
Upper_bound Finds the last point in a sequence that is not
greater than some value.

EXAMPLES

1. Two of the simplest algorithms are count( ) and count_ifs. Their general forms are shown here:

template <class Inlter, class T>


size_t count(inlter start; Inlter end, const T &val);

template <class Inlter, class T>


size_t count(inlter start; Inlter end, UnPred pfn);

The count() algorithm returns the number of elements in the sequence beginning at start and ending at end
that match val. The countjf( ) algorithm returns the number of elemen•ts in the sequence beginning at start
and ending at end for which the unary predicate pf’n returns true.

The following program demonstrates count() and


count_if().

// Demonstrate count and count_if.


#include <jostream>
#inciude <vector>
#inclide <algorithm>
using namespace std;

/* This is a unary predicate that determines if a value is even. */


bool even(int x)
{
return !(x%2);
}

int main()
{
vector<int> v;
int i;

for(i=0; i<20; i++) {


if(i%2) v.push_back(l);
else v.push_back(2);
cout « ‘Sequence: “;
for(i=0; i<v.size(); i++) cout « v[i] << “;
cout « endl;

int n;
n = count(v.begin(), v.end(), 1);
cout « n « “ elements are l\n’;

n = count_if(v.begin(), v.end(), even);


cout « n « elements are even.\n;

return 0;
}

This program displays the following output:


Sequence: 21212121212121212121
10 elements are 1.
10 elements are even.

The program begins by creating a 20-element vector that contains alternating ls and 2s. Next, count( ) is used
to count the number of ls. Then count_if() counts the number of elements that are even. Notice how the
unary predicate even() is coded. All unary predicates receive as a parameter an object that is of the same type
as that stored in the container upon which the predicate is operating. The predicate must then return a true or
false result based upon this object.

2. Sometimes it is useful to generate a new sequence that consists of only certain items from an original
sequence. One algorithm that does this is remove_copy(). Its general form is shown here:

template <class mIter, class Outlter, class T>


Outlter remove_copy(Inlter start; Inlter end,
Outlter result, const T &vaI);

The remove_copy() algorithm copies elements from the specified range that are equal to val and puts the
result into the sequence pointed to by result. It returns an iterator to the end of the result. The output
container must be large enough to hold the result.

The following program demonstrates remove...copyU. It creates a sequence of is and 2s. It then removes all
of the is from the sequence.

II Demonstrate removecopy.
#include <jostream>
#include <vector>
#include <algorithm>
using nainespace std;

int main()
{
vector<int> v, v2(20);
int 1;

for(i=0; i<20; i++) {


if(i%2) V.push_back(l);
else v.push_back(2);
}

cout « “Sequence: “;
for(i=0; i<v.size(); i++) cout « v[i] « “ “;
cout « endl;

II Remove ls
Remove_copy(v.begin(), v.end(), v2.begin(), 1);
cout « “Result: “;
for(i=0; i<v2.size(); i+-.-) cout « v2[i] « “ “;
cout « endl « endl;
return 0;
}

The output produced by this program is shown here.

Sequence: 2 12 12 12 12 12 12 12 12 12 1
Result: 22222222220000000000

3. An often usefi.il algorithm is reverse( ), which reverses a sequence. Its general form is

template <class Bilter> void reverse(Bilter stan; Bilter end);

The reverse( ) algorithm reverses ~he order of the range specified by start and end.

The following program demonstrates reverse():

II Demonstrate reverse.
#include <lostream>
#include <vector>
#include <algorithm>
using nainespace std;

int main()
{
vector<int> v;
int i;

for(i=0; i<10; i++) v.push_back(i);


cout « “Initial: “;
for(i=0; i<v.size(); i++) cout « v[i] « “ “;
cout « endl;

reverse(v.begin (), v.end ());

cout « “Reversed: “;
for(i=0; i<v.size (); i++) cout « v[i] « “ “;
return 0;

The output from this program is shown here.


Initial: 0 1 2 3 4 5 6 7 8 9
Reversed: 9876543210

4. One of the more interesting algorithms is transform( ), which modifies each element in a range
according to a function that you provide. The transform() algorithm has these two general forms:

template <class Inlter, class Outlter, class Func)


Outiter transform(Inlter start; inlter end, Outlter result Func unaryfunc);

template <class Initer1, class Inlter2, class Outlter, class Func)


Outlter transform(Inlter start1, Inlter1 end1, lnlter2 start2,
Outlter result, Func binaryfunc);

The transform() algorithm applies a function to a range of elements and stores the outcome in result. In the
first form, the range is specified by start and end. The function to be applied is specified by unaryfune. This
function receives the value of an element in its parameter, and it must return the element’s transformation. In
the second form, the transformation is applied using a binary operator function that receives the value of an
element from the sequence to be transformed in its first parameter and an element from a second sequence as
its second parameter. Both versions return an iterator to the end of the resulting sequence.
The following program uses a simple transformation function called xform( ) to square the contents of a list.
Notice that the resulting sequence is stored in the same list that provided the original sequence.

// An example of the transform algorithm.


#include <iostream>
#include <list>
#include <algorithm>
using namespace std;

// A simple transformation function.


int xform(int i) {
return i * i; II square original, value
}

int main()
{
list<int> x1;
int i;
II put values into list
for(i=0; i<l0; i++) xl.push_back(i);

cout « ‘Original contents of xl: “;


list<int>::iterator p = xl.begin();
while(p != xl.end() ) {
cout « *p « “ “;
p++;
}
cout « endl;
// transform xl
p = transform(xl.begin(), xl.end(), xl.begin(), xform);

cout « “Transformed contents of xl: “;


p = xl.begin();
while(p xl.end()) {
cout « *p « “ “;
p++;
}
return 0;
}

The output produced by the program is shown here:

Original contents of xl: 0123456789


Transformed contents of xl: 0 1 4 9 16 25 36 49 64 81

As you can see, each element in the x1 list has been squared.

Exercises

1. The sort() algorithm has these forms:

template <class Rand Iter> void sort(Randlter stait Rand Iter end);
template <class Randiter, class Comp>
void sort(Randlter start Randiter end, Comp cmpfn);

It sorts the range specified by start and end. The second form allows you to specify a comparison function that
determines whether one element is less than another. Write a program that demonstrates sort(). (Use either
form you like.)

2. The merge() algorithm merges two ordered sequences and places the result into a third. One of its
general forms is shown here:

template <class Inlteri, class lnlter2, class Outlter>


Outlter mergeOnlter1 start1, Inlter1 end1, lnlter2 start2, Inlter2 end2, Outlter resulf);

The sequences to be merged are defined by start 1, end) and start2, end2. The result is put into the sequence
pointed to by result. An iterator to the end of the resulting sequence is returned. Demonstrate this algorithm.
THE STRING CLASS

As you know, C++ does not support a built-in string type, per se. It does, however, provide two ways to
handle strings. First, you can use the traditional, null-terminated character array with which you are already
familiar. This is sometimes referred to as a C string. The second method is to use a class object of type string.
This is the approach that is examined here.

Actually, the string class is a specialization of a more general template class called basicstring. In fact, there are
two specializations of basicstring: string, which supports 8-bit character strings, and wstring, which supports
wide character strings. Since 8-bit characters are by far the most commonly used characters in normal
programming, string is the version of basicstring examined here.

Before you look at the string class, it is important that you understand why it is part of the C++ library.
Standard classes have not been casually added to C++. In fact, a significant amount of thought and debate
has accompanied each new addition. Given that C++ already contains some support for strings as null-
terminated character arrays, it might at first seem that the inclusion of the string class is an exception to this
rule. However, this is actually far from the truth. Here is why: Null-terminated strings cannot be manipulated by
any of the standard C++ operators, nor can they take part in normal C++ expressions. For example, consider
this fragment:

char sl[801, s2[80], s3[80];


sl = “one”; II can’t do
s2 = “two”; // can’t do
s3 = s1 + s2; II error, not allowed

As the comments show, in C++ it is not possible to use the assignment operator to give a character array a
new value (except during initialization), nor is it possible to use the + operator to concatenate two strings.
These operations must be written using library functions. as shown here:

strcpy(sl, “one”);
strcpy(s2, “two”);
strcpy(s3, sl);
strcat(s3, s2);

Since null-terminated character arrays are not technically data types in their own right, the C++ operators
cannot be applied to them. This makes even the most rudimentary string operations clumsy. More than
anything else, it is the inability to operate on null-terminated strings using the standard C++ operators that has
driven the development of a standard string class. Remember, when you define a class in C++, you are
defining a new data type that can be fully integrated into the C++ environment. This, of course, means that
the operators can be overloaded relative to the new class. Therefore, with the addition of a standard string
class, it becomes possible to manage strings in the same way that any other type of data is managed: through
the use of operators.

There is, however, one other reason for the standard string class:
safety. An inexperienced or careless programmer can very easily overrun the end of an array that holds a null-
terminated string. For example, consider the standard string copy function strcpy( ). This function contains no
provision for checking the boundary of the target array. If the source array contains more characters than the
target array can hold, a program error or system crash is possible (likely). As you will see, the standard string
class prevents such errors.

In the final analysis, there are three reasons for the inclusion of the standard string class: consistency (a string
now defines a data type), convenience (you can use the standard C++ operators), and safety (array
boundaries will not be overrun). Keep in mind that there is no reason that you should abandon normal, null-
terminated strings altogether; They are still the most efficient way in which to implement strings. However,
when speed is not an overriding concern, the new string class gives you access to a safe and fully integrated
way to manage strings.

Although not traditionally thought of as part of the STL, string is another container class defined by C++. This
means that it supports the algorithms described in the previous section. However, strings have additional
capabilities. To have access to the string class you must include <string> in your program.

The string class is very large, with many constructors and member functions. Also, many member functions
have multiple overloaded forms. For this reason, it is not possible to look at the entire contents of string in this
chapter. Instead, we will examine several of its most commonly used features. Once you have a general
understanding of how string works, you will be able to easily explore the rest of it on your own.

The string class supports several constructors. The prototypes for three of its most commonly used
constructors are shown here.

string ();
string(const char *str);
string(const string &str);

The first form creates an empty string object. The second creates a string object from the null-terminated
string pointed to by str. This form provides a conversion from null-terminated strings to string objects. The
third form creates a string object from another string object.

A number of operators that apply to strings are defined for string objects, including those listed in the
following table:

Operator Meaning
=` Assignment
+ Concatenation
+= Concatenation assignment
== Equality
!= Inequality
< Less than
<= Less than or equal
> Greater than
>= Greater than or equal
[] Subscripting
<< Output
>> Input

These operators allow the use of string objects in normal expressions and eliminate the need for calls to
functions such as strcpy( ) or strcatU, for example. In general, you can mix string objects with normal, null-
terminated strings in expressions. For example, a string object can be assigned a null-terminated string.

The + operator can be used to concatenate a string object with another string object or a string object with a
C-style string. That is, the following variations are supported:

string + string
string + C-string
C-string + string
The + operator can also be used to concatenate a character onto the end of a string.

The string class defines the constant npos, which is usually -1. This constant represents the length of the
longest possible string.

Although most simple string operations can be accomplished with the string operators, more complex or subtle
ones are accomplished with string class member functions. Although there are far too many to discuss in this
chapter, we will examine several of the most common ones. To assign one string to another, use the assign()
function. Two of its forms are shown here:

string &assign(const string &strob, size_type start size_type num);


string &assign(const char *str. Size_type num);

In the first form, num characters from strob beginning at the index specified by start will be assigned to the
invoking object. In the second form, the first num characters of the null-terminated string str are assigned to
the invoking object. In each case, a reference to the invoking object is returned. Of course, it is much easier to
use the = operator to assign one entire string to another. You will need to use the assign() function only when
assigning a partial string.

You can append part of one string to another using the append() member function. Two of its forms are
shown here:

string &append(const string &strob, size_type start, size_type num);


string &append(const char *str, size_type num);

In the first form, num characters from strob, beginning at the index specified by start, will be appended to the
invoking object. In the second form, the first num characters of the null-terminated string str are appended to
the invoking object. In each case, a reference to the invoking object is returned. Of course, it is much easier to
use the + operator to append one entire string to another. You will need to use the append( ) function only
when appending a partial string.

You can insert or replace characters within a string using insert() and replace( ). The prototypes for their
most common forms are shown here:

string &insert(size type start, const string &strob);

string &insert(size.jype start, const string &strob,


size_type insStart, size_type num);

string &replace(size type start, size_type num, const string &strob);

string &replace(size_type start, size_type orgNum, const string &strob, size_type replaceStart sizetype
replaceNum);

The first form of insert( ) inserts strob into the invoking string at the index specified by start. The second form
of insert( ) inserts num. characters from strob beginning at insStart into the invoking string at the index
specified by start.

Beginning at start, the first form of replace( ) replaces num characters from the invoking string with strob.
The second form replaces orgNum characters, beginning at start in the invoking string, with replaceNum
characters from the string specified by strob beginning at replaceStart. In both cases, a reference to the
invoking object is returned.

You can remove characters from a string using erase(). One of its forms is shown here:

string &erase(size_type start = 0, size_type num = npos);

It removes num characters from the invoking string beginning at start. A reference to the invoking string is
returned.

The string class provides several member functions, including find() and rfind( ), that search a string. Here
are the prototypes for the most common versions of these two functions:

Size_type find(const string &strob, size_type start=O) const;

Size_type rfind(const string &strob, size_type start=npos) const;

Beginning at start, find() searches the invoking string for the first occurrence of the string contained in
strob. If the search string is found, flnd() returns the index at which the match occurs within the invoking
string. If no match is found, npos is retunied. rfind( ) is the opposite of finds. Beginning at start, it searches
the invoking string in the reverse direction for the first occurrence of the string contained in strob. (I.e, it finds
the last occurrence of strob within the invoking string.) If the search string is found, rfind() returns the index at
which the match occurs within the invoking string. If no match is found, npos is returned.

To compare the entire contents of one string object to those of another, you will normally use the overloaded
relational operators described earlier. However, if you want to compare a portion of one string to another, you
will need to use the compare( ) member function, shown here:

int compare(sizetype start size_type num, const string &strob) const;

Here num characters in strob, beginning at start, will be compared against the invoking string. If the invoking
string is less than strob, compare() will return less than 0. If the invoking string is greater than strob, it will
return greater than 0. If strob is equal to the invoking string, compare() will return 0.

Although string objects are useful in their own right, there will be times when you will need to obtain a null-
terminated character array version of the string. For example, you might use a string object to construct a file
name. However, when opening a file, you will need to specify a pointer to a standard, null-terminated string.
To solve this problem, the member function c_str() is provided. Its prototype is shown here:

const char *c_str() const;

This function returns a pointer to a null-terminated version of the string contained in the invoking string object.
The null-terminated string must not be altered. It is also not guaranteed to be valid after any other operations
have taken place on the string object.

Because it is a container, string supports the begin() and end() functions that return an iterator to the start
and end of a string, respectively. Also included is the size() function, which returns the number of characters
currently in a string.

EXAMPLES
1. Although the traditional, C-style strings have always been simple to use, the C++ string class makes
string handling extraordinarily easy. For example, with string objects you can use the assignment
operator to assign a quoted string to a string, the + operator to concatenate strings, and the
comparison operators to compare strings. The following program illustrates these operations.

II A short string demonstration.


#include <jostream>
#include <string>
using namespace std;

int main()
{
string str1 (“Demonstrating Strings’);
string str2(”String Two”);
string str3;

// assign a string
str3= str1;
cout « str1 « “\n” « str3 « “/n”;

// concatenate two strings


str3 = stri + str2;
cout « str3 « “/n”;

// compare strings
if(str3 > str1) cout « “str3 > strl\n”;
if(str3 == str1+str2)
cout « “str3== str1+-str2\n”;

/* A string object can also be


assigned a normal string. */
str1= “This is a normal string.\n”;
cout « str1;

// create a string abject using another string object


string str4(strl);
cout « str4;

// input a string
cout « “Enter a string: “;
cin » str4; cout « str4;

return 0;
}

This program produces the following output:

Demonstrating Strings
Demonstrating Strings
Demonstrating StringsString Two
str3 > str1
str3 == strl+str2
This is a normal string.
This is a normal string.
Enter a string: Hello
Hello

As you can see, objects of type string can be manipulated with techniques similar to those used to work with
C++’s built-in data types. In fact, this is the main advantage to the string class.

Notice the ease with which the string handling is accomplished. For example, the + is used to concatenate
strings and the > is use to compare two strings. To accomplish these operations using C-style, null-terminated
strings, you must use the less convenient calls to the strcat() and strcmp() functions. Because C++ string
objects can be freely mixed with C-style, null-terminated strings, there is no disadvantage to using them in your
program—and there are considerable benefits to be gained.

There is one other thing to notice in the preceding program:


the sizes of the strings are not specified. A string object is
automatically sized to hold the string that it is given. Thus, when you are assigning or concatenating strings,
the target string will grow as needed to accommodate the size of the new string. It is not possible to overrun
the end of the string. This dynamic aspect of string objects is one of the reasons that they are considered
better than standard null-terminated strings (which are subject to boundary overruns).

2. The following program demonstrates the insert( ), erases, and replace( ) fuhctions.

// Demonstrate insert(), erase(), and replace()


#include <iostream>
#include <string>
using namespace std;

int main()
{
string strl (“This is a test”)
string str2 ( “ABCDEFG”);

cout « “Initial strings:\n”;


cout « “strl: “ « stri « endl;
cout « “str2: “ « str2 « “\n\n”;

// demonstrate insert()
cout « “Insert str2 into strl:\n”;
strl.insert(5,. str2);
cout « strl « “\n\n”;

// demonstrate erase()
cout « “Remove 7 characters from strl:\n”;
str1.erase(5, 7);
cout « stri «“\n\n”;

II demonstrate replace
cout « “Replace 2 characters in str1 with str2:\n”;
str1.replace(5, 2, str2);
cout « strl « endl;
return 0;
}

The output produced by this program is shown here:


Initial strings:
Str1: This is a test
str2: ABCDEFG

Insert str2 into stri:


This ABCDEFGis a test

Remove 7 characters from str1:


This is a test

Replace 2 characters in str1 with str2:


This ABCDEFG a test

3. Since string defines a data type, it is possible to create containers that hold objects of type string. For
example, here is a better way to write the word/opposite program shown in Example 3 of section 14.5.

IIA map of word opposites, using strings.


#include <iostream>
#include <map>
#include <string>
using namespace std;

int main()
{
map<string, string> m;
int i;

m.insert(pair<scring, string>(’yes”, ‘no”));


m.insert(pair<stiring, string>(”up”, “down’));
m.insert(pair<scring, string>( “left’, “right’));
m.insert(pair<stiring, string>(”good”, “bad”));

string s;
cout « “Enter word: “;
cin » S;

map<string, string>::iterator p;

p m.find(s);
if(p !=m.end())
cout « “Opposite: “ « p—>second;
else
cout « “Word not in map.\n’;

return 0;
}
I.exe

Anda mungkin juga menyukai