Course Lecture Notes
Course Lecture Notes
1
programming languages have some form of written specification of their syntax and
semantics; some are defined only by an official implementation. In general, programming
languages allow humans to communicate instructions to machines.
A main purpose of programming languages is to provide instructions to a computer. As
such, programming languages differ from most other forms of human expression in that
they require a greater degree of precision and completeness. When using a natural language
to communicate with other people, human authors and speakers can be ambiguous and
make small errors, and still expect their intent to be understood. However, computers do
exactly what they are told to do, and cannot understand the code the programmer
"intended" to write. So computers need to be instructed to perform all the tasks. The
combination of the language definition, the program, and the program's inputs must fully
specify the external behavior that occurs when the program is executed. Computer
languages have relatively few, exactly defined, rules for composition of programs, and
strictly controlled vocabularies in which unknown words must be defined before they can
be used.
Available programming languages come in a variety of forms and types. Thousands of
different programming languages have been developed, used, and discarded. Programming
languages can be divided in to two major categories: low-level and high-level languages.
Low-level languages
Computers only understand one language and that is binary language or the language of 1s
and 0s. Binary language is also known as machine language, one of low-level languages.
In the initial years of computer programming, all the instructions were given in binary
form. Although the computer easily understood these programs, it proved too difficult for a
normal human being to remember all the instructions in the form of 0s and 1s. Therefore,
computers remained mystery to a common person until other languages such as assembly
language was developed, which were easier to learn and understand. Assembly language
correspondences symbolic instructions and executable machine codes and was created to
use letters (called mnemonics) to each machine language instructions to make it easier to
remember or write. For example:
ADD A, B – adds two numbers in memory location A and B
2
Assembly language is nothing more than a symbolic representation of machine code, which
allows symbolic designation of memory locations. However, no matter how close assembly
language is to machine code, computers still cannot understand it. The assembly language
must be translated to machine code by a separate program called assembler. The machine
instruction created by the assembler from the original program (source code) is called
object code. Thus assembly languages are unique to a specific computer (machine).
Assemblers are written for each unique machine language.
High-level languages
Although programming in assembly language is not as difficult and error prone as stringing
together ones and zeros, it is slow and cumbersome. In addition it is hardware specific. The
lack of portability between different computers led to the development of high-level
languages—so called because they permitted a programmer to ignore many low-level
details of the computer's hardware. Further, it was recognized that the closer the syntax,
rules, and mnemonics of the programming language could be to "natural language" the less
likely it became that the programmer would inadvertently introduce errors (called "bugs")
into the program. High-level languages are more English-like and, therefore, make it easier
for programmers to "think" in the programming language. High-level languages also
require translation to machine language before execution. This translation is accomplished
by either a compiler or an interpreter. Compilers translate the entire source code program
before execution. Interpreters translate source code programs one line at a time. Interpreters
are more interactive than compilers. FORTRAN (FORmula TRANslator), BASIC (Bingers
All Purpose Symbolic Instruction Code), PASCAL, C, C++, Java are some examples of
high-level languages.
The question of which language is best is one that consumes a lot of time and energy
among computer professionals. Every language has its strengths and weaknesses. For
example, FORTRAN is a particularly good language for processing numerical data, but it
does not lend itself very well to organizing large programs. Pascal is very good for writing
well-structured and readable programs, but it is not as flexible as the C programming
language. C++ embodies powerful object-oriented features
As might be expected in a dynamic and evolving field, there is no single standard for
classifying programming languages. Another most fundamental ways programming
3
languages are characterized (categorized) is by programming paradigm. A programming
paradigm provides the programmer's view of code execution. The most influential
paradigms are examined in the next three sections, in approximate chronological order.
Procedural Programming Languages
Procedural programming specifies a list of operations that the program must complete to
reach the desired state. Each program has a starting state, a list of operations to complete,
and an ending point. This approach is also known as imperative programming. Integral to
the idea of procedural programming is the concept of a procedure call.
Procedures, also known as functions, subroutines, or methods, are small sections of code
that perform a particular function. A procedure is effectively a list of computations to be
carried out. Procedural programming can be compared to unstructured programming,
where all of the code resides in a single large block. By splitting the programmatic tasks
into small pieces, procedural programming allows a section of code to be re-used in the
program without making multiple copies. It also makes it easier for programmers to
understand and maintain program structure.
Two of the most popular procedural programming languages are FORTRAN and BASIC.
Structured Programming Languages
Structured programming is a special type of procedural programming. It provides
additional tools to manage the problems that larger programs were creating. Structured
programming requires that programmers break program structure into small pieces of code
that are easily understood. It also frowns upon the use of global variables and instead uses
variables local to each subroutine. One of the well-known features of structural
programming is that it does not allow the use of the GOTO statement. It is often associated
with a "top-down" approach to design. The top-down approach begins with an initial
overview of the system that contains minimal details about the different parts. Subsequent
design iterations then add increasing detail to the components until the design is complete.
The most popular structured programming languages include C, Ada, and Pascal.
Object-Oriented Programming Languages
Object-oriented programming is one the newest and most powerful paradigms. In object-
oriented programs, the designer specifies both the data structures and the types of
operations that can be applied to those data structures. This pairing of a piece of data with
4
the operations that can be performed on it is known as an object. A program thus becomes
a collection of cooperating objects, rather than a list of instructions. Objects can store state
information and interact with other objects, but generally each object has a distinct, limited
role.
1.2 Problem solving Techniques
Computer solves varieties of problems that can be expressed in a finite number of steps
leading to a precisely defined goal by writing different programs. A program is not needed
only to solve a problem but also it should be reliable, (maintainable) portable and efficient.
In computer programming two facts are given more weight:
The first part focuses on defining the problem and logical procedures to follow in
solving it.
The second introduces the means by which programmers communicate those
procedures to the computer system so that it can be executed.
There are system analysis and design tools, particularly flowchart and structure chart, that
can be used to define the problem in terms of the steps to its solution. The programmer uses
programming language to communicate the logic of the solution to the computer.
Before a program is written, the programmer must clearly understand what data are to be
used, the desired result, and the procedure to be used to produce the result. The procedure,
or solution, selected is referred to as an algorithm. An algorithm is defined as a step-by-
step sequence of instructions that must terminate and describe how the data is to be
processed to produce the desired outputs. Simply, algorithm is a sequence of instructions.
Algorithms are a fundamental part of computing. There are three commonly used tools to
help to document program logic (the algorithm). These are flowcharts, structured chart,
and Pseudo code. We will use the three methods here. Generally, flowcharts work well for
small problems but Pseudo code is used for larger problems.
1.2.1 Pseudo code
Pseudocode (derived from pseudo and code) is a compact and informal high-level
description of a computer algorithm that uses the structural conventions of programming
languages, but typically omits detailes such as subroutines, variables declarations and
system-specific syntax. The programming language is augmented with natural language
descriptions of the details, where convenient, or with compact mathematical notation. The
5
purpose of using pseudocode is that it may be easier for humans to read than conventional
programming languages, and that it may be a compact and environment-independent
generic description of the key principles of an algorithm. No standard for pseudocode
syntax exists, as a program in pseudocode is not an executable program. As the name
suggests, pseudocode generally does not actually obey the synatx rules of any particular
language; there is no systematic standard form, although any particular writer will
generally borrow the appearance of a particular language.
The programming process is a complicated one. You must first understand the program
specifications, of course, Then you need to organize your thoughts and create the program.
This is a difficult task when the program is not trivial (i.e. easy). You must break the main
tasks that must be accomplished into smaller ones in order to be able to eventually write
fully developed code. Writing pseudocode will save you time later during the construction
& testing phase of a program's development.
Example:
Original Program Specification:
Write a program that obtains two integer numbers from the user. It will print out the
sum of those numbers.
Pseudocode:
Prompt the user to enter the first integer
Prompt the user to enter a second integer
Compute the sum of the two user inputs
Display an output prompt that explains the answer as the sum
Display the result
1.2.2 Structured Charts
Structured chart depicts the logical functions to the solution of the problem using a chart. It
provides an overview that confirms the solution to the problem without excessive
consideration to detail. It is high-level in nature.
Example: Write a program that asks the user to enter a temperature reading in centigrade
and then prints the equivalent Fahrenheit value.
6
Input Process Output
Centigrade Prompt for centigrade value Fahrenheit
Read centigrade value
Compute Fahrenheit value
Display Fahrenheit value
CelsusToFarh
(main func)
centigard Fahrenheit
centigard
Fahrenheit
1.2.3 Flowchart
A flowchart (also spelled flow-chart and flow chart) is a schematic representation of an
algorithm or a process. The advantage of flowchart is it doesn’t depend on any particular
programming language, so that it can used, to translate an algorithm to more than one
programming language. Flowchart uses different symbols (geometrical shapes) to
represent different processes. The following table shows some of the common symbols.
7
Example 1: - Draw flow chart of an algorithm to add two numbers and display their result.
Algorithm description
Read the rules of the two numbers (A and B)
Add A and B
Assign the sum of A and B to C
Display the result ( c)
The flow chart is:
Start
Read A, B
C= A+B
Print C
End
8
Example 2: Write an algorithm description and draw a flow chart to check a number is
negative or not.
Algorithm description.
1/ Read a number x
2/ If x is less than zero write a message negative
else write a message not negative
Some times there are conditions in which it is necessary to execute a group of statements
repeatedly. Until some condition is satisfied. This condition is called a loop. Loop is a
sequence of instructions, which is repeated until some specific condition occurs. A loop
normally consists of four parts. These are:
Initialization: - Setting of variables of the computation to their initial values and setting the
counter for determining to exit from the loop.
Computation: - Processing
Test: - Every loop must have some way of exiting from it or else the program would
endlessly remain in a loop.
Increment: - Re-initialization of the loop for the next loop.
Example 3: - Write the algorithmic description and draw a flow chart to find the following
sum.
Sum = 1+2+3+…. + 50
Algorithmic description
1. Initialize sum to 0 and counter to 1
1.1.If the counter is less than or equal to 50
• Add counter to sum
9
• Increase counter by 1
• Repeat step 1.1
1.2.Else
• Exit
2. Write sum
10
The process of developing a large information system can be very costly, and the
investigation stage may require a preliminary study called a feasibility study, which
includes e.g. the following components:
a. Organizational Feasibility
How well the proposed system supports the strategic objectives of the organization.
b. Economic Feasibility
Cost savings
Increased revenue
Decreased investment
Increased profits
c. Technical Feasibility
Hardware, software, and network capability, reliability, and availability
d. Operational Feasibility
End user acceptance
Management support
Customer, supplier, and government requirements
1.3.2 Requirements analysis
Requirements analysis is the process of analyzing the information needs of the end users,
the organizational environment, and any system presently being used, developing the
functional requirements of a system that can meet the needs of the users. Also, the
requirements should be recorded in a document, email, user interface storyboard,
executable prototype, or some other form. The requirements documentation should be
referred to throughout the rest of the system development process to ensure the developing
project aligns with user needs and requirements.
End users must be involved in this process to ensure that the new system will function
adequately and meets their needs and expectations.
1.3.3 Designing solution
After the requirements have been determined, the necessary specifications for the
hardware, software, people, and data resources, and the information products that will
satisfy the functional requirements of the proposed system can be determined. The design
11
will serve as a blueprint for the system and helps detect problems before these errors or
problems are built into the final system.
The created system design, but must reviewed by users to ensure the design meets users'
needs.
1.3.4 Testing designed solution
A smaller test system is sometimes a good idea in order to get a “proof-of-concept”
validation prior to committing funds for large scale fielding of a system without knowing if
it really works as intended by the user.
1.3.5 Implementation
The real code is written here. Systems implementation is the construction of the new
system and its delivery into production or day-to-day operation. The key to understanding
the implementation phase is to realize that there is a lot more to be done than programming.
Implementation requires programming, but it also requires database creation and
population, and network installation and testing. You also need to make sure the people are
taken care of with effective training and documentation. Finally, if you expect your
development skills to improve over time, you need to conduct a review of the lessons
learned.
1.3.6 Unit testing
Normally programs are written as a series of individual modules, these subject to separate
and detailed test.
1.3.7 Integration and System testing
Brings all the pieces together into a special testing environment, then checks for errors,
bugs and interoperability. The system is tested to ensure that interfaces between modules
work (integration testing), the system works on the intended platform and with the
expected volume of data (volume testing) and that the system does what the user requires
(acceptance/beta testing).
1.3.8 Maintenance
What happens during the rest of the software's life: changes, correction, additions, moves to
a different computing platform and more. This, the least glamorous and perhaps most
important step of all, goes on seemingly forever.
12
Chapter Two
-1-
2. C++ Basics
2.1. Structure of C++ Program
A C++ program has the following structure
[Comments]
[Preprocessor directives]
[Global variable declarations]
[Prototypes of functions]
[Definitions of functions]
2.2. C++ IDE
The complete development cycle in C++ is: Write the program, compile the source code,
link the program, and run it.
Writing a Program
To write a source code, your compiler may have its own built-in text editor, or you may
be using a commercial text editor or word processor that can produce text files. The
important thing is that whatever you write your program in, it must save simple, plain-
text files, with no word processing commands embedded in the text. Examples of safe
editors include Windows Notepad, the DOS Edit command, EMACS, and vi. Many
commercial word processors, such as WordPerfect, Word, and dozens of others, also
offer a method for saving simple text files.
The files you create with your editor are called source files, and for C++ they typically
are named with the extension .CPP.
Compiling
Your source code file can't be executed, or run, as a program can. To turn your source
code into a program, you use a compiler. How you invoke your compiler, and how you
tell it where to find your source code, will vary from compiler to compiler; check your
documentation. In Borland's Turbo C++ you pick the RUN menu command or type
tc <filename>
from the command line, where <filename> is the name of your source code file (for
example, test.cpp). Other compilers may do things slightly differently. After your source
code is compiled, an object file is produced. This file is often named with the extension
-2-
.OBJ. This is still not an executable program, however. To turn this into an executable
program, you must run your linker.
Linking
C++ programs are typically created by linking together one or more OBJ files with one or
more libraries. A library is a collection of linkable files that were supplied with your
compiler, that you purchased separately, or that you created and compiled. All C++
compilers come with a library of useful functions (or procedures) and classes that you can
include in your program. A function is a block of code that performs a service, such as
adding two numbers or printing to the screen. A class is a collection of data and related
functions.
Summary
The steps to create an executable file are
1. Create a source code file, with a .CPP extension.
2. Compile the source code into a file with the .OBJ extension.
3. Link your OBJ file with any needed libraries to produce an executable program.
2.3. Showing Sample program
Any meaningful program written in C++ has to contain a number of components: the
main function; some variable declarations; and some executable statements. For example,
the following is a very basic C++ program:
1: #include <iostream.h>
2:
3: int main()
4: {
5: cout << "Hello World!\n";
6: return 0;
7: }
On line 1, the file iostream.h is included in the file. The first character is the # symbol,
which is a signal to the preprocessor. Each time you start your compiler, the preprocessor
is run. The preprocessor reads through your source code, looking for lines that begin with
the pound symbol (#), and acts on those lines before the compiler runs.
include is a preprocessor instruction that says, "What follows is a filename. Find that file
and read it in right here." The angle brackets around the filename tell the preprocessor to
-3-
look in all the usual places for this file. If your compiler is set up correctly, the angle
brackets will cause the preprocessor to look for the file iostream.h in the directory that
holds all the H files for your compiler. The file iostream.h (Input-Output-Stream) is used
by cout, which assists with writing to the screen. The effect of line 1 is to include the file
iostream.h into this program as if you had typed it in yourself.
The preprocessor runs before your compiler each time the compiler is invoked. The
preprocessor translates any line that begins with a pound symbol (#) into a special
command, getting your code file ready for the compiler.
Line 3 begins the actual program with a function named main(). Every C++ program has
a main() function. In general, a function is a block of code that performs one or more
actions. Usually functions are invoked or called by other functions, but main() is special.
When your program starts, main() is called automatically.
main(), like all functions, must state what kind of value it will return. The return value
type for main() in HELLO.CPP is int, which means that this function will return an
integer value.
All functions begin with an opening brace ({) and end with a closing brace (}). The
braces for the main() function are on lines 4 and 7. Everything between the opening and
closing braces is considered a part of the function.
The meat and potatoes of this program is on line 5. The object cout is used to print a
message to the screen. cout is used in C++ to print strings and values to the screen. A
string is just a set of characters.
Here's how cout is used: type the word cout, followed by the output redirection operator
(<<). Whatever follows the output redirection operator is written to the screen. If you
want a string of characters written, be sure to enclose them in double quotes ("), as shown
on line 5. A text string is a series of printable characters.
The final two characters, \n, tell cout to put a new line after the words Hello World! All
ANSI-compliant programs declare main() to return an int. This value is "returned" to the
operating system when your program completes. Some programmers signal an error by
returning the value 1.
The main() function ends on line 7 with the closing brace.
-4-
2.4. Basic Elements
2.4.1. Keywords (reserved words)
Reserved/Key words have a unique meaning within a C++ program. These symbols, the
reserved words, must not be used for any other purposes. All reserved words are in
lower-case letters. The following are some of the reserved words of C++.
asm auto bool break case catch
const_cast class const char continue default
dynamic_cast do double delete else enum
explicit extern false float for friend
goto if inline int long mutable
namespace new operator private protected public
reinterpret_cast register return short signed sizeof
static_cast static struct switch template this
throw true try typedef typeid typename
union unsigned using virtual void volatile
wchar_t
Notice that main is not a reserved word. However, this is a fairly technical distinction,
and for practical purposes you are advised to treat main, cin, and cout as if they were
reserved as well.
2.4.2. Identifiers
An identifier is name associated with a function or data object and used to refer to that
function or data object. An identifier must:
Start with a letter or underscore
Consist only of letters, the digits 0-9, or the underscore symbol _
Not be a reserved word
Syntax of an identifier
Letter
Letter
-
Digit
-5-
For the purposes of C++ identifiers, the underscore symbol, _, is considered to be a letter.
Its use as the first character in an identifier is not recommended though, because many
library functions in C++ use such identifiers. Similarly, the use of two consecutive
underscore symbols, _ _, is forbidden.
The following are valid identifiers
Length days_in_year DataSet1 Profit95
Int _Pressure first_one first_1
Although using _Pressure is not recommended.
The following are invalid:
days-in-year 1data int first.val
throw my__best No## bestWish!
Although it may be easier to type a program consisting of single character identifiers,
modifying or correcting the program becomes more and more difficult. The minor typing
effort of using meaningful identifiers will repay itself many fold in the avoidance of
simple programming errors when the program is modified.
At this stage it is worth noting that C++ is case-sensitive. That is lower-case letters are
treated as distinct from upper-case letters. Thus the word NUM different from the word
num or the word Num. Identifiers can be used to identify variable or constants or
functions. Function identifier is an identifier that is used to name a function.
2.4.3. Literals
Literals are constant values which can be a number, a character of a string. For example
the number 129.005, the character ‘A’ and the string “hello world” are all literals. There
is no identifier that identifies them.
2.4.4. Comments
A comment is a piece of descriptive text which explains some aspect of a program.
Program comments are totally ignored by the compiler and are only intended for human
readers. C++ provides two types of comment delimiters:
Anything after // (until the end of the line on which it appears) is considered a
comment.
Anything enclosed by the pair /* and */ is considered a comment.
-6-
2.5. Data Types, Variables, and Constants
2.5.1. Variables
A variable is a symbolic name for a memory location in which data can be stored and
subsequently recalled. Variables are used for holding data values so that they can be
utilized in various computations in a program. All variables have two important
attributes:
A type, which is, established when the variable is defined (e.g., integer, float,
character). Once defined, the type of a C++ variable cannot be changed.
A value, which can be changed by assigning a new value to the variable. The kind
of values a variable can assume depends on its type. For example, an integer
variable can only take integer values (e.g., 2, 100, -12) not real numbers like
0.123.
Variable Declaration
Declaring a variable means defining (creating) a variable. You create or define a variable
by stating its type, followed by one or more spaces, followed by the variable name and a
semicolon. The variable name can be virtually any combination of letters, but cannot
contain spaces and the first character must be a letter or an underscore. Variable names
cannot also be the same as keywords used by C++. Legal variable names include x,
J23qrsnf, and myAge. Good variable names tell you what the variables are for; using
good names makes it easier to understand the flow of your program. The following
statement defines an integer variable called myAge:
int myAge;
IMPORTANT- Variables must be declared before used!
As a general programming practice, avoid such horrific names as J23qrsnf, and restrict
single-letter variable names (such as x or i) to variables that are used only very briefly.
Try to use expressive names such as myAge or howMany.
A point worth mentioning again here is that C++ is case-sensitive. In other words,
uppercase and lowercase letters are considered to be different. A variable named age is
different from Age, which is different from AGE.
-7-
You can create more than one variable of the same type in one statement by writing the
type and then the variable names, separated by commas. For example:
int myAge, myWeight; // two int variables
long area, width, length; // three longs
As you can see, myAge and myWeight are each declared as integer variables. The second
line declares three individual long variables named area, width, and length. However
keep in mind that you cannot mix types in one definition statement.
Assigning Values to Your Variables
You assign a value to a variable by using the assignment operator (=). Thus, you would
assign 5 to Width by writing
int Width;
Width = 5;
You can combine these steps and initialize Width when you define it by writing
int Width = 5;
Initialization looks very much like assignment, and with integer variables, the difference
is minor. The essential difference is that initialization takes place at the moment you
create the variable.
Just as you can define more than one variable at a time, you can initialize more than one
variable at creation. For example:
// create two int variables and initialize them
int width = 5, length = 7;
This example initializes the integer variable width to the value 5 and the length variable
to the value 7. It is possible to even mix definitions and initializations:
int myAge = 39, yourAge, hisAge = 40;
This example creates three type int variables, and it initializes the first and third.
2.5.2. Basic Data Types
When you define a variable in C++, you must tell the compiler what kind of variable it is:
an integer, a character, and so forth. This information tells the compiler how much room
to set aside and what kind of value you want to store in your variable.
Several data types are built into C++. The varieties of data types allow programmers to
select the type appropriate to the needs of the applications being developed. The data
-8-
types supported by C++ can be classified as basic (fundamental) data types, user defined
data types, derived data types and empty data types. However, the discussion here will
focus only on the basic data types.
Basic (fundamental) data types in C++ can be conveniently divided into numeric and
character types. Numeric variables can further be divided into integer variables and
floating-point variables. Integer variables will hold only integers whereas floating
number variables can accommodate real numbers.
Both the numeric data types offer modifiers that are used to vary the nature of the data to
be stored. The modifiers used can be short, long, signed and unsigned.
The data types used in C++ programs are described in Table 1.1. This table shows the
variable type, how much room it takes in memory, and what kinds of values can be stored
in these variables. The values that can be stored are determined by the size of the variable
types.
-9-
and long) without the word "unsigned" are assumed to be signed. Signed integers are
either negative or positive. Unsigned integers are always positive.
Because you have the same number of bytes for both signed and unsigned integers, the
largest number you can store in an unsigned integer is twice as big as the largest positive
number you can store in a signed integer. An unsigned short integer can handle numbers
from 0 to 65,535. Half the numbers represented by a signed short are negative, thus a
signed short can only represent numbers from -32,768 to 32,767.
Example: A demonstration of the use of variables.
2: #include <iostream.h>
3:
4: int main()
5: {
6: unsigned short int Width = 5, Length;
7: Length = 10;
8:
9: // create an unsigned short and initialize with result
10: // of multiplying Width by Length
11: unsigned short int Area = Width * Length;
12:
13: cout << "Width:" << Width << "\n";
14: cout << "Length: " << Length << endl;
15: cout << "Area: " << Area << endl;
16: return 0;
17: }
Output: Width:5
Length: 10
Area: 50
Line 2 includes the required include statement for the iostream's library so that cout will
work. Line 4 begins the program.
On line 6, Width is defined as an unsigned short integer, and its value is initialized to 5.
Another unsigned short integer, Length, is also defined, but it is not initialized. On line 7,
the value 10 is assigned to Length.
- 10 -
On line 11, an unsigned short integer, Area, is defined, and it is initialized with the value
obtained by multiplying Width times Length. On lines 13-15, the values of the variables
are printed to the screen. Note that the special word endl creates a new line.
Wrapping around integer values
The fact that unsigned long integers have a limit to the values they can hold is only rarely
a problem, but what happens if you do run out of room? When an unsigned integer
reaches its maximum value, it wraps around and starts over, much as a car odometer
might. The following example shows what happens if you try to put too large a value into
a short integer.
Example: A demonstration of putting too large a value in a variable
1: #include <iostream.h>
2: int main()
3: {
4: unsigned short int smallNumber;
5: smallNumber = 65535;
6: cout << "small number:" << smallNumber << endl;
7: smallNumber++;
8: cout << "small number:" << smallNumber << endl;
9: smallNumber++;
10: cout << "small number:" << smallNumber << endl;
11: return 0;
12: }
Output: small number:65535
small number:0
small number:1
A signed integer is different from an unsigned integer, in that half of the values you can
represent are negative. Instead of picturing a traditional car odometer, you might picture
one that rotates up for positive numbers and down for negative numbers. One mile from 0
is either 1 or -1. When you run out of positive numbers, you run right into the largest
negative numbers and then count back down to 0. The whole idea here is putting a
number that is above the range of the variable can create unpredictable problem.
- 11 -
Example: A demonstration of adding too large a number to a signed integer.
1: #include <iostream.h>
2: int main()
3: {
4: short int smallNumber;
5: smallNumber = 32767;
6: cout << "small number:" << smallNumber << endl;
7: smallNumber++;
8: cout << "small number:" << smallNumber << endl;
9: smallNumber++;
10: cout << "small number:" << smallNumber << endl;
11: return 0;
12: }
Output: small number:32767
small number:-32768
small number:-32767
IMPORTANT – To any variable, do not assign a value that is beyond its range!
2.5.4. Characters
Character variables (type char) are typically 1 byte, enough to hold 256 values. A char
can be interpreted as a small number (0-255) or as a member of the ASCII set. ASCII
stands for the American Standard Code for Information Interchange. The ASCII character
set and its ISO (International Standards Organization) equivalent are a way to encode all
the letters, numerals, and punctuation marks.
In the ASCII code, the lowercase letter "a" is assigned the value 97. All the lower- and
uppercase letters, all the numerals, and all the punctuation marks are assigned values
between 1 and 128. Another 128 marks and symbols are reserved for use by the computer
maker, although the IBM extended character set has become something of a standard.
2.5.5. Characters and Numbers
When you put a character, for example, `a', into a char variable, what is really there is just
a number between 0 and 255. The compiler knows, however, how to translate back and
forth between characters (represented by a single quotation mark and then a letter,
numeral, or punctuation mark, followed by a closing single quotation mark) and one of
the ASCII values.
- 12 -
The value/letter relationship is arbitrary; there is no particular reason that the lowercase
"a" is assigned the value 97. As long as everyone (your keyboard, compiler, and screen)
agrees, there is no problem. It is important to realize, however, that there is a big
difference between the value 5 and the character `5'. The latter is actually valued at 53,
much as the letter `a' is valued at 97.
2.6. Operators
C++ provides operators for composing arithmetic, relational, logical, bitwise, and
conditional expressions. It also provides operators which produce useful side-effects,
such as assignment, increment, and decrement. We will look at each category of
operators in turn. We will also discuss the precedence rules which govern the order of
operator evaluation in a multi-operator expression.
2.6.1. Assignment Operators
The assignment operator is used for storing a value at some memory location (typically
denoted by a variable). Its left operand should be an lvalue, and its right operand may be
an arbitrary expression. The latter is evaluated and the outcome is stored in the location
denoted by the lvalue.
An lvalue (standing for left value) is anything that denotes a memory location in which a
value may be stored. The only kind of lvalue we have seen so far is a variable. Other
kinds of lvalues (based on pointers and references) will be described later. The
assignment operator has a number of variants, obtained by combining it with the
arithmetic and bitwise operators.
Operato Example Equivalent To
r
= n = 25
+= n += 25 n = n + 25
-= n -= 25 n = n - 25
*= n *= 25 n = n * 25
/= n /= 25 n = n / 25
%= n %= 25 n = n % 25
&= n &= 0xF2F2 n = n & 0xF2F2
- 13 -
|= n |= 0xF2F2 n = n | 0xF2F2
^= n ^= 0xF2F2 n = n ^ 0xF2F2
<<= n <<= 4 n = n << 4
>>= n >>= 4 n = n >> 4
An assignment operation is itself an expression whose value is the value stored in its left
operand. An assignment operation can therefore be used as the right operand of another
assignment operation. Any number of assignments can be concatenated in this fashion to
form one expression. For example:
int m, n, p;
m = n = p = 100; // means: n = (m = (p = 100));
m = (n = p = 100) + 2; // means: m = (n = (p = 100)) + 2;
This is equally applicable to other forms of assignment. For example:
m = 100;
m += n = p = 10; // means: m = m + (n = p = 10);
2.6.2. Arithmetic Operators
C++ provides five basic arithmetic operators. These are summarized in table below
Except for remainder (%) all other arithmetic operators can accept a mix of integer and
real operands. Generally, if both operands are integers then the result will be an integer.
However, if one or both of the operands are reals then the result will be a real (or double
to be exact).
- 14 -
When both operands of the division operator (/) are integers then the division is
performed as an integer division and not the normal division we are used to. Integer
division always results in an integer outcome (i.e., the result is always rounded down).
For example:
9/2 // gives 4, not 4.5!
-9 / 2 // gives -5, not -4!
Unintended integer divisions are a common source of programming errors. To obtain a
real division when both operands are integers, you should cast one of the operands to be
real:
int cost = 100;
int volume = 80;
double unitPrice = cost / (double) volume; // gives 1.25
The remainder operator (%) expects integers for both of its operands. It returns the
remainder of integer-dividing the operands. For example 13%3 is calculated by integer
dividing 13 by 3 to give an outcome of 4 and a remainder of 1; the result is therefore 1.
It is possible for the outcome of an arithmetic operation to be too large for storing in a
designated variable. This situation is called an overflow. The outcome of an overflow is
machine-dependent and therefore undefined. For example:
unsigned char k = 10 * 92; // overflow: 920 > 255
It is illegal to divide a number by zero. This results in a run-time division-by-zero failure,
which typically causes the program to terminate.
There are also a number of predefined library functions, which perform arithmetic
operations. As with input & output statements, if you want to use these you must put a
#include statement at the start of your program. Some of the more common library
functions are summarised below.
- 15 -
<math.h> pow(x, y) float float x raised to the power of y
<math.h> sin(x) float float Sine of x (x is in radians)
<math.h> sqrt(x) float float Square root of x
<math.h> tan(x) float float Tangent of x
- 16 -
2.6.4. Logical Operators
C++ provides three logical operators for combining logical expression. These are
summarized in the table below. Like the relational operators, logical operators evaluate to
1 or 0.
Operator Name Example
! Logical Negation !(5 == 5) // gives 0
&& Logical And 5 < 6 && 6 < 6 // gives 1
|| Logical Or 5 < 6 || 6 < 5 // gives 1
Logical operators
Logical negation is a unary operator, which negates the logical value of its single
operand. If its operand is nonzero it produces 0, and if it is 0 it produces 1.
Logical and produces 0 if one or both of its operands evaluate to 0. Otherwise, it
produces 1. Logical or produces 0 if both of its operands evaluate to 0. Otherwise, it
produces 1.
Note that here we talk of zero and nonzero operands (not zero and 1). In general, any
nonzero value can be used to represent the logical true, whereas only zero represents the
logical false. The following are, therefore, all valid logical expressions:
!20 // gives 0
10 && 5 // gives 1
10 || 5.5 // gives 1
10 && 0 // gives 0
C++ does not have a built-in boolean type. It is customary to use the type int for this
purpose instead. For example:
int sorted = 0; // false
int balanced = 1; // true
2.6.5. Bitwise Operators
C++ provides six bitwise operators for manipulating the individual bits in an integer
quantity. These are summarized in the table below.
- 17 -
Operator Name Example
~ Bitwise Negation ~'\011' // gives '\366'
& Bitwise And '\011' & '\027' // gives '\001'
| Bitwise Or '\011' | '\027' // gives '\037'
^ Bitwise Exclusive '\011' ^ '\027' // gives '\036'
Or
<< Bitwise Left Shift '\011' << 2 // gives '\044'
>> Bitwise Right Shift '\011' >> 2 // gives '\002'
Bitwise operators
Bitwise operators expect their operands to be integer quantities and treat them as bit
sequences. Bitwise negation is a unary operator which reverses the bits in its operands.
Bitwise and compares the corresponding bits of its operands and produces a 1 when both
bits are 1, and 0 otherwise. Bitwise or compares the corresponding bits of its operands
and produces a 0 when both bits are 0, and 1 otherwise. Bitwise exclusive or compares
the corresponding bits of its operands and produces a 0 when both bits are 1 or both bits
are 0, and 1 otherwise.
Bitwise left shift operator and bitwise right shift operator both take a bit sequence as their
left operand and a positive integer quantity n as their right operand. The former produces
a bit sequence equal to the left operand but which has been shifted n bit positions to the
left. The latter produces a bit sequence equal to the left operand but which has been
shifted n bit positions to the right. Vacated bits at either end are set to 0.
Table 2.1 illustrates bit sequences for the sample operands and results in Table 2.Error!
Bookmark not defined.. To avoid worrying about the sign bit (which is machine
dependent), it is common to declare a bit sequence as an unsigned quantity:
unsigned char x = '\011';
unsigned char y = '\027';
Table 2.1 How the bits are calculated.
Example Octal Value Bit Sequence
x 011 0 0 0 0 1 0 0 1
y 027 0 0 0 1 0 1 1 1
~x 366 1 1 1 1 0 1 1 0
- 18 -
x&y 001 0 0 0 0 0 0 0 1
x|y 037 0 0 0 1 1 1 1 1
x^y 036 0 0 0 1 1 1 1 0
x << 2 044 0 0 1 0 0 1 0 0
x >> 2 002 0 0 0 0 0 0 1 0
2.6.6. Increment/decrement Operators
The auto increment (++) and auto decrement (--) operators provide a convenient way of,
respectively, adding and subtracting 1 from a numeric variable. These are summarized in
the following table. The examples assume the following variable definition:
int k = 5;
- 19 -
Level Operator Kind Order
Highest :: Unary Both
() [] -> . Binary Left to Right
+ ++ ! * new sizeof() Unary Right to Left
- -- ~ & delete
->* .* Binary Left to Right
* / % Binary Left to Right
+ - Binary Left to Right
<< >> Binary Left to Right
< <= > >= Binary Left to Right
== != Binary Left to Right
& Binary Left to Right
^ Binary Left to Right
| Binary Left to Right
& Binary Left to Right
&
|| Binary Left to Right
?: Ternary Left to Right
= += *= ^= &= <<= Binary Right to Left
-= /= %= |= >>=
Lowest , Binary Left to Right
For example, in
a == b + c * d
c * d is evaluated first because * has a higher precedence than + and ==. The result is
then added to b because + has a higher precedence than ==, and then == is evaluated.
Precedence rules can be overridden using brackets. For example, rewriting the above
expression as
a == (b + c) * d
causes + to be evaluated before *.
Operators with the same precedence level are evaluated in the order specified by the last
column of Table 2.7. For example, in
a = b += c
- 20 -
the evaluation order is right to left, so first b += c is evaluated, followed by a = b.
2.8. Simple Type Conversion
A value in any of the built-in types we have see so far can be converted (type-cast) to any
of the other types. For example:
(int) 3.14 // converts 3.14 to an int to give 3
(long) 3.14 // converts 3.14 to a long to give 3L
(double) 2 // converts 2 to a double to give 2.0
(char) 122 // converts 122 to a char whose code is 122
(unsigned short) 3.14 // gives 3 as an unsigned short
As shown by these examples, the built-in type identifiers can be used as type operators.
Type operators are unary (i.e., take one operand) and appear inside brackets to the left of
their operand. This is called explicit type conversion. When the type name is just one
word, an alternate notation may be used in which the brackets appear around the operand:
int(3.14) // same as: (int) 3.14
In some cases, C++ also performs implicit type conversion. This happens when values of
different types are mixed in an expression. For example:
double d = 1; // d receives 1.0
int i = 10.5; // i receives 10
i = i + d; // means: i = int(double(i) + d)
In the last example, i + d involves mismatching types, so i is first converted to double
(promoted) and then added to d. The result is a double which does not match the type of i
on the left side of the assignment, so it is converted to int (demoted) before being
assigned to i.
The above rules represent some simple but common cases for type conversion.
2.9. Statements
This chapter introduces the various forms of C++ statements for composing programs.
Statements represent the lowest-level building blocks of a program. Roughly speaking,
each statement represents a computational step which has a certain side-effect. (A side-
effect can be thought of as a change in the program state, such as the value of a variable
changing because of an assignment.) Statements are useful because of the side-effects
they cause, the combination of which enables the program to serve a specific purpose
(e.g., sort a list of names).
- 21 -
A running program spends all of its time executing statements. The order in which
statements are executed is called flow control (or control flow). This term reflect the fact
that the currently executing statement has the control of the CPU, which when completed
will be handed over (flow) to another statement. Flow control in a program is typically
sequential, from one statement to the next, but may be diverted to other paths by branch
statements. Flow control is an important consideration because it determines what is
executed during a run and what is not, therefore affecting the overall outcome of the
program.
Like many other procedural languages, C++ provides different forms of statements for
different purposes. Declaration statements are used for defining variables. Assignment-
like statements are used for simple, algebraic computations. Branching statements are
used for specifying alternate paths of execution, depending on the outcome of a logical
condition. Loop statements are used for specifying computations which need to be
repeated until a certain logical condition is satisfied. Flow control statements are used to
divert the execution path to another part of the program. We will discuss these in turn.
2.9.1. Input/Output Statements
The most common way in which a program communicates with the outside world is
through simple, character-oriented Input/Output (IO) operations. C++ provides two
useful operators for this purpose: >> for input and << for output. We have already seen
examples of output using <<. Example 2.1 also illustrates the use of >> for input.
Example
#include <iostream.h>
int main (void)
{
int workDays = 5;
float workHours = 7.5;
float payRate, weeklyPay;
cout << "What is the hourly pay rate? ";
cin >> payRate;
weeklyPay = workDays * workHours * payRate;
cout << "Weekly Pay = ";
cout << weeklyPay;
cout << '\n';
}
- 22 -
Analysis
This line outputs the prompt ‘What is the hourly pay rate? ’ to seek user input.
This line reads the input value typed by the user and copies it to payRate. The input
operator >> takes an input stream as its left operand (cin is the standard C++ input stream
which corresponds to data entered via the keyboard) and a variable (to which the input
data is copied) as its right operand.
When run, the program will produce the following output (user input appears in bold):
What is the hourly pay rate?33.55
Weekly Pay = 1258.125
Both << and >> return their left operand as their result, enabling multiple input or multiple
output operations to be combined into one statement. This is illustrated by example below
which now allows the input of both the daily work hours and the hourly pay rate.
Example
#include <iostream.h>
int main (void)
{
int workDays = 5;
float workHours, payRate, weeklyPay;
cout << "What are the work hours and the hourly pay rate? ";
cin >> workHours >> payRate;
weeklyPay = workDays * workHours * payRate;
cout << "Weekly Pay = " << weeklyPay << '\n';
}
Analysis
This line reads two input values typed by the user and copies them to workHours and
payRate, respectively. The two values should be separated by white space (i.e., one or
more space or tab characters). This statement is equivalent to:
(cin >> workHours) >> payRate;
Because the result of >> is its left operand, (cin >> workHours) evaluates to cin which is
then used as the left operand of the next >> operator.
This line is the result of combining lines 10-12 from example 2.1. It outputs "Weekly Pay
= ", followed by the value of weeklyPay, followed by a newline character. This statement
is equivalent to:
((cout << "Weekly Pay = ") << weeklyPay) << '\n';
- 23 -
Because the result of << is its left operand, (cout << "Weekly Pay = ") evaluates to cout
which is then used as the left operand of the next << operator, etc.
When run, the program will produce the following output:
What are the work hours and the hourly pay rate?7.5 33.55
Weekly Pay = 1258.125
- 24 -
Chapter Three
3.2.Conditional Statements
3.2.1. The if Statement
It is sometimes desirable to make the execution of a statement dependent upon a condition being
satisfied. The if statement provides a way of expressing this, the general form of which is:
if (expression)
statement;
First expression is evaluated. If the outcome is nonzero (true) then statement is executed.
Otherwise, nothing happens.
For example, when dividing two values, we may want to check that the denominator is nonzero:
if (count != 0)
average = sum / count;
To make multiple statements dependent on the same condition, we can use a compound statement:
if (balance > 0) {
interest = balance * creditRate;
balance += interest;
}
-1-
A variant form of the if statement allows us to specify two alternative statements: one which is
executed if a condition is satisfied and one which is executed if the condition is not satisfied. This
is called the if-else statement and has the general form:
if (expression)
statement1;
else
statement2;
First expression is evaluated. If the outcome is nonzero (true) then statement1 is executed.
Otherwise, statement2 is executed.
For example:
if (balance > 0) {
interest = balance * creditRate;
balance += interest;
} else {
interest = balance * debitRate;
balance += interest;
}
Given the similarity between the two alternative parts, the whole statement can be simplified to:
if (balance > 0)
interest = balance * creditRate;
else
interest = balance * debitRate;
balance += interest;
Or just:
balance += balance * (balance > 0 ? creditRate : debitRate);
If statements may be nested by having an if statement appear inside another if statement. For
example:
if (callHour > 6) {
if (callDuration <= 5)
charge = callDuration * tarrif1;
else
charge = 5 * tarrif1 + (callDuration - 5) * tarrif2;
} else
charge = flatFee;
A frequently-used form of nested if statements involves the else part consisting of another if-else
statement. For example:
if (ch >= '0' && ch <= '9')
kind = digit;
else {
-2-
if (ch >= 'A' && ch <= 'Z')
kind = upperLetter;
else {
if (ch >= 'a' && ch <= 'z')
kind = lowerLetter;
else
kind = special;
}
}
For improved readability, it is conventional to format such cases as follows:
if (ch >= '0' && ch <= '9')
kind = digit;
else if (cha >= 'A' && ch <= 'Z')
kind = capitalLetter;
else if (ch >= 'a' && ch <= 'z')
kind = smallLetter;
else
kind = special;
-3-
switch (operator) {
case '+': result = operand1 + operand2;
break;
case '-': result = operand1 - operand2;
break;
case '*': result = operand1 * operand2;
break;
case '/': result = operand1 / operand2;
break;
default:cout << "unknown operator: " << ch << '\n';
break;
}
As illustrated by this example, it is usually necessary to include a break statement at the end of
each case. The break terminates the switch statement by jumping to the very end of it. There are,
however, situations in which it makes sense to have a case without a break. For example, if we
extend the above statement to also allow x to be used as a multiplication operator, we will have:
switch (operator) {
case '+': result = operand1 + operand2;
break;
case '-': result = operand1 - operand2;
break;
case 'x':
case '*': result = operand1 * operand2;
break;
case '/': result = operand1 / operand2;
break;
default:cout << "unknown operator: " << ch << '\n';
break;
}
Because case 'x' has no break statement (in fact no statement at all!), when this case is satisfied,
execution proceeds to the statements of the next case and the multiplication is performed.
It should be obvious that any switch statement can also be written as multiple if-else statements.
The above statement, for example, may be written as:
if (operator == '+')
result = operand1 + operand2;
else if (operator == '-')
result = operand1 - operand2;
else if (operator == 'x' || operator == '*')
result = operand1 * operand2;
else if (operator == '/')
result = operand1 / operand2;
else
cout << "unknown operator: " << ch << '\n';
However, the switch version is arguably neater in this case. In general, preference should be given
to the switch version when possible. The if-else approach should be reserved for situation where a
-4-
switch cannot do the job (e.g., when the conditions involved are not simple equality expressions,
or when the case labels are not numeric constants).
3.3.Looping Statements
3.3.1. The ‘while’ Statement
The while statement (also called while loop) provides a way of repeating a statement while a
condition holds. It is one of the three flavors of iteration in C++. The general form of the while
statement is:
while (expression)
statement;
First expression (called the loop condition) is evaluated. If the outcome is nonzero then statement
(called the loop body) is executed and the whole process is repeated. Otherwise, the loop is
terminated.
For example, suppose we wish to calculate the sum of all numbers from 1 to some integer denoted
by n. This can be expressed as:
i = 1;
sum = 0;
while (i <= n)
sum += i;
For n set to 5, Table 2.9 provides a trace of the loop by listing the values of the variables involved
and the loop condition.
It is not unusual for a while loop to have an empty body (i.e., a null statement). The following
loop, for example, sets n to its greatest odd factor.
while (n % 2 == 0 && n /= 2)
;
-5-
Here the loop condition provides all the necessary computation, so there is no real need for a
body. The loop condition not only tests that n is even, it also divides n by two and ensures that the
loop will terminate should n be zero.
First expression1 is evaluated. Each time round the loop, expression2 is evaluated. If the outcome is
nonzero then statement is executed and expression3 is evaluated. Otherwise, the loop is terminated.
The general for loop is equivalent to the following while loop:
expression1;
while (expression2) {
statement;
expression3;
}
The most common use of for loops is for situations where a variable is incremented or
decremented with every iteration of the loop. The following for loop, for example, calculates the
sum of all integers from 1 to n.
sum = 0;
for (i = 1; i <= n; ++i)
sum += i;
This is preferred to the while-loop version we saw earlier. In this example, i is usually called the
loop variable.
C++ allows the first expression in a for loop to be a variable definition. In the above loop, for
example, i can be defined inside the loop itself:
for (int i = 1; i <= n; ++i)
sum += i;
Contrary to what may appear, the scope for i is not the body of the loop, but the loop itself. Scope-
wise, the above is equivalent to:
-6-
int i;
for (i = 1; i <= n; ++i)
sum += i;
Any of the three expressions in a for loop may be empty. For example, removing the first and the
third expression gives us something identical to a while loop:
for (; i != 0;) // is equivalent to: while (i != 0)
something; // something;
Removing all the expressions gives us an infinite loop. This loop's condition is assumed to be
always true:
for (;;) // infinite loop
something;
For loops with multiple loop variables are not unusual. In such cases, the comma operator is used
to separate their expressions:
for (i = 0, j = 0; i + j < n; ++i, ++j)
something;
Because loops are statements, they can appear inside other loops. In other words, loops can be
nested. For example,
for (int i = 1; i <= 3; ++i)
for (int j = 1; j <= 3; ++j)
cout << '(' << i << ',' << j << ")\n";
produces the product of the set {1,2,3} with itself, giving the output:
(1,1)
(1,2)
(1,3)
(2,1)
(2,2)
(2,3)
(3,1)
(3,2)
(3,3)
First statement is executed and then expression is evaluated. If the outcome of the latter is nonzero
then the whole process is repeated. Otherwise, the loop is terminated.
The do loop is less frequently used than the while loop. It is useful for situations where we need
the loop body to be executed at least once, regardless of the loop condition. For example, suppose
-7-
we wish to repeatedly read a value and print its square, and stop when the value is zero. This can
be expressed as the following loop:
do {
cin >> n;
cout << n * n << '\n';
} while (n != 0);
Unlike the while loop, the do loop is never used in situations where it would have a null body.
Although a do loop with a null body would be equivalent to a similar while loop, the latter is
always preferred for its superior readability.
3.4.Other Statements
3.4.1. The ‘continue’ Statement
The continue statement terminates the current iteration of a loop and instead jumps to the next
iteration. It applies to the loop immediately enclosing the continue statement. It is an error to use
the continue statement outside a loop.
In while and do loops, the next iteration commences from the loop condition. In a for loop, the
next iteration commences from the loop’s third expression. For example, a loop which repeatedly
reads in a number, processes it but ignores negative numbers, and terminates when the number is
zero, may be expressed as:
do {
cin >> num;
if (num < 0) continue;
// process num here...
} while (num != 0);
This is equivalent to:
do {
cin >> num;
if (num >= 0) {
// process num here...
}
} while (num != 0);
A variant of this loop which reads in a number exactly n times (rather than until the number is
zero) may be expressed as:
for (i = 0; i < n; ++i) {
cin >> num;
if (num < 0) continue; // causes a jump to: ++i
// process num here...
}
-8-
When the continue statement appears inside nested loops, it applies to the loop immediately
enclosing it, and not to the outer loops. For example, in the following set of nested loops, the
continue applies to the for loop, and not the while loop:
while (more) {
for (i = 0; i < n; ++i) {
cin >> num;
if (num < 0) continue; // causes a jump to: ++i
// process num here...
}
//etc...
}
-9-
where label is an identifier which marks the jump destination of goto. The label should be
followed by a colon and appear before a statement within the same function as the goto statement
itself. For example, the role of the break statement in the for loop in the previous section can be
emulated by a goto:
for (i = 0; i < attempts; ++i) {
cout << "Please enter your password: ";
cin >> password;
if (Verify(password)) // check password for correctness
goto out; // drop out of the loop
cout << "Incorrect!\n";
}
out:
//etc...
Because goto provides a free and unstructured form of jumping (unlike break and continue), it can
be easily misused. Most programmers these days avoid using it altogether in favor of clear
programming. Nevertheless, goto does have some legitimate (though rare) uses.
- 10 -
Chapter Four
Array, Pointers and Strings
1
Chapter Four
4.1. Introduction
Variables in a program have values associated with them. During program execution
these values are accessed by using the identifier associated with the variable in
expressions etc. In none of the programs written so far have very many variables been
used to represent the values that were required. Thus even though programs have been
written that could handle large lists of numbers it has not been necessary to use a separate
identifier for each number in the list. This is because in all these programs it has never
been necessary to keep a note of each number individually for later processing. For
example in summing the numbers in a list only one variable was used to hold the current
entered number which was added to the accumulated sum and was then overwritten by
the next number entered. If that value were required again later in the program there
would be no way of accessing it because the value has now been overwritten by the later
input.
If only a few values were involved a different identifier could be declared for each
variable, but now a loop could not be used to enter the values. Using a loop and assuming
that after a value has been entered and used no further use will be made of it allows the
following code to be written. This code enters six numbers and outputs their sum:
sum = 0.0;
for (i = 0; i < 6; i++)
{
cin >> x;
sum += x;
}
This of course is easily extended to n values where n can be as large as required.
However if it was required to access the values later the above would not be suitable. It
would be possible to do it as follows by setting up six individual variables:
float a, b, c, d, e, f;
2
sum = 0.0;
cin >> a; sum += a;
cin >> b; sum += b;
cin >> c; sum += c;
cin >> d; sum += d;
cin >> e; sum += e;
cin >> f; sum += f;
which is obviously a very tedious way to program. To extend this solution so that it
would work with more than six values then more declarations would have to be added,
extra assignment statements added and the program re-compiled. If there were 10000
values imagine the tedium of typing the program (and making up variable names and
remembering which is which)!
To get round this difficulty all high-level programming languages use the concept of a
data structure called an Array.
An array can be thought of as a collection of numbered boxes each containing one data
item. The number associated with the box is the index of the item. To access a particular
item the index of the box associated with the item is used to access the appropriate box.
The index must be an integer and indicates the position of the element in the array. Thus
the elements of an array are ordered by the index.
For example data on the average temperature over the year in Ethiopia for each of the last
100 years could be stored in an array declared as follows:
float annual_temp[100];
3
This declaration will cause the compiler to allocate space for 100 consecutive float
variables in memory. The number of elements in an array must be fixed at compile time.
It is best to make the array size a constant and then, if required, the program can be
changed to handle a different size of array by changing the value of the constant,
then if more records come to light it is easy to amend the program to cope with more
values by changing the value of NE. This works because the compiler knows the value of
the constant NE at compile time and can allocate an appropriate amount of space for the
array. It would not work if an ordinary variable was used for the size in the array
declaration since at compile time the compiler would not know a value for it.
An array element is accessed by writing the identifier of the array followed by the
subscript in square brackets. Thus to set the 15th element of the array above to 1.5 the
following assignment is used:
annual_temp[14] = 1.5;
Note that since the first element is at index 0, then the ith element is at index i-1. Hence
in the above the 15th element has index 14.
An array element can be used anywhere an identifier may be used. Here are some
examples assuming the following declarations:
const int NE = 100,
N = 50;
int i, j, count[N];
float annual_temp[NE];
float sum, av1, av2;
4
count[i] = count[i] + 5;
count[i] += 5;
Array elements can form part of the condition for an if statement, or indeed, for any other
logical expression:
if (annual_temp[j] < 10.0)
cout << "It was cold this year "
<< endl;
for statements are the usual means of accessing every element in an array. Here, the first
NE elements of the array annual_temp are given values from the input stream cin.
for (i = 0; i < NE; i++)
cin >> annual_temp[i];
The following code finds the average temperature recorded in the first ten elements of the
array.
sum = 0.0;
for (i = 0; i <10; i++)
sum += annual_temp[i];
av1 = sum / 10;
Notice that it is good practice to use named constants, rather than literal numbers such as
10. If the program is changed to take the average of the first 20 entries, then it all too easy
to forget to change a 10 to 20. If a const is used consistently, then changing its value will
be all that is necessary.
For example, the following example finds the average of the last k entries in the array. k
could either be a variable, or a declared constant. Observe that a change in the value of k
will still calculate the correct average (provided k<=NE).
sum = 0.0;
for (i = NE - k; i < NE; i++)
sum += annual_temp[i];
av2 = sum / k;
Important - C++ does not check that the subscript that is used to reference an array
element actually lies in the subscript range of the array. Thus C++ will allow the
assignment of a value to annual_temp[200], however the effect of this assignment is
unpredictable. For example it could lead to the program attempting to assign a value to a
memory element that is outside the program's allocated memory space. This would lead
5
to the program being terminated by the operating system. Alternatively it might actually
access a memory location that is within the allocated memory space of the program and
assign a value to that location, changing the value of the variable in your program which
is actually associated with that memory location, or overwriting the machine code of your
program. Similarly reading a value from annual_temp[200] might access a value that
has not been set by the program or might be the value of another variable. It is the
programmer's responsibility to ensure that if an array is declared with n elements then no
attempt is made to reference any element with a subscript outside the range 0 to n-1.
Using an index, or subscript, that is out of range is called Subscript Overflow. Subscript
overflow is one of the commonest causes of erroneous results and can frequently cause
very strange and hard to spot errors in programs.
Note that the array has not been given a size, the compiler will make it large enough to
hold the number of elements in the list. In this case primes would be allocated space for
seven elements. If the array is given a size then this size must be greater than or equal to
the number of elements in the initialization list. For example:
A set of positive data values (200) are available. It is required to find the average value of
these values and to count the number of values that are more than 10% above the average
value.
6
Since the data values are all positive a negative value can be used as a sentinel to signal
the end of data entry. Obviously this is a problem in which an array must be used since
the values must first be entered to find the average and then each value must be compared
with this average. Hence the use of an array to store the entered values for later re-use.
In the above the variable nogt10 is the number greater than 10% above the average value.
It is easy to argue that after exiting the while loop, count is set to the number of positive
numbers entered. Before entering the loop count is set to zero and the first number is
entered, that is count is one less than the number of numbers entered. Each time round the
loop another number is entered and count is incremented hence count remains one less
than the number of numbers entered. But the number of numbers entered is one greater
than the number of positive numbers so count is therefore equal to the number of positive
numbers.
A main() program written from the above algorithmic description is given below:
void main()
{
const int NE = 200; // maximum no of elements in array
float sum = 0.0; // accumulates sum
int count = 0; // number of elements entered
int nogt10 = 0; // counts no greater than 10%
7
// above average
float x; // holds each no as input
float indata[NE]; // array to hold input
float average; // average value of input values
int i; // control variable
// calculate average
average = sum/count;
// Output results
cout << "Number of values input is " << n;
cout << endl
<< "Number more than 10% above average is "
<< nogt10 << endl;
}
Since it was assumed in the specification that there would be less than 200 values the
array size is set at 200. In running the program less than 200 elements may be entered, if
n elements where n < 200 elements are entered then they will occupy the first n places in
the array indata. It is common to set an array size to a value that is the maximum we
think will occur in practice, though often not all this space will be used.
The following program simulates the throwing of a dice by using a random number
generator to generate integers in the range 0 to 5. The user is asked to enter the number of
trials and the program outputs how many times each possible number occurred.
An array has been used to hold the six counts. This allows the program to increment the
correct count using one statement inside the loop rather than using a switch statement
8
with six cases to choose between variables if separate variables had been used for each
count. Also it is easy to change the number of sides on the dice by changing a constant.
Because C++ arrays start at subscript 0 the count for an i occurring on a throw is held in
the i-1th element of this count array. By changing the value of the constant die_sides the
program could be used to simulate a die_sides-sided die without any further change.
#include <iostream.h>
#include <stdlib.h> // time.h and stdlib.h required for
#include <time.h> // random number generation
void main()
{
const int die_sides = 6; // maxr-sided die
int count[die_sides]; // holds count of each
// possible value
int no_trials, // number of trials
roll, // random integer
i; // control variable
float sample; // random fraction 0 .. 1
9
x = y ; // Error - Illegal
Only individual elements can be assigned to using the index operator, e.g., x[1] =
y[2];.
To make all elements in 'x' the same as those in 'y' (equivalent to assignment), a loop has
to be used.
// Loop to do copying, one element at a time
for (int i = 0 ; i < SIZE; i++)
x[i] = y[i];
This code will copy the elements of array y into x, overwriting the original contents of x.
A loop like this has to be written whenever an array assignment is needed.
Notice the use of a constant to store the array size. This avoids the literal constant '10'
appearing a number times in the code. If the code needs to be edited to use different sized
arrays, only the constant needs to be changed. If the constant is not used, all the '10's
would have to be changed individually - it is easy to miss one out.
Arrays can have any number of dimensions, although most of the arrays that you create
will likely be of one or two dimensions.
Suppose the program contains a class named square. The declaration of array named
board that represents would be
Square board[8][8];
The program could also represent the same data with a one dimensional, 64-square array.
For example, it could include the statement
Square board[64];
Such a representation does not correspond as closely to the real-world object as the two
dimensional array, however.
10
Suppose that when the game begins. The king id located in the fourth position in the first
row. Counting from zero that position corresponds to board[0][3] in the two dimensional
array, assuming that the first subscript corresponds to the row, and the second to the
column.
int x[] = { 1, 2, 3, 4} ;
This initialization creates an array of four elements.
Note however:
int x[][] = { {1,2}, {3,4} } ; // error is not allowed.
and must be written
int x[2][2] = { {1,2}, {3,4} } ;
11
Example of multidimensional array
#include<iostream.h>
void main(){
int SomeArray[5][2] = {{0,0},{1,2}, {2,4},{3,6},
{4,8}}
for ( int i=0; i<5; i++)
for (int j = 0; j<2;j++)
{
cout<<"SomeArray["<<i<<"]["<<j<<'']: '';
cout<<endl<<SomeArray[i][ j];
}
}
In C++ strings of characters are held as an array of characters, one character held in
each array element. In addition a special null character, represented by `\0', is appended
to the end of the string to indicate the end of the string. Hence if a string has n characters
then it requires an n+1 element array (at least) to store it. Thus the character `a' is stored
in a single byte, whereas the single-character string "a" is stored in two consecutive bytes
holding the character `a' and the null character.
The string variable s1 could hold strings of length up to nine characters since space is
needed for the final null character. Strings can be initialized at the time of declaration just
as other variables are initialized. For example:
char s1[] = "example";
char s2[20] = "another example"
would store the two strings as follows:
s1 |e|x|a|m|p|l|e|\0|
s2 |a|n|o|t|h|e|r| |e|x|a|m|p|l|e|\0|?|?|?|?|
In the first case the array would be allocated space for eight characters, that is space for
the seven characters of the string and the null character. In the second case the string is
set by the declaration to be twenty characters long but only sixteen of these characters are
12
set, i.e. the fifteen characters of the string and the null character. Note that the length of a
string does not include the terminating null character.
Note that the last two elements are a relic of the initialization at declaration time. If the
string that is entered is longer than the space available for it in the character array then
C++ will just write over whatever space comes next in memory. This can cause some
very strange errors when some of your other variables reside in that space!
13
To read a string with several words in it using cin we have to call cin once for each
word. For example to read in a name in the form of a Christian name followed by a
surname we might use code as follows:
Thus it will read strings consisting of a single word, but anything typed after a space is
thrown away.
We have solved the problem of reading strings with embedded blanks, but what about
strings with multiple lines? It turns out that the cin::get() function can take a third
argument to help out in this situation.
This argument specifies the character that tells the function to stop reading. The default
value of this argument is the newline('\n')character, but if you call the function
with some other character for this argument, the default will be overridden by the
specified character.
14
In the next example, we call the function with a dollar sign ('$') as the third argument
//reads multiple lines, terminates on '$' character
#include<iostream.h>
void main(){
const int max=80;
char str[max];
cout<<"\n Enter a string:\n";
cin.get(str, max, '$'); //terminates with $
cout<<\n You entered:\n"<<str; }
now you can type as many lines of input as you want. The function will continue to
accept characters until you enter the terminated character $ (or untill you exceed the size
of the array. Remember, you must still press Enter key after typing the '$' character .
However, it is possible to tell the >> operator to limit the number of characters it places
in an array.
#include<iostream.h>
void main(){
char str[] = "Welcome to C++ programming language";
cout<<str;
}
if you tried to the string program with strings that contain more than one word , you may
have unpleasant surprise. Copying string the hard way
15
The best way to understand the true nature of strings is to deal with them character by
character
#include<iostream.h>
#include<string.h> //for strlen()
void main()
{
const int max=80;
char str1[]='' Oh, Captain, my Captain!"
our fearful trip is done";
char str2[max];
for(int i=0; i<strlen(str1);i++)
str2[i]=str1[1];
str2[i]='\0';
cout<<endl;
cout<<str2;
}
strcpy(destination, source);
strcpy copies characters from the location specified by source to the location
specified by destination. It stops copying characters after it copies the terminating null
character.
Example:
#include <iostream.h>
#include <string.h>
void main(){
char me[20] = "David";
cout << me << endl;
strcpy(me, "YouAreNotMe");
cout << me << endl ;
return;
}
16
There is also another function strncpy, is like strcpy, except that it copies only a
specified number of characters.
strncpy(destination, source, int n);
The function strcat concatenates (appends) one string to the end of another string.
strcat(destination, source);
o The first character of the source string is copied to the location of the terminating null
character of the destination string.
o The destination string must have enough space to hold both strings and a terminating
null character.
Example:
#include <iostream.h>
#include <string.h>
void main() {
char str1[30];
strcpy(str1, "abc");
cout << str1 << endl;
strcat(str1, "def");
cout << str1 << endl;
17
cout << str1 << endl;
str1[4] = '\0';
cout << str1 << endl;
}
The function strncat is like strcat except that it copies only a specified number of
characters.
strncat(destination, source, int n);
It may not copy the terminating null character.
Example:
#include <iostream.h>
#include <string.h>
void main() {
char str1[30];
strcpy(str1, "abc");
cout << str1 << endl;
strncat(str1, "def", 2);
str1[5] = '\0';
cout << str1 << endl;
char str2[] = "xyz";
strcat(str1, str2);
cout << str1 << endl;
str1[4] = '\0';
cout << str1 << endl;
}
strncmp does not compare characters after a terminating null character has been found
in one of the strings.
18
Example:
#include <iostream.h>
#include <string.h>
void main()
{
cout << strncmp("abc", "def", 2) << endl;
cout << strncmp("abc", "abcdef", 3) << endl;
cout << strncmp("abc", "abcdef", 2) << endl;
cout << strncmp("abc", "abcdef", 5) << endl;
cout << strncmp("abc", "abcdef", 20) << endl;
}
4.6. Pointers
A pointer is simply the address of a memory location and provides an indirect way of
accessing data in memory. A pointer variable is defined to ‘point to’ data of a specific
type. For example:
int num;
we can write:
ptr1 = #
The symbol & is the address operator; it takes a variable as argument and returns the
memory address of that variable. The effect of the above assignment is that the address of
num is assigned to ptr1. Therefore, we say that ptr1 points to num. Figure 5.Error!
Bookmark not defined. illustrates this diagrammatically.
ptr1 num
Figure: A simple integer pointer.
Given that ptr1 points to num, the expression
*ptr1
dereferences ptr1 to get to what it points to, and is therefore equivalent to num. The
symbol * is the dereference operator; it takes a pointer as argument and returns the
contents of the location to which it points.
19
In general, the type of a pointer must match the type of the data it is set to point to. A
pointer of type void*, however, will match any type. This is useful for defining pointers
which may point to data of different types, or whose type is originally unknown. A
pointer may be cast (type converted) to another type. For example,
Two operators are used for allocating and deallocating memory blocks on the heap. The
new operator takes a type as argument and allocated a memory block for an object of that
type. It returns a pointer to the allocated block. For example,
Memory allocated from the heap does not obey the same scope rules as normal
variables. For example, in
when Foo returns, the local variable str is destroyed, but the memory block
pointed to by str is not. The latter remains allocated until explicitly released
by the programmer.
20
The delete operator is used for releasing memory blocks allocated by new. It
takes a pointer as argument and releases the memory block to which it points. For
example:
Listing 5.1
1 #include <string.h>
5 strcpy(copy, str);
6 return copy;
7 }
Annotation (analysis)
1 This is the standard string header file which declares a variety of
functions for manipulating strings.
4 The strlen function (declared in string.h) counts the characters in its
string argument up to (but excluding) the final null character. Because
the null character is not included in the count, we add 1 to the total and
allocate an array of characters of that size.
5 The strcpy function (declared in string.h) copies its second argument
to its first, character by character, including the final null character.
22
new
You allocate memory on the free store in C++ by using the new keyword. new is followed
by the type of the object that you want to allocate so that the compiler knows how much
memory is required. Therefore, new int allocates two bytes in the free store, and new long
allocates four.
The return value from new is a memory address. It must be assigned to a pointer. To
create an int on the free store, you might write
int * pPointer;
pPointer = new int;
You can, of course, initialize the pointer at its creation with
int * pPointer = new int(10);
In either case, pPointer now points to an int on the free store. You can use this like any
other pointer to a variable and assign a value into that area of memory by writing
*pPointer = 72;
Or it can be written as
int * pPointer = new int (72);
This means, "Put 72 at the value in pPointer," or "Assign the value 72 to the area on the
free store to which pPointer points."
If new cannot create memory on the free store (memory is, after all, a limited resource) it
returns the null pointer. You must check your pointer for null each time you request new
memory.
WARNING: Each time you allocate memory using the new keyword, you must check to
make sure the pointer is not null.
delete
When you are finished with your area of memory, you must call delete on the pointer.
delete returns the memory to the free store. Remember that the pointer itself--as opposed
to the memory to which it points--is a local variable. When the function in which it is
declared returns, that pointer goes out of scope and is lost. The memory allocated with
new is not freed automatically, however. That memory becomes unavailable--a situation
called a memory leak. It's called a memory leak because that memory can't be recovered
until the program ends. It is as though the memory has leaked out of your computer.
To restore the memory to the free store, you use the keyword delete. For example,
23
delete pPointer;
When you delete the pointer, what you are really doing is freeing up the memory whose
address is stored in the pointer. You are saying, "Return to the free store the memory that
this pointer points to." The pointer is still a pointer, and it can be reassigned. The
following program demonstrates allocating a variable on the heap, using that variable,
and deleting it.
1: // Listing 8.4
2: // Allocating and deleting a pointer
3:
4: #include <iostream.h>
5: int main()
6: {
7: int localVariable = 5;
8: int * pLocal= &localVariable;
9: int * pHeap = new int;
10: if (pHeap == NULL)
11: {
12: cout << "Error! No memory for pHeap!!";
13: return 0;
14: }
15: *pHeap = 7;
16: cout << "localVariable: " << localVariable << "\n";
17: cout << "*pLocal: " << *pLocal << "\n";
18: cout << "*pHeap: " << *pHeap << "\n";
19: delete pHeap;
20: pHeap = new int;
21: if (pHeap == NULL)
22: {
23: cout << "Error! No memory for pHeap!!";
24: return 0;
25: }
26: *pHeap = 9;
27: cout << "*pHeap: " << *pHeap << "\n";
28: delete pHeap;
29: return 0;
30: }
Output: localVariable: 5
*pLocal: 5
*pHeap: 7
*pHeap: 9
Analysis: Line 7 declares and initializes a local variable. Line 8 declares and initializes a
pointer with the address of the local variable. Line 9 declares another pointer but
24
initializes it with the result obtained from calling new int. This allocates space on the free
store for an int. Line 10 verifies that memory was allocated and the pointer is valid (not
null). If no memory can be allocated, the pointer is null and an error message is printed.
To keep things simple, this error checking often won't be reproduced in future programs,
but you must include some sort of error checking in your own programs.
Line 15 assigns the value 7 to the newly allocated memory. Line 16 prints the value of the
local variable, and line 17 prints the value pointed to by pLocal. As expected, these are the
same. Line 19 prints the value pointed to by pHeap. It shows that the value assigned in line
15 is, in fact, accessible.
In line 19, the memory allocated in line 9 is returned to the free store by a call to delete.
This frees the memory and disassociates the pointer from that memory. pHeap is now free
to point to other memory. It is reassigned in lines 20 and 26, and line 27 prints the result.
Line 28 restores that memory to the free store.
Although line 28 is redundant (the end of the program would have returned that memory)
it is a good idea to free this memory explicitly. If the program changes or is extended, it
will be beneficial that this step was already taken care of.
WARNING: When you call delete on a pointer, the memory it points to is freed. Calling
delete on that pointer again will crash your program! When you delete a pointer, set it to
zero (null). Calling delete on a null pointer is guaranteed to be safe. For example:
Animal *pDog = new Animal;
delete pDog; //frees the memory
pDog = 0; //sets pointer to null //... delete pDog; //harmless
Another way you might inadvertently create a memory leak is by reassigning your
pointer before deleting the memory to which it points. Consider this code fragment:
1: float * pPointer = new float;
2: *pPointer = 72.0;
3: pPointer = new float;
4: *pPointer = 84.5;
Line 1 creates pPointer and assigns it the address of an area on the free store. Line 2 stores
the value 72 in that area of memory. Line 3 reassigns pPointer to another area of memory.
Line 4 places the value 84 in that area. The original area--in which the value 72 is now
held--is unavailable because the pointer to that area of memory has been reassigned.
25
There is no way to access that original area of memory, nor is there any way to free it
before the program ends.
The code should have been written like this:
1: float * pPointer = new float;
2: *pPointer = 72;
3: delete pPointer;
4: pPointer = new float;
5: *pPointer = 84;
Now the memory originally pointed to by pPointer is deleted, and thus freed, in line 3.
NOTE: For every time in your program that you call new, there should be a call to delete.
It is important to keep track of which pointer owns an area of memory and to ensure that
the memory is returned to the free store when you are done with it.
C++ also provides the possibility to allocate an entire array in the heap, keeping a pointer
to its first element. Again we use the new operator but mention the size of the array in
square brackets:
int *table;
table = new int[100];
This will allocate 100 * sizeof(int) = 100 * 4 = 400 bytes of memory and assign the
starting address of the memory block to the table pointer.
Arrays allocated in the heap are similar to those allocated in the data or stack segments,
so we may access an arbitrary element using the indexing operator []. For example the
next loop initializes each element of the array to zero:
For (int i = 0; i < 100; i++)
table[i]=0;
Bad news is C++ does not provide any method of initializing heap arrays as you would
an ordinary dynamic variable, so we have to do it ourselves using a loop similar to the
one above. The following line will generate errors at compilation :
table = new int[100](0);
To erase a heap-allocated array we will use the delete operator, but this time add a pair of
square brackets so that the compiler can differentiate it from an ordinary dynamic
variable.
delete [] table;
26
4.6.2. Pointer Arithmetic
In C++ one can add an integer quantity to or subtract an integer quantity from
a pointer. This is frequently used by programmers and is called pointer
arithmetic. Pointer arithmetic is not the same as integer arithmetic, because
the outcome depends on the size of the object pointed to. For example,
suppose that an int is represented by 4 bytes. Now, given
char *str = "HELLO";
int nums[] = {10, 20, 30, 40};
int *ptr = &nums[0]; // pointer to first element
str++ advances str by one char (i.e., one byte) so that it points to the second
character of "HELLO", whereas ptr++ advances ptr by one int (i.e., four
bytes) so that it points to the second element of nums. Figure 5.1 illustrates
this diagrammatically.
str ptr
str++ ptr++
Listing 5.2
1 void CopyString (char *dest, char *src)
2 {
3 while (*dest++ = *src++)
4 ;
5 }
Annotation
3 The condition of this loop assigns the contents of src to the contents of
dest and then increments both pointers. This condition becomes 0 when
the final null character of src is copied to dest.
27
In turns out that an array variable (such as nums) is itself the address of
the first element of the array it represents. Hence the elements of nums can
also be referred to using pointer arithmetic on nums, that is, nums[i] is
equivalent to *(nums + i). The difference between nums and ptr is that nums
is a constant, so it cannot be made to point to anything else, whereas ptr is a
variable and can be made to point to any other integer.
Listing 5.3 shows how the HighestTemp function (shown earlier in
Listing 5.Error! Bookmark not defined.) can be improved using pointer
arithmetic.
Listing 5.3
1 int HighestTemp (const int *temp, const int rows, const int columns)
2 {
3 int highest = 0;
Annotation
1 Instead of passing an array to the function, we pass an int pointer and
two additional parameters which specify the dimensions of the array. In
this way, the function is not restricted to a specific array size.
6 The expression *(temp + i * columns + j) is equivalent to
temp[i][j] in the previous version of this function.
Listing 5.4
1 int HighestTemp (const int *temp, const int rows, const int columns)
2 {
3 int highest = 0;
28
defines a function pointer named Compare which can hold the address of any
function that takes two constant character pointers as arguments and returns
an integer. The string comparison library function strcmp, for example, is
such. Therefore:
Compare = &strcmp; // Compare points to strcmp function
Listing 5.5
29
1 int BinSearch (char *item, char *table[], int n,
2 int (*Compare)(const char*, const char*))
3 {
4 int bot = 0;
5 int top = n - 1;
6 int mid, cmp;
Annotation
1 Binary search is a well-known algorithm for searching through a sorted
list of items. The search list is denoted by table which is an array of
strings of dimension n. The search item is denoted by item.
2 Compare is the function pointer to be used for comparing item against the
array elements.
7 Each time round this loop, the search span is reduced by half. This is
repeated until the two ends of the search span (denoted by bot and top)
collide, or until a match is found.
9 The item is compared against the middle item of the array.
10 If item matches the middle item, the latter’s index is returned.
11 If item is less than the middle item, then the search is restricted to the
lower half of the array.
14 If item is greater than the middle item, then the search is restricted to the
upper half of the array.
16 Returns -1 to indicate that there was no matching item.
The following example shows how BinSearch may be called with
strcmp passed as the comparison function:
4.6.4. References
A reference introduces an alias for an object. The notation for defining
references is similar to that of pointers, except that & is used instead of *. For
example,
30
double num1 = 3.14;
double &num2 = num1; // num is a reference to num1
defines num2 as a reference to num1. After this definition num1 and num2 both
refer to the same object, as if they were the same variable. It should be
emphasized that a reference does not create a copy of an object, but merely a
symbolic alias for it. Hence, after
num1 = 0.16;
You can also initialize a reference to a constant. In this case a copy of the
constant is made (after any necessary type conversion) and the reference is set
to refer to the copy.
int &n = 1; // n refers to a copy of 1
The 1 in the first and the 1 in the third line are likely to be the same object
(most compilers do constant optimization and allocate both 1’s in the same
memory location). So although we expect y to be 3, it could turn out to be 4.
However, by forcing x to be a copy of 1, the compiler guarantees that the
object denoted by x will be different from both 1’s.
The most common use of references is for function parameters.
Reference parameters facilitates the pass-by-reference style of arguments, as
opposed to the pass-by-value style which we have used so far. To observe
the differences, consider the three swap functions in Listing 5.6.
Listing 5.6
31
1 void Swap1 (int x, int y) // pass-by-value (objects)
2 {
3 int temp = x;
4 x = y;
5 y = temp;
6 }
4.6.5. Typedefs
Typedef is a syntactic facility for introducing symbolic names for data types.
Just as a reference defines an alias for an object, a typedef defines an alias for
32
a type. Its main use is to simplify otherwise complicated type declarations as
an aid to improved readability. Here are a few examples:
typedef char *String;
Typedef char Name[12];
typedef unsigned int uint;
The effect of these definitions is that String becomes an alias for char*,
Name becomes an alias for an array of 12 chars, and uint becomes an alias
for unsigned int. Therefore:
String str; // is the same as: char *str;
Namename; // is the same as: char name[12];
uintn; // is the same as: unsigned int n;
The typedef introduces Compare as a new type name for any function with the
given prototype. This makes BinSearch’s signature arguably simpler.
33
Chapter Three
Modular Programming
-1-
Define the function before it is called by any other function. When you do this, the
definition acts as its own declaration.
Although you can define the function before using it, and thus avoid the necessity of
creating a function prototype, this is not good programming practice for three reasons.
First, it is a bad idea to require that functions appear in a file in a particular order. Doing so
makes it hard to maintain the program as requirements change. Second, it is possible that
function A() needs to be able to call function B(), but function B() also needs to be able to
call function A() under some circumstances. It is not possible to define function A() before
you define function B() and also to define function B() before you define function A(), so at
least one of them must be declared in any case. Third, function prototypes are a good and
powerful debugging technique. If your prototype declares that your function takes a
particular set of parameters, or that it returns a particular type of value, and then your
function does not match the prototype, the compiler can flag your error instead of waiting
for it to show itself when you run the program.
Function Prototypes
Many of the built-in functions you use have their function prototypes already written in the
files you include in your program by using #include. For functions you write yourself, you
must include the prototype. The function prototype is a statement, which means it ends
with a semicolon. It consists of the function's return type, name, and parameter list. The
parameter list is a list of all the parameters and their types, separated by commas. Function
Prototype Syntax:
return_type function_name ( [type [parameterName1] type [parameterName2]]...);
The function prototype and the function definition must agree exactly about the return type,
the name, and the parameter list. If they do not agree, you will get a compile-time error.
Note, however, that the function prototype does not need to contain the names of the
parameters, just their types. A prototype that looks like this is perfectly legal:
long Area(int, int);
This prototype declares a function named Area() that returns a long and that has two
parameters, both integers. Although this is legal, it is not a good idea. Adding parameter
names makes your prototype clearer. The same function with named parameters might be
long Area(int length, int width);
-2-
It is now obvious what this function does and what the parameters are. Note that all
functions have a return type. If none is explicitly stated, the return type defaults to int. Your
programs will be easier to understand, however, if you explicitly declare the return type of
every function, including main().
Function Prototype Examples
long FindArea(long length, long width); // returns long, has two parameters
void PrintMessage(int messageNumber); // returns void, has one parameter
int GetChoice(); // returns int, has no parameters
BadFunction(); // returns int, has no parameters
Listing 5.1 demonstrates a program that includes a function prototype for the Area()
function.
Listing 5.1. A function declaration and the definition and use of that function.
1: // Listing 5.1 - demonstrates the use of function prototypes
2:
3: typedef unsigned short USHORT;
4: #include <iostream.h>
5: USHORT FindArea(USHORT length, USHORT width); //function prototype
6:
7: int main()
8: {
9: USHORT lengthOfYard;
10: USHORT widthOfYard;
11: USHORT areaOfYard;
12:
13: cout << "\nHow wide is your yard? ";
14: cin >> widthOfYard;
15: cout << "\nHow long is your yard? ";
16: cin >> lengthOfYard;
17:
18: areaOfYard= FindArea(lengthOfYard,widthOfYard);
19:
20: cout << "\nYour yard is ";
21: cout << areaOfYard;
22: cout << " square feet\n\n";
23: return 0;
24: }
-3-
25:
26: USHORT FindArea(USHORT l, USHORT w)
27: {
28: return l * w;
29: }
Output: How wide is your yard? 100
How long is your yard? 200
Your yard is 20000 square feet
Analysis: The prototype for the FindArea() function is on line 5. Compare the prototype with
the definition of the function on line 26. Note that the name, the return type, and the
parameter types are the same. If they were different, a compiler error would have been
generated. In fact, the only required difference is that the function prototype ends with a
semicolon and has no body. Also note that the parameter names in the prototype are length
and width, but the parameter names in the definition are l and w. The names in the prototype
are not used; they are there as information to the programmer. When they are included,
they should match the implementation when possible. This is a matter of good
programming style and reduces confusion, but it is not required, as you see here.
The arguments are passed in to the function in the order in which they are declared and
defined, but there is no matching of the names. Had you passed in widthOfYard, followed by
lengthOfYard, the FindArea() function would have used the value in widthOfYard for length and
lengthOfYard for width. The body of the function is always enclosed in braces, even when it
consists of only one statement, as in this case.
5.1.1.2. Defining the Function
The definition of a function consists of the function header and its body. The header is
exactly like the function prototype, except that the parameters must be named, and there is
no terminating semicolon. The body of the function is a set of statements enclosed in
braces. Function Definition Syntax
return_type function_name ( [type parameterName1], [type parameterName2]...)
{
statements;
}
-4-
A function prototype tells the compiler the return type, name, and parameter list. Func-
tions are not required to have parameters, and if they do, the prototype is not required to list
their names, only their types. A prototype always ends with a semicolon (;). A function
definition must agree in return type and parameter list with its prototype. It must provide
names for all the parameters, and the body of the function definition must be surrounded by
braces. All statements within the body of the function must be terminated with semicolons,
but the function itself is not ended with a semicolon; it ends with a closing brace. If the
function returns a value, it should end with a return statement, although return statements can
legally appear anywhere in the body of the function. Every function has a return type. If
one is not explicitly designated, the return type will be int. Be sure to give every function an
explicit return type. If a function does not return a value, its return type will be void.
Function Definition Examples
long Area(long l, long w)
{
return l * w;
}
-5-
you can see the entire function at one time. This is a rule of thumb, often broken by very
good programmers, but a smaller function is easier to understand and maintain. Each
function should carry out a single, easily understood task. If your functions start getting
large, look for places where you can divide them into component tasks.
Execution of Functions
When you call a function, execution begins with the first statement after the opening brace
({). Branching can be accomplished by using the if statement. Functions can also call other
functions and can even call themselves (see the section "Recursion," later in this chapter).
Each function has its own name, and when that name is encountered, the execution of the
program branches to the body of that function. When the function returns, execution
resumes on the next line of the calling function. This flow is illustrated in the following
figure.
-6-
they had been defined within the body of the function. Variables declared within the
function are said to have "local scope." That means that they are visible and usable only
within the function in which they are defined. In fact, in C++ you can define variables
anywhere within the function, not just at its top. The scope of the variable is the block in
which it is defined. Thus, if you define a variable inside a set of braces within the function,
that variable is available only within that block. Listing 5.2 is an example of using
parameters and locally defined variables within a function.
Listing 5.2. The use of local variables and parameters.
1: #include <iostream.h>
2:
3: float Convert(float);
4: int main()
5: {
6: float TempFer;
7: float TempCel;
8:
9: cout << "Please enter the temperature in Fahrenheit: ";
10: cin >> TempFer;
11: TempCel = Convert(TempFer);
12: cout << "\nHere's the temperature in Celsius: ";
13: cout << TempCel << endl;
14: return 0;
15: }
16:
17: float Convert(float TFer)
18: {
19: float TCel;
20: TCel = ((TFer - 32) * 5) / 9;
21: return TCel;
22: }
Output: Please enter the temperature in Fahrenheit: 212
Here's the temperature in Celsius: 100
Please enter the temperature in Fahrenheit: 32
Here's the temperature in Celsius: 0
Please enter the temperature in Fahrenheit: 85
Here's the temperature in Celsius: 29.4444
Analysis: On lines 6 and 7, two float variables are declared, one to hold the temperature in
Fahrenheit and one to hold the temperature in degrees Celsius. The user is prompted to
enter a Fahrenheit temperature on line 9, and that value is passed to the function Convert().
-7-
Execution jumps to the first line of the function Convert() on line 19, where a local variable,
named TCel, is declared. Note that this is local variable that exists only within the function
Convert(). The value passed as a parameter, TFer, is also just a local copy of the variable
passed in by main().The local function variable TCel is assigned the value that results from
subtracting 32 from the parameter TFer, multiplying by 5, and then dividing by 9. This
value is then returned as the return value of the function, and on line 11 it is assigned to the
variable TempCel in the main() function. The value is printed on line 13. The program is run
three times. The first time, the value 212 is passed in to ensure that the boiling point of
water in degrees Fahrenheit (212) generates the correct answer in degrees Celsius (100).
The second test is the freezing point of water. The third test is a random number chosen to
generate a fractional result.
Variables declared within a block are scoped to that block; they can be accessed only
within that block and "go out of existence" when that block ends. Global variables have
global scope and are available anywhere within your program. Normally scope is obvious,
but there are some tricky exceptions. Currently, variables declared within the header of a for
loop (for int i = 0; i<SomeValue; i++) are scoped to the block in which the for loop is created.
5.2.2. Global Variables
Variables defined outside of any function have global scope and thus are available from
any function in the program, including main(). Local variables with the same name as global
variables do not change the global variables. A local variable with the same name as a
global variable hides the global variable, however. If a function has a variable with the
same name as a global variable, the name refers to the local variable--not the global--when
used within the function. Listing 5.3 illustrates these points.
Listing 5.3. Demonstrating global and local variables.
1: #include <iostream.h>
2: void myFunction(); // prototype
3:
4: int x = 5, y = 7; // global variables
5: int main()
6: {
7:
8: cout << "x from main: " << x << "\n";
9: cout << "y from main: " << y << "\n\n";
10: myFunction();
-8-
11: cout << "Back from myFunction!\n\n";
12: cout << "x from main: " << x << "\n";
13: cout << "y from main: " << y << "\n";
14: return 0;
15: }
16:
17: void myFunction()
18: {
19: int y = 10;
20:
21: cout << "x from myFunction: " << x << "\n";
22: cout << "y from myFunction: " << y << "\n\n";
23: }
Output: x from main: 5
y from main: 7
x from myFunction: 5
y from myFunction: 10
Back from myFunction!
x from main: 5
y from main: 7
Analysis: This simple program illustrates a few key, and potentially confusing, points
about local and global variables. On line 1, two global variables, x and y, are declared. The
global variable x is initialized with the value 5, and the global variable y is initialized with
the value 7. On lines 8 and 9 in the function main(), these values are printed to the screen.
Note that the function main() defines neither variable; because they are global, they are
already available to main().
When myFunction() is called on line 10, program execution passes to line 18, and a local
variable, y, is defined and initialized with the value 10. On line 21, myFunction() prints the
value of the variable x, and the global variable x is used, just as it was in main(). On line 22,
however, when the variable name y is used, the local variable y is used, hiding the global
variable with the same name.The function call ends, and control returns to main(), which
again prints the values in the global variables. Note that the global variable y was totally
unaffected by the value assigned to myFunction()'s local y variable.
-9-
Listing 5.4. Variables scoped within a block.
1: // Listing 5.4 - demonstrates variables
2: // scoped within a block
3:
4: #include <iostream.h>
5:
6: void myFunc();
7:
8: int main()
9: {
10: int x = 5;
11: cout << "\nIn main x is: " << x;
12:
13: myFunc();
14:
15: cout << "\nBack in main, x is: " << x;
16: return 0;
17: }
18:
19: void myFunc()
20: {
21:
22: int x = 8;
23: cout << "\nIn myFunc, local x: " << x << endl;
24:
25: {
26: cout << "\nIn block in myFunc, x is: " << x;
27:
28: int x = 9;
29:
30: cout << "\nVery local x: " << x;
31: }
32:
33: cout << "\nOut of block, in myFunc, x: " << x << endl;
34: }
Output: In main x is: 5
In myFunc, local x: 8
In block in myFunc, x is: 8
Very local x: 9
Out of block, in myFunc, x: 8
Back in main, x is: 5
- 10 -
Analysis: This program begins with the initialization of a local variable, x, on line 10, in
main(). The printout on line 11 verifies that x was initialized with the value 5. MyFunc() is
called, and a local variable, also named x, is initialized with the value 8 on line 22. Its value
is printed on line 23. A block is started on line 25, and the variable x from the function is
printed again on line 26. A new variable also named x, but local to the block, is created on
line 28 and initialized with the value 9. The value of the newest variable x is printed on line
30. The local block ends on line 31, and the variable created on line 28 goes "out of scope"
and is no longer visible.
When x is printed on line 33, it is the x that was declared on line 22. This x was unaffected
by the x that was defined on line 28; its value is still 8. On line 34, MyFunc() goes out of
scope, and its local variable x becomes unavailable. Execution returns to line 15, and the
value of the local variable x, which was created on line 10, is printed. It was unaffected by
either of the variables defined in MyFunc(). Needless to say, this program would be far less
confusing if these three variables were given unique names!
Global Variables: A Word of Caution
In C++, global variables are legal, but they are almost never used. Globals are dangerous
because they are shared data, and one function can change a global variable in a way that is
invisible to another function. This can and does create bugs that are very difficult to find.
Scope resolution operator
When a local variable has the same name as a global variable, all references to the variable
name made with in the scope of the local variable. This situation is illustrated in the
following program.
# include<iostream.h>
float num = 42.8; //global variable
int main()
{
float num = 26.4; //local variable
cout<<”the value of num is:”<<num<<endl;
return 0;
}
The output of the above program is: the value of num is: 26.4
- 11 -
As shown by the above output, the local variable name takes precedence ovger the global
variable. In such cases, we can still access the global variable by using C++’s scope
resolution operator (::). This operator must be placed immediately before the variable
name, as in ::num. When used in this manner, the :: tells the compiler to use global
variable.E.g
float num = 42.8; //global variable
int main()
{
float num = 26.4; //local variable
cout<<”the value of num is:”<< ::num<<endl;
return 0;
}
The out is:
the value of the number is 42.8
As indicated the above output, the scope resolution operator causes the global, rather the
local variable to be accessed.
5.3. Function Arguments
Function arguments do not have to all be of the same type. It is perfectly reasonable to
write a function that takes an integer, two longs, and a character as its arguments. Any valid
C++ expression can be a function argument, including constants, mathematical and logical
expressions, and other functions that return a value.
Using Functions as Parameters to Functions
Although it is legal for one function to take as a parameter a second function that returns a
value, it can make for code that is hard to read and hard to debug. As an example, say you
have the functions double(), triple(), square(), and cube(), each of which returns a value. You
could write
Answer = (double(triple(square(cube(myValue)))));
This statement takes a variable, myValue, and passes it as an argument to the function cube(),
whose return value is passed as an argument to the function square(), whose return value is
in turn passed to triple(), and that return value is passed to double(). The return value of this
doubled, tripled, squared, and cubed number is now passed to Answer.
- 12 -
It is difficult to be certain what this code does (was the value tripled before or after it was
squared?), and if the answer is wrong it will be hard to figure out which function failed. An
alternative is to assign each step to its own intermediate variable:
unsigned long myValue = 2;
unsigned long cubed = cube(myValue); // cubed = 8
unsigned long squared = square(cubed); // squared = 64
unsigned long tripled = triple(squared); // tripled = 196
unsigned long Answer = double(tripled); // Answer = 392
Now each intermediate result can be examined, and the order of execution is explicit.
5.4. Passing arguments
5.4.1. Pass by Value
The arguments passed in to the function are local to the function. Changes made to the
arguments do not affect the values in the calling function. This is known as passing by
value, which means a local copy of each argument is made in the function. These local
copies are treated just like any other local variables. Listing 5.5 illustrates this point.
Listing 5.5. A demonstration of passing by value.
1: // Listing 5.5 - demonstrates passing by value
2:
3: #include <iostream.h>
4:
5: void swap(int x, int y);
6:
7: int main()
8: {
9: int x = 5, y = 10;
10:
11: cout << "Main. Before swap, x: " << x << " y: " <<y<< "\n";
12: swap(x,y);
13: cout << "Main. After swap, x: " << x << " y: " <<y << "\n";
14: return 0;
15: }
16:
17: void swap (int x, int y)
18: {
19: int temp;
20:
21: cout << "Swap. Before swap, x: " << x << " y: " <<y<< "\n";
22:
23: temp = x;
- 13 -
24: x = y;
25: y = temp;
26:
27: cout << "Swap. After swap, x: " << x << " y: " << y << "\n";
28:
29: }
Output: Main. Before swap, x: 5 y: 10
Swap. Before swap, x: 5 y: 10
Swap. After swap, x: 10 y: 5
Main. After swap, x: 5 y: 10
Analysis: This program initializes two variables in main() and then passes them to the swap()
function, which appears to swap them. When they are examined again in main(), however,
they are unchanged! The variables are initialized on line 9, and their values are displayed
on line 11. swap() is called, and the variables are passed in. Execution of the program
switches to the swap() function, where on line 21 the values are printed again. They are in
the same order as they were in main(), as expected. On lines 23 to 25 the values are
swapped, and this action is confirmed by the printout on line 27. Indeed, while in the swap()
function, the values are swapped. Execution then returns to line 13, back in main(), where
the values are no longer swapped.
As you've figured out, the values passed in to the swap() function are passed by value,
meaning that copies of the values are made that are local to swap(). These local variables are
swapped in lines 23 to 25, but the variables back in main() are unaffected.
5.4.2. Pass by reference
In C++, passing by reference is accomplished in two ways: using pointers and using
references. The syntax is different, but the net effect is the same. Rather than a copy being
created within the scope of the function, the actual original object is passed into the
function. Passing an object by reference allows the function to change the object being
referred to. In previous section showed that a call to the swap() function did not affect the
values in the calling function.
- 14 -
Listing 9.7. swap() rewritten with references.
1: //Listing 9.7 Demonstrates passing by reference
2: // using references!
3:
4: #include <iostream.h>
5:
6: void swap(int &x, int &y);
7:
8: int main()
9: {
10: int x = 5, y = 10;
11:
12: cout << "Main. Before swap, x: " << x << " y: " <<y<< "\n";
13: swap(x,y);
14: cout << "Main. After swap, x: " << x << " y: " <<y << "\n";
15: return 0;
16: }
17:
18: void swap (int &rx, int &ry)
19: {
20: int temp;
21:
22: cout << "Swap. Before swap, rx: " <<rx<< " ry: " <<ry<<"\n";
23:
24: temp = rx;
25: rx = ry;
26: ry = temp;
27:
28: cout << "Swap. After swap, rx: " <<rx<< " ry: " <<ry<< "\n";
29:
30: }
Output: Main. Before swap, x:5 y: 10
Swap. Before swap, rx:5 ry:10
Swap. After swap, rx:10 ry:5
Main. After swap, x:10, y:5
Anaylsis: Two variables are declared on line 10 and their values are printed on line 12. On
line 13, the function swap() is called, but note that x and y, not their addresses, are passed.
The calling function simply passes the variables.
When swap() is called, program execution jumps to line 18, where the variables are
identified as references. Their values are printed on line 22, but note that no special
operators are required. These are aliases for the original values, and can be used as such.
- 15 -
On lines 24-26, the values are swapped, and then they're printed on line 28. Program
execution jumps back to the calling function, and on line 14, the values are printed in
main(). Because the parameters to swap() are declared to be references, the values from main()
are passed by reference, and thus are changed in main() as well. References provide the
convenience and ease of use of normal variables.
5.5. Return Values
Functions return a value or return void. Void is a signal to the compiler that no value will be
returned. To return a value from a function, write the keyword return followed by the value
you want to return. The value might itself be an expression that returns a value. For
example:
return 5;
return (x > 5);
return (MyFunction());
These are all legal return statements, assuming that the function MyFunction() itself returns a
value. The value in the second statement, return (x > 5), will be zero if x is not greater than 5,
or it will be 1. What is returned is the value of the expression, 0 (false) or 1 (true), not the
value of x.
When the return keyword is encountered, the expression following return is returned as the
value of the function. Program execution returns immediately to the calling function, and
any statements following the return are not executed. It is legal to have more than one return
statement in a single function. Listing 5.6 illustrates this idea.
Listing 5.6. A demonstration of multiple return statements.
1: // Listing 5.6 - demonstrates multiple return
2: // statements
3:
4: #include <iostream.h>
5:
6: int Doubler(int AmountToDouble);
7:
8: int main()
9: {
10:
11: int result = 0;
12: int input;
13:
14: cout << "Enter a number between 0 and 10,000 to double: ";
- 16 -
15: cin >> input;
16:
17: cout << "\nBefore doubler is called... ";
18: cout<< "\ninput: " << input << " doubled: " <<result<<"\n";
19:
20: result = Doubler(input);
21:
22: cout << "\nBack from Doubler...\n";
23: cout<< "\ninput: " << input << " doubled: " <<result<<"\n";
24:
25:
26: return 0;
27: }
28:
29: int Doubler(int original)
30: {
31: if (original <= 10000)
32: return original * 2;
33: else
34: return -1;
35: cout << "You can't get here!\n";
36: }
Output: Enter a number between 0 and 10,000 to double: 9000
Before doubler is called...
input: 9000 doubled: 0
Back from doubler...
input: 9000 doubled: 18000
Enter a number between 0 and 10,000 to double: 11000
Before doubler is called...
input: 11000 doubled: 0
Back from doubler...
input: 11000 doubled: -1
Analysis: A number is requested on lines 14 and 15, and printed on line 18, along with the
local variable result. The function Doubler() is called on line 20, and the input value is
passed as a parameter. The result will be assigned to the local variable result, and the values
will be reprinted on lines 22 and 23. On line 31, in the function Doubler(), the parameter is
tested to see whether it is greater than 10,000. If it is not, the function returns twice the
original number. If it is greater than 10,000, the function returns -1 as an error value. The
- 17 -
statement on line 35 is never reached, because whether or not the value is greater than
10,000, the function returns before it gets to line 35, on either line 32 or line 34. A good
compiler will warn that this statement cannot be executed, and a good programmer will
take it out!
5.6. Default Parameters
For every parameter you declare in a function prototype and definition, the calling function
must pass in a value. The value passed in must be of the declared type. Thus, if you have a
function declared as
long myFunction(int);
the function must in fact take an integer variable. If the function definition differs, or if you
fail to pass in an integer, you will get a compiler error. The one exception to this rule is if
the function prototype declares a default value for the parameter. A default value is a value
to use if none is supplied. The preceding declaration could be rewritten as
long myFunction (int x = 50);
This prototype says, "myFunction() returns a long and takes an integer parameter. If an
argument is not supplied, use the default value of 50." Because parameter names are not
required in function prototypes, this declaration could have been written as
long myFunction (int = 50);
The function definition is not changed by declaring a default parameter. The function
definition header for this function would be
long myFunction (int x)
If the calling function did not include a parameter, the compiler would fill x with the
default value of 50. The name of the default parameter in the prototype need not be the
same as the name in the function header; the default value is assigned by position, not
name. Any or all of the function's parameters can be assigned default values. The one
restriction is this: If any of the parameters does not have a default value, no previous
parameter may have a default value.
If the function prototype looks like
long myFunction (int Param1, int Param2, int Param3);
you can assign a default value to Param2 only if you have assigned a default value to Param3.
You can assign a default value to Param1 only if you've assigned default values to both
Param2 and Param3. Listing 5.7 demonstrates the use of default values.
- 18 -
Listing 5.7. A demonstration of default parameter values.
1: // Listing 5.7 - demonstrates use
2: // of default parameter values
3:
4: #include <iostream.h>
5:
6: int AreaCube(int length, int width = 25, int height = 1);
7:
8: int main()
9: {
10: int length = 100;
11: int width = 50;
12: int height = 2;
13: int area;
14:
15: area = AreaCube(length, width, height);
16: cout << "First area equals: " << area << "\n";
17:
18: area = AreaCube(length, width);
19: cout << "Second time area equals: " << area << "\n";
20:
21: area = AreaCube(length);
22: cout << "Third time area equals: " << area << "\n";
23: return 0;
24: }
25:
26: AreaCube(int length, int width, int height)
27: {
28:
29: return (length * width * height);
30: }
Output: First area equals: 10000
Second time area equals: 5000
Third time area equals: 2500
Analysis: On line 6, the AreaCube() prototype specifies that the AreaCube() function takes
three integer parameters. The last two have default values. This function computes the area
of the cube whose dimensions are passed in. If no width is passed in, a width of 25 is used
and a height of 1 is used. If the width but not the height is passed in, a height of 1 is used. It is
not possible to pass in the height without passing in a width. On lines 10-12, the dimensions
length, height, and width are initialized, and they are passed to the AreaCube() function on line
15. The values are computed, and the result is printed on line 16. Execution returns to line
- 19 -
18, where AreaCube() is called again, but with no value for height. The default value is used,
and again the dimensions are computed and printed. Execution returns to line 21, and this
time neither the width nor the height is passed in. Execution branches for a third time to line
27. The default values are used. The area is computed and then printed.
DO remember that function parameters act as local variables within the function. DON'T
try to create a default value for a first parameter if there is no default value for the second.
DON'T forget that arguments passed by value can not affect the variables in the calling
function. DON'T forget that changes to a global variable in one function change that
variable for all functions.
5.7. Inline Functions
When you define a function, normally the compiler creates just one set of instructions in
memory. When you call the function, execution of the program jumps to those instructions,
and when the function returns, execution jumps back to the next line in the calling function.
If you call the function 10 times, your program jumps to the same set of instructions each
time. This means there is only one copy of the function, not 10.
There is some performance overhead in jumping in and out of functions. It turns out that
some functions are very small, just a line or two of code, and some efficiency can be gained
if the program can avoid making these jumps just to execute one or two instructions. When
programmers speak of efficiency, they usually mean speed: the program runs faster if the
function call can be avoided.
If a function is declared with the keyword inline, the compiler does not create a real
function: it copies the code from the inline function directly into the calling function. No
jump is made; it is just as if you had written the statements of the function right into the
calling function.
Syntax
inline return_type function_name ( [type parameterName1], [type
parameterName2]...)
{
statements;
}
- 20 -
Note that inline functions can bring a heavy cost. If the function is called 10 times, the
inline code is copied into the calling functions each of those 10 times. The tiny
improvement in speed you might achieve is more than swamped by the increase in size of
the executable program. What's the rule of thumb? If you have a small function, one or two
statements, it is a candidate for inline. When in doubt, though, leave it out. Listing 5.9
demonstrates an inline function.
Listing 5.9. Demonstrates an inline function.
1: // Listing 5.9 - demonstrates inline functions
2:
3: #include <iostream.h>
4:
5: inline int Double(int);
6:
7: int main()
8: {
9: int target;
10:
11: cout << "Enter a number to work with: ";
12: cin >> target;
13: cout << "\n";
14:
15: target = Double(target);
16: cout << "Target: " << target << endl;
17:
18: target = Double(target);
19: cout << "Target: " << target << endl;
20:
21:
22: target = Double(target);
23: cout << "Target: " << target << endl;
24: return 0;
25: }
26:
27: int Double(int target)
28: {
29: return 2*target;
30: }
Output: Enter a number to work with: 20
Target: 40
Target: 80
Target: 160
- 21 -
Analysis: On line 5, Double() is declared to be an inline function taking an int parameter and
returning an int. The declaration is just like any other prototype except that the keyword
inline is prepended just before the return value. This compiles into code that is the same as if
you had written the following:
target = 2 * target;
everywhere you entered
target = Double(target);
By the time your program executes, the instructions are already in place, compiled into the
OBJ file. This saves a jump in the execution of the code, at the cost of a larger program.
5.8. Recursive Functions
Most mathematical functions that we are familiar with are described by a simple formula.
For example, we can convert temperatures from Fahrenheit to Celsius by applying the
formula
C = 5 (F-32) / 9
Given this formula, it is trivial to write a C++ function; with declarations and braces
removed, the one line formula above translates into one line C++ statement. Mathematical
functions are sometimes defined in a less standard form. For example we can define a
function f, valid on non negative integers, that satisfies:
f(0) = 0
f(x) = 2, f(x-1) +x2 for x>0, x Z
From this definition,
f(3) = 2f(2) + 32
= 2f(2) + 9 now we should compute f(2) first.
f(2) = 2f(1) + 22
= 2f(1) + 4 now we should compute f(1) first.
f(1) = 2f(0) + 12
= 2f(0) + 1 now we should compute f(0) first.
f(0) = 0
f(1) = 2 (0) + 1
=0+1 =1
- 22 -
f(2) = 2 (1) + 4
=2+4 =6
=> f(3) = 2 (6) + 9
= 12 + 9 = 21
A function that is defined in terms of it self is called recursive. f(0) in the above function is
the value which is the function directly known with out resorting to recursion. Such case is
known as the base case. A recursive function without a base case is meaningless. When
writing recursive routines, it is crucial to keep in mind the four basic rules of recursion.
These are:
1. Base case: you must always have some Base case, which can be solved with out
recursion.
2. Making progress: for the cases that are to be solved recursively, the recursive call
must always be to a case that makes progress to ward a base case.
3. Design rule: Assume that all recursive calls work without fail. In order to find f(n)
recursively you can assume f(n-k) will work (k 1)
4. Compound interest rule: never duplicate work by solving the same instance of a
problem in separate recursive rule.
Some examples of recursive problems
a) The factorial function f(n) = n!
f(n) can be defined as:
f(N) = N x (N-1) x (N-2) x (N-3) x ……..x 1 this is iterative defn
Nx f(N - 1) for N 0
f (N ) this is called recursion.
1 for N 0
This function can be implemented both iteratively or recursively as
//iterative implementation
int factorial (int N)
{
int fact =1;
for(int i=1;i<=n; i++)
{
fact *=I;
}
return fact;
}
- 23 -
//recursive implementation
int factorial (int n)
{
if (n == 0) return 1;
return n*factorial(n-1)
}
b) Fibonacci progress :
is a sequence in which the ith term equals f(i) defined as
f(i - 1) f(i - 2) for i 2
f(i)
1 for i 1 and 2
Some of the elements in the sequence are
1, 1, 2, 3, 5, 8, 13, 21, 34 …….
The function can be implemented both iteratively and recursively. The recursive
implementation is shown bellow.
int i_th_fibonacci_element (int n)
{
if (n == 1|| n==2) return 1;
else if n > 2
{
int val1 = i_th_fibonacci_element ( n-1);
int val2 = i_th_fibonacci_element ( n-2);
return (val1 + val2);
}
}
Some function cannot be implemented easily without recursion. Other functions,
which do admit both iterative and recursive solution are easier to understand in their
recursive form. So recursion is an essential tool for computer scientist.
However recursion is very expensive in terms of time complexity of a program so
that it is not generally advisable to use recursion to problems that is simple to
implement in iterative way. This is because of the time requirement to manipulate
stack at each function call.
Some more recursive problems and their implementation
C) The Euclidean algorithm
A positive integer d is a divisor of a larger integer n if n is some multiple of d; i.e. n
= k. d for some positive integer k
- 24 -
A positive integer is a common divisor of two larger integer n and m if it is a
divisor of both m & n. d is said to be greatest common divisor of two large integers
m& n, if it is a common divisor of m & n, and no other common divisor of m & n is
greater than d.
This can be denoted as d = gcd (m, n).
It might seem that finding gcd of large integers could be very tedious and time
consuming. But fortunately the ancient Greek scientist Euclid discovered a clever
recursive algorithm as follow.
N if N M
gcd (M, N) gcd (N, M) if M N
gcd (N - M, M) if M N
int gcd (int m, int n)
{
if (m == n) return n;
else if (m > n) return gcd (n, m);
else return gcd (n-m, m);
}
D) Binary Search
Binary search for an element x in sorted array A[] looks at the middle of the array. If that is
not x (the middle element of A ≠ x) then it continues the search in the half that potential
could hold x. Assuming the array is sorted in ascending order, the potential half array is the
left array if the middle is larger than x other wise the right the potential list to hold x. The
process contributes this recursive step until either x is found or the sub array is empty.
What should be returned in the index of the array that hold x if it founds or -1.
Implementation
int find (float*a, int start, int stop, float x )
{
if (start > stop) return -1;
int mid = (start + stop) / 2;
if (x == a[mid] return mid;
if (x < a[mid] return find (a, start, mid -1, x );
else return find (a, mid +1, start , x );
}
The call from other program to this function may look like
- 25 -
index = find(list, 0, size-1);
e) The tower of Hanoi
A classic example to demonstrate the power of recursion in problem solving is the tower of
Hanoi puzzle. In this puzzle we are given three pegs labeled A, B and C. Initially a tower
of N disk of different sizes is stacked on peg A in order of decreasing size. That is, the
largest disk is on the bottom, and the smallest disk is on the top of the tower as shown
bellow
A B C
The objective is to transfer the entire disks from peg A to one of the other pegs, say C
moving only one disk at one time, never moving the larger disk on the top of smaller disk.
The solution follows a simple procedure.
if N = 1 then just move it to destination peg. Otherwise
1- Reduce the problem that say move the top N-1 disk from peg A to B using C .
2- Move the Nth disk (which is the largest) from peg A to C.
3- Now move the N-1 disks from B to C using A. And this will tell us what to do for
any N.
The implementation is as follows:
#include <iostream.h>
#include <conio.h>
void Form(int N, char pegA ,char pegB , char pegC )
{
if (N==1)
cout<<"move top disk on peg "<<pegA<<" to peg "<<pegC<<endl;
else
{
Form(N-1,pegA,pegC,pegB);
cout<<"move top disk on peg "<<pegA<<" to peg "<<pegC<<endl;
Form(N-1,pegB,pegA,pegC);
}
}
- 26 -
int main ()
{
char F;
int N;
cout<<"How many disks ?=====>";cin>>N;
Form(N,'A','B','C') ;
getch () ;
return 0;
}
- 27 -
Chapter Six
6. Structures and File Management
An array is a data structure which holds multiple numbers of objects having the same basic property (data
type) in a contiguous memory slots. Programmer can also reserve contiguous memory space for aggregates of
elements of arbitrary data types each. A data type which is created to reserve such type of memory space is
called user defined data type.
User defined data types can be equally used like predefined data types to declare variable identifiers. For
example we can define simple, array, or pointer variable identifier from this user defined data type.
6.1.1. Structure
The term structure in C++ means both a user-defined type which is a grouping of variables as well as meaning a
variable based on a user-defined structure type. For the purpose of distinction we will refer to the user-defined
type side as structure definition and the variable side as structure variable.
A structure definition is a user-defined variable type which is a grouping of one or more variables. The type
itself has a name, just like ‘int’, ‘double’, or ‘char’ but it is defined by the user and follows the normal rules of
identifiers. Once the type has been defined through the C++ ‘struct’ keyword, you can create variables from it
just like you would any other type.
Since a structure definition is a grouping of several types: it is a group of one or more variables. These are
known as elements or member variables as they are members of the structure definition they are part of.
Following through with our hinted example, a structure definition could be a ‘date’ which might be made up of
three ‘int’ member variables: ‘day’, ‘month’, and ‘year’.
Before creating a structure variable you must create a structure definition. This is a blue print for the compiler
that is used each time you create a structure variable of this type. The structure definition is a listing of all
member variables with their types and names.
When you create a structure variable based on a structure definition, all of the member variables names are
retained. The only name you have to give is that of the new structure variable. The element names within that
variable will be the same as in the structure type. If you create two structure variables from ‘date’, both will
have all three member variables with the same name in both: ‘day’, ‘month’, and ‘year’.
Member variables are distinguished by the structure variable they are part of. You wouldn’t simply be able to
use ‘day’ by itself; instead you’d have to refer to both the structure variable’s name (that you gave when you
created it) as well as ‘day’.
struct structname
{
datatype1 variable1;
datatype2 variable2;
};
Discussion: Writing a structure definition begins with the word ‘struct’ followed by the type-to-be, and ended
with a structure block that is ultimately terminated with a semicolon. Do not forget this
trailing semi-colon when defining a structure!
The name of a structure definition is known as the structure tag. This will be the name of the type that you
create, like ‘int’ or ‘float’. It is the type that you will specify when creating a structure variable. This structure
block is remarkably similar to a statement block since it starts and ends with curly braces. But don’t forget that
it ultimately ends with a semi-colon. Within the structure block you declare all the member variables you want
associated with that type. Declare them as you would normal variables, but do not try to initialize them. This
is simply a data blue print, it is not logic or instructions and the compiler does not execute it.
The data members (synonym for member variables) of a structure won’t actually be created until a variable based
on the structure is created. Technically an ‘int’ is just this as well. It’s a description of a storage unit. That
storage unit isn’t reserved until you create a variable with it.
struct student
{
int id;
char name[15];
};
Example: The follow defines a structure called ‘date’ which contains three ‘int’ member variables: ‘day’, ‘month’,
and ‘year’:
struct date {
int day;
int month;
int year;
};
struct date
{
int day, month, year;
};
Note: You cannot initialize member variables in a structure definition. The following is wrong and will
not compile:
struct date{
int day = 24, month = 10, year = 2001;
};
A structure definition has the same type of scoping as a variable. If you define a structure in a function, you will
only be able to use it there. If you define it in a nested statement block, you will only be able to use it inside
there and any statement blocks nested within it. But the most common place is defining it globally, as in
outside of any functions or blocks. Typically, you’ll want to be able to create variables of the defined structure
anywhere in your program, so the definition will go at the top. The following is an empty program that defines a
‘date’ structure globally:
#include <iostream.h>
struct date{
int day, month, year;
};
int main(){
return 0;
}
student std1;
date birthday;
The above declaration statements would create a variable called ‘birthday’ whose type is the structure ‘date’.
The variable contains three parts: ‘day’, ‘month’, and ‘year’. What this actually does is set aside a whole block of
memory that can contain all of the member variables. Each member variable then occupies a chunk of it for
their individual storage units. The member variables are not actually created one at a time.
Storage for member variables exist at some offset from the beginning of the glob of memory reserved by the
entire structure. For example, in our ‘date’ structure variable the first member variable is ‘day’ so it exists at
offset 0. The next member variable, ‘month’ in this case, will exist at the next available offset. If ‘day’ uses 4
bytes (32 bits), then ‘month’ will be at offset 4:
int i;
student std1;
The above statements are similar in nature because both declare a variable of a given type. The former is a built
in type, which is integer while the later declares a variable type of user-defined type. std1 will have the
characteristics of representing id and name of a student.
6.1.3.1. Initializing Structure Variables
You cannot initialize member variables in the structure definition. This is because that definition is only a map,
or plan, of what a variable based on this type will be made of. You can, however, initialize the member variables
of a structure variable. That is, when you create a variable based on your structure definition you can pass each
member variable an initializer.
To initialize a structure variable’s members, you follow the original declaration with the assignment operator
(=). Next you define an initialization block which is a list of initializers separated by commas and enclosed in
curly braces. Lastly, you end it with a semi-colon. These values are assigned to member variables in the order
that they occur. Let’s look at an example:
If you try to assign more values than are member variables, you will get a compiler error. However, you can
assign fewer values than there are member variables. If there are no more values to be assigned, the assignment
will simply end. For example, if we had omitted the last value, ‘1979’, then no value would be assigned to ‘year’.
It is possible to use any expression that you normally would. But remember that the expression must result in a
value. Here is an example of initialization with things other than literals:
int x;
x = 0;
This doesn’t:
date nco_birthday;
nco_birthday = { 19, 8, 1979 };
Assigning values to multiple members of a structure variable is only possible when that variable is first created.
Once a structure variable has been declared, you must access each member individually.
structure.member
Example: to reading and displaying values to and from structure s1.
cin>>s1.id; //storing to id item of s1
cin>>s1.name; //storing a name to s1
cout<<s1.id; //displaying the content of id of s1.
cout<<s1.name; //displaying name
Example:-a program that creates student struct and uses it to store student information.
#include<iostream.h>
#include<conio.h>
struct student
{
int id;
char name[15];
};
void main()
{
//creating three student variables
student s1,s2;
cout<<"\n Enter Student Id";
cin>>s1.id;
cout<<"\nEnter Name";
cin>>s1.name;
cout<<"\n Enter Student Id";
cin>>s2.id;
cout<<"\nEnter Name";
cin>>s2.name;
cout<<"\nStudents Information";
cout<<"\n Student id\t Student Name";
cout<<endl<<s1.id<<"\t"<<s1.name;
cout<<endl<<s2.id<<"\t"<<s2.name;
getch();
}
The above program shows you how to capture data in student variables.
Example 2: This program demonstrates using member variables for user input, output, and mathematical
operations.
#include <iostream.h>
struct date
{
int day, month, year;
};
int main()
{
date birth;
cout << “Enter your birth date!” << endl;
cout << “Year: “;
cin >> birth.year;
cout << “Month: “;
cin >> birth.month;
cout << “Day: “;
cin >> birth.day;
cout << “You entered “ << birth.month << “/”<< birth.day
<< “/” << birth.year << endl;
cout << “You were born in the “<< ((birth.year / 100) + 1)
<< “th Century!”<< endl;
return 0;
}
struct tag
{
member(s);
} variable;
The structure variables created in this way will have the same scope as their structure definition. This is a nice
thing to use when you want to group some variables in one place in your program without it affecting other
things. You can create a structure definition as well as variables from it in that one local place and not have to
ever use it again.
Example: A ‘point’ variable right after the ‘pointtag’ structure is defined:
struct pointtag{
int x, y;
} point;
In this, ‘point’ is a variable just as if we had declared it separately. In fact, this statement is identical to:
struct pointtag{
int x, y;
};
pointtag point;
Rather than defining a structure under a name and then creating variables which refer to the named definition,
the definition becomes part of the variable declaration. It is even possible to omit the structure tag which may
make more sense in this situation:
struct{
int x, y;
} point;
The above creates a structure variable called ‘point’ which has two member variables. Because the structure
definition is not named, it cannot be used elsewhere to create variables of the same structure type. However,
like in any variable declaration, you can create multiple variables of the same type by separating them with
commas:
struct{
int x, y;
} point1, point2;
Now we have two structure variables of the same nameless structure type. Even more fun we can initialize
these structure variables as we normally would:
struct{
int x, y;
} point1 = { 0, 0}, point2 = {0, 0};
This creates the two structure variables, ‘point1’ and ‘point2’, and initializes both members, ‘x’ and ‘y’, on each
to zero (0). You don’t need to make a name-less definition to do this. You could still have a structure tag and
initialize the variables created after the definition.
Note: Not only can you write name-less structures, but you can write them without declaring any variables.
This will cause a warning on smarter compilers, but is perfectly legal:
struct
{
int x, y;
};
The above structure definition can’t be used to create any variables because it has no name. Likewise, no
variables are declared after it either. So this is a nice way to fill up your programs with useless source code. But
why stop there? You could make an empty, name-less definition:
struct { };
void main()
{
clrscr();
//creating 5 student using an array
student s[5];
int i;
for(i=0; i < 5; i ++)
{
cout<<"\n Enter Student Id";
cin>>s[i].id;
cout<<"\nEnter Name";
cin>>s[i].name;
}
cout<<"\n Displaying student Info";
cout<<"\nStudent Id \t Student Name";
cout<<"\n============================";
getch(); }
Memory map of the above struct declaration.
0 id 1
name Tameru
1 id 2
name Hassen
2 id 3
name Selamawit
3 id 4
name Asia
4 id 5
name Micheal
If you use s[0].id you will be referring to 1, and s[0].name will refer to Tameru.
The following program declares and uses a book struct. Also swaps the content of two book struct variables.
#include<iostream.h>
#include<conio.h>
struct Book {
int id;
char title[15];
};
void main(){
//creating three Book variables
Book b1,b2,temp;
cout<<"\n Enter Book Id";
cin>>b1.id;
cout<<"\nEnter Title";
cin>>b1.title;
cout<<"\n Enter Book Id";
cin>>b2.id;
cout<<"\nEnter Title";
cin>>b2.title;
cout<<"\n Book Information";
cout<<"\n Before Changing Contents";
cout<<"\n Book id\t Title";
cout<<"\n=========================";
cout<<endl<<b1.id<<"\t\t\t"<<b1.title;
cout<<endl<<b2.id<<"\t\t\t"<<b2.title;
//swapping content
temp=b1;
b1=b2;
b2=temp;
cout<<"\nAfter swapping contents";
cout<<"\n Book Information";
cout<<"\n Book id\t Title";
cout<<"\n=========================";
cout<<endl<<b1.id<<"\t"<<b1.title;
cout<<endl<<b2.id<<"\t"<<b2.title;
getch();
}
To be able to define a structure you only must know the types and the names of the member variables declared
inside. With the above we declare the structures ‘date’ and ‘time’ but do not define them until later. This
simply acknowledges that they exist and they can therefore be used within ‘moment’.
What if ‘date’ and ‘time’ hadn’t defined? It would still be legal, but then I would not be able to use ‘moment’ at
all. Why? Since ‘date’ or ‘time’ have not been defined, the compiler does not know how big they are supposed
to be or what kind of data they contain. You couldn’t then create a variable based on ‘moment’ because the
compiler doesn’t know how big of a memory block to allocate. Likewise if you try to use a structure that has
been declared before it has been defined, you will encounter the same problem.
struct moment
{
struct date
{
int day, month, year;
} theDate;
struct time
{
int second, minute, hour;
} theTime;
};
The drawback of the above is that the ‘date’ and ‘time’ definitions cannot be used elsewhere without also
referring to the parent structure. If the ‘date’ definition isn’t going to be used elsewhere anyway, the structure
tag can simply be omitted. Thus we could remove the ‘date’ and ‘time’ structure type identifiers and it would
work fine. You can write any number of variables in a structure definition and a valid variable declaration
statement (minus initialization of course) is valid inside a structure definition.
Let’s say we want to be able to use ‘date’ elsewhere, but not ‘time’. The following program demonstrates how
the structure definitions would be written as well as uses the defined structure types in an extended “birth
date” sample:
#include <iostream.h>
struct date { int day, month, year; } ;
struct moment
{
date theDate;
struct
{
int sec, min, hour;
} theTime;
};
int main()
{
moment birth;
cout << “Enter your birth moment!” << endl;
cout << “Year: “;
cin >> birth.theDate.year;
cout << “Month: “;
cin >> birth.theDate.month;
cout << “Day: “;
cin >> birth.theDate.day;
cout << “Hour (military): “;
cin >> birth.theTime.hour;
cout << “Minute: “;
cin >> birth.theTime.min;
cout << “Second: “;
cin >> birth.theTime.sec;
return 0;
}
Any number of structure definitions can be nested; you can get extremely complex with structures, which is
why they are sometimes known as complex types.
The following creates a structure variable based on ‘date’ and a reference to it:
date birth;
date &mybirth = birth;
Both of these, ‘birth’ and ‘mybirth’, would have access to the same member variables and their values. Thus they
can be used interchangeably:
birth.year = 1981;
mybirth.year -= 2;
Can you guess what the value of ‘birth.year’ would be from the above? It would be ‘1979’. The reference
‘mybirth’ is just an alias to ‘birth’ and its group of member variables. Remember that a reference is not a real
variable (it has no value), but simply a nickname for another.
Utilizing pointers with structures, unfortunately, adds a previously unseen complexity. A pointer to a variable
cannot be used to modify the variable’s value until it has been dereferenced. This case is no different from
structures. But it affects the way you access the structure variable’s members. Recall that a pointer simply
contains a memory address. The pointer knows nothing of what this memory address is used for, which is why
you have to dereference a pointer to a specific type. Thus, you cannot access a structure variable’s members
until you dereference a pointer to the structure type:
date birth;
date *p = &birth;
(*p).year = 1979;
The pointer ‘p’ above, had to be dereferenced before the member variable ‘year’ could be accessed through it. It
was surrounded in parenthesis because the indirection operator (asterisk ‘*’) has a lower precedence than the
member operator. Thus the following would not work:
*p.year = 1979;
This would be seen as “get the value pointed to by ‘p.year’” and ‘p.year’ is an invalid identifier; hence the
parenthesis around the indirection operation. This method of accessing a structure variable’s members is
cumbersome and requires two operations simply to get at the member variable: indirection and then member.
For this purpose, there is an operator specifically for accessing a structure variable’s members through a
pointer. This is a member operator known specifically as a pointer-to-member operator or a dash followed by a
greater than sign ‘->’ (also known as an “arrow”):
p->year = 1979;
This operator only works on pointers to structure variables. You cannot use it on normal structure variables for
members. The following would not work:
birth->year = 1979;
The left operand must be a pointer to a variable with members, and the right operand must be the name of a
member variable within that.
7.1 File Management
File handling is an important part of all programs. Most of the applications will have their own features to save
some data to the local disk and read data from the disk again. Files which are on the secondary storage device
are called physical files. In order to process file through program, logical file must be created on the RAM. This
logical file is nothing but an object having file data type. As an object there should be a variable identifier that
points to it. This variable is called file variable and sometimes also called file handler. C++ File I/O classes
simplify such file read/write operations for the programmer by providing easier to use classes.
6.1.1.1. Streams
The C++ file system is designed to work with a wide variety of devices, including terminals, disk drives, and
tape drives. Even though each device is very different, the C++ file system transforms each into a logical device
called stream. There are two types of streams: text and binary.
a. Text Streams
A text stream is a sequence of characters. In a text stream, certain character translations may occur as required
by the host environment. For example a new line may be converted to a carriage return/linefeed pair. There may
not be a one-to-one relationship between the characters that are written (or read) and those on the external
device. Because of possible transformations, the number of characters written (or read) may not be the same as
those on the external device.
b. Binary streams
A binary stream is a sequence of bytes with a one-to-one correspondence to those in the external device i.e., no
character translations occur. The number of bytes written (or read) is the same as the number on the external
device. However, an implementation-defined number of null bytes may be appended to a binary stream. These
null bytes might be used to pad the information so that it fills a sector on a disk, for example.
6.1.1.2. Files
In C++, a file can be anything from a disk file to a terminal or printer. You associate a stream with a specific file
by performing an open operation. Once a file is open, information can be exchanged between it and a program.
All streams are the same but all files are not. If the file can support position requests, opening that file also
initializes the file position indicator to the start of the file. As each character is read from or written to the file, the
position indicator is incremented. You disassociate a file from a specific stream with a close operation. If you close
a file opened for output, then contents, if any, of its associated stream are written to the external device. -- this
process is referred to as flushing the stream. All files are closed automatically when the program terminates
normally. Files are not closed when a program terminates abnormally. Each stream that is associated with a file
has a file control structure of type FILE. This structure FILE is defined in the header stdio.h.
The File Pointer
A file pointer is a pointer to information that defines various things about the file, including its name, status,
and the current t position of the file. In essence, the file pointer identifies a specific disk file and is used by the
associated stream to direct the operation of the I/O functions. A file pointer is a pointer variable of type FILE.
FILE * fp;
Where:
Filename is the name of the file.
The value of the mode determines how the file is opened. It must be one (or more) of these values:
Mode Description
ios::ate Open a file for output and move to the end of the file (normally used to append data to a
file). Data can be written anywhere in the file.
You can combine two or more of these values by using them together.
ofstream out ;
out.open ( "test", ios::out); // correct statement
ofstream out1 ;
out.open ( " test"); // the default value of mode is ios::out –
// correct statment
To open a stream for input and output, you must specify both the ios::in and the ios::out mode values. (Noe
default value for mode is supplied in this case.)
fstream myStream;
myStream.open ( "test", ios::in | ios::out );
If open ( ) fails, myStrream will be zero
if (myStream){
cout << "Cannot open a file.\n";
// handle error
}
Each one of the open() member functions of the classes ofstream, ifstream and fstream has a default mode that
is used if the file is opened without a second argument:
class default mode parameter
ofstream ios::out
ifstream ios::in
fstream ios::in | ios::out
For ifstream and ofstream classes, ios::in and ios::out are automatically and respectively assumed, even if a mode
that does not include them is passed as second argument to the open() member function. The default value is
only applied if the function is called without specifying any value for the mode parameter. If the function is
called with any value in that parameter the default mode is overridden, not combined.
File streams opened in binary mode perform input and output operations independently of any format
considerations. Non-binary files are known as text files, and some translations may occur due to formatting of
some special characters (like newline and carriage return characters).
Since the first task that is performed on a file stream object is generally to open a file, these three classes include
a constructor that automatically calls the open() member function and has the exact same parameters as this
member. Therefore, we could also have declared the previous myfile object and conducted the same opening
operation in our previous example by writing:
int main () {
ofstream myfile ("example.txt");
if (myfile.is_open())
{
myfile << "This is a line.\n";
myfile << "This is another line.\n";
myfile.close();
}
else cout << "Unable to open file";
return 0;
}
Example 3: reading a text file
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main () {
string line;
ifstream myfile ("example.txt");
if (myfile.is_open())
{
while (! myfile.eof() )
{
getline (myfile,line);
cout << line << endl;
}
myfile.close();
}
return 0;
}
This last example reads a text file and prints out its content on the screen. Notice how we have used a new
member function, called eof() that returns true in the case that the end of the file has been reached. We have
created a while loop that finishes when indeed myfile.eof() becomes true (i.e., the end of the file has been
reached).
Checking state flags
In addition to eof(), which checks if the end of file has been reached, other member functions exist to check the
state of a stream (all of them return a bool value):
Function Description
bad() Returns true if a reading or writing operation fails. For example in the case that we
try to write to a file that is not open for writing or if the device where we try to
write has no space left.
fail() Returns true in the same cases as bad(), but also in the case that a format error
happens, like when an alphabetical character is extracted when we are trying to
read an integer number.
eof() Returns true if a file open for reading has reached the end.
good() It is the most generic state flag: it returns false in the same cases in which calling any
of the previous functions would return true.
In order to reset the state flags checked by any of these member functions we have just seen we can use the
member function clear(), which takes no parameters.
get and put stream pointers
All I/O streams objects have, at least, one internal stream pointer:
ifstream, like istream, has a pointer known as the get pointer that points to the element to be read in
the next input operation.
ofstream, like ostream, has a pointer known as the put pointer that points to the location where the
next element has to be written.
Finally, fstream, inherits both, the get and the put pointers, from iostream (which is itself derived from
both istream and ostream).
These internal stream pointers that point to the reading or writing locations within a stream can be
manipulated using the following member functions:
tellg() and tellp()
These two member functions have no parameters and return a value of the member type pos_type, which is an
integer data type representing the current position of the get stream pointer (in the case of tellg) or the put
stream pointer (in the case of tellp).
seekg() and seekp()
These functions allow us to change the position of the get and put stream pointers. Both functions are
overloaded with two different prototypes. The first prototype is:
seekg ( position );
seekp ( position );
Using this prototype the stream pointer is changed to the absolute position position (counting from the
beginning of the file). The type for this parameter is the same as the one returned by functions tellg and tellp:
the member type pos_type, which is an integer value.
The other prototype for these functions is:
char in;
ifstream in ( "test", ios::in | ios::binary);
if (!in){
cout <<"Cannot open file";
return 1;
}
while (in) //inn will be 0 when eof is reached
{ in.get ( ch );
cout << ch;
}
When the end-of-file is reached, the stream associated with the file becomes zero.
ofstream out ( "chars", io::out | ios::binary);
for (int i= 0; i < 256; i++)
out.put ( (char ) i ) ; //write all characters to disk
out.close ( );
int gcount ( );
This get ( ) method reads characters into the array pointed to by the buf until either num characters have been
read, or the character specified by delim has been encountered. The array pointed to by buf will be null
terminated by get ( ). If the delimiter character is encountered in the input stream, it is not extracted. Instead, it
remains in the stream until the next input operation.
a. int get ( )
It returns the next character from the stream. It returns EOF if the end of file is encountered.
b. getline ( )
istream & getline ( char *buf, int num, char delim ='\n');
This method is virtually identical to the get ( buf, num, delim) version of get ( ). The difference is getline ( ) reads
and removes the delimiter from the input stream.
#include <iostream>
#include <fstream>
using namespace std;
ifstream::pos_type size;
char * memblock;
int main () {
ifstream file ("example.txt", ios::in|ios::binary|ios::ate);
if (file.is_open())
{
size = file.tellg();
memblock = new char [size];
file.seekg (0, ios::beg);
file.read (memblock, size);
file.close();
delete[] memblock;
}
else cout << "Unable to open file";
return 0;
}
In this example the entire file is read and stored in a memory block. Let's examine how this is done:
First, the file is open with the ios::ate flag, which means that the get pointer will be positioned at the end of the
file. This way, when we call to member tellg(), we will directly obtain the size of the file. Notice the type we
have used to declare variable size:
ifstream::pos_type size;
ifstream::pos_type is a specific type used for buffer and file positioning and is the type returned by file.tellg().
This type is defined as an integer type, therefore we can conduct on it the same operations we conduct on any
other integer value, and can safely be converted to another integer type large enough to contain the size of the
file. For a file with a size under 2GB we could use int:
int size;
size = (int) file.tellg();
Once we have obtained the size of the file, we request the allocation of a memory block large enough to hold the
entire file:
Function Description
Detecting EOF It returns nonzero when the end of the file has been reached;
int eof ( ); otherwise it returns zero.
Reading and discarding characters from Reads and discards characters until either num characters have been
the input stream. nignored (1 by default ) or until the charcter specified by delim is
istream & ignore (int num = 1, int delim encounterdd (EOF by default). If the delimiting character is
= EOF); encountered, it is not removed from the input stream.
Obtain the next character in the input One can obtain the next character in the input stream without
stream without removing it from that removing it from that stream by using peek ( ). It returns the next
stream character in the stream or EOF if the end of file is encountered.
int peek ( );
One can return the last character read from a stream to that stream
istream & putback ( char c); using putback ( ).
Forcing data to be physically written to When the output is performed, data is not necessarily immediately
the disk written to the physical device linked to the stream. Instead,
information is stored in an internal buffer until the buffer is full.
ostream & flush ( ); Only then are the contents of that buffer written to disk. However,
you can force the information to be physically written to the disk
before the buffer is full by calling flush ( ).
streampos tellg ( );
streampows tellp ( );
Here, streampos is a type defined in iostream.h that is capable of holding the largest value that either function
can return.
These flags are enumerated inside ios. Also defined in ios is goodbit, which has the value 0. There are two ways
in which you can obtain I/O status information.
int rdstate ( );
rdstate function returns the current status of the error flags encoded into an integer. It returns zero, when no
error has occurred. Otherwise, an error bit is turned on.
Method Description
int bad ( ) Returns true if badbit is set.
int fail ( ) Returns true if failbit is set.
int eof ( ) Returns true if there are no errors.
int good ( ) Otherwise they return false.
Once an error has occurred, it may need to be cleared before your program continues. to do this, use the clear ( )
method.
void clear ( int flags = 0);
If flag = zero (as it is by default), all error flags are cleared (reset to zero). Otherwise, set flags to the flags or
values you want to clear.
if(out-file.fail())
{
cout<<”file open error”;
exit(1);
}
in-out(out-file);
return 0;
}
void in-out(ofstream& file-out)
{
const int LINELEN= 80;
const int NUMLINES = 5;
int count;
char line[LINELEN] ;
cout<<”please enter five lines of text”<<endl;
for(cout=0;cout<NUMLINES;count++)
{
file-out<<line<<endl;
}
return;
}
When the file is closed: before closing a file all buffers that have not yet been flushed are
synchronized and all pending data is written or read to the physical medium.
When the buffer is full: Buffers have a certain size. When the buffer is full it is automatically
synchronized.
Explicitly, with manipulators: When certain manipulators are used on streams, an explicit
synchronization takes place. These manipulators are: flush and endl.
Explicitly, with member function sync(): Calling stream's member function sync(), which takes
no parameters, causes an immediate synchronization. This function returns an int value equal to -1
if the stream has no associated buffer or in case of failure. Otherwise (if the stream buffer was
successfully synchronized) it returns 0.
Exercise
1. Write a program that accept N student record from the keyboard & store the list on a file “D:\\ Test.txt” in
a text file format
2. Write a program that reads students record from the text file “D:|\ Test.txt” and display on the screen.
3. Do Q1 in binary format.
4. Do Q2 in binary format.
Note Student record consists of first name, last name, gender, age and Id.
Solution
Consider the file Header .h which contains the basic preprocessing include files, structure definition and
functions other than the main function.
Header. h // this file is saved at D: and it has the following structure.
#include<iostream.h>
#include<fstream.h>
#include<conio.h>
struct studList
{
char firstName[12];
char lastName[12];
int age;
char gender;
char Id[12];
};
studList getStudent()
{
studList std;
cout<<"Enter student first name ===>";cin>>std.firstName;
cout<<"Enter student last name ===>";cin>>std.lastName;
cout<<"Enter student age ===>";cin>>std.age;
cout<<"Enter student gender ===>";cin>>std.gender;
cout<<"Enter student Id ===>";cin>>std.Id;
return std;
}
void DisplayStudent(studList std)
{
cout<<"Student first name:\t"<<std.firstName<<endl;
cout<<"Student last name :\t"<<std.lastName<<endl;
cout<<"Student age:\t\t"<<std.age<<endl;
cout<<"Student gender:\t\t"<<std.gender<<endl;
cout<<"Student Id:\t\t"<<std.Id<<endl;
}
Solution 1
#include "D:\ header.h"
int main() {
studList std;
fstream outf;
outf.open("d:\\test.txt",ios::app);
if(outf.fail()) {
cout<<"unable to open the file d:\test.txt\n";
return 1;
}
clrscr();
int N;
cout<<"Enter the number of students ===> ";cin>>N;
for(int i = 0; i < N; i++) {
std = getStudent();
clrscr();
outf<<std.firstName<<" "<<std.lastName<<" "<<std.Id<<" " <<std.gender<<" "<<std.age<<endl;
}
getch();
return 0;
}
Solution 2
#include "D:\ header.h"
int main(){
studList std;
fstream inpf;
inpf.open("d:\\test.txt",ios::in);
if(inpf.fail())
{
cout<<"unable to open the file d:\test.txt\n"; return 1;
}
clrscr();
while (!inpf.eof())
{
inpf>>std.firstName>>std.lastName>>std.Id>>std.gender>>std.age;
if(inpf.eof()) break;
DisplayStudent(std);
cout<<"=====================================\n";
getch();
}
inpf.close();
return 0;
}
Solution 3
#include "D:\ header.h"
int main()
{
studList std;
fstream outf;
outf.open("d:\\test2.txt",ios::app|ios::binary);
if(outf.fail())
{
cout<<"unable to open the file d:\test.txt\n"; return 1;
}
clrscr();
int N;
cout<<"Enter the number of students ===> ";cin>>N;
for(int i = 0; i < N; i++)
{
std = getStudent();
clrscr();
outf.write((char *) &std, sizeof(std));
}
getch();
return 0;
}
Solution 4
#include "D:\ header.h"
int main() {
studList std;
fstream inpf;
inpf.open("d:\\test2.txt",ios::in|ios::binary);
if(inpf.fail())
{
cout<<"unable to open the file d:\test.txt\n";
return 1;
}
clrscr();
while (!inpf.eof()){
{
inpf.read((char*)&std, sizeof(std));
//inpf>>std.firstName>>std.lastName>>std.Id>>std.gender>>std.age;
if(inpf.eof()) break;
DisplayStudent(std);
cout<<"======================================\n";
getch();
}
inpf.close();
return 0;
} Annex
streampos tellg ( ) ;
stream pos tellp ( ) ;
istream & seekg ( streampos pos ) ;
ostream * seekp (streampos pos ) ;
int rdstate ( ) ;
inr bad ( ) ;
int eof ( ) ;
int fail ( ) ;
int good ( );
void clear ( int flags = 0 ) ;
Chapter Five
File Management
4.1. File Management
File handling is an important part of all programs. Most of the applications will have their own features to save
some data to the local disk and read data from the disk again. Files which are on the secondary storage device are
called physical files. In order to process file through program, logical file must be created on the RAM. This logical
file is nothing but an object having file data type. As an object there should be a variable identifier that points to it.
This variable is called file variable and some times also called file handler. C++ File I/O classes simplify such file
read/write operations for the programmer by providing easier to use classes.
4.1.1. Streams and Files
The I/O system supplies a consistent interface to the C++ programmer independent of the actual device being
accessed. This provides a level of abstraction between the programmer and the device. This abstraction is called
stream. The actual device is called a file.
4.1.1.1. Streams
The C++ file system is designed to work with a wide variety of devices, including terminals, disk drives, and tape
drives. Even though each device is very different, the C++ file system transforms each into a logical device called
stream. There are two types of streams: text and binary.
a. Text Streams
A text stream is a sequence of characters. In a text stream, certain character translations may occur as required by
the host environment. For example a new line may be converted to a carriage return/linefeed pair. There may not be
a one-to-one relationship between the characters that are written (or read) and those on the external device.
Because of possible transformations, the number of characters written (or read) may not be the same as those on the
external device.
b. Binary streams
A binary stream is a sequence of bytes with a one-to-one correspondence to those in the external device i.e., no
character translations occur. The number of bytes written (or read) is the same as the number on the external
device. However, an implementation-defined number of null bytes may be appended to a binary stream. These null
bytes might be used to pad the information so that it fills a sector on a disk, for example.
4.1.1.2. Files
In C++, a file can be anything from a disk file to a terminal or printer. You associate a stream with a specific file by
performing an open operation. Once a file is open, information can be exchanged between it and a program. All
streams are the same but all files are not. If the file can support position requests, opening that file also initializes the
file position indicator to the start of the file. As each character is read from or written to the file, the position indicator is
incremented. You disassociate a file from a specific stream with a close operation. If you close a file opened for output,
then contents, if any, of its associated stream are written to the external device. -- this process is referred to as
flushing the stream. All files are closed automatically when the program terminates normally. Files are not closed
1
when a program terminates abnormally. Each stream that is associated with a file has a file control structure of type
FILE. This structure FILE is defined in the header stdio.h.
The File Pointer
A file pointer is a pointer to information that defines various things about the file, including its name, status, and
the current t position of the file. In essence, the file pointer identifies a specific disk file and is used by the
associated stream to direct the operation of the I/O functions. A file pointer is a pointer variable of type FILE.
FILE * fp;
4.1.1.3. The standard streams
When ever a program starts execution, three streams are opened automatically.
stdin --- standard input.
stdout -- standard output
stderr -- standard error
Normally, these streams refer to the console. Because the standard streams are file pointers, they can be used by the
ANSI C file system to perform I/O operations on the console.
4.1.1.4. C++ File I/O Classes and Functions
To perform file I/O, the header file fstream.h is requied. fstream.h defines several classes, including ifstream, ofstream,
and fstream. These classes are derived form istream and ostream, repectively. istream and ostream are derived form ios.
Three file I/O classes are used for File Read/Write operations:
a. ifstream - Can be used for File read/input operations
b. ofstream - Can be used for File write/output operations
c. fstream - Can be used for both read/write c++ file I/O operations
These classes are derived directly or indirectly from the classes istream, and ostream. We have already used objects
whose types were these classes: cin is an object of class istream and cout is an object of class ostream. Therefore, we
have already been using classes that are related to our file streams. And in fact, we can use our file streams the same
way we are already used to use cin and cout, with the only difference that we have to associate these streams with
physical files. Let's see an example:
4.1.2. Text and Binary Files
In file processing, files are generally classified into two as
Text file and
Binary file
Text file is a file in which its content is treated as a sequence of characters and can be accessed sequentially. Where
as binary file is a file in which its content is treated as record sequence in a binary format. Binary format refers to the
actual format the data is going to be placed and processed in the memory which is directly related to its data type.
For example, the value int count 321 will be stored in three byte if it is written in text file considering the digit
sequence ‘3’, ‘2’, ‘1’. It will be stored in two byte if it is written in binary file since int requires two byte to store any
of its value. When you open the binary file you will see the character equivalence of the two bytes.
321 in binary equals 0000 0001 0100 0001
2
The first byte holds the character with ASCII value equals to one and the second byte a character with ASCII value
equals 65 which is ‘A’. Then if you open the binary file you will see these characters in place of 321.
4.1.3. Text File processing
File processing involves the following major steps
1. Declaring file variable identifier
2. Opening the file
3. Processing the file
4. Closing the file when process is completed.
4.1.3.1. Opening and Closing a file
An open file is represented within a program by a stream object and any input or output operation performed on
this stream object will be applied to the physical file associated to it. In C++, you open a file by linking it to a
stream. Before you can open a file, you must first obtain a stream. There are three types of streams: input, output, and
input/output. To create an input stream, you must declare the stream to be of class ifstream. To create an output stream,
you must declare it as class ofstream. Streams that will be performing both input and output operations must be
declared as class fstream.
ifstream in ; //input stream
ofstream out ; // output stream
fstream io ; // input and output
Once you have declared a stream, you associate it with a file by using the method open().
The method open ( ) is a member of each of the three stream classes. Its prototype is:
void open (const char *filename, int mode, int access = filebuf::penprot );
Where:
Filename is the name of the file.
The value of the mode determines how the file is opened. It must be one (or more) of these values:
Mode Description
ios::app Write all output to the end of the file
ios::ate Open a file for output and move to the end of the file (normally used to append data to a
file). Data can be written anywhere in the file.
ios::binary Cause the file to be opened in binary mode.
ios::in Open a file for input
ios::nocreate If the file does not exist, the open operation fails.
ios::noreplace If the file exists, the open operation fails.
ios::out Open a file for output
ios:trunc Discard the file's content if it exists (this is also the default action ios::out)
You can combine two or more of these values by using them together.
ofstream out ;
out.open ( "test", ios::out); // correct statement
3
ofstream out1 ;
out.open ( " test"); // the default value of mode is ios::out – // correct statment
To open a stream for input and output, you must specify both the ios::in and the ios::out mode values. (Noe default
value for mode is supplied in this case.)
fstream myStream;
myStream.open ( "test", ios::in | ios::out );
If open ( ) fails, myStrream will be zero
if (myStream){
cout << "Cannot open a file.\n";
// handle error
}
Each one of the open() member functions of the classes ofstream, ifstream and fstream has a default mode that is
used if the file is opened without a second argument:
class default mode parameter
ofstream ios::out
ifstream ios::in
fstream ios::in | ios::out
For ifstream and ofstream classes, ios::in and ios::out are automatically and respectively assumed, even if a mode
that does not include them is passed as second argument to the open() member function. The default value is only
applied if the function is called without specifying any value for the mode parameter. If the function is called with
any value in that parameter the default mode is overridden, not combined.
File streams opened in binary mode perform input and output operations independently of any format
considerations. Non-binary files are known as text files, and some translations may occur due to formatting of some
special characters (like newline and carriage return characters).
Since the first task that is performed on a file stream object is generally to open a file, these three classes include a
constructor that automatically calls the open() member function and has the exact same parameters as this
member. Therefore, we could also have declared the previous myfile object and conducted the same opening
operation in our previous example by writing:
Combining object construction and stream opening in a single statement. Both forms to open a file are valid and
equivalent.
To check if a file stream was successful opening a file, you can do it by calling to member is_open() with no
arguments. This member function returns a bool value of true in the case that indeed the stream object is associated
with an open file, or false otherwise:
if (myfile.is_open()) { /* ok, proceed with output */ }
ifstream myStream ( "myfile" ); // open file for input
4
When we are finished with our input and output operations on a file we shall close it so that its resources become
available again. In order to do that we have to call the stream's member function close(). This member function
takes no parameters, and what it does is to flush the associated buffers and close the file:
myfile.close();
Once this member function is called, the stream object can be used to open another file, and the file is available
again to be opened by other processes.
In case that an object is destructed while still associated with an open file, the destructor automatically calls the
member function close(). The close method takes no parameters and returns no value.
4.1.3.2. Reading and writing text files
Simply use the << and >> operators in the same way you do when performing console I/O except that instead of using
cin and cout, you substitute a stream that is linked to a file.
ofstream out ("inventory");
out <<"Radios" << 39.95 << endl;
out << "Toastors" << 19.95 << endl;
out.close ( );
Example: Basic file operations
#include <iostream>
#include <fstream>
using namespace std;
int main () {
ofstream myfile;
myfile.open ("example.txt");
myfile << "Writing this to a file.\n";
myfile.close();
return 0;
}
This code creates a file called example.txt and inserts a sentence into it in the same way we are used to do with
cout, but using the file stream myfile instead.
Example 2: writing on a text file
#include <iostream>
#include <fstream>
using namespace std;
int main () {
ofstream myfile ("example.txt");
if (myfile.is_open())
{
myfile << "This is a line.\n";
myfile << "This is another line.\n";
myfile.close();
}
5
else cout << "Unable to open file";
return 0;
}
Example 3: reading a text file
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main () {
string line;
ifstream myfile ("example.txt");
if (myfile.is_open())
{
while (! myfile.eof() )
{
getline (myfile,line);
cout << line << endl;
}
myfile.close();
}
6
get and put stream pointers
All I/O streams objects have, at least, one internal stream pointer:
ifstream, like istream, has a pointer known as the get pointer that points to the element to be read in the
next input operation.
ofstream, like ostream, has a pointer known as the put pointer that points to the location where the next
element has to be written.
Finally, fstream, inherits both, the get and the put pointers, from iostream (which is itself derived from
both istream and ostream).
These internal stream pointers that point to the reading or writing locations within a stream can be manipulated
using the following member functions:
tellg() and tellp()
These two member functions have no parameters and return a value of the member type pos_type, which is an
integer data type representing the current position of the get stream pointer (in the case of tellg) or the put stream
pointer (in the case of tellp).
seekg() and seekp()
These functions allow us to change the position of the get and put stream pointers. Both functions are overloaded
with two different prototypes. The first prototype is:
seekg ( position );
seekp ( position );
Using this prototype the stream pointer is changed to the absolute position position (counting from the beginning
of the file). The type for this parameter is the same as the one returned by functions tellg and tellp: the member type
pos_type, which is an integer value.
The other prototype for these functions is:
seekg ( offset, direction );
seekp ( offset, direction );
Using this prototype, the position of the get or put pointer is set to an offset value relative to some specific point
determined by the parameter direction. offset is of the member type off_type, which is also an integer type. And
direction is of type seekdir, which is an enumerated type (enum) that determines the point from where offset is
counted from, and that can take any of the following values:
ios::beg offset counted from the beginning of the stream
ios::cur offset counted from the current position of the stream pointer
ios::end offset counted from the end of the stream
The following example uses the member functions we have just seen to obtain the size of a file:
Example: obtaining file size
#include <iostream>
#include <fstream>
using namespace std;
int main () {
7
long begin,end;
ifstream myfile ("example.txt");
begin = myfile.tellg();
myfile.seekg (0, ios::end);
end = myfile.tellg();
myfile.close();
cout << "size is: " << (end-begin) << " bytes.\n";
return 0;
}
4.1.4. Binary File processing
In binary files, to input and output data with the extraction and insertion operators (<< and >>) and functions like
getline is not efficient, since we do not need to format any data, and data may not use the separation codes used by
text files to separate elements (like space, newline, etc...).
File streams include two member functions specifically designed to input and output binary data sequentially:
write and read. The first one (write) is a member function of ostream inherited by ofstream. And read is a member
function of istream that is inherited by ifstream. Objects of class fstream have both members. Their prototypes are:
write ( memory_block, size );
read ( memory_block, size );
Where memory_block is of type "pointer to char" (char*), and represents the address of an array of bytes where the
read data elements are stored or from where the data elements to be written are taken. The size parameter is an
integer value that specifies the number of characters to be read or written from/to the memory block.
There are two ways to write and read binary data to and from a file.
get ( ) and put ( )
read ( ) and write ( )
If you will be performing binary operations on a file, be sure to open it using the ios::binary mode specifier.
4.1.4.1. get ( ) and put ( )
These functions are byte-oriented.
o get ( ) will read a byte of data.
o put ( ) will write a bye of data.
The get ( ) method has many forms.
istream & get( char ch );
ostream & put ( char ch);
The get ( ) method read a single character from the associated stream and puts the value in ch, and returns a
reference to the stream. The put ( ) method writes ch to the stream and returns a reference to the stream.
char in;
ifstream in ( "test", ios::in | ios::binary);
if (!in){
8
cout <<"Cannot open file";
return 1;
}
while (in) //in will be 0 when eof is reached
{ in.get ( ch );
cout << ch;
}
When the end-of-file is reached, the stream associated with the file becomes zero.
ofstream out ( "chars", io::out | ios::binary);
for (int i= 0; i < 256; i++)
out.put ( (char ) i ) ; //write all characters to disk
out.close ( );
4.1.4.2. read ( ) and write ( )
The read ( ) method reads num bytes from the associated stream, and puts them in a memory buffer (pointed to by
buf).
istream & read ( unsigned char * buf, int num );
The write ( ) method writes num bytes to the associated stream from the memory buffer (pointed to by buf).
ostream & write ( const unsigned char * buf, int num );
If the end-of-file is reached before num characters have been read, then read ( ) simply stops, and the buffer contains
as many characters as were available. You can find out how many characters have been read by using another
member function, called gcount ( ), which has the prototype:
int gcount ( );
4.1.4.3. More get ( ) functions
The method get ( ) is overloaded in several ways.
istream &get (char *buf, int num, char delim = '\n');
This get ( ) method reads characters into the array pointed to by the buf until either num characters have been read,
or the character specified by delim has been encountered. The array pointed to by buf will be null terminated by get
( ). If the delimiter character is encountered in the input stream, it is not extracted. Instead, it remains in the stream
until the next input operation.
a. int get ( )
It returns the next character from the stream. It returns EOF if the end of file is encountered.
b. getline ( )
istream & getline ( char *buf, int num, char delim ='\n');
This method is virtually identical to the get ( buf, num, delim) version of get ( ). The difference is getline ( ) reads and
removes the delimiter from the input stream.
Example: reading a complete binary file
#include <iostream.h>
9
#include <fstream.h>
using namespace std;
char * memblock;
int main () {
ifstream file ("example.txt", ios::in|ios::binary|ios::ate);
if (file.is_open())
{
long size = file.tellg();
memblock = new char [size];
file.seekg (0, ios::beg);
file.read (memblock, size);
file.close();
cout << "the complete file content is in memory";
delete[] memblock;
}
else cout << "Unable to open file";
return 0;
}
In this example the entire file is read and stored in a memory block. Let's examine how this is done:
First, the file is open with the ios::ate flag, which means that the get pointer will be positioned at the end of the file.
This way, when we call to member tellg(), we will directly obtain the size of the file. Once we have obtained the
size of the file, we request the allocation of a memory block large enough to hold the entire file:
memblock = new char[size];
Right after that, we proceed to set the get pointer at the beginning of the file (remember that we opened the file
with this pointer at the end), then read the entire file, and finally close it:
file.seekg (0, ios::beg);
file.read (memblock, size);
file.close();
At this point we could operate with the data obtained from the file. Our program simply announces that the
content of the file is in memory and then terminates.
Function Description
Detecting EOF It returns nonzero when the end of the file has been reached;
int eof ( ); otherwise it returns zero.
Reading and discarding characters from Reads and discards characters until either num characters have been
the input stream. nignored (1 by default ) or until the charcter specified by delim is
istream & ignore (int num = 1, int delim encounterdd (EOF by default). If the delimiting character is
= EOF); encountered, it is not removed from the input stream.
Obtain the next character in the input One can obtain the next character in the input stream without
stream without removing it from that removing it from that stream by using peek ( ). It returns the next
stream character in the stream or EOF if the end of file is encountered.
10
int peek ( );
One can return the last character read from a stream to that stream
istream & putback ( char c); using putback ( ).
Forcing data to be physically written to When the output is performed, data is not necessarily immediately
the disk written to the physical device linked to the stream. Instead,
information is stored in an internal buffer until the buffer is full.
ostream & flush ( ); Only then are the contents of that buffer written to disk. However,
you can force the information to be physically written to the disk
before the buffer is full by calling flush ( ).
11
failbit -- 1 when a (possibly) nonfatal I/O error has occurred; 0 otherwise
badbit -- 1 when a fatal I/O error has ocurred; 0 otherwise
These flags are enumerated inside ios. Also defined in ios is goodbit, which has the value 0. There are two ways in
which you can obtain I/O status information.
a. Use the rdstate function/method.
int rdstate ( );
rdstate function returns the current status of the error flags encoded into an integer. It returns zero, when no error
has occurred. Otherwise, an error bit is turned on.
b. Use one or more of these methods.
Method Description
int bad ( ) Returns true if badbit is set.
int fail ( ) Returns true if failbit is set.
int eof ( ) Returns true if there are no errors.
int good ( ) Otherwise they return false.
Once an error has occurred, it may need to be cleared before your program continues. to do this, use the clear ( )
method.
void clear ( int flags = 0);
If flag = zero (as it is by default), all error flags are cleared (reset to zero). Otherwise, set flags to the flags or value s
you want to clear.
Exercise
1. Write a program that accept N student record from the keyboard & store the list on a file “D:\\ Test.txt” in a
text file format
2. Write a program that reads students record from the text file “D:|\ Test.txt” and display on the screen.
3. Do Q1 in binary format.
4. Do Q2 in binary format.
Note Student record consists of first name, last name, gender, age and Id.
4.1.6. File Streams as Function Arguments
A file stream object can be a function argument. The only requirement is that the function’s formal parameter be a
reference to an appropriate stream, either as ifstream& or ofstream&. See the following example
#include <fstream.h>
#include<stdlib.h>
void in-out(ofstream&);
int main()
{
const int MAXCHARS = 21;
char fname[MAXCHARS]=”list.dat”;
ofstream out-file(fame);
12
if(out-file.fail())
{
cout<<”file open error”;
exit(1);
}
in-out(out-file);
return 0;
}
void in-out(ofstream& file-out)
{
const int LINELEN= 80;
const int NUMLINES = 5;
int count;
char line[LINELEN] ;
cout<<”please enter five lines of text”<<endl;
for(cout=0;cout<NUMLINES;count++)
{
file-out<<line<<endl;
}
return;
}
13