Concepts of Programming Languages Lectur
Concepts of Programming Languages Lectur
Introduction
What is OOP
In simple term, OOP is programming with objects.
Objects
o Objects are the evolutionary extension of the concept of records that
enable us to organize data into packages.
o It allow us to combine both data and code into a single package.
o Therefore, an object is a language construct that ties data with the functions
that operates on the data.
OOP viewpoint
Keyboard control
object
The figure above show how to create a set of objects for a word processor application.
The arrangement is divided into objects such as low level object, window
object, file management objects etc.
Advantages
- Major operation of a program can easily be isolated.
- Some objects are used to create other objects.
- This helps to hide the details of the lower level objects.
1
- This role reveals in the way objects are used, rather than passing objects(data) to
functions, objects are used to call functions.
- Programs can avoid large functions that contain logic for multiple cases, instead
multiple objects are created to represent the different logical components of a
program.
Components of OOP
Classes
Objects
Instance variables
Methods
Sample class
Class
2
End time
When programming with C++ „s object oriented features, there are three underlying
properties that surface again and again:
Encapsulation
Polymorphism
Inheritance
Encapsulation
- Encapsulation is the technique of combining data and the operations needed to
process the data under one package[object].
- It is what gives objects their building block flavor.
- Encapsulation provides two important features:
o Puts data and functions under one roof.
o Provides data hiding capabilities.
Polymorphism
- Polymorphism is the quality that allows one name to be used for two or more related but
technically different purposes.
- Polymorphism allows one name to specify a general class of actions. Within a general
class of actions, the specific action to be applied is determined by the type of data.
For example, in C, the absolute value action requires three distinct function names: abs( ) for integer, labs( )
for long integer, and fabs( ) for floating-point value. However in C++, each function can be called by the
same name, such as abs( ). The type of data used to call the function determines which specific version of the
function is actually executed.
- In C++ it is possible to use one function name for many different purposes. This type of
polymorphism is called function overloading.
- Polymorphism can also be applied to operators. In that case it is called operator overloading.
- The ability to hide many different implementations behind a single interface.
What is an interface?
- A named set of operations that characterize the behaviour of an element.
The interface formalizes polymorphism
<<interface>>
Triangle
Polygon 9
draw
Inheritance
- Inheritance is the technique of taking classes we already have, and extending
them to do new things.
- Inheritance enables one to derive a new class from an existing class. As a
result, we can:
o Add data and code to a class without having to change the original class
o Reuse code
o Change the behavior of a class.
Relationships: Generalization
- A relationship among classes where one class shares the structure and / or
behaviour of one or more classes.
- Defines a hierarchy of abstractions in which a subclass inherits from one or
more super classes
o single inheritance
o multiple inheritance
- Generalization is an “is –a-kind of” relationship
Ancestor
BankAccount
balance
Superclass
name
number Generalization
(Parent) Relationship
withdraw()
CreateStatement()
Subclasses
Current Savings
withdraw() GetInterest() 10
Withdraw()
Descendants
Abstraction
Encapsulation
Modularity
Hierarchy
Inheritance
What is Abstraction?
A model that includes most important aspects of a given problem while ignoring less
important details
What is Encapsulation?
What is Modularity?
The breaking up of something complex into manageable pieces or modules
Queue
Order Placement
Canteen System
Delivery
Billing
What is Hierarchy?
Level of abstraction
11
Differences between C and C++
Although C++ is a subset of C, there are some small differences between the two.
First, in C, when a function takes no parameters, its prototype has the word void inside its function
parameter list. For example if a function f1( ) takes no parameters (and returns a char), its prototype
will look like this:
char f1(void); /* C version */
In C++, the void is optional. Therefore the prototype for f1( ) is usually written as:
char f1( ); //C++ version
this means that the function has no parameters.
Another difference between C and C++ is that, in a C++ program, all functions must be
prototyped. Remember in C prototypes are recommended but technically optional.
A third difference between C and C++ is that in C++, if a function is declared as returning a value, it
must return a value. That is, if a function has a return type other than void, any return statement
within the function must contain a value.
In C, a non void function is not required to actually return a value. If it doesn‟t, a garbage value is
„returned‟.
In C++, you must explicitly declare the return type of all functions.
Another difference is that in C, local variables can be declared only at the start of a block, prior to
any „action‟ statement.
In C++, local variables can be declared anywhere. Thus, local variables can be declared close to
where they are first use to prevent unwanted side effects.
C++ defines the bool date type, which is used to store Boolean values. C++ also defines the
keywords true and false, which are the only values that a value of type bool can have.
In C, it is not an error to declare a global variable several times, even though it is bad programming
practice. In C++, this is an error.
In C, you can call main( ) from within the program. In C++, this is not allowed.
12
In C, you cannot take the address of a register variable. In C++, you can.
In C, the type wchar_t is defined with a typedef. In C++, wchar_t is a keyword.
New headers
Since C++ is build on C, the skeleton should be familiar, but pay attention to the #include statement. This
statement includes the file iostream.h, which provides support for C++‟s I/O system. It is to C++ what stdio.h
is to C.
First in the #include statement, there is no .h after the name iostream. And second, the next line,
specifying a namespace is new.
The only difference is that in C or traditional C++, the #include statement includes a file (file-
name.h). While the Standard C++ do not specify filenames.
Since the new-style header is not a filename, it does not have a .h extension. Such header consists
only of the header name between angle brackets:
< iostream >
< fstream >
< vector >
< string >
Standard C++ supports the entire C function library, it still supports the C-style header files
associated with the library. That is, header files such as stdio.h and ctype.h are still available.
However Standard C++ also defines new-style headers that you can use in place of these header
files. For example,
13
< math.h > < cmath >
< string.h > < cstring >
Remember, while still common in existing C++ code, old-style headers are obsolete.
Namespace
When you include a new-style header in your program, the contents of that header are contained in
the std namespace. The namespace is simply a declarative region.
The purpose of a namespace is to localise the names of identifiers to avoid name collision.
However, the contents of new-style headers are place in the std namespace. Using the statement,
using namespace std; brings the std namespace into visibility. After this statement has been
compiled, there is no difference working with an old-style header and a new-style one.
by
The most common C++-specific feature used is its approach to console I/O. While you still can use
functions such as printf( ) and scanf( ).
Example:
14
int main( ) {
// local variables
int i;
float f;
// program code
cout << "Enter an integer then a float ";
// no automatic newline
cin >> i >> f; // input an integer and a float
cout << "i= " << i << " f= " << f << "\n";
// output i then f and newline
return 0;
}
You can input any items as you like in one input statement. As in C, individual data items must be
separated by whitespace characters (spaces, tabs, or newlines).
When a string is read, input will stop when the first whitespace character is encountered.
keywords
Identifiers
C++ programs can be written using many English words. It is useful to think of words found in a program as
being one of three types:
1. Reserved Words. These are words such as if, int and else, which have a predefined meaning
that cannot be changed. Here's a more complete list:
15
case do goto protected struct unsigned
true false
2. Library Identifiers. These words are supplied default meanings by the programming environment,
and should only have their meanings changed if the programmer has strong reasons for doing so.
Examples are cin, cout and sqrt (square root).
3. Programmer-supplied Identifiers. These words are "created" by the programmer, and are typically
variable names, such as year_now and another_age.
An identifier cannot be any sequence of symbols. A valid identifier must start with a letter of the
alphabet or an underscore ("_") and must consist only of letters, digits, and underscores.
Data Types
Integers
In C++, variables of this type are used to represent integers (whole numbers).
Declaring a variable to be of type int signals to the compiler that it must associate enough memory
with the variable's identifier to store an integer value or integer values as the program executes. But
there is a (system dependent) limit on the largest and smallest integers that can be stored.
Hence C++ also supports the data types short int and long int which represent, respectively, a
smaller and a larger range of integer values than int. Adding the prefix unsigned to any of these
types means that we wish to represent non-negative integers only.
o For example, the declaration
1. Decimal points cannot be used; although 26 and 26.0 have the same value, "26.0" is not of type
"int".
2. Commas cannot be used in integers, so that (for example) 23,897 has to be written as "23897".
3. Integers cannot be written with leading zeros. The compiler will, for example, interpret "011" as
an octal (base 8) number, with value 9.
16
Real numbers
Variables of type "float" are used to store real numbers. Plus and minus signs for data of type
"float" are treated exactly as with integers, and trailing zeros to the right of the decimal point are
ignored. Hence "+523.5", "523.5" and "523.500" all represent the same value. The computer also
accepts real numbers in floating-point form (or "scientific notation"). Hence 523.5 could be written
as "5.235e+02" (i.e. 5.235 x 10 x 10), and -0.0034 as "-3.4e-03".
In addition to "float", C++ supports the types "double" and "long double", which give
increasingly precise representation of real numbers, but at the cost of more computer memory.
Type Casting
Sometimes it is important to guarantee that a value is stored as a real number, even if it is in fact a whole
number. A common example is where an arithmetic expression involves division. When applied to two values
of type int, the division operator "/" signifies integer division, so that (for example) 7/2 evaluates to 3. In
this case, if we want an answer of 3.5, we can simply add a decimal point and zero to one or both numbers -
"7.0/2", "7/2.0" and "7.0/2.0" all give the desired result. However, if both the numerator and the divisor
are variables, this trick is not possible. Instead, we have to use a type cast. For example, we can convert "7" to
a value of type double using the expression "static_cast<double>(7)". Hence in the expression
the "/" will always be interpreted as real-number division, even when both " numerator" and "denominator"
have integer values. Other type names can also be used for type casting. For example,
"static_cast<int>(14.35)" has an integer value of 14.
Characters
Variables of type "char" are used to store character data. In standard C++, data of type " char" can only be a
single character (which could be a blank space). These characters come from an available character set which
can differ from computer to computer.
However, it always includes upper and lower case letters of the alphabet, the digits 0, ... , 9, and some special
symbols such as #, £, !, +, -, etc. Perhaps the most common collection of characters is the ASCII character
set.
Character constants of type "char" must be enclosed in single quotation marks when used in a program,
otherwise they will be misinterpreted and may cause a compilation error or unexpected program behaviour.
For example, "'A'" is a character constant, but " A" will be interpreted as a program variable. Similarly, " '9'"
is a character, but "9" is an integer.
There is, however, an important (and perhaps somewhat confusing) technical point concerning data of type
"char". Characters are represented as integers inside the computer. Hence the data type " char" is simply a
subset of the data type " int". We can even do arithmetic with characters. For example, the following
expression is evaluated as true on any computer using the ASCII character set:
'9' - '0' == 57 - 48 == 9
17
The ASCII code for the character '9' is decimal 57 (hexadecimal 39) and the ASCII code for the character '0'
is decimal 48 (hexadecimal 30) so this equation is stating that
It is often regarded as better to use the ASCII codes in their hexadecimal form.
However, declaring a variable to be of type " char" rather than type "int" makes an important difference as
regards the type of input the program expects, and the format of the output it produces. For example, the
program
#include <iostream>
using namespace std;
int main()
{
int number;
char character;
number = character;
return 0;
}
Type in a character:
9
The character '9' is represented as the number 57 in the computer.
We could modify the above program to print out the whole ASCII table of characters using a "for loop". The
"for loop" is an example of a repetition statement. (Exercise).
We can also 'manipulate' the output to produce the hexadecimal code. Hence to print out the ASCII table, the
program above can be modified to:
#include <iostream>
using namespace std;
int main()
{
int number;
char character;
18
for (number = 32 ; number <= 126 ; number = number + 1) {
character = number;
cout << "The character '" << character;
cout << "' is represented as the number ";
cout << dec << number << " decimal or "
<<hex<<number<< " hex.\n";
}
return 0;
}
Strings
Our example programs have made extensive use of the type " string" in their output. As we have seen, in C+
+ a string constant must be enclosed in double quotation marks. Hence we have seen output statements such
as
in programs. In fact, "string" is not a fundamental data type such as " int", "float" or "char". Instead,
strings are represented as arrays of characters.
This is programmer define data types. This facility provides a powerful programming tool when complex
structures of data need to be represented and manipulated by a C++ program.
When program output contains values of type " float", "double" or "long double", we may wish to restrict
the precision with which these values are displayed on the screen, or specify whether the value should be
displayed in fixed or floating point form.
The following example program uses the library identifier " sqrt" to refer to the square root function, a
standard definition of which is given in the header file cmath (or in the old header style math.h).
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
float number;
19
cout << sqrt(number) << ".\n";
return 0;
}
We can also include tabbing in the output using a statement such as "cout.width(20)". This specifies that the
next item output will have a width of at least 20 characters (with blank space appropriately added if
necessary). This is useful in generating tables. However the C++ compiler has a default setting for this
member function which makes it right justified. In order to produce output left-justified in a field we need to
use some fancy input and output manipulation. The functions and operators which do the manipulation are to
be found in the library file iomanip (old header style iomanip.h) and to do left justification we need to set a
flag to a different value (i.e. left) using the setiosflags operator:
#include <iostream>
#include <cmath>
#include <iomanip>
using namespace std;
int main()
{
int number;
cout.setf(ios::fixed);
cout.precision(2);
for (number = 1 ; number <= 10 ; number = number + 1) {
cout.width(20);
cout << number << sqrt( (double) number) << "\n";
}
return 0;
}
1 1.00
2 1.41
3 1.73
4 2.00
5 2.24
6 2.45
7 2.65
8 2.83
9 3.00
10 3.16
20
21
CLASSES
In C++, a class is declared using the class keyword. The syntax of a class declaration is similar to that of a
structure.
Functions and variables declared inside the class declaration are said to be members of the class. By
default, all member functions and variables are private to that class. This means that they are accessible
by other members of that class.
To declare public class members, the public keyword is used, followed by a colon. All functions and
variables declared after the public specifier are accessible both by other members of the class and by any
part of the program that contains the class.
This class has one private variable, called a, and two public functions set_a( ) and
get_a( ). Notice that the functions are declared within a class using their prototype forms. The
functions that are declared to be part of a class are called member functions.
Since a is private, it is not accessible by any code outside myclass. However since
set_a( ) and get_a( ) are member of myclass, they have access to a and as they are declared as public
member of myclass, they can be called by any part of the program that contains myclass.
The member functions need to be defined. You do this by preceding the function name with the class
name followed by two colons (:: are called scope resolution operator). For example, after the class
declaration, you can declare the member function as
22
In general to declare a member function, you use this form:
Here the class-name is the name of the class to which the function belongs.
The declaration of a class does not define any objects of the type myclass. It only defines the type of
object that will be created when one is actually declared.
To create an object, use the class name as type specifier. For example,
Remember that an object declaration creates a physical entity of that type. That is, an object occupies
memory space, but a type definition does not.
Once an object of a class has been created, your program can reference its public members by using
the dot operator in much the same way that structure members are accessed. Assuming the preceding
object declaration, here some examples,
...
Ob1.set_a(10); // set ob1’s version of a to 10
ob2.set_a(99); // set ob2’s version of a to 99
cout << ob1.get_a( ); << "\n";
cout << ob2.get_a( ); << "\n";
ob1.a=20; // error cannot access private member
ob2.a=80; // by non-member functions.
...
There can be public variables, for example
23
It is important to remember that although all objects of a class share their functions, each object
creates and maintains its own data.
When two or more functions share the same name, they are said overloaded. Overloaded functions
can help reduce the complexity of a program by allowing related operations to be referred to by the
same name.
To overload a function, simply declare and define all required versions. The compiler will
automatically select the correct version based upon the number and/or type of the arguments used to
call the function.
The following example illustrates the overloading of the absolute value function:
The compiler automatically calls the correct version of the function based upon the type of data used
as an argument.
24
Overloaded functions can also differ in the number of arguments. But, you must remember that the
return type alone is not sufficient to allow function overloading.
If two functions differ only in the type of data they return, the compiler will not always be able to
select the proper one to call. For example, the following fragment is incorrect,
When applied to real problems, virtually every object you create will require some sort of
initialisation. C++ allows a constructor function to be included in a class declaration.
A class‟s constructor is called each time an object of that class is created. Thus, any initialisation to
be performed on an object can be done automatically by the constructor function.
A constructor function has the same name as the class of which it is a part a part and has not return
type. Here is a short example,
In this example, the constructor is called when the object is created, and the constructor initialises
the private variable a to 10.
25
For a global object, its constructor is called once, when the program first begins execution. For local
objects, the constructor is called each time the declaration statement is executed.
Destructors
The complement of a constructor is the destructor. This function is called when an object is
destroyed. For example, an object that allocates memory when it is created will want to free that
memory when it is destroyed.
The name of a destructor is the name of its class preceded by a ∼.
For example,
#include < iostream >
using namespace std;
// class declaration
class myclass {
int a;
public:
myclass( ); //constructor
∼myclass( ); //destructor
void show( );
};
myclass::myclass( ) {
cout << "In constructor\n";
a = 10;
}
myclass::∼myclass( ) {
cout << "Destructing...\n";
} // ...
Local objects are destroyed when they go out of scope. Global objects are destroyed when the
program ends.
It is possible to pass one or more arguments to a constructor function. Simply add the appropriate
parameters to the constructor function‟s declaration and definition. Then, when you declare an
object, specify the arguments.
26
a=x;
}
void myclass::show( ) {
cout << a <<"\n";
}
int main( ) {
myclass ob(4);
ob.show( );
return 0;
}
Pay particular attention to how ob is declared in main( ). The value 4, specified in the parentheses
following ob, is the argument that is passed to myclass( )‟s parameter x that is used to initialise a.
OBJECT POINTERS
So far, you have been accessing members of an object by using the dot operator. This is the correct
method when you are working with an object.
However, it is also possible to access a member of an object via a pointer to that object. When a
pointer is used, the arrow operator (->) rather than the dot operator is employed.
You declare an object pointer just as you declare a pointer to any other type of variable. Specify its
class name, and then precede the variable name with an asterisk.
To obtain the address of an object, precede the object with the & operator, just as you do when
taking the address of any other type of variable.
Just as pointers to other types, an object pointer, when incremented, will point to the next object of
its type.
myclass *p;
creates a pointer to an object of myclass. It is important to understand that creation of an object pointer does
not create an object. It creates just a pointer to one. The address of ob is put into p by using the statement:
p=&ob;
Finally, the program shows how the members of an object can be accessed through a pointer.
IN-LINE FUNCTIONS
In C++, it is possible to define functions that are not actually called but, rather, are expanded in
line, at the point of each call.
The advantage of in-line functions is that they can be executed much faster than normal functions.
The disadvantage of in-line functions is that if they are too large and called to often, your program
grows larger. For this reason, in general only short functions are declared as in-line functions.
To declare an in-line function, simply precede the function‟s definition with the inline specifier.
For example,
In this example the function even( ) which return true if its argument is even, is declared as being in-
line.
This example also points out another important feature of using inline: an in-line function must be
define before it is first called.
28
If it is not, the compiler has no way to know that it is supposed to be expanded in-line. This is why
even( ) was defined before main( ).
Automatic in-lining
If a member function‟s definition is short enough, the definition can be included inside the class declaration.
Doing so causes the function to automatically become an in-line function, if possible. When a function is
defined within a class declaration, the inline keyword is no longer necessary. However, it is not an error to
use it in this situation.
) { i = a; j = b; }
int divisible( ) { return !(i%j); }
};
//...
29
MORE ABOUT CLASSES
Assigning object
One object can be assigned to another provided that both are of the same type. By default, when
one object is assigned to another, a bitwise copy of all the data members is made. For example,
when an object called o1 is assigned to an object called o2 , the contents of all o1‟s data are copied
into the equivalent members of o2.
//an example of object assignment.
//...
class myclass {
int a, b;
public:
void set(int i, int j) { a = i; b = j; };
void show( ) { cout << a << " " << b << "\n"; }
};
int main( ) {
30
myclass o1, o2;
o1.set(10, 4);
//assign o1 to o2
o2 = o1;
o1.show( );
o2.show( );
return 0;
}
Thus, when run this program displays
10 4
10 4
Remember that assignment between two objects simply makes the data, in those objects, identical.
The two objects are still completely separate.
Only object of the same type can by assign. Further it is not sufficient that the types just be
physically similar - their type names must be the same:
// This program has an error
// ...
class myclass {
int a, b;
public:
void set(int i, int j) { a = i; b = j; };
void show( ) { cout << a << " " << b << "\n"; }
};
/* This class is similar to myclass but uses a different
type name and thus appears as a different type to
the compiler
*/
class yourclass {
int a, b;
public:
void set(int i, int j) { a = i; b = j; };
void show( ) { cout << a << " " << b << "\n"; }
};
int main( ) {
myclass o1;
yourclass o2;
o1.set(10, 4);
o2 = o1; //ERROR objects not of same type
o1.show( );
o2.show( );
return 0;
}
It is important to understand that all data members of one object are assigned to another when
assignment is performed. This included compound data such as arrays. But be careful not to destroy
any information that may be needed later.
Passing object to functions
Objects can be passed to functions as arguments in just the same way that other types of data are
passed. Simply declare the function‟s parameter as a class type and then use an object of that class
as an argument when calling the function. As with other types of data, by default all objects are
passed by value to a function.
// ...
31
class samp {
int i;
public:
samp(int n) { i = n; }
int get_i( ) { return i; }
};
// Return square of o.i
int sqr_it(samp o) {
return o.get_i( )* o.get_i( );
}
int main( ) {
samp a(10), b(2);
cout << sqr_it(a) << "\n";
cout << sqr_it(b) << "\n";
return 0;
}
As stated, the default method of parameter passing in C++, including objects, is by value. This
means that a bitwise copy of the argument is made and it is this copy that is used by the function.
Therefore, changes to the object inside the function do not affect the object in the call.
As with other types of variables the address of an object can be passed to a function so that the
argument used in the call can be modify by the function.
// ...
32
// Set o.i to its square.
// This affect the calling argument
void sqr_it(samp *o) {
o->set(o->get_i( )*o->get_i( ));
}
// ...
int main( ) {
samp a(10);
sqr_it(&a); // pass a’s address to sqr_it
// ...
}
Notice that when a copy of an object is created because it is used as an argument to a function, the
constructor function is not called. However when the copy is destroyed (usually by going out of
scope when the function returns), the destructor function is called.
Be careful, the fact that the destructor for the object that is a copy of the argument is executed when
the function terminates can be a source of problems. Particularly, if the object uses as argument
allocates dynamic memory and frees that that memory when destroyed, its copy will free the same
memory when its destructor is called.
One way around this problem of a parameter‟s destructor function destroying data needed by the
calling argument is to pass the address of the object and not the object itself. When an address is
passed no new object is created and therefore no destructor is called when the function returns.
A better solution is to use a special type of constructor called copy constructor, which we will see
later on.
Returning object from functions
Functions can return objects. First, declare the function as returning a class type. Second, return an
object of that type using the normal return statement.
Remember that when an object is returned by a function, a temporary object is automatically
created which holds the return value. It is this object that is actually returned by the function. After
the value is returned, this object is destroyed. The destruction of the temporary object might cause
unexpected side effects in some situations (e.g. when freeing dynamically allocated memory).
//Returning an object
// ...
class samp {
char s[80];
public:
void show( ) { cout << s << "\n"; }
void set(char *str) { strcpy(s, str); }
};
//Return an object of type samp
samp input( ) {
char s[80];
samp str;
cout << "Enter a string: ";
cin >> s;
str.set(s);
return str;
}
int main( ) {
samp ob;
//assign returned object to ob
ob = input( );
33
ob.show( );
return 0;
}
Friend functions: an introduction
There will be time when you want a function to have access to the private members of a class
without that function actually being a member of that class. Towards this, C++ supports friend
functions. A friend function is not a member of a class but still has access to its private elements.
Friend functions are useful with operator overloading and the creation of certain types of I/O
functions.
A friend function is defined as a regular, nonmember function. However, inside the class
declaration for which it will be a friend, its prototype is also included, prefaced by the keyword
friend. To understand how this works, here a short example:
//Example of a friend function
// ...
class myclass {
int n, d;
public:
myclass(int i, int j) { n = i; d = j; }
//declare a friend of myclass
34
friend int isfactor(myclass ob);
};
/* Here is friend function definition. It returns true
if d is a factor of n. Notice that the keyword friend
is not used in the definition of isfactor( ).
*/
int isfactor(myclass ob) {
if ( !(ob.n % ob.d) ) return 1;
else return 0;
}
int main( ) {
myclass ob1(10, 2), ob2(13, 3);
if (isfactor(ob1)) cout << "2 is a factor of 10\n";
else cout << "2 is not a factor of 10\n";
if (isfactor(ob2)) cout << "3 is a factor of 13\n";
else cout << "3 is not a factor of 13\n";
return 0;
}
It is important to understand that a friend function is not a member of the class for which it is a
friend. Thus, it is not possible to call a friend function by using an object name and a class member
access operator (dot or arrow). For example, what follows is wrong.
ob1.isfactor( ); //wrong isfactor is not a member
//function
Instead friend functions are called just like regular functions.
Because friends are not members of a class, they will typically be passed one or more objects of the
class for which they are friends. This is the case with isfactor( ). It is passed an object of myclass,
called ob. However, because isfactor( ) is a friend of myclass, it can access ob‟s private members. If
isfactor( ) had not been made a friend of myclass it would not have access to ob.d or ob.n since n
and d are private members of myclass.
A friend function is not inherited. That is, when a base class includes a friend function, that friend
function is not a friend function of the derived class.
A friend function can be friends with more than one class. For example,
// ...
class truck; //This is a forward declaration
class car {
int passengers;
int speed;
public:
car(int p, int s) { passengers = p; speed =s; }
friend int sp_greater(car c, truck t);
};
class truck {
int weight;
int speed;
public:
truck(int w, int s) { weight = w; speed = s; }
friend int sp_greater(car c, truck t);
};
int sp_greater(car c, truck t) {
return c.speed - t.speed;
}
35
int main( ) {
// ...
}
This program also illustrates one important element: the forward declaration (also called a forward
reference), to tell the compiler that an identifier is the name of a class without actually declaring it.
A function can be a member of one class and a friend of another class. For example,
// ...
class truck; // forward declaration
class car {
int passengers;
int speed;
public:
car(int p, int s) { passengers = p; speed =s; }
int sp_greater( truck t);
};
class truck {
int weight;
int speed;
36
public:
truck(int w, int s) { weight = w; speed = s; }
//note new use of the scope resolution operator
friend int car::sp_greater( truck t);
};
int car::sp_greater( truck t) {
return speed - t.speed;
}
int main( ) {
// ...
}
One easy way to remember how to use the scope resolution operation it is never wrong to fully
specify its name as above in class truck,
friend int car::sp_greater( truck t);
However, when an object is used to call a member function or access a member variable, the full
name is redundant and seldom used. For example,
// ...
int main( ) {
int t;
car c1(6, 55);
truck t1(10000, 55);
t = c1.sp_greater(t1); //can be written using the
//redundant scope as
t = c1.car::sp_greater(t1);
//...
}
However, since c1 is an object of type car the compiler already knows that sp_greater( ) is a
member of the car class, making the full class specification unnecessary.
ARRAYS, POINTERS, AND REFERENCES
Arrays of objects
Objects are variables and have the same capabilities and attributes as any other type of variables.
Therefore, it is perfectly acceptable for objects to be arrayed. The syntax for declaring an array of
objects is exactly as that used to declare an array of any other type of variable. Further, arrays of
objects are accessed just like arrays of other types of variables.
#include < iostream >
using namespace std;
class samp {
int a;
public:
void set_a(int n) {a = n;}
int get_a( ) { return a; }
};
int main( ) {
samp ob[4]; //array of 4 objects
int i;
for (i=0; i<4; i++) ob[i].set_a(i);
for (i=0; i<4; i++) cout << ob[i].get_a( ) << " ";
cout << "\n";
return 0;
}
If the class type include a constructor, an array of objects can be initialised,
37
// Initialise an array
#include < iostream >
using namespace std;
class samp {
int a;
public:
samp(int n) {a = n; }
int get_a( ) { return a; }
};
int main( ) {
samp ob[4] = {-1, -2, -3, -4};
int i;
for (i=0; i<4; i++) cout << ob[i].get_a( ) << " ";
cout << "\n"
return 0;
}
38
You can also have multidimensional arrays of objects. Here an example,
// Create a two-dimensional array of objects
// ...
class samp {
int a;
public:
samp(int n) {a = n; }
int get_a( ) { return a; }
};
int main( ) {
samp ob[4][2] = {
1, 2,
3, 4,
5, 6,
7, 8 };
int i;
for (i=0; i<4; i++) {
cout << ob[i][0].get_a( ) << " ";
cout << ob[i][1].get_a( ) << "\n";
}
cout << "\n";
return 0;
}
This program displays,
1 2
3 4
5 6
7 8
When a constructor uses more than one argument, you must use the alternative format,
// ...
class samp {
int a, b;
public:
samp(int n, int m) {a = n; b = m; }
int get_a( ) { return a; }
int get_b( ) { return b; }
};
int main( ) {
samp ob[4][2] = {
samp(1, 2), samp(3, 4),
samp(5, 6), samp(7, 8),
samp(9, 10), samp(11, 12),
samp(13, 14), samp(15, 16)
};
// ...
Note you can always the long form of initialisation even if the object takes only one argument. It is
just that the short form is more convenient in this case.
Using pointers to objects
As you know, when a pointer is used, the object‟s members are referenced using the arrow (- >)
operator instead of the dot (.) operator.
39
Pointer arithmetic using an object pointer is the same as it is for any other data type: it is performed
relative to the type of the object. For example, when an object pointer is incremented, it points to
the next object. When an object pointer is decremented, it points to the previous object.
// Pointer to objects
// ...
class samp {
int a, b;
public:
samp(int n, int m) {a = n; b = m; }
int get_a( ) { return a; }
int get_b( ) { return b; }
};
int main( ) {
samp ob[4] = {
samp(1, 2),
samp(3, 4),
samp(5, 6),
samp(7, 8)
};
int i;
samp *p;
p = ob; // get starting address of array
for (i=0; i<4; i++) {
cout << p->get_a( ) << " ";
cout << p->get_b( ) << "\n"; p+
+; // advance to next object
}
// ...
40
The THIS pointer
C++ contains a special pointer that is called this. this is a pointer that is automatically passed to any
member function when it is called, and it points to the object that generates the call. For example,
this statement,
ob.f1( ); // assume that ob is an object
the function f1( ) is automatically passed as a pointer to ob, which is the object that invokes the call.
This pointer is referred to as this.
It is important to understand that only member functions are passed a this pointer. For example a
friend does not have a this pointer.
// Demonstrate the this pointer
#include < iostream >
#include < cstring >
using namespace std;
class inventory {
char item[20];
double cost;
int on_hand;
public:
inventory(char *i, double c, int o) {
//access members through
//the this pointer
strcpy(this->item, i);
this->cost = c;
this->on_hand = o;
}
void show( );
};
void inventory::show( ) {
cout << this->item; //use this to access members
cout << ": £" << this->cost;
cout << "On hand: " << this->on_hand <<"\n";
}
int main( ) {
// ...
}
Here the member variables are accessed explicitly through the this pointer. Thus, within show( ),
these two statements are equivalent:
cost = 123.23;
this->cost = 123.23;
In fact the first form is a shorthand for the second. Though the second form is usually not used for
such simple case, it helps understand what the shorthand implies.
The this pointer has several uses, including aiding in overloading operators (see later).
By default, all member functions are automatically passed a pointer to the invoking object.
Using NEW and DELETE
When memory needed to be allocated, you have been using malloc( ) and free() for freeing the
allocated memory. Of course the standard C dynamic allocation functions are available in C++,
however C++ provides a safer and more convenient way to allocate and free memory. In C++, you
can allocate memory using new and release it using delete. These operator take the general form,
p-var = new type;
delete p-var;
41
Here type is the type of the object for which you want to allocate memory and p-var is a pointer
to that type. new is an operator that returns a pointer to dynamically allocated memory that is large
enough to hold an object of type type. delete releases that memory when it is no longer needed.
delete can be called only with a pointer previously allocated with new. If you call delete with an
invalid pointer, the allocation system will be destroyed, possibly crashing your program.
If there is insufficient memory to fill an allocation request, one of two actions will occur. Either
new will return a null pointer or it will generate an exception. In standard C++, the default
behaviour of new is to generate an exception. If the exception is not handle by your program, your
program will be terminated. The trouble is that your compiler may not implement new as in defined
by Standard C++.
Although new and delete perform action similar to malloc( ) and free( ), they have several
advantages. First, new automatically allocates enough memory to hold an object of the specified
type. You do not need to use sizeof. Second, new automatically returns a pointer of the specified
type. You do not need to use an explicit type cast the way you did when you allocate memory using
malloc( ). Third, both new and delete can be overloaded, enabling you to easily
42
implement your own custom allocation system. Fourth, it is possible to initialise a dynamically
allocated object. Finally, you no longer need to include < cstdlib > with your program.
// A simple example of new and delete
#include < iostream >
using namespace std;
int main( ) {
int *p;
p = new int; //allocate room for an integer
if (!p) {
cout << "Allocation error\n";
return 1;
}
*p = 1000;
cout << "Here is integer at p: " << *p << "\n";
delete p; // release memory
return 0;
}
// Allocating dynamic objects
#include < iostream >
using namespace std;
class samp {
int i, j;
public:
void set_ij(int a, int b) { i=a; j=b; }
int get_product( ) { return i*j; }
};
int main( ) {
samp *p;
p = new samp; //allocate object
if (!p) {
cout << "Allocation error\n";
return 1;
}
p- >set_ij(4, 5);
cout<< "product is: "<< p- >get_product( ) << "\n";
delete p; // release memory
return 0;
}
More about new and delete
Dynamically allocated objects can be given initial values by using this form of statement:
p-var = new type (initial-value);
To dynamically allocate a one-dimensional array, use
p-var = new type [size];
After execution of the statement, p-var will point to the start of an array of size elements of the
type specified.
Note, it is not possible to initialise an array that is dynamically allocated
To delete a dynamically allocated array, use
delete [ ] p-var;
This statement causes the compiler to call the destructor function for each element in the array. It
does not cause p-var to be freed multiple time. p-var is still freed only once.
43
// Example of initialising a dynamic variable
44
#include < iostream >
using namespace std;
int main( ) {
int *p;
p = new int(9); //allocate and give initial value
if (!p) {
cout << "Allocation error\n";
return 1;
}
*p = 1000;
cout << "Here is integer at p: " << *p << "\n";
delete p; // release memory
return 0;
}
// Allocating dynamic objects
#include < iostream >
using namespace std;
class samp {
int i, j;
public:
45
samp(int a, int b) { i=a; j=b; }
int get_product( ) { return i*j; }
};
int main( ) {
samp *p;
p = new samp(6, 5); //allocate object
// with initialisation
if (!p) {
cout << "Allocation error\n";
return 1;
}
cout<< "product is: "<< p- >get_product( ) << "\n";
delete p; // release memory
return 0;
}
Example of array allocation
// Allocating dynamic objects
#include < iostream >
using namespace std;
class samp {
int i, j;
public:
void set_ij(int a, int b) { i=a; j=b; }
∼samp( ) { cout << "Destroying...\n"; }
int get_product( ) { return i*j; }
};
int main( ) {
samp *p;
int i;
p = new samp [10]; //allocate object array
if (!p) {
cout << "Allocation error\n";
return 1;
}
for (i=0; i<10; i++) p[i].set_ij(i, i);
for (i=0; i<10; i++) {
cout << "product [" << i << "] is: ";
cout << p[i].get_product( ) << "\n";
}
delete [ ] p; // release memory the destructor
// should be called 10 times
return 0;
}
References
C++ contains a feature that is related to pointer: the reference. A reference is an implicit pointer
that for all intents and purposes acts like another name for a variable. There are three ways that a
reference can be used: a reference can be passed to a function; a reference can be return by a
function, an independent reference can be created.
The most important use of a reference is as a parameter to a function.
To help you understand what a reference parameter is and how it works, let's first start with a
program the uses a pointer (not a reference) as parameter.
46
#include < iostream >
using namespace std;
void f(int *n); // use a pointer parameter
int main( ) {
int i=0;
f(&i);
cout << "Here is i's new value: " << i << "\n";
return 0;
}
// function definition
void f(int *n) {
*n = 100; // put 100 into the argument
// pointed to by n
}
Here f( ) loads the value 100 into the integer pointed to by n. In this program, f( ) is called with the
address of i in main( ). Thus, after f( ) returns, i contains the value 100.
This program demonstrates how pointer is used as a parameter to manually create a call-by-
reference parameter-passing mechanism.
In C++, you can completely automate this process by using a reference parameter. To see how, let's
rework the previous program,
#include < iostream >
using namespace std;
void f(int &n); // declare a reference parameter
47
int main( ) {
int i=0;
f(i);
cout << "Here is i's new value: " << i << "\n";
return 0;
}
// f( ) now use a reference parameter
void f(int &n) {
// note that no * is needed in the following
//statement
n = 100; // put 100 into the argument
// used to call f( )
}
First to declare a reference variable or parameter, you precede the variable's name with the &.
This is how n is declared as a parameter to f( ). Now that n is a reference, it is no longer necessary -
even legal- to apply the * operator. Instead, n is automatically treated as a pointer to the argument
used to call f( ). This means that the statement n=100 directly puts the value 100 in the variable i
used as argument to call f( ).
Further, as f( ) is declared as taking a reference parameter, the address of the argument is
automatically passed to the function (statement: f(i) ). There is no need to manually generate the
address of the argument by preceding it with an & (in fact it is not allowed).
It is important to understand that you cannot change what a reference is pointing to. For example, if
the statement, n++, was put inside f( ), n would still be pointing to i in the main. Instead, this
statement increments the value of the variable being reference, in this case i.
// Classic example of a swap function that exchanges the
// values of the two arguments with which it is called
#include < iostream >
using namespace std;
void swapargs(int &x, int &y); //function prototype
int main( ) {
int i, j;
i = 10;
j = 19;
cout << "i: " << i <<", ";
cout << "j: " << j << "\n";
swapargs(i, j);
cout << "After swapping: ";
cout << "i: " << i <<", ";
cout << "j: " << j << "\n";
return 0;
}
// function declaration
void swapargs(int &x, int &y) { // x, y reference
int t;
t = x;
x = y;
y = t;
}
If swapargs( ) had been written using pointer instead of references, it would have looked like this:
void swapargs(int *x, int *y) { // x, y pointer
int t;
48
t = *x;
*x = *y;
*y = t;
}
Passing references to objects
Remember that when an object is passed to a function by value (default mechanism), a copy of that
object is made. Although the parameter's constructor function is not called, its destructor function is
called when the function returns. As you should recall, this can cause serious problems in some
case when the destructor frees dynamic memory.
One solution to this problem is to pass an object by reference (the other solution involves the use of
copy constructors, see later).
When you pass an object by reference, no copy is made, and therefore its destructor function is not
called when the function returns. Remember, however, that changes made to the object inside the
function affect the object used as argument
It is critical to understand that a reference is not a pointer. Therefore, when an object is passed by
reference, the member access operator remains the dot operator.
The following example shows the usefulness of passing an object by reference. First, here the
version that passes an object of myclass by value to a function called f():
#include < iostream >
using namespace std;
class myclass {
int who;
public:
myclass(int i) {
who = i;
cout << "Constructing " << who << "\n";
}
∼myclass( ) { cout<< "Destructing "<< who<< "\n";
}
int id( ) { return who; }
};
// o is passed by value
void f(myclass o) {
cout << "Received " << o.id( ) << "\n";
}
int main( ) {
myclass x(1);
f(x);
return 0;
}
This program displays the following:
Constructing 1
Received 1
Destructing 1
Destructing 1
The destructor function is called twice. First, when the copy of object 1 is destroyed when f( )
terminates and again when the program finishes.
However, if the program is change so that f( ) uses a reference parameter, no copy is made and,
therefore, no destructor is called when f( ) returns:
// ...
49
class myclass {
int who;
public:
myclass(int i) {
who = i;
cout << "Constructing " << who << "\n";
}
∼myclass( ) { cout<< "Destructing "<< who<< "\n";
}
int id( ) { return who; }
};
// Now o is passed by reference
void f(myclass &o) {
// note that . operator is still used !!!
cout << "Received " << o.id( ) << "\n";
}
int main( ) {
myclass x(1);
f(x);
return 0;
}
This version displays:
Constructing 1
Received 1
Destructing 1
Remember, when accessing members of an object by using a reference, use the dot operator not the
arrow.
Returning references
A function can return a reference. You will see later that returning a reference can be very useful
when you are overloading certain type of operators. However, it also can be employed to allow a
function to be used on the left hand side of an assignment statement. Here, a very simple program
that contains a function that returns a reference:
// ...
int &f( ); // prototype of a function
// that returns a reference.
int x; // x is a global variable
int main( )
{
f( ) = 100; // assign 100 to the reference
// returned by f( ).
cout << x << "\n";
return 0;
}
// return an int reference
int &f( ) {
return x; // return a reference to x
}
Here, f( ) is declared as returning a reference to an integer. Inside the body of the function, the
statement
return x;
50
does not return the value of the global variable x, but rather, it automatically returns address of x
(in the form of a reference). Thus, inside main( ) the statement
f( ) = 100;
put the value 100 into x because f( ) has returned a reference to it.
To review, function f( ) returns a reference. Thus, when f( ) is used on the left side of the
assignment statement, it is this reference, returned by f( ), that is being assigned. Since f( ) returns a
reference to x (in this example), it is x that receives the value 100.
You must be careful when returning a reference that the object you refer to does not go out of
scope. For example,
// return an int reference
int &f( ) {
int x; // x is now a local variable
return x; // returns a reference to x
}
In this case, x is now local to f( ) and it will go out of scope when f( ) returns. This means that the
reference returned by f( ) is useless.
Some C++ compilers will not allow you to return a reference to a local variable. However, this
type of problem can manifest itself on other ways, such as when objects are allocated dynamically.
Independent references and restrictions
The independent reference is another type of reference that is available in C++. An independent
reference is a reference variable that is simply another name for another variable. Because
references cannot be assigned new values, an independent reference must be initialised when it is
declared.
Further independent references exist in C++ largely because there was no compelling reason to
disallow them. But for most part their use should be avoided.
// program that contains an independent reference
// ...
int main( ) {
int x;
int &ref = x; // create an independent reference
x = 10; // these two statements are
ref = 10; // functionally equivalent
ref = 100;
// this print the number 100 twice
cout << x << " " << ref << "\n";
return 0;
}
There are a number of restrictions that apply to all types of references:
• References must be initialised unless they are members of a class, or are function parameters.
FUNCTION OVERLOADING
51
Overloading constructor functions
It is possible to overload a class's constructor function. However, it is not possible to overload
destructor functions. You will want to overload a constructor:
- to gain flexibility,
- to support arrays,
- to create copy constructors (see next section)
One thing to keep in mind, as you study the examples, is that there must be a constructor function
for each way that an object of a class will be created. If a program attempts to create an object for
which no matching constructor is found, a compiler-time error occurs. This is why overloaded
constructor functions are so common to C++ program.
Perhaps the most frequent use of overloaded constructor functions is to provide the option of either
giving an object an initialisation or not giving it one. For example, in the following program, o1 is
given an initial value, but o2 is not. If you remove the constructor that has the empty argument list,
the program will not compile because there is no constructor that matches the non-initialised object
of type myclass.
// ...
class myclass {
int x;
public:
// overload constructor two ways
myclass( ) { x = 0; } // no initialiser
myclass(int n ) { x = n; } // initialiser
int getx( ) { return x; }
};
int main( ) {
myclass o1(10); // declare with initial value
myclass o2; // declare without initialiser
cout << "o1: " << o1.getx( ) << "\n";
cout << "o2: " << o2.getx( ) << "\n";
return 0;
}
Another reason to overload constructor functions, is to allow both individual objects and arrays of
objects to occur with the program. For example, assuming the class myclass from the previous
example, both of the declarations are valid:
myclass ob(10);
myclass ob[10];
By providing both a parameterised and a parameterless constructor, your program allows the
creation of objects that are either initialised or not as needed. Of course, once you have defined both
types of constructor you can use them to initialise or not arrays.
// ...
class myclass {
int x;
public:
// overload constructor two ways
myclass( ) { x = 0; } // no initialiser
myclass(int n ) { x = n; } // initialiser
int getx( ) { return x; }
};
int main( ) {
// declare array without initialisers
myclass o1[10];
52
// declare with initialisers
myclass o2[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int i;
for (i=0; i<10; i++) {
cout<< "o1["<< i << "]: "<< o1[i].getx( )<< "\n";
cout<< "o2["<< i << "]: "<< o2[i].getx( )<< "\n";
}
return 0;
}
In this example, all elements of o1 are set to 0 by the constructor. The elements of o2 are initialised
as shown in the program.
Another situation is when you want to be allowed to select the most convenient method of
initialising an object:
#include < iostream >
#include < cstdio > // included for sscanf( )
using namespace std;
class date {
int day, month, year;
public:
date(char *str);//accept date as character string
date(int m, int d, int y) {// passed as three ints
day = d;
month = m;
year = y;
}
void show( ) {
cout << day << "/" << month << "/" << year;
cout << "\n";
}
};
date::date(char *str) { sscanf(str,"%d%*c%d%*c
%d", &day, &month, &year);
}
int main( ) {
// construct date object using string
date sdate("31/12/99");
// construct date object using integer
date idate(12, 31, 99);
sdate.show( );
idate.show( );
return 0;
}
Another situation in which you need to overload a class's constructor function is when a dynamic
array of that class will be allocated. As you should recall, a dynamic array cannot be initialised.
Thus, if the class contains a constructor that takes an initialiser, you must include an overloaded
version that takes no initialiser.
// ...
class myclass {
int x;
public:
// overload constructor two ways
53
myclass( ) { x = 0; } // no initialiser
myclass(int n ) { x = n; } // initialiser
int getx( ) { return x; }
void setx(int x) { x = n; }
};
int main( ) {
myclass *p;
myclass ob(10); // initialise single variable
p = new myclass[10]; // can't use initialiser here
if (!p) {
cout << "Allocation error\n";
return 1;
}
int i;
// initialise all elements of ob
for (i=0; i<10; i++) p[i]= ob;
for (i=0; i<10; i++)
cout<< "p["<< i << "]: "<< p[i].getx( ) << "\n";
return 0;
}
Without the overloaded version of myclass( ) that has no initialiser, the new statement would have
generated a compile-time error and the program would not have been compiled.
54
• when a temporary object is created for use as a return value by a function.
A copy constructor is only for initialisation. The following sequence does not call the copy
constructor defined in the preceding program.
array a(10);
array b(10);
b = a; // does not call the copy constructor. It performs
// the assignment operation.
A copy constructor also helps prevent some of the problems associated with passed certain types of
objects to function. Here, a copy constructor is defined for the strtype class that allocates memory
for the copy when the copy is created.
// This program uses a copy constructor to allow strtype
// objects to be passed to functions
#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;
class strtype {
char *p;
public:
strtype(char *s); // constructor
strtype(const strtype &o); // copy constructor
∼strtype( ) { delete [ ] p; } // destructor
56
char *get( ) { return p; }
57
};
// Constructor
strtype::strtype(char *s) {
int l;
l = strlen(s) + 1;
p = new char [l];
if (!p) {
cout << "Allocation error\n";
exit(1);
}
strcpy(p, s);
}
// Copy constructor
strtype::strtype(const strtype &o) {
int l;
l = strlen(o.p) + 1;
p = new char [l]; // allocate memory for new copy
if (!p) {
cout << "Allocation error\n";
exit(1);
}
strcpy(p, o.p); // copy string into copy
}
void show(strtype x) {
char *s;
s = x.get( );
cout << s << "\n";
}
int main( ) {
strtype a("Hello"), b("There");
show(a);
show(b);
return 0;
}
Here, when show( ) terminates and x goes out of scope, the memory pointed to by x.p (which will
be freed) is not the same as the memory still in use by the object passed to the function.
59
Overloading and ambiguity
When you are overloading functions, it is possible to introduce ambiguity into your program.
Overloading-caused ambiguity can be introduce through type conversions, reference parameters,
and default arguments. Further, some types of ambiguity are caused by the overloaded functions
themselves. Other types occur in the manner in which an overloaded function is called. Ambiguity
must be removed before your program will compile without error.
Finding the address of an overloaded function
Just as in C, you can assign the address of a function (that is, its entry point) to a pointer and access
that function via that pointer. A function's address is obtained by putting its name on the right side
of an assignment statement without any parentheses or argument. For example, if zap( ) is a
function, assuming proper declarations, this is a valid way to assign p the address of zap( ):
p = zap;
In C, any type of pointer can be used to point to a function because there is only one function that
can point to. However, in C++ it is a bit more complex because a function can be overloaded.
The solution is both elegant and effective. When obtaining the address of an overloaded function, it
is the way the pointer is declared that determines which overloaded function's address will be
obtained. In essence, the pointer's declaration is matched against those of the overloaded functions.
The function whose declaration matches is the one whose address is used.
Here is a program that contains two versions of a function called space( ). The first version outputs
count number of spaces to the screen. The second version outputs count number of whatever type of
character is passed to ch . In main( ) two function pointers are declared. The first one is specified as
a pointer to a function having only one integer parameter. The second is declared as a pointer to a
function taking two parameters.
60
return 0;
Overview
Inheritance
Inheritance in C++
Types of Inheritance
Polymorphism
Virtual Function
Pure Virtual Function
Function Overloading
Operator Overloading
Inheritance
o Introduction
Living being understands and communicates about their environment
by grouping the objects around them into classes
For example, a class of trees belongs to the higher order class of plants.
Within the class of trees, we have subclasses of evergreen and deciduous
trees
This classification gives a hierarchy of classes that objects can belong to
See the figure below
Plants
Shrubs Trees
Deciduous Evergreen
61
In a hierarchy of classes, the classes towards the top are called are called
are base classes or super class
Those further down are derived classes or sub classes
Derived classes are specialization of base class
Derived class
object Base class
object
For example, an object has four legs, covered with fur and carnivorous
Given these descriptions, this object will likely be an animal of some sort
If we are told another property that it can be kept as pet, then we can say it
is a domestic animal
If the object purr when content, then we are talking about a cat
If the object had the property of barking, then we could be fair to say it‟s a
dog
In this example, we identify four different classes that form a
hierarchy: furry animal, domestic pet, cat and dog as represented below
Animal
Domestic
Cat Dog
Pet hierarchy
62
o Inheritance in C++
In all OOP languages, we are able to use classes that we have
already declared as base classes and derive specialization from them
Example
Company Employee can be paid either
Salaried or
Hourly
A convenient way is to define a class that incorporates all attributes
and behaviour of an employee and use this as the base class for the two
specialized classes
The two derived classes would inherit all the members of the base classes
In addition, they would need to specify the difference
between themselves and the base class
Having define the base class, we reuse the definition in the
derived classes
Three advantages
No code duplication
Once the base class is tested and works for one derived class, it will
work for subsequent classes
To create a class of another type, much of the work is done already
The definition is
class Employee
{
protected:
int empCode; // describes the type of
employee int empNo; //unique employee no
int salary; //annual salary
public:
Employee(int no, float sal); //sets employee code, no & salary
void pay(); //cal mnthly pay
void identify(); // show employee no & type
};
The last two functions pay() and identify() have been chosen because
One will be common to both employee types and so, can be
inherited from the base class
The other will change depending on the employee type and so
will have to be redefined for each derived class
63
Data member access specifiers
public - any function inside and outside the class definition
private - any function inside the class definition
protected - any function inside the class definition and classes
derived from it
Class derivation
Class SalariedEmp derived from Employee
class SalariedEmp: public Employee
{
public:
SalariedEmp(int no, float salary);
void pay();
};
Derived class is SalariedEmp
Base class is Employee
Type of inheritance is public
Classwork
Derive class HourlyEmp from Employee
o Types of inheritance
The data member and member functions of the base class can be accessed
in the derived class depending on the type of inheritance as depicted in the
table below
64
o Complete definition
Employee class
Employee::Employee(int no, float sal)
{
empNo = no;
salary = sal;
empCode = Unknown;
}
void Employee::pay()
{
cout << “Annual Salary ” << salary << endl;
}
void Employee::identify()
{
cout << “Employee: ” << empNo << endl << “Type: ” << empCode << endl;
}
Derived class
SalariedEmp
SalariedEmp::SalariedEmp(int no, float salary): Employee(no, salary)
{
empCode = Salaried;
}
void SalariedEmp::pay()
{
cout << “Annual Salary ” << salary << endl
<< “This month ” << salary/12 << endl;
}
HourlyEmp
HourlyEmp::HourlyEmp(int no, float salary): Employee(no, salary)
{
empCode = Hourly;
}
void Hourly::pay()
{
int hrs;
cout << “Annual Salary ” << salary << endl;
cout << “Enter Hours Worked: ”;
cin >> hrs;
cout << “This month: ” << hrs * salary/2080 << endl;
}
65
Multiple Inheritance
o A class can inherit from more than one class
o Example: a Cleaning_Liquid class, a Drinking_Liquid class and a Water class
Cleaning_Liquid Drinking_Liquid
Water
66
na = na_var;
temperature = temperature_var;
cout << "Drinking_liquid constructor invoked" << endl;
}
Water::Water() : Drinking_liquid(32, 4), Cleaning_liquid(21, 36, 87)
{
cout << "Water constructor invoked" << endl << endl;
cout << "Ca = " << ca << " Mg = " << mg << " Na = " << na << endl
<< "Cleaning_liquid temperature = " << Cleaning_liquid::temperature << endl
<< "Drinking_liquid temperature = " << Drinking_liquid::temperature << endl;
}
void main()
{
Water spring_water;
}
67
o Note
Because the inheritance for both classes is public, public and protected data
members of the base classes become public and protected member of the
derived class respectively
Private base classes data member remain private to their classes
Since temperature is a member of both base classes, we cannot refer to
temperature in water function but included the class name and scope
resolution operator e.g. Drinking_Liquid::temperature
Arguments are passed to Cleaning_Liquid and Drinking_Liquid class
constructors using the initialization list the Water constructor
Polymorphism
o Having many shapes
o It refers to the ability to use the same name to perform different functions
in different classes
o This concept can be demonstrated via
Virtual function
Overloaded function
o Virtual function
Provides a mechanism that allows us to ensure that the member function
from the derived class is invoked, whether it is called directly or whether
we are referring to the object via a base class pointer
To achieve this, the declaration of the function is preceded with the keyword
„virtual‟ in both the base and the derived classes as below
class Employee
{
protected:
int empCode;
int empNo;
int salary;
public:
Employee(int no, float
sal); virtual void pay();
void identify();
};
class SalariedEmp: public Employee
{
public:
SalariedEmp(int no, float salary);
virtual void pay();
};
68
class HourlyEmp: public Employee
{
public:
HourlyEmp(int no, float salary);
virtual void pay();
};
Note
Whenever pay() is invoked, the pay() called depends on the
object calling it
The word virtual is used in the derived classes so that themselves can be
used as base classes that further specialist classes can be derived from
Static binding
The explanation for the difference in behaviour between virtual and
non- virtual member functions lies in how the compiler refers to them
When a non-virtual function for an object is used in a program, the
actual function to be invoked is fixed at the time the program is
compiled and the code produced by the compiler contains a
specific reference to the function. This is static binding
In static binding, the program code for a function is directly bound to
the name that invokes it, by the compiler
Dynamic binding
When virtual member function is used, the compiler produces code
that will invoke the correct function for the object that is been referred
to while the program is running
If a base class pointer is being used to refer to a number of
different derived class objects during the course of a program, then
when a member function is called, the correct function for the
appropriate derived class is invoked
This may not be known at the time the program is compiled and so the
binding of the correct member function to its name is performed as
the program is running. This is dynamic binding
69
class Base {
…
public:
virtual return_type fnName(type, type) = 0;
};
class Derived {
…
public:
return_type fnName(type, type);
};
The corresponding functions in the derived classes do not use
the keyword virtual in their declarations and must be defined
Whenever a pure virtual function is a member of a class, the class
is called a virtual class.
A virtual class is a class for which we are not allowed to create
independent objects. It is used as a base class for other classes
for which we do want independent objects
o Overloaded function
This concept means a situation where the same function name can refer
to more than one function definition
The case above (virtual function) illustrates function overloading
When an overloaded function is called, the compiler decides which of
the function definition applies
Function overloading can be used whether the functions be members
of classes or not
Examples
int x, y; //float x, y;
x + y;
array initialization
void initialization(int a[], int len)
{
int i;
for (i=0; i<len, i++) a[i]=0;
}
void initialization(float a[], int len)
{
int i;
for (i=0; i<len, i++) a[i]=0;
}
void initialization(char a[])
{
a[0]=‟\0‟;
}
70
these overloaded functions perform initialization of integer
array, float array and character array
the functions all have the same name but different parameter list
the compiler distinguishes between the functions because they
have different signatures but the programmer only needs to
remember one name
in addition, any program that uses the functions will be
more understandable than if three different names were used
It enhances program readability
o Operator overloading
Just as functions can be overloaded, operators also can be overloaded
Suppose we want the + operator, when used between two objects of a class,
to do a member-by-member summation, C++ allows us to define that
action for + by using operator overloading
The method is to create a function for a class called operator+() same as we
create any other function
When a client of a class (function that uses class object) has the
expression object1 + object2, the operator+() function is automatically
called by C++ and the member-by-member addition is done
Similarly, we could create operator-() function for a class
You could also make operator+() perform multiplication or read input but it
is best to have your operator functions perform actions similar to the
original operator meaning
C++ operators can either be
Unary
Binary
We cannot change this classification when doing operator overloading
in our programs
Sample source code
#include <iostream.h>
class Point
{
friend Point operator-(Point&, Point&);
private:
double x, y;
public:
Point();
Point(double, double);
Point operator++();
Point
operator=(Point&); void
show_data();
71
};
Point Point::operator++()
{
++x;
++y;
return *this;
}
void Point::show_data()
{
cout << "x = " << x << " y = " << y << endl;
}
void main()
{
Point p1(10, 3), p2(4, 8), p3;
p3 = p1 - p2;
++p3;
p3.show_data();
}
72
Description
Equivalent function calls
The equivalent function call is dependent upon classification of
the operator function being friend or member, binary or unary
Binary friend functions
o In general, a binary friend function has the following
expression and equivalent function call
object1 + object2 expression
73
Function definitions
It is the responsibility of the programmer to write the function
definition and must be consistent with the equivalent function
call
o Consistent with the equivalent function call for binary
friend functions
o Consistent with the equivalent function call for binary
member functions
o Consistent with the equivalent function call for unary
member functions
Rules for operator overloading
The table below shows operators that can be overloaded
++ -- + - * / % = += -= *= /=
%= ! < > <= >= = = != >> << () []
&& || new delete new[] delete[] -> ,
~ ^ & | ^= &= |= <<= >>= ->*
We are not allowed to make up our own operator, say $ for example
We cannot change the classification of an operator. If it is unary,
we must use it as unary operator
The operator precedence rules still apply
Each friend or freestanding operator function must take at least one
object as an argument. I.e. we cannot make such a function use
only C++ standard data types as arguments
File
A file is a bunch of bytes stored on some device perhaps magnetic tape, optical disk,
floppy disk or hard disk.
The C++ I/O class package handles file input and output much as it handles standard
input and output.
To write to a file, you create a stream object and use the ostream methods, such as
the << insertion operator or write (). To read a file, you create a stream object and
use the istream methods such sthe >> extraction operator or get (). To handle file
management tasks, C++ defines several new classes in the fstream.h file including
an ifstream class for file input, an ofstream class for file output and an fstream class
for simultaneous file I/O. You have to associate a newly opened file with a stream.
You can open a file in read-only mode, write-only mode or read-and-write mode.
74
These classes are derived from the classes in the iostream.h file, so objects of these
new classes will be able to use the methods provided by iostream.h.
Simple File I/O
Suppose you want a program to write to a file, you need to do the following:
Create an ofstream object to manage the output stream
Associate that object with a particular file
To accomplish this, begin by including the fstream.h header file. Incuding this file
automatically include the iostream.h file. Then declare an ofstream object and
initialize it using the name of the file to be opened. For example, to open the cookies
file for output, do the following:
ofstream fout(“cookies”); // create fout object
To put the word “Dull Data” into the file, you can do the
following: fout<<”Dull Data”;
ostream is a base class for the ofstream class. You can use all the ostream methods,
including the various insertion operator definitions and the formatting methods and
manipulators. The ofstream class uses buffered output, so the programs allocates
space for an output buffer when it creates an ofstream object like fout.
If you create two ofstream objects, the program creates two buffers, one for each
object. An ofstream object like fout collects output byte by byte from the program,
then when the buffer is filled, transfers the buffer contents en masse to the
destination file.
Opening s file for output this way creates a new file if there is no file of that name.
If a file by that name exists prior to opening it for output, the act of opening it
truncates it so that output starts with a clear file.
The requirements for reading a file are much like those for writing to a file:
Create an ifstream object to manage the input stream
Associate that object with a particular file.
You need to firstly include the fstream.h header file, then declare an ifstream object
initializing it with the file name. For example, to read the cookies file, you can do
the following:
ifstream fin(“cookies”); // open cookies for reading
You can use fin much like cin. For instance, you can do the following:
char ch;
fin>>ch; // read a character from the file
char buf[80];
fin>>buf; // read a line from the file
Input is also buffered and fin creates an input buffer which fin object manages.
Buffering generally moves data much faster than byte-by-byte transfer.
75
The connections with a file are closed automatically when the input and the output
stream objects expire, for example when the program terminates. You can also close
a connection with a file explicitly by using the close() method:
fout.close(); // close the output connection to file
fin.close( ); // close input connection to file
Closing such a connection does not eliminate the stream; it just disconnects it from
the file. However, the stream management apparatus remains in place. For instance,
the fin object still exists along with the input buffer it manages.
Example 1
The program below asks you for a file name. It creates a file having that name,
writes some information to it and closes the file. Closing the file flushes the buffer
guaranteeing that the file is updated. Then the program opens the same file for
reading and displays its contents.
# include<fstream.h>
int main(void)
{
char filename[20];
cout<<"Enter name for new file\n";
cin>>filename;
// create output stream object for new file and call it fout
ofstream fout(filename);
fout<<"For your eyes only!\n"; // write to file
cout<<"Enter your secret number:\n"; // writ to screen
float secret;
cin>>secret;
fout<<"Your Secret Number is "<<secret<< "\n";
fout.close(); // close file
// create input stream object for new file and call it fin
ifstream fin(filename);
cout<< "Here are the contents of "<< filename<<"\n";
char ch;
while(fin.get(ch)) cout <<ch; // write it to screen
cout<< "Done\n";
return 0;
}
76
If a group of files is to be processed sequentially, then you can open a single stream
and associate it with each file in turn. For instance, this is how you can handle two
files in succession:
ifstream fin; // create stream
fin.open(“fat.dat”); // associate stream with fat.dat file
-
- // Do stuff
-
fin.close( );
Stream Checking
The C++ file stream classes inherit a stream state member from the ios class. This
member stores information reflecting the stream status: all is well, end-of-file has
been reached, I/O operation failed, etc. If all is well, the stream state is zero. The
various other states are recorded by setting particular bits to 1. The file stream
classes also inherit methods that report about the stream state.
Method Returns
eof() Nonzero on end-of-file
fail() Nonzero if last I/O operation failed or if
invalid operation
bad() Nonzero if invalid operation attempted or
if there has been an unrecoverable error
good() Nonzero if all stream state bits are zero
rdstate() The stream state
clear(int n = 0) Returns nothing, but sets stream state to
n; the default value of n is 0
if (fin.fail( ))
{
cerr<<”Couldn‟t open file\n”;
}
File Modes
The file mode describes how a file is to be used: read it, write to it, append it, etc.
When you associate a stream with a file, you can provide a second argument
specifying the file mode:
ifstream fin(“banjo”, mode1 );
ofstream fout ( );
fout.open(“harp”, mode2);
77
The file mode is type int and you can choose from several constants defined in the
ios class. The table below lists the constants and their meanings:
Constants Meaning
ios::in Open file for reading
ios::out Open file for writing
ios::ate Seek to eof upon opening file
ios::app Append to end of file
ios::trunk Truncate file if it exists
ios::nocreate Open fails if file does not exist
ios::replace Open fails if file does exist
ios::binary Binary file
Opening a file in the ios::out mode also opens it in the ios::trunc mode by default.
That means existing file is truncated when opened; that is, its previous contents are
discarded.
Both ios::ate and ios::app place you at the end of the file just opened. The difference
between the two is that the ios::app mode allows you to add data to the end of the
file only, while ios::ate mode lets you write data anywhere in the file, even over old
data.
C++ bitwise OR operator represented by the | symbol is used to combine modes. For
instance, if you want a program to open a file in the append mode and also want the
file opening operation to fail if the file does not exist, use the following statement:
Example 2
The program below displays the current contents of a file, if it exists. It uses good()
method after attempting to open the file to check if the file exists. The program then
open the file for output using the ios::app mode. Next, the program solicits input
from the keyboard to add to the file. Finally, the program will display the revised
contents.
# include <fstream.h>
# include <stdlib.h>// for exit()
const int len=40;
const char* file = "guests.dat";
int main(void)
{
char ch;
// show initial contents
ifstream fin;
78
fin.open(file);
if(fin.good())
{
cout<<"Here are the current contents of the file\n";
while(fin.get(ch)) cout<<ch;
}
fin.close();
// add new names
ofstream fout(file,ios::app);
if(fout.fail())
{
cerr<<"Can't open " << file<< "file for output\n";
exit(1);
}
cout<<"Enter guest names(enter q to quit):\n";
char name[len];
cin.getline(name,len);
while(name[0]!='q')
{
fout<<name<<"\n";
cin.getline(name,len);
}
fout.close();
// show the revised file
fin.open(file);
if(fin.good())
{
cout<<"Here are the new contents of the file\n";
while(fin.get(ch)) cout<<ch;
}
fin.close();
return 0;
}
Binary Files
Data stored in a file can either be in text form or binary format. Text form means
that everything is stored as text even numbers. For instance, storing the value -2.434
in text form means storing the six characters used to write this number. That
requires converting the computer‟s internal representation of a floating-point
number to character form and that‟s exactly what the << insertion operator does.
Binary format however, means storing the computer‟s internal representation of a
value. That is, instead of storing characters, store the eight-bit double representation
79
of the value. For a character, the binary representation is the same as the text
representation.
1. It is easy to read.
2. You can use an ordinary editor or word processor to read and edit a text file.
3. You can easily transfer a text file from one computer system to another.
Disadvantage
1. Transferring data to another system can be a problem if the new system
uses a different internal representation for values.
To save the contents of the structure pl in text form, you can do this:
ofstream fout(“planet.data”, ios::app);
fout<<pl.name<<” “<<pl.poplaytion<<” “<<pl.g<<”\n”;
If the structure contains 30 members say, this could be tedious. To save the same
information in binary format, you can do this:
ofstream fout(“planet.dat”, ios::app|ios::binary);
fout.write((char*)&pl, sizeof pl);
Note: To recover the information from a file use the corresponding read()
method with an ifstream object:
ifstream fin(“planets.dat”, ios::binary);
fin.read((char*)&pl, sizeof pl);
80
Example 3
The program below uses those methods to create and read a binary file:
# include<fstream.h>
#include<iomanip.h>
#include<stdlib.h>// for exit
struct planet
{
char name[20];
double population;
double g;
};
const char* file="planets.dat";
int main(void)
{
planet pl;
cout.setf(ios::fixed,ios::floatfield);
// show initial contents
ifstream fin;
fin.open(file,ios::binary);
if(fin.good())
{
cout<<"Here are the current contents of " <<file<<"file\n";
while(fin.read((char*)&pl, sizeof pl))
{
cout<<setw(20)<<pl.name<<":"<<setprecision(0)<<setw(12)<<pl.population
<<setprecision(2)<<setw(6)<<pl.g<<"\n";
}
}
fin.close();
// add new data
ofstream fout(file,ios::app|ios::binary);
if(fout.fail())
{
cerr<<"can\'t open "<<file<<"file for output:\n";
exit(1);
}
char ch;
cout<<"Enter planet name (q to quit):\n";
cin.get(pl.name,20).get(ch);
while(pl.name[0]!='q')
{
81
cout<<"Enter planetary population:\n";
cin>>pl.population;
cout<<"Enter planet's acceleration of gravity:\n";
cin>>pl.g;
cin.ignore(80,'\n');// read and discard input up through the newline character
fout.write((char*)&pl, sizeof pl);
cout<<"Enter planet name( q to quit:\n";
cin.get(pl.name,20).get(ch);
}
fout.close();
//show revised file
fin.open(file,ios::binary);
if(fin.good())
{
cout<<"Here are the new contents of the file "<<file<<"\n";
{
while(fin.read((char*)&pl, sizeof pl))
{
cout<<setw(20)<<pl.name<<":"<<setprecision(0)<<setw(12)<<pl.population
<<setprecision(2)<<setw(6)<<pl.g<<"\n";
}
}
}
fin.close();
return 0;
}
finout.open(file,ios:in|ios::out|ios::ate|ios::binary);
Next, you need a way to move through the file. The fstream class inherits two
methods for this:
seekg(): moves the input pointer to a given file location
82
seekp(): moves the output pointer to a given file location.
You can also use seekg() with an ifstream object and seekp() with an ostream object.
The first prototype represents locating a file position measured in bytes from the
beginning of a file. The second prototype represents locating a file position
measured, in bytes, as an offset from a file location specified by the second
argument.
The streampos and streamoff types are integers. The streampos argument represents
the file position measured in bytes from the beginning of the file. File numbering
begins with 0.
fin.seekg(112); locates the file pointer at byte 112, which is the 113th byte in the file.
The streamoff argument represents the file position in bytes measured as an offset
from one of three locations.
The seek_dir argument can be set to one of three constants:
1. ios::beg measure the offset from the beginning of the file
2. ios::cur measure the offset from the current position
3. ios::end measure the offset from the end of the file.
Examples
fin.seekg(30); // go to byte number 30 in the
file fin.seekg(30, ios::beg); // same as above
fin.seekg(-1, ios::cur); // back up one byte
fin.seekg(0, ios::end); // go to the end of the file
fin.seekg(0); // go to the start of the file
To check the current position of a file pointer, use tellg() method input streams and
tellp() method for output streams.
Each returns a streampos value representing the current position, in bytes, measured
from the beginning of the file. When you create an fstream object, the input and
output pointers move in tandem, so tellg() and tellp() return the same value. But if
you use an istream object to manage the input stream and an ostream object to
manage the output stream to the same file, then the input and output pointers move
independently of one another and tellg() and tellp() can return different values.
Example 4
The example below does the following:
Display the current contents of the planet.dat file
83
Ask which record you wish to modify
Modify that record
Show the revised file.
# include<fstream.h>
# include<iomanip.h>
# include<stdlib.h>
struct planet
{
char name[20];
double population;
double g;
};
const char* file="planet.dat";
int main (void)
{
planet pl;
cout.setf(ios::fixed,ios::floatfield);
//show initial contents
fstream finout;// read and write streams
finout.open(file,ios::in|ios::out|ios::ate|ios::binary);
int ct=0;
if(finout.good())
{
finout.seekg(0); // go to the beginning
cout<<"Here are the current contents of the "<<file <<" file\n";
while( finout.read((char*)&pl, sizeof pl))
{
cout<<ct++<<":"<<setw(20)<<pl.name<<":"<<setprecision(0)<<setw(12)<<
pl.population<<setprecision(2)<<setw(6)<<pl.g<<"\n";
}
if(finout.eof()) finout.clear();// clear eof flag
else{
cerr<<"Error in reading "<<file<< "\n";
exit(1);
}
}
else{
cerr<<file<<" could not be opened\n";
exit(2);
}
// change a record
84
char ch;
cout<<"Enter the record number you wish to change\n";
streampos record;
cin>>record;
cin.ignore(80,'\n');// get rid of newline if(record<0||
record>=ct)
{
cerr<<"Invalid record number bye\n";
exit(3);
}
finout.seekg(record*sizeof pl);
if(finout.fail())
{
cerr<<"Error on attempted seek\n";
exit(4);
}
finout.read((char*)&pl, sizeof pl);
cout<<"Your Selection:\n";
cout<<record<<":"<<
setw(20)<<pl.name<<":"<<setprecision(0)<<setw(12)<<pl.population<<setpreci
sion(2)<<setw(6)<<pl.g<<"\n";
if(finout.eof())finout.clear();//clear eof flag
finout.seekp(record*sizeof pl);//go back
cout<<"Enter planet name:\n";
cin.get(pl.name,20).get(ch);
cout<<"Enter planetary population:\n";
cin>>pl.population;
cout<<"Enter planet\'s acceleration of gravity:\n";
cin>>pl.g;
finout.write((char*)&pl, sizeof pl)<<flush;
if(finout.fail())
{
cerr<<"Error on attempted write\n";
exit(5);
}
//show revised file
ct=0;
finout.seekg(0);// go to beginning of file
cout<<"Here are the new contents of the "<<file<<"file\n";
while(finout.read((char*)&pl, sizeof pl))
{
cout<<ct++<<setw(20)<<pl.name<<":"<<setprecision(0)<<setw(12)<<pl.popula
tion<<setprecision(2)<<setw(6)<<pl.g<<"\n";
85
}
finout.close();
return 0;
}
86
Containers: These are objects that hold other objects, such as list, stack or queue.
Iterators: They provide a way to cycle through the contents of a container. They are
a generalization of pointers. An iterator represents a certain position in a container.
If you have an iterator called curr, you can access the item in the container that
curr references by using the notation *curr.
The following fundamental operations define the behavior of an iterator:
Operator * : Returns the element of the current position. If the elements
have members, you can use operator -> to access those members directly
from the iterator.
Operator ++: Lets the iterator step forward to the next element.
Operators == and != : Return whether two iterators represent the same
position.
Operator =: Assigns an iterator (the position of the element to which it
refers).
Categories of Iterators
Iterators are subdivided into different categories that are based on their general
abilities. Their categories are:
1. Bidirectional Iterators: They enable you to move to either the next or
previous items in a container. The iterators of the container classes list, set
multiset, map and multimap are bidirectional.
87
2. Random Access Iterators: They have all the properties of bidirectional
iterators but in addition can perform random access. They particularly
provide operators for さ iterator arith マ etic ざ (in accordance with the さ
poi ミ ter arith マ etic ざ of an ordinary pointer. You can add and subtract
offsets, process differences and compare iterators using relational
operators: < and
>. The iterators of the container classes vector, deque and strings are
random access.
All container classes provide the same basic member functions that enable them to
use iterators to navigate over their elements. The most important of these functions
are as follows:
begin(): Returns an iterator that represents the beginning of the elements
in the containers.
end(): Returns an iterator that represents the end of the elements in the
container. The end is the position behind the last element.
Templates
Templates are functions or classes that are written for one or more types not yet
specified. When you use template you pass the types as arguments, explicitly or
implicitly.
Examples
1. The function below returns the maximum of two values:
template <class T>
inline const T& max (const T& a, const T& b)
{
// if a<b then use b else use a
return a<b?b:a
}
The first line defines T as an arbitrary data type that is specified by the caller when
the caller calls the function. You can use any identifier as a parameter name, but
using T is very common.
2. Class templates allow you to develop a class and defer certain data-type
information until you are ready to use the class. With templates, data types are
left as data-type parameters in the definition of the class. The class definition is
preceded by template<class T>, where the data-type parameter T represents
the data type that the client will specify.
template <class T> class myclass
{
private:
T theData;
public:
88
myclass( );
myclass (T initialData);
void setData(T newData);
T getData();
};
When you declare instances of the class, you specify the actual data type the
parameter T represents. For example, consider:
Int main()
{
myclass<int> a;
myclass<double> b(5.4);
a.setData(5);
cout<<b.getData()<<endl;
}
Lists
Suppose you write a one-column list of items, you probably add new items to the
end of the list. You could also add items to the beginning of the list or add them so
that your list is sorted alphabetically. Regardless, the items on a list appear in
sequence. The list has one first item and one last item. Except for the first and last
items, each item has a unique predecessor and a unique successor. The first item-
the head or front of the list – does not have a predecessor, and the last item- the tail
or end of the list does not have a successor.
89
The ADT list is simply an ordered collection of items that you reference by position
number. A list manages its elements as a doubly linked list.
namespace std
{
template<class T, class Allocator=allocator<T>> class list;
}
The elements of a list may have any type T that is assignable and copyable. The
optional second template parameter defines the memory model. The default memory
model is the model allocator, which is provided by the C++ standard library.
List Operations
Create, Copy and Destroy Operations
Operation Effect
list<Elem> c Creates an empty list without any elements
list<Elem>c1(c2) Creates a copy of another list of the same type (all elements
are copied)
list<Elem> c(n) Creates a list with n elements that are created by the default
constructor
list<Elem>c(n,elem) Creates a list initialized with n copies of element elem
list<Elem>c(beg,end) Creates a list initialized with the elements of the range[beg,
end)
c.~list<Elem>() Destroys all elements and frees the memory
Nonmodifying Operations
operation Effect
c.size() Returns the actual number of elements
c.empty() Returns whether the container is empty
c.max_size() Returns the maximum number of elements possible
c1==c2 Returns whether c1 is equal to c2
c1!=c2 Returns whether c1 is not equal to c2
c1<c2 Returns whether is less than c2
c1>c2 Returns whether c11 is greater than c2
c1<=c2 Returns whether c1 is less or equal to c2
c1>=c2 Returns whether c1 is greater or equal to c2
Assignments
90
Lists also provide the usual assignment operations for sequence containers.
Operation Effect
c1 =c2 Assigns all elements of c2 to c1
c.assign(n, elem) Assigns n copies of element elem
c.assign(beg,end) Assigns the elements of the range [beg,end)
c.swap(c2) Swaps the data of c1 and c2
swap(c1,c2) Same (as global function)
Element Access
Because a list does not have random access, it provides only front() and back() for
accessing element directly.
Operation Effect
c.front() Returns the first element
c.back() Returns the last element
Iterator Functions
You use iterators to access all elements of a list.
Operation Effect
c.begin() Returns a bidirectional iterator for the first element
c.end() Returns a bidirectional iterator for the position after the last
element
c.rbegin() Returns a reverse iterator for the first element of a reverse
iteration
c.rend() Returns a reverse iterator for the position after the last element
of a reverse iteration
Operation Effect
c.insert(pos, elem) Inserts at iteration position pos, a copy of elem and returns
the position of the new element
c.insert(pos,n,elem) Inserts at iterator position pos n copies of elem (returns
nothing)
c.remove(val) Removes all elements with value val
c.clear() Removes all elements (makes the container empty)
For other operations provided by ADT list, consult “ The C++ Standard Library”
by Nicolai M. Josuttis, published by Addison-Wesley
91
Example
The STL class list is used to maintain a grocery list in the program below:
#include<list>
#include
<iostream.h>
#include<string>
using namespace std;
int main(void)
{
list<string> grocerylist;// create an empty list
list<string>::iterator i = grocerylist.begin();
i=grocerylist.insert(i,"apples");
i=grocerylist.insert(i,"bread");
i=grocerylist.insert(i,"juice");
i=grocerylist.insert(i,"carrots");
cout<<"Number of items on my grocery list: "<<grocery.size()<<endl;
i=grocerylist.begin();
while(i!=grocerylist.end())
{
cout<<*i<<endl;
i++;
}
return 0;
}
int main()
vector<int> coll;
vector<int>::iterator pos;
// insert elements from 1 to 6 in arbitrary order
coll.push_back(2);
coll.push_back(5);
coll.push_back(4);
coll.push_back(1);
coll.push_back(6);
coll.push_back(3);
// find and print minimum and maximum elements
pos=min_element(coll.begin(), col.end());
92
cout<<”min: “<< *pos<<endl;
pos=max_element(coll.begin( ), col.end());
cout<<”max:”<< *pos<<endl;
// sort all elements
sort(coll.begin(), coll.end());
// print all element
For(pos=coll.begin(); pos!=col.end(); ++pos) cout<<*pos<<‟ „;
cout<<endl;
}
Stack
A stack is a linear list in which insertions and removals take place at the same end.
The end is called the top; the other end is called the bottom.
insertion deletion
top
bottom
A stack behaves in LIFO (Last in First Out) manner.
ADT Stack
The following operations define the ADT stack:
1. Create an empty stack
2. Destroy a stack
3. Determine whether a stack is empty
4. Add a new item to the stack
5. Remove from the stack the item that was added most recently
6. Retrieve from the stack the item that was added most recently.
The class stack<> implements a stack. With push(), you can insert any number of elements
into the stack. With pop(), you can remove the elements in the opposite order in which they
were inserted.
To use a stack, you have to include the header file <stack> ( In the original STL, the
header file for stack was <stack.h>).
93
# include <stack>
In <stack>, the class stack is defined as follows:
namespace std{template<class T>, class Container = deque <T>> class stack;}
The first template parameter is the type of the elements. The optional second template
parameter defines the container that is used internally by the stack for its elements.
The following declares a stack of integers:
std::stack<int> st; // integer stack
Operations
stack::stack()
The default constructor
Creates an empty stack
explicit stack::stack(const Container& cont)
Creates a stack that is initialized by the elements of cont
All elements of cont are copied
size_type stack::size() const
Returns the current number of elements
bool stack::empty()const
Returns whether the stack is empty
It is equivalent to stack::size()==0, but it might be faster
void stack::push(const value_type& elem)
Inserts a copy of elem as the new first element in the stack
value_type& stack::top()
const value_type& stack::top() const
Both forms return the next element of the stack. The next element that was inserted last
(after all other elements in the stack)
The caller has to ensure that the stack contains an element (size()>0); otherwise, the
behaviour is undefined.
The first form for nonconstant stacks returns a reference. Thus, you could modify the
next element while it is in the stack. It is up to you to decide whether this is good style.
void stack::pop()
94
Removes the next element from the stack. The next element is the element that was
inserted last (after all other elements in the stack)
It has no return value. To process this next element, you must call top() first.
The caller must ensure that the stack contains an element (size()>0); otherwise, the
behavior is undefined.
bool comparison( const stack& stack1, const stack& stack2)
Returns the result of the comparison of two stacks of the same type.
comparison might be any of the following:
o operator ==
o operator !=
o operator <
o oprator >
o operator >
o operator <=
o operator >=
Two stacks are equal if they have the same number of elements and contain the same
elements in the same order.
To check whether a stack is less than another stack, the stacks are compared
lexicographically.
Example
# include <iostream>
# include<stack>
using namespace std;
int main()
{
stack<int> astack;
int item;
// right now, the stack is empty
if(astack.empty()) cout<<”The stack is empty”<<endl;
for(int j=0 j<5; j++) astack.push(j);// places items on the top of stack
95
while(!astack.empty())
{
cout<<astack.top()<<” “;
astack.pop();
}
return 0;
}
The output of the program is:
The stack is empty
43210
Queues
A queue is like a line of people. The first person to join a line is the first person
served, that is, to leave the line. New items enter a queue at its back, or rear and
items leave a queue from its front. Operations on a queue occur only at its two ends.
This characteristic gives a queue its first-in, first-out (FIFO) behaviour.
ADT Queue Operations
Create an empty queue
Destroy a queue
Determine whether a queue is empty
Add a new item to the queue
Remove from the queue the item that was added earliest
Retrieve from the queue the item that was added earliest.
The class queue<> implements a queue.
To use a queue, you must include the header file <queue>
#include <queue>
In <queue>, the class queue is defined as follows:
namespace std {template<class T, class Container=deque<T>>class queue;
}
96
The first template parameter is the type of the elements. The optional second
template parameter defines the container that is used internally by the queue for its
elements. The default container is a deque.
The following declaration defines a queue of strings: std::queue<std::string>buffer;//
string queue
The Core Interface
The core interface of queues is provided by the member functions push(), front(),
back() and pop():
push() inserts an element into the queue
front() returns the next element in the queue(element inserted first)
back() returns the last element in the queue (element inserted last)
pop() removes an element from the queue
To check whether the queue contains elements, the member functions size() and
empty() are provided.
Example of Using Queues
#include <iostream>
#include<queue>
#include<string>
using namespace std;
int main()
{
queue<string> q;
// inserts three elements into the queue
q.push(“These”);
q.push(“are”);
q.push(“more than”);
97
// read and print two elements from the queue
cout<<q.front();
q.pop();
cout<< q.front();
q.pop();
// insert two new elements
q.push(“four”);
q.push(“words”);
//skip one element
q.pop();
//read and print two elements
cout<<q.front();
q.pop();
cout<<q.front()<<endl;
q.pop();
//print number of elements in the queue
cout<<”number of elements in the queue: “<<q.size()<<endl;
}
98
size_type queue::size() const
Returns the current number of elements
To check whether the queue is empty (contains no elements) because it
might be faster.
bool queue::empty() const
Returns whether the queue is empty (contains no elements)
It is equivalent to queue::size()==0, but it might be faster
void queue::push(const value_type& elem)
Insert a copy of elem as the new last element in the queue
value_type& queue::front()
const value_type& queue::front()const
Both forms return the last element of the queue. The last element that was
inserted first (before all other elements in the queue)
The caller has to ensure that the queue contains an element (size()>0);
otherwise, the behavior is undefined
The first form for nonconstant queues returns a reference. Thu, you could
modify the next element while it is in the queue. It is u to you to decide
whether that is good style.
value_type& queue::back()
const value_type& queue::back()const
Both forms return the last element of the queue. The last element is the
element that was inserted last (after all other elements in the queue)
The caller must ensure that the queu contains an element (size()>0);
otherwise, the behavior is undefined
The first form for nonconstant queues returns a reference. Thus, you
could modify the last element while it is in the queue.
99
void queue::pop()
Removes the next element from the queue. The next element is the
element that was inserted first )before all other elements in the queue)
Note that this function ha no return value. To process the next element,
you must call front() first
The caller must ensure that the queue contains an element (size()>0);
otherwise, the behavior is undefined
bool comparison(const queue& queue1, const queue& queue2)
Returns the result of the comparison of two queues of the same type
comparison might be any of the following:
operator==, operator !=, operator <, operator >, operator <=, operator>=
two queues are equal if they have the same number of elements and
contain the same elements in the same order (all comparisons of two
corresponding elements must yield true)
To check whether a queue is less than another queue, the queues are
compared lexicographically.
10
0