Escolar Documentos
Profissional Documentos
Cultura Documentos
CS-102L
Faculty
Table of Contents
LAB 1 ............................................................................................................................................................. 3
Review of CS101L & Arrays (1-D and 2-D) .................................................................................................... 3
Lab 2: ........................................................................................................................................................... 13
Pointers in C++ ............................................................................................................................................ 13
Lab 3: ........................................................................................................................................................... 25
Strings in C++............................................................................................................................................... 25
Lab 4: ........................................................................................................................................................... 41
Structures I ................................................................................................................................................. 41
Lab 5: ........................................................................................................................................................... 48
Structures II ................................................................................................................................................. 48
Lab 6: ........................................................................................................................................................... 55
Object Orientation in C++ I ........................................................................................................................ 55
Lab 7: ........................................................................................................................................................... 64
Object Orientation in C++ II ....................................................................................................................... 64
Lab 8 ............................................................................................................................................................ 76
Friend Function & Classes, This Pointer, and static Variables .................................................................... 76
Lab 9 ............................................................................................................................................................ 85
Operator Overloading ................................................................................................................................. 85
Lab 10: ......................................................................................................................................................... 87
Inheritance .................................................................................................................................................. 88
Lab 11: ........................................................................................................................................................ 91
Introduction to Polymorphism and Abstract Base Classes ......................................................................... 92
Lab 12: ......................................................................................................................................................... 97
File Handling in C++..................................................................................................................................... 97
Page 2
LAB 1
Review of CS101L & Arrays (1-D and 2-D)
1-Dimensional Arrays:
Array basics
Let's start by looking at a single variable used to store a person's age.
Example 1.1:
1: #include <iostream.h>
2:
3: int main()
4: {
5:
short age;
6:
age=23;
7:
cout<<"The age is = <<age<<endl;
8:
return 0;
9: }
Not much to it. The variable age is created at line (5) as a short. A value
is assigned to age. Finally, age is printed to the screen.
Now let's keep track of 4 ages instead of just one. We could create 4 separate variables, but 4
separate variables have limited appeal. (If using 4 separate variables is appealing to you, then
consider keeping track of 93843 ages instead of just 4). Rather than using 4 separate variables,
we'll use an array.
Here's how to create an array and one way to initialize an array:
Example 1.2:
1: #include <iostream.h>
2:
3: int main()
4: {
5:
short age[4];
6:
age[0]=23;
7:
age[1]=34;
8:
age[2]=65;
9:
age[3]=74;
10:
return 0;
FCS&E, GIK Institute Topi, Pakistan
Page 3
11: }
On line (5), an array of 4 short's is created. Values are assigned to each variable in the array on
line (6) through line (9).
Accessing any single short variable, or element, in the array is straightforward. Simply provide
a number in square braces next to the name of the array. The number identifies which of the 4
elements in the array you want to access.
The program above shows that the first element of an array is accessed with the number 0 rather than
1. Later in the tutorial, We'll discuss why 0 is used to indicate the first element in the array.
Printing arrays
Our program is a bit unrevealing in that we never print the array to screen. Here is the same program
with an attempt to print the array to the screen:
Example 1.3:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
#include <iostream.h>
int main()
{
short age[4];
age[0]=23;
age[1]=34;
age[2]=65;
age[3]=74;
cout<<"\n" <<age;
return 0;
}
Line (11) is meant to print the 4 ages to the screen. But instead of printing out the four short
variables, what appears to be nonsense prints out instead.
What the "nonsense" output actually is and why the 4 array values were not printed will be addressed
later in the tutorial. For now, the important point to come away with is that simply providing the name of
the array in an output statement will not print out the elements of the array.
How about printing out each of the values separately? Try this:
FCS&E, GIK Institute Topi, Pakistan
Page 4
Example 1.4:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
#include <iostream.h>
int main()
{
short age[4];
age[0]=23;
age[1]=34;
age[2]=65;
age[3]=74;
cout<<"Age at
cout<<"Age at
cout<<"Age at
cout<<"Age at
return 0;
}
index[0]=
index[1]=
index[2]=
index[3]=
"<<age[0]<<endl;
"<<age[1]<<endl;
"<<age[2]<<endl;
"<<age[3]<<endl;
Lines (10) through line (13) produce the output we are expecting.
There is no single statement in the language that says "print an entire array to the screen". Each
element in the array must be printed to the screen individually.
Copying arrays
Suppose that after filling our 4 element array with values, we need to copy that array to another
array of 4 short's? Try this:
Example 1.5:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
#include <iostream.h>
int main()
{
short age[4];
short same_age[4];
age[0]=23;
age[1]=34;
age[2]=65;
age[3]=74;
same_age=age;
cout<<"The
cout<<"The
cout<<"The
cout<<"The
return 0;
age
age
age
age
at
at
at
at
index[0]"<<same_age[0];
index[1]"<<same_age[1];
index[2]"<<same_age[2];
index[3]"<<same_age[3];
Line (12) tries to copy the age array into the same_age array. What happened when you tried to
compile the program above?
Page 5
The point here is that simply assigning one array to another will not copy the elements of the array. The
hard question to answer is why the code doesn't compile. Later in the tutorial this example will be reexamined to explain why line (12) doesn't work. This code should not compile on either C or C++
compilers. However, some older C++ compilers may ignore the ISO C++ standard and allow line 12 to
compile. If it does compile on your C++ compiler, make a mental note that it is incorrect behavior.
Let's try copying arrays using a technique similar to the technique used to print arrays (that is,
one element at a time):
Example 1.6:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
#include <iostream.h>
int main()
{
short age[4];
short same_age[4];
age[0]=23;
age[1]=34;
age[2]=65;
age[3]=74;
same_age[0]=age[0];
same_age[1]=age[1];
same_age[2]=age[2];
same_age[3]=age[3];
cout<<"The
cout<<"The
cout<<"The
cout<<"The
return 0;
age
age
age
age
at
at
at
at
index[0]=
index[1]=
index[2]=
index[3]=
"<<same_age[0]<<endl;
"<<same_age[1]<<endl;
"<<same_age[2]<<endl;
"<<same_age[3]<<endl;
This technique for copying arrays works fine. Two arrays are created: age and same_age. Each
element of the age array is assigned a value. Then, in order to copy of the four elements in age
into the same_age array, we must do it element by element.
Page 6
The technique used to copy one array into another is exactly the same as the technique used to
copy 4 separate variables into 4 other variables. So what is the advantage to using arrays over
separate variables?
One significant advantage of an array over separate variables is the name. In our examples, using
four separate variables requires 4 unique names. The 4 short variables in our array have the
same name, age. The 4 short's in the array are identical except for an index number used to
access them. This distinction allows us to shorten our code in a way that would be impossible
with 4 variables, each with unique names:
Example 1.7:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
#include <iostream.h>
int main()
{
short age[4];
short same_age[4];
int i, j;
age[0]=23;
age[1]=34;
age[2]=65;
age[3]=74;
for(i=0; i<4; i++)
same_age[i]=age[i];
for(j=0; j<4; j++)
cout<<"The age is = "<<same_age[j]<<endl;
return 0;
}
Since the only difference between each of the short's in the arrays is their index, a loop and a
counter can be used to more easily copy all of the elements. The same technique is used to
shorten the code that prints the array to the screen.
FCS&E, GIK Institute Topi, Pakistan
Page 7
Even though arrays give us some convenience when managing many variables of the same type,
there is little difference between an array and variables declared individually. There is no single
statement to copy an array, there is no single statement to print an array.
If we want to perform any action on an array, we must repeatedly perform that action on each element
in the array.
Example 1.8:
#include<iostream.h>
void main( )
{
int stud[4][2] ;
int i,j ;
for ( i = 0 ; i <= 3 ; i++ )
{
cout<<"\n Enter roll no. and marks "<<endl ;
cin>>stud[i][0]>>stud[i][1];
}
cout<<"The Roll No. and Marks are: "<<endl<<endl;
cout<<"RollNo.\t Marks\n";
for ( i = 0 ; i <= 3 ; i++ )
cout<<stud[i][0]<<" \t "<<stud[i][1]<<endl ;
}
There are two parts to the programin the first part through a for loop we read in the values of roll
no. and marks, whereas, in second part through another for loop we print out these values.
Look at the cin( ) statement used in the first for loop:
cin>>stud[i][0]>>stud[i][1];
In stud[i][0] and stud[i][1] the first subscript of the variable stud, is row number which changes for
every student. The second subscript tells which of the two columns are we talking aboutthe zeroth
FCS&E, GIK Institute Topi, Pakistan
Page 8
column which contains the roll no. or the first column which contains the marks. Remember the
counting of rows and columns begin with zero. The complete array arrangement is shown below.
Thus, 1234 is stored in stud[0][0], 56 is stored in stud[0][1] and so on. The above arrangement
highlights the fact that a two- dimensional array is nothing but a collection of a number of onedimensional arrays placed one below the other.
In our sample program the array elements have been stored rowwise and accessed rowwise.
However, you can access the array elements columnwise as well. Traditionally, the array elements
are being stored and accessed rowwise; therefore we would also stick to the same strategy.
Page 9
Example 1.9:
This example will simply define an array of 2x3 and take input from user and prints scanned
array.
#include<iostream.h>
void main()
{
int arr[2][3],i,j;
for(i=0;i<2;i++)
for(j=0;j<3;j++)
{
cout<<"Enter Value for " <<i+1 << " row " <<j+1 <<" column\n";
cin>>arr[i][j];
}
cout<<"\n!!!Scanning Array Complete!!!\n\n\n";
for(i=0;i<2;i++)
{
for(j=0;j<3;j++)
FCS&E, GIK Institute Topi, Pakistan
Page 10
cout<<arr[i][j] <<"\t";
cout<<"\n\n";
}
}
Example 1.10:
#include <iostream>
using namespace std;
int main()
{
// A 2-Dimensional array
double distance[2][4];
cout<<"Enter values\n";
for(int i = 0; i < 2; ++i)
cin>>distance[i][j];
// For Rows
// For Columns
cout << "\nDistance [" << i << "][" << j << "]: " << distance[i][j];
Page 11
would do for any regular function and, in its parentheses, specify that the argument is an array.
Here is an example:
#include <iostream>
using namespace std;
void DisplayTheArray(double member[]) //function
{
for(int i = 0; i < 5; ++i)
cout << "\nDistance " << i + 1 << ": " << member[i];
cout << endl;
}
int main()
{
const int numberOfItems = 5;
double distance[] = {44.14, 720.52, 96.08, 468.78, 6.28};
cout << "Members of the array";
DisplayTheArray(distance);
//calling the function
return 0;
}
Note:
Before doing your lab exercises run the examples.
Exercises will be provided by the instructors in the lab.
Page 12
Lab 2:
Pointers in C++
In this lab we will be discussing pointers in detail. This is one of the most important
concepts in C++ language. Pointers are used everywhere in C++, so if you want to use the C++
language fully you have to have a very good understanding of pointers. They have to become comfortable
for you.
C++ uses pointers in three different ways:
C++ uses pointers to create dynamic data structures -- data structures built up from blocks of
memory allocated from the heap at run-time.
Pointers in C++ provide an alternative way to access information stored in arrays. Pointer
techniques are especially valuable when you work with strings. There is an intimate link between
arrays and pointers in C++.
To fully grasp the concept of pointers all you need is the concept and practice of pointers. Before
talking about pointers lets talk a bit about computer memory.
Computer Memory
Essentially, the computer's memory is made up of bytes. Each byte has a number, an address,
associated with it. The picture below represents several bytes of a computer's memory. In the
picture, addresses 924 thru 940 are shown.
Example 2.1:
#include<iostream>
using namespace std;
int main()
FCS&E, GIK Institute Topi, Pakistan
Page 13
{
float fl=3.14;
cout<<fl;
cin>>fl;
return 0;
}
The output for this program will be
Pointer:
In C++ a pointer is a variable that points to or references a memory location in which data is stored. A
pointer is a variable that points to another variable. This means that a pointer holds the memory address
of another variable. Put another way, the pointer does not hold a value in the traditional sense; instead, it
holds the address of another variable. A pointer "points to" that other variable by holding a copy of its
address. Because a pointer holds an address rather than a value, it has two parts. The pointer itself holds
the address and that address points to a value.
Page 14
Pointer declaration:
A pointer is a variable that contains the memory location of another variable. The syntax is as
shown below. You start by specifying the type of data stored in the location identified by the
pointer. The asterisk tells the compiler that you are creating a pointer variable. Finally you give
the name of the variable.
Data_type *variable_name
Such a variable is called a pointer variable (for reasons which hopefully will become clearer a
little later). In C++ when we define a pointer variable we do so by preceding its name with an
asterisk. In C++ we also give our pointer a type which, in this case, refers to the type of data
stored at the address we will be storing in our pointer. For example, consider the variable
declaration:
int *ptr;
int k;
ptr is the name of our variable (just as k is the name of our integer variable). The '*' informs the
compiler that we want a pointer variable, i.e. to set aside however many bytes is required to store
an address in memory. The int says that we intend to use our pointer variable to store the address
of an integer.
Referencing Operator
Suppose now that we want to store in ptr the address of our integer variable k. To do this we use
the unary & operator and write:
ptr = &k;
What the & operator does is retrieve the address of k, and copies that to the contents of our
pointer ptr. Now, ptr is said to "point to" k.
Dereferencing operator
The "dereferencing operator" is the asterisk and it is used as follows:
*ptr = 7;
will copy 7 to the address pointed to by ptr. Thus if ptr "points to" (contains the address of) k,
the above statement will set the value of k to 7. That is, when we use the '*' this way we are
referring to the value of that which ptr is pointing to, not the value of the pointer itself.
Similarly, we could write:
cout<<*ptr<<endl;
to print to the screen the integer value stored at the address pointed to by ptr;.
Page 15
This Example will be very helpful in understanding the pointers. Understand it thoroughly how it
works and then proceed.
Example 2.2:
#include<iostream>
using namespace std;
int main ()
{
int firstvalue = 5, secondvalue = 15;
int * p1, * p2;
p1 = &firstvalue;
// p1 = address of firstvalue
p2 = &secondvalue;
// p2 = address of secondvalue
*p1 = 10;
// value pointed by p1 = 10
*p2 = *p1;
// value pointed by p2 = value pointed by p1
p1 = p2;
// p1 = p2 (value of pointer is copied)
*p1 = 20;
// value pointed by p1 = 20
cout<<"First Value is " << firstvalue<<endl;
cout<<"Second Value is " <<secondvalue<<endl;
return 0;
}
Output of this listing is as follows:
How it Works:
FCS&E, GIK Institute Topi, Pakistan
Page 16
Here in this code we are trying to play with memory and address of our variables for the better
understanding of Pointers. On line number 5 we have two integer variables (i.e firstvalue and
secondvalue). Both are assigned values of 5 and 15 respectively. On line number 6 we have two
integer pointer variables (i.e p1 and p2). Both are assigned addresses of variables in line 5
firstvalue and secondvalue respectively in line 7 and 8.
firstvalue
secondvalue
758
15
754
P1
P2
In line 9 we see that *p1 is assigned value 10. This means that 10 should be copied in the
variable, which is lying on an address to which p1 is pointing. We know that p1 is pointing to
address of firstvalue. So line 9 results in assigning firstvalue the value of 10.
firstvalue
secondvalue
10
758
15
754
P1
P2
In line 10 we encounter another assignment which says that value of variable pointed by p2
should be replaced with the value of variable pointed by p1. So now secondvalue is assigned
with value 10 as well.
firstvalue
secondvalue
10
758
10
754
P1
P2
Well the assignment in line 11 is a bit confusing but very simple, all this assignment is doing is
that now p1 is pointing to the same address as p2. So now we can say p1 and p2 are pointing at
same address.
firstvalue
FCS&E, GIK Institute Topi, Pakistan
secondvalue
Page 17
10
758
754
P2
P1
In line 12 we see that *p1 is assigned value 20. This means that 10 should be copied in the
variable, which is lying on an address to which p1 is pointing. We know that p1 is now pointing
to address of secondvalue because in last line we pointed p1 to the address being pointed by p2.
So line 12 results in assigning secondvalue the value of 20.
firstvalue
secondvalue
10
758
20
754
P2
P1
Now when we print the value of first value and second value it prints 10 for firstvalue and 20 for
secondvalue; which is right due to the reasons explained above.
Pointers: Pointing to the Same Address
Here is a cool aspect of C++: Any number of pointers can point to the same address. For
example, you could declare p, q, and r as integer pointers and set all of them to point to i, as shown here:
int i;
int *p, *q, *r;
p = &i;
q = &i;
r = p;
Note that in this code, r points to the same thing that p points to, which is i. You can assign pointers to
one another, and the address is copied from the right-hand side to the left-hand side during the
assignment. After executing the above code, this is how things would look:
Page 18
The variable i now has four names: i, *p, *q and *r. There is no limit on the number of pointers that can
hold (and therefore point to) the same address.
Pointer Arithmetics
Like other variables pointer variables can be used in expressions. For example if p1 and p2 are
properly declared and initialized pointers, then the following statements are valid.
y=*p1 * *p2;
sum=sum+*p1;
z= 5 - *p2/*p1;
*p2= *p2 + 10;
C++ allows us to add integers to or subtract integers from pointers as well as to subtract one
pointer from the other. We can also use short hand operators with the pointers p1+=; sum+=*p2;
etc.,
we can also compare pointers by using relational operators the expressions such as p1 >p2 ,
p1==p2 and p1!=p2 are allowed.
When an integer is added to, or subtracted from, a pointer, the pointer is not simply incremented
or decremented by that integer, but by that integer times the size of the object to which the
pointer refers. The number of bytes depends on the object's data type.
/*Program to illustrate the pointer expression and pointer arithmetic*/
Example 2.3:
1:#include<iostream>
2:using namespace std;
3: int main()
4: {
5:int *ptr1,*ptr2;
6:int a,b,x,y,z;
7:a=30;b=6;
FCS&E, GIK Institute Topi, Pakistan
Page 19
8:ptr1=&a;
9:ptr2=&b;
10:x=*ptr1 + *ptr2 - 6;
11:y=6 - *ptr1 / *ptr2 +30;
12: cout<<"Address of a: "<<ptr1<<endl;
13: cout<<"Address-Value in ptr1: "<<*ptr1<<endl<<endl;
//The comment value is the value of ptr1 (address of the a)
14: cout<<"Address of b: "<<ptr2<<endl;
15: cout<<"Address-Value in ptr1: "<<*ptr2<<endl<<endl;
//The comment value is the value of ptr2 (address of the b)
16: cout<<"a: " <<a<<" b: "<<b<<endl<<endl;
//Simply prints the value of a and b
17: cout<<"x: " <<x<<" y: "<<y<<endl<<endl;
//Simply prints the value of x and y.
18: ptr1=ptr1 + 1; // adds 4 in address of ptr1. (1*4 = 4)
19: ptr2= ptr2;
20: cout<<"a: " <<a<<" b: "<<b<<endl;
//Simply prints the value of a and b
21: cout<<"Value in ptr1: "<<ptr1<<endl<<endl; // 2293564
//The comment value is the new memory location value of ptr1
22: cout<<"\nAddress-Value in ptr1: "<<*ptr1<<endl<<endl; // garbage value
//The comment value is the new value of ptr1 (garbage value)
23: cout<<"Address of b: "<<ptr2<<endl<<endl;
24: cout<<"\nAddress-Value in ptr1: "<<*ptr2<<endl<<endl;
cin>>a;
}
Here note that adding some thing in *ptr1 changes the value of the address stored
in ptr. However adding some thing in ptr will change the address it is pointing to.
Printing ptr1 after adding 1 in it gives different address as it has changed by 4
Bytes.
Page 20
How it Works:
This code explains all the rules related to arithematic of pointers. From line 1 to line 11, it is
simply adding, subtracting and like manipulating with the pointers and variables. After all the
manipulations and arithmetics it started printing values of pointers and other simple variables till
line 12.
a
30
7864
7868
7872
7876
7880
ptr1
7884
7888
ptr2
At line 18 it adds 1 to ptr1. Mostly people think that this will change the address of the pointer,
but they are totally wrong. Remember pointer is pointing to an address. This addition does not
change the address of the pointer, infact it changes the value of the pointer (not the value of the
address pointer is pointing at.). ptr1 has the address of variable of variable a . So it adds 1 *4
Btytes = 4bytes in the address of a which is stored in ptr1. Where as ptr2 points at the same value
as before due to the assignment of line 19.
a
b
30
7864
7868
7872
0XF
7876
ptr1
7880
7884
7888
ptr2
Line 20 prints the same value as was printed by the Line 16, because values of the variable was
never changed, in fact ptr1s value which was address of a was changed. Now Line 21 will print
Page 21
the value stored in ptr1; which is address of memory 4 bytes ahead of variable a. Line 22 is
trying to print the value at address, ptr1 is now pointing to, which was never assigned any value.
Example 2.4:
#include<iostream>
void swap(int *,int *);
using namespace std;
int main( )
{
int a = 10, b = 20 ;
int *p, *q;
p = &a;
q = &b;
swap( p, q) ;
cout<<"a: "<<a<<"b: "<<b<<endl;
cin>>a;
}
void swap( int *x, int *y )
{
int t = *x ;
*x = *y;
*y = t;
}
The output of the above program would be:
a = 20 b = 10
Note that this program manages to exchange the values of a and b using their addresses stored in x and
y.
Function Pointers
FCS&E, GIK Institute Topi, Pakistan
Page 22
A function pointer is a variable that stores the address of a function that can later be called through that
function pointer. A useful technique is the ability to have pointers to functions. Their declaration is
easy: write the declaration as it would be for the function, say
int func(int a, float b);
And simply put brackets around the name and a * in front of it: that declares the pointer. Because of
precedence, if you don't parenthesize the name, you declare a function returning a pointer:
/* function returning pointer to int */
int *func(int a, float b); //Wrong
/* pointer to function returning int */
int (*func)(int a, float b);
Once you've got the pointer, you can assign the address of the right sort of function just by using its
name: like an array, a function name is turned into an address when it's used in an expression. You can
call the function as:
(*func)(1,2);
Example 2.5
#include<iostream>
using namespace std;
void func(int);
int main(){
void (*fp)(int);
fp = func;
(*fp)(1);
cout<<endl;
fp(2);
system("PAUSE");
return 0;
}
Void func(int arg)
{
cout<<arg<<endl;
}
Page 23
mypointer = myarray;
After that, mypointer and myarray would be equivalent and would have very similar properties. The
main difference being that mypointer can be assigned a different address, whereas myarray can
never be assigned anything, and will always represent the same block of 20 elements of type int.
Therefore, the following assignment would not be valid:
myarray = mypointer;
Let's see an example that mixes arrays and pointers:
Example 2.6:
#include <iostream>
using namespace std;
int main ()
{
int numbers[5];
int * p;
p = numbers; *p = 10;
p++; *p = 20;
p = &numbers[2]; *p = 30;
p = numbers + 3; *p = 40;
p = numbers; *(p+4) = 50;
for (int n=0; n<5; n++)
cout << numbers[n] << ", ";
return 0;}
Output would be
10, 20, 30, 40, 50,
Pointers and arrays support the same set of operations, with the same meaning for both. The main
difference being that pointers can be assigned new addresses, while arrays cannot.
In the chapter about arrays, brackets ([]) were explained as specifying the index of an element of the
array. Well, in fact these brackets are a dereferencing operator known as offset operator. They
dereference the variable they follow just as * does, but they also add the number between brackets to
the address being dereferenced. For example:
a[5] = 0;
*(a+5) = 0;
// a [offset of 5] = 0
// pointed by (a+5) = 0
These two expressions are equivalent and valid, not only if a is a pointer, but also if a is an array.
Remember that if an array, its name can be used just like a pointer to its first element.
Note:
Before doing your lab exercises run the examples.
Exercises will be provided by the instructors in the lab.
Page 24
Lab 3:
Strings in C++
Handling of Character String
Reading string
Displaying strings
Combining or concatenating strings
Copying one string to another.
Comparing string & checking whether they are equal
Extraction of a portion of a string
Strings are stored in memory as ASCII codes of characters that make up the string
appended with \0 (ASCII value of null). Normally each character is stored in one byte;
successive characters are stored in successive bytes.
Arrays of Characters
Re-Introduction to Characters
As it happens, strings are the most used items of computers. In fact, anything the user types is a
string. It is up to you to convert it to another, appropriate, type of your choice. This is because
calculations cannot be performed on strings. On the other hand, strings can be a little complex,
FCS&E, GIK Institute Topi, Pakistan
Page 25
which is why we wanted to first know how to use the other types and feel enough comfortable
with them.
Consider a name such as James. This is made of 5 letters, namely J, a, m, e, and s. Such letters,
called characters, can be created and initialized as follows:
char L1 = 'J', L2 = 'a', L3 = 'm', L4 = 'e', L5 = 's';
To display these characters as a group, you can use the following:
cout << "The name is " << L1 << L2 << L3 << L4 << L5;
Here is such a program:
Example 2.3:
#include <iostream>
using namespace std;
int main()
{
char L1 = 'J', L2 = 'a', L3 = 'm', L4 = 'e', L5 = 's';
cout << "The name is " << L1 << L2 << L3 << L4 << L5;
return 0;
}
Page 26
The C/C++ provides another alternative. It allows you to declare and initialize the array as
a whole. To do this, include the name in double-quotes. With this technique, the curly
brackets that delimit an array are not necessary anymore. Here is an example:
char Name[12] = "James";
With this technique, the item between the double-quotes is called a string. It is also
referred to as the value of the string or the value of the variable.
When declaring and initializing an array of characters, the compiler does not need to know
the number of characters of the string. In fact, you can let the compiler figure it out.
Therefore, you can leave the square brackets empty:
char Name[] = "James";
After declaring such an array, the compiler would count the number of characters of the
variable, add one more variable to it and allocate enough space for the variable. The
character added is called the null-terminated character and it is represented as \0.
Therefore, a string such as James would be stored as follows:
FCS&E, GIK Institute Topi, Pakistan
Page 27
\0
Page 28
Today is Thursday
Page 29
Student 1: Hermine
Student 2: Paul
Student 3: Gertrude
Student 4: Leon
When declaring and initializing such an array, the compiler does not need to know the
number of strings in the array; it can figure it out on its own. Therefore, you can leave the
first pair square brackets empty. If you are only declaring the array but cannot initialize,
then you must specify both dimensions.
Pointers can be quite challenging to learn. The diagram should provide a better insight.
The variable name (a pointer to a char) was located at address 0x007fe78. A pointer is 32 bits, ie. 4
bytes long and holds an address of the string which was 0x00408004. Addresses are always given
in hexadecimal. The arrow shows what the pointer is pointing to, ie what is at the address in the
pointer variable.
At this address there were 13 bytes holding the string "David Bolton" with a terminating NULL, the
same value as '\0'.
Page 30
To request the value of an array of characters from the user, you can declare a pointer to char and
initialize it with an estimate number of characters using the new operator. Here is an example:
Example 3.4:
#include <iostream>
using namespace std;
int main()
{
char *StudentName = new char[20];
cout << "Enter Sudent First Name: ";
cin >> StudentName;
cout << "\nStudent First Name: " << StudentName;
return 0;
}
On the other hand, the following declaring of an array of characters and its later initialization is
perfectly legal:
Example 3.8:
#include <iostream>
using namespace std;
int main()
{
char *Country;
Country = "Republique d'Afrique du Sud";
cout << "Country Name: " << Country << "\n\n";
return 0;
}
Page 31
Page 32
String I/O
A common problem with reading strings from user input is that it could contain white
spaces. Remember that white space (e.g. space, tab, newline) is treated as termination for
cin.
Take the following code for example:
cout << Enter your full name: ;
string fullname;
//only the first name will be read in!!
cin >> fullname;
String Processing
In addition to giving us a new data type to hold strings, the string library offers many
useful string processing methods.
You can find most of them of them in the book, but here are a few useful ones.
at(index)
This method returns the character at the specified index. Indices start from 0.
Example:
string n = Vikram;
//the character V will be output.
cout << n.at(0) << endl;
FCS&E, GIK Institute Topi, Pakistan
Page 33
erase(index)
This method removes all characters from the string starting from the specified index to
the end.
The length of the new string is reset to index!
Example:
string os = Operating Systems;
os.erase(9);
//the string Operating is output
cout << os << endl;
//length is now 9, the index
cout << os.length() << endl;
find(str)
This method returns the integer index of the first occurrence of the specified string
Example:
string d = data data data;
//0 is output
cout << d.find(data) << endl;
Page 34
find(str, index)
This method returns the integer index of the first occurrence of the specified string
starting from the specified index.
Returns -1 if pattern is not found.
Example:
string d = data data data;
//5 is output
cout << d.find(data, 1) << endl;
Why? Because by specifying a starting index of 1, we only consider
ata data data
insert(index, str)
Inserts the specified string at the specified index.
Example:
string animal = Hippo;
animal.insert(0, Happy );
//outputs Happy Hippo
cout << animal << endl;
replace(index, n, str)
Removes n characters in the string starting from the specified index, and inserts the
specified
string, str, in its place.
Example:
string transport = Speed Boat;
transport.replace(0, 5, Sail);
//outputs Sail Boat
cout << transport << endl;
substr(index, n)
Returns the string consisting of n characters starting from the specified index.
Page 35
Example:
string transport = Cruise Ship;
//outputs Ship
cout << transport.substr(7, 4) << endl;
strlen()
strcpy()
strncpy()
strcat()
strncat()
strcmp()
strncmp()
Strlen ()
Syntax:
len = strlen(ptr);
where len is an integer and
ptr is a pointer to char
strlen() returns the length of a string, excluding the null. The following code will result in
len having the value 13.
int len;
char str[15];
strcpy(str, "Hello, world!");
len = strlen(str);
Example 3.9:
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
Page 36
int len;
char str[15];
strcpy(str, "Hello, world!");
len = strlen(str);
cout<<len<<endl;
system("PAUSE");
return 0;
}
Strcpy ()
strcpy(ptr1, ptr2);
where ptr1 and ptr2 are pointers to char
strcpy() is used to copy a null-terminated string into a variable. Given the following
declarations, several things are possible.
char S[25];
char D[25];
N.B. If you fail to ensure that the source string is null-terminated, very strange and
sometimes very ugly things may result.
Strncpy ()
strncpy(ptr1, ptr2, n);
where n is an integer and
ptr1 and ptr2 are pointers to char
strncpy() is used to copy a portion of a possibly null-terminated string into a variable. Care
must be taken because the '\0' is put at the end of destination string only if it is within the
part of the string being copied. Given the following declarations, several things are possible.
char S[25];
FCS&E, GIK Institute Topi, Pakistan
Page 37
char D[25];
Assume that the following statement has been executed before each of the remaining code
fragments.
Strcat ()
strcat(ptr1, ptr2);
where ptr1 and ptr2 are pointers to char
strcat() is used to concatenate a null-terminated string to end of another string variable.
This is equivalent to pasting one string onto the end of another, overwriting the null
terminator. There is only one common use for strcat().
Char S[25] = "world!";
Char D[25] = "Hello, ";
N.B. If you fail to ensure that the source string is null-terminated, very strange and
sometimes very ugly things may result.
Strncat ()
Syntax: strncat(ptr1, ptr2, n);
where n is an integer and
ptr1 and ptr2 are pointers to char
strncat() is used to concatenate a portion of a possibly null-terminated string onto the end
of another string variable. Care must be taken because some earlier implementations of C
do not append the '\0' at the end of destination string. Given the following declarations,
several things are possible, but only one is commonly used.
Char S[25] = "world!";
FCS&E, GIK Institute Topi, Pakistan
Page 38
Concatenating five characters from the beginning of S onto the end of D and placing
a null at the end:
strncat(D, S, 5);
strncat(D, S, strlen(S) -1);
Both would result in D containing "Hello, world".
N.B. If you fail to ensure that the source string is null-terminated, very strange and
sometimes very ugly things may result.
Strcmp ()
Syntax: diff = strcmp(ptr1, ptr2);
where diff is an integer and
ptr1 and ptr2 are pointers to char
strcmp() is used to compare two strings. The strings are compared character by character
starting at the characters pointed at by the two pointers. If the strings are identical, the
integer value zero (0) is returned. As soon as a difference is found, the comparison is halted
and if the ASCII value at the point of difference in the first string is less than that in the
second (e.g. 'a' 0x61 vs. 'e' 0x65) a negative value is returned; otherwise, a positive value is
returned. Examine the following examples.
char s1[25] = "pat";
char s2[25] = "pet";
diff will have a negative value after the following statement is executed.
diff = strcmp(s1, s2);
diff will have a positive value after the following statement is executed.
diff = strcmp(s2, s1);
diff will have a value of zero (0) after the execution of the following statement, which
compares s1 with itself.
diff = strcmp(s1, s1);
Strncmp ()
Syntax: diff = strncmp(ptr1, ptr2, n);
where diff and n are integers
ptr1 and ptr2 are pointers to char
Page 39
strncmp() is used to compare the first n characters of two strings. The strings are
compared character by character starting at the characters pointed at by the two pointers.
If the first n strings are identical, the integer value zero (0) is returned. As soon as a
difference is found, the comparison is halted and if the ASCII value at the point of difference
in the first string is less than that in the second (e.g. 'a' 0x61 vs. 'e' 0x65) a negative value is
returned; otherwise, a positive value is returned. Examine the following examples.
char s1[25] = "pat";
char s2[25] = "pet";
diff will have a negative value after the following statement is executed.
diff = strncmp(s1, s2, 2);
diff will have a positive value after the following statement is executed.
diff = strncmp(s2, s1, 3);
diff will have a value of zero (0) after the following statement.
diff = strncmp(s1, s2, 1);
Page 40
Lab 4:
Structures I
Structure Declaration
Structure Definition
Structure Variables
Structure Membership
Arrays of structures
STRUCTURES
Arrays require that all elements be of the same data type. Many times it is necessary to group
information of different data types. An example is a materials list for a product. The list typically
includes a name for each item, a part number, dimensions, weight, and cost.
C and C++ support data structures that can store combinations of character, integer floating point and
enumerated type data. They are called a STRUCTS.
Often multiple variables must be passed from one function to another, and often these variables have
different data types. Thus it is useful to have a container to store various types of variables in. Structs
allow the programmer to do just that
A struct is a derived data type that consists of members that are each fundamental or derived data
types.
struct is used to declare a new data-type. Basically this means grouping variables together.
Structures Definition
Before a structure is created, it is necessary to define its overall composition. The format of the a
structure is provided in the shape of a template or pattern which is then used to creatstructure
variables of the same composition. The template is composed of the names and attributes of the data
items to be included in the structure. The definition begins with the keyword structwhich is followed by
a structure declaration consist of a set of user-defined data names and data types. These entries are
separated by semicolons and enclosed within a pair of curly brackets. The definition ends with a
semicolon. Thus, in general, the structure definition has the form
struct tag-name{
type var-1;
type-var-2;
........
typevar-n;
};
wheretag-name is the user-supplied name that identified the structure template; type refers to any
valid data type such as char, int, float, and so forth; and var-1, var-2, .var-n are user-defined variables
Page 41
names, arrays or pointers. The components of a structure are commonly referred to as members or
field.
Example 4.1:
struct employee
{
char name[30];
int age;
float salary;
}
Representation in Memory
struct student
{
char name[20];
introll_no;
};
struct student st;
...
0123
19
roll_no
Structures Variables
This code fragment illustrates how structure variables of the type structemployee are defined.
struct employee{
char name[30];
int age;
float salary;
};
structemployee emp1, emp2, emp3;
In the above example three structure variables emp1, emp2, and emp3 are created. Each structure
consists of three members identified by name, age, and salary.
OR
Page 42
struct employee
{char name[30];
int age;
float salary;
} emp1, emp2, emp3;
Structure membership
Members themselves are not variables they should be linked to structure variables in order to make
them meaningful members.
The link between a member and a variable is established using the member operator . i.e. dot
operator or period operator. We access individual members of a structure with the .operator. For
example to assign a value, we can enter:
structx
{int a;
int b;
int c;
};
main()
{
struct x z;
z.a = 10; // assigns member a of structure variable z value 10.
z.b = 20;
z.c = 30;
}
Page 43
Example 4.2:
structlib_books
{
char title[20];
char author[15];
int pages;
float price;
} book1,book2,book3;
Now
Book1.price
is the variable representing the price of book1 and can be treated like any other ordinary variable.
We can use scanf statement to assign values like
cin>>book1.pages;
Or we can assign variables to the members of book1
strcpy(book1.title,basic);
strcpy(book1.author,Balagurusamy);
book1.pages=250;
book1.price=28.50;
Page 44
Page 45
struct x
{
int a;
int b;
int c;
};
main()
{
struct x z;
z.a = 10;
z.a++;
cout<<" first member is << a; // Error!
// a is not a variable. It is only the name of a member in a structure
cout<<first member is "<<x.a; // Error!
// x is not the name of a variable. It is the name of a type
}
Arrays of structures
It is possible to define a array of structures for example if we are maintaining information of all the
students in the college and if 100 students are studying in the college. We need to use an array than
single variables. We can define an array of structures as shown in the following example:
structure information
{
intid_no;
char name[20];
char address[20];
char combination[3];
int age;
}
student[100];
An array of structures can be assigned initial values just as any other array can. Remember that
each element is a structure that must be assigned corresponding initial values as illustrated below.
int main (void)
{
struct info
{
intid_no;
char name[20];
char address[20];
int age;
}std[100];
intI,n;
cout<<"Enter the number of students "<<endl;
FCS&E, GIK Institute Topi, Pakistan
Page 46
cin>>n;
cout<<endl<<"Enter Id_no,name address combination agem"<<endl;
for(I=0;I <n;I++)
{
cout<<endl<<"\tEnter Record for student "<<I+1<<endl;
cout<<"Student ID:"<<endl;
cin>>std[I].id_no;
cout<<"Student Name: "<<endl;
cin>>std[I].name;
cout<<"Student city: "<<endl;
cin>>std[I].address;
cout<<"Student Age: "<<endl;
cin>>std[I].age;
}
cout<<endl<<"Student information"<<endl;
for (I=0;I<n;I++)
{ cout<<endl<<"Student ID:\n\t"<<std[I].id_no<<endl;
cout<<"Student Name: "<<std[I].name<<endl;
cout<<"Student city: "<<std[I].address<<endl;
cout<<"Student Age: "<<std[I].age;
}
}
Note:
Before doing your lab exercises run the examples.
Exercises will be provided by the instructors in the lab.
Page 47
Lab 5:
Structures II
Structures and functions
Pointers to Structure
Enumerations
Bitwise Operations
Example 5.1:
//Pass 'struct' elements to a function.
int main ()
{
struct { char name[20];
int age;
} record;
strcpy(record.name, "Joe Brown");
record.age = 21;
display (record.name, record.age);
return 0;
}
void display(char *name, int age)
{
cout<<"Name is "<<name <<"\nAge is "<<age;
}
Example 5.2:
Page 48
*/
Pointers to Structure
As we have learnt a memory location can be accessed by a pointer variable. In the similar way a
structure is also accessed by its pointer. The syntax for structure pointer is same as for the ordinary
pointer variable. In general, the structure pointer is defined by the statement struct-type *sptr;
where struct-type refers to structure type specifier, and sptr ia a variable that points to the
structure. It must be ensured that the pointer definition must preceed the structure declaration. For
example, a pointer to struct employee may be defined by the statement struct employee *sptr; In
other words the variable sptr can hold the address value for a structure of type struct employee.
We can create several pointers using a single declaration, as follows:
Struct employee *sptr1, *sptr2, *sptr3;
We have a structure:
struct employee{
char name[30];
int age;
float salary;
};
We define its pointer and variable as follow:
struct employee *sptr1, emp1;
Page 49
A pointer to a structure must be initialized before it can be used anywhere in program. The address
of a structure variable can be obtained by applying the address operator & to the variable. For
example, the statement sptr1 = &emp1;
Enumerations
An enumeration provides context to describe a range of values. The following example shows an
enumeration that contains the four suits in a deck of cards.
enum Suit { Diamonds, Hearts, Clubs, Spades };
Every name of the enumeration becomes an enumerator and is assigned a value that corresponds
to its place in the order of the values in the enumeration. By default, the first value is assigned 0, the
next one is assigned 1, and so on. You can set the value of an enumerator.
enum Suit { Diamonds = 1,
Hearts,
Clubs,
Spades };
The enumerator Diamonds is assigned the value 1. This affects the values that are assigned to
subsequent enumerators; Hearts is assigned the value 2, Clubs is 3, and so on.
Page 50
Example 5.3:
#include<iostream>
#include<conio.h>
using namespace std;
int main()
{
enum Days{Monday=13,Tuesday=8,Wednesday,Thursday};
Days d1=Wednesday;
cout<<d1;
cout<<endl;
int x;
x=int( d1+1);
cout<<x;
getch();
return 0;
}
Bitwise Operations
~
complement
&
Bitwise And
Bitwise Or
Bitwise Exclusive Or
Bitwise Complement: The bitwise complement operator, the tilde, ~, flips every bit. The tilde is
sometimes called a twiddle, and the bitwise complement twiddles every bit: This turns out to be a
great way of finding the largest possible value for an unsigned number.
unsigned int max = ~0;
Bitwise AND: The bitwise AND operator is a single ampersand: &:
01001000 &
10111000 =
---------------00001000
FCS&E, GIK Institute Topi, Pakistan
Page 51
myBits ^ 0 : No change
myBits ^ 1 : Flip
Example 5.4:
#include <iostream>
#include <iomanip>
using namespace std;
void binary(unsigned int u)
{
int upper;
if(u < 256)
upper = 128;
else
upper = 32768;
cout << setw(5) << u << ": ";
// check if bit is set starting from the highest bit
// (ex) upper = 128, 10000000, 01000000, 00100000, ..., 00000001
for(int i = upper; i > 0; i = i/2) {
if(u & i)
cout << "1 ";
else
cout << "0 ";
FCS&E, GIK Institute Topi, Pakistan
Page 52
}
cout << "\n";
}
int main()
{
binary(5);
binary(55);
binary(255);
binary(4555);
binary(14555);
system("PAUSE");
return 0;
}
Example 5.5:
#include <iostream>
#include <iomanip>
#include <bitset>
using namespace std;
void binary(unsigned int u)
{
cout << setw(5) << u << ": ";
cout << bitset<16>((int)u);
cout << "\n";
}
int main()
{
binary(5);
binary(55);
binary(255);
binary(4555);
binary(14555);
system("PAUSE");
return 0;
}
Page 53
Page 54
First Class
The notion of class was invented by an Englishman to keep the general population happy. It derives from
the theory that people who knew their place and function in society would be much more secure and
comfortable in life than those who did not. The famous Dane, Bjarne Stroustrup, who invented C++,
Page 55
undoubtedly acquired a deep knowledge of class concepts while at Cambridge University in England,
and appropriated the idea very successfully for use in his new language.
The idea of a class in C++ is similar to the English concept, in that each class usually has a very precise
role and a permitted set of actions. However, it differs from the English idea, because class in C++ has
largely socialist overtones, concentrating on the importance of working classes. Indeed, in some ways it
is the reverse of the English ideal, because, as we shall see, working classes in C++ often live on the
backs of classes that do nothing at all.
Operations on Classes
In C++ you can create new data types as classes to represent whatever kinds of objects you like. As you'll
come to see, classes aren't limited to just holding data; you can also define member functions that act
on your objects, or even operations that act between objects of your classes using the standard C++
operators. You can define the class Box, for example, so that the following statements work and have
the meanings you want them to have:
Box Box1;
Box Box2;
if(Box1 > Box2)
// Fill the larger box
Box1.Fill();
else
Box2.Fill();
You could also implement operations as part of the Box class for adding, subtracting or even multiplying
boxes - in fact, almost any operation to which you could ascribe a sensible meaning in the context of
boxes.
We're talking about incredibly powerful medicine here and it constitutes a major change in the
approach that we can take to programming. Instead of breaking down a problem in terms of what are
essentially computer-related data types (integer numbers, floating point numbers and so on) and then
writing a program, we're going to be programming in terms of problem-related data types, in other
words classes. These classes might be named Employee, or Cowboy, or Cheese or Chutney, each defined
specifically for the kind of problem that you want to solve, complete with the functions and operators
that are necessary to manipulate instances of your new types.
Program design now starts with deciding what new application-specific data types you need to solve the
problem in hand and writing the program in terms of operations on the specifics that the problem is
concerned with, be it Coffins or Cowpokes.
Terminology
Let's summarize some of the terminology that we will be using when discussing classes in C++:
A class is a user-defined data type
Object-oriented programming is the programming style based on the idea of defining your own
data types as classes
Declaring an object of a class is sometimes referred to as instantiation because you are creating
an instance of a class
The idea of an object containing the data implicit in its definition, together with the functions
that operate on that data, is referred to as encapsulation
When we get into the detail of object-oriented programming, it may seem a little complicated in places,
but getting back to the basics of what you're doing can often help to make things clearer, so always keep
in mind what objects are really about. They are about writing programs in terms of the objects that are
FCS&E, GIK Institute Topi, Pakistan
Page 56
specific to the domain of your problem. All the facilities around classes in C++ are there to make this as
comprehensive and flexible as possible. Let's get down to the business of understanding classes.
Understanding Classes
A class is a data type that you define. It can contain data elements, which can either be variables of the
basic types in C++ or other user-defined types. The data elements of a class may be single data
elements, arrays, pointers, arrays of pointers of almost any kind or objects of other classes, so you have
a lot of flexibility in what you can include in your data type. A class can also contain functions which
operate on objects of the class by accessing the data elements that they include. So, a class combines
both the definition of the elementary data that makes up an object and the means of manipulating the
data belonging to individual instances of the class.
The data and functions within a class are called members of the class. Funnily enough, the members of a
class that are data items are called data members and the members that are functions are called
function members or member functions. The member functions of a class are also sometimes referred
to as methods, although we will not be using this term.
When you define a class, you define a blueprint for a data type. This doesn't actually define any data,
but it does define what the class name means, that is, what an object of the class will consist of and
what operations can be performed on such an object. It's much the same as if you wrote a description of
the basic type double. This wouldn't be an actual variable of type double, but a definition of how it's
made up and how it operates. To create a variable, you need to use a declaration statement. It's exactly
the same with classes, as you will see.
Defining a Class
Let's look again at the class we started talking about at the start of the chapter - a class of boxes. We
defined the Box data type using the keyword class as follows:
class Box
{
public:
double length;
// Length of a box in inches
double breadth;
// Breadth of a box in inches
double height;
// Height of a box in inches
};
The name that we've given to our class appears following the keyword and the three data members
are defined between the curly braces. The data members are defined for the class using the
declaration statements that we already know and love, and the whole class definition is terminated
with a semicolon. The names of all the members of a class are local to a class. You can therefore use
the same names elsewhere in a program without causing any problems.
Page 57
Box Box1;
// Declare Box1 of type Box
Box Box2;
// Declare Box2 of type Box
Both of the objects Box1 and Box2 will, of course, have their own data members. This is illustrated
here:
The object name Box1 embodies the whole object, including its three data members. They are not
initialized to anything, however - the data members of each object will simply contain junk values,
so we need to look at how we can access them for the purpose of setting them to some specific
values.
Example 6.1:
// Creating and using boxes
#include <iostream>
using namespace std;
class Box
// Class definition at global scope
{
public:
double length; // Length of a box in inches
double breadth; // Breadth of a box in inches
double height; // Height of a box in inches
};
int main(void)
{
Box Box1;
// Declare Box1 of type Box
Box Box2;
// Declare Box2 of type Box
double volume = 0.0; // Store the volume of a box here
Box1.height = 18.0; // Define the values
Box1.length = 78.0; // of the members of
Box1.breadth = 24.0; // the object Box1
Box2.height = Box1.height - 10; // Define Box2
Box2.length = Box1.length/2.0; // members in
Box2.breadth = 0.25*Box1.length; // terms of Box1
FCS&E, GIK Institute Topi, Pakistan
Page 58
How It Works
Everything here works as we would have expected from our experience with structures. The definition
of the class appears outside of the function main() and, therefore, has global scope. This enables objects
to be declared in any function in the program and causes the class to show up in the ClassView once the
program has been compiled.
We've declared two objects of type Box within the function main(), Box1 and Box2. Of course, as with
variables of the basic types, the objects Box1 and Box2 are local to main(). Objects of a class obey the
same rules with respect to scope as variables declared as one of the basic types (such as the variable
volume, which is used in this example).
The first three assignment statements set the values of the data members of Box1. We define the values
of the data members of Box2 in terms of the data members of Box1 in the next three assignment
statements.
We then have a statement which calculates the volume of Box1 as the product of its three data
members. This value is then output to the screen. Next, we output the sum of the data members of
Box2 by writing the expression for the sum of the data members directly in the output statement. The
final action in the program is to output the number of bytes occupied by Box1, which is produced by the
operator sizeof.
If you run this program, you should get this output:
The last line shows that the object Box1 occupies 24 bytes of memory, which is a result of it having 3
data members of 8 bytes each. The statement which produced the last line of output could equally well
have been written like this:
cout << endl
// Display the size of a box in memory
<< "A Box object occupies "
<< sizeof (Box) << " bytes.";
Page 59
Here, we've used the type name between parentheses, rather than a specific object name - this is
standard syntax for the sizeof operator.
This example has demonstrated the mechanism for accessing the public data members of a class. It also
shows that they can be used in exactly the same way as ordinary variables.
Example 6.2:
// Calculating the volume of a box with a member function
#include <iostream>
using namespace std;
class Box
// Class definition at global scope
{
public:
double length;
// Length of a box in inches
double breadth;
// Breadth of a box in inches
double height;
// Height of a box in inches
// Function to calculate the volume of a box
double Volume(void)
{
FCS&E, GIK Institute Topi, Pakistan
Page 60
How It Works
The new code that we've added to the class definition is highlighted. It's just the definition of the
function Volume(), which is a member function of the class. It also has the same access attribute as the
data members: public. This is because every class member declared following an access attribute will
have that access attribute, until another one is specified within the class definition. The function
Volume() returns the volume of a Box object as a value of type double. The expression in the return
statement is just the product of the three data members of the class.
There's no need to qualify the names of the class members in any way when accessing them in
member functions. The unqualified member names automatically refer to the members of the
object that is current when the member function is executed.
The member function Volume() is used in the highlighted statements in main(), after initializing the data
members (as in the first example). Using the same name for a variable in main() causes no conflict or
problem. You can call a member function of a particular object by writing the name of the object to be
processed, followed by a period, followed by the member function name. As we noted above, the
function will automatically access the data members of the object for which it was called, so the first use
of Volume() calculates the volume of Box1. Using only the name of a data member will always refer to
the member of the object for which the member function has been called.
The member function is used a second time directly in the output statement to produce the volume of
Box2. If you execute this example, it will produce this output:
Page 61
Note that the Box object is still the same number of bytes. Adding a function member to a class doesn't
affect the size of the objects. Obviously, a member function has to be stored in memory somewhere, but
there's only one copy regardless of how many class objects have been declared, and it isn't counted
when the operator sizeof produces the number of bytes that an object occupies.
The names of the class data members in the member function automatically refer to the data members
of the specific object used to call the function, and the function can only be called for a particular object
of the class. In this case, this is done by using the direct member access operator (.) with the name of an
object.
If you try to call a member function without specifying an object name, your program will not
compile.
Positioning a Member Function Definition
A member function definition need not be placed inside the class definition. If you want to put it outside
the class definition, you need to put the prototype for the function inside the class. If we rewrite the
previous class with the function definition outside, then the class definition looks like this:
class Box
// Class definition at global scope
{
public:
double length;
// Length of a box in inches
double breadth; // Breadth of a box in inches
double height;
// Height of a box in inches
double Volume(void); // Member function prototype
};
Now we need to write the function definition, but there has to be some way of telling the compiler that
the function belongs to the class Box. This is done by prefixing the function name with the name of the
class and separating the two with the scope resolution operator, ::, which is formed from two
successive colons. The function definition would now look like this:
// Function to calculate the volume of a box
double Box::Volume(void)
{
return length * breadth * height;
}
It will produce the same output as the last example. However, it isn't exactly the same program. In the
second case, all calls to the function are treated in the way that we're already familiar with. However,
when we defined the function within the definition of the class in Ex6_02.cpp, the compiler implicitly
treated the function as an inline function.
Inline Functions
With an inline function, the compiler tries to expand the code in the body of the function in place of a
call to the function. This avoids much of the overhead of calling the function and, therefore, speeds up
your code. This is illustrated here:
Page 62
Of course, the compiler takes care of ensuring that expanding a function inline doesn't cause any
problems with variable names or scope.
The compiler may not always be able to insert the code for a function inline (such as with recursive
functions or functions for which you have obtained an address), but generally it will work. It's best used
for very short, simple functions, such as our function Volume(), because they will execute faster and will
not significantly increase the size of the executable module.
With the function definition outside of the class definition, the compiler treats the function as a normal
function and a call of the function will work in the usual way. However, it's also possible to tell the
compiler that, if possible, we would like the function to be considered as inline. This is done by simply
placing the keyword inline at the beginning of the function header. So, for our function, the definition
would be as follows:
// Function to calculate the volume of a box
inline double Box::Volume(void)
{
return length * breadth * height;
}
With this definition for the function, the program would be exactly the same as the original. This allows
you to put the member function definitions outside of the class definition, if you so choose, and still
retain the speed benefits of inlining.
You can apply the keyword inline to ordinary functions in your program that have nothing to do with
classes and get the same effect. However, remember that it's best used for short, simple functions.
We now need to understand a little more about what happens when we declare an object of a class.
Note:
Before doing your lab exercises run the examples.
Exercises will be provided by the instructors in the lab.
Page 63
Lab 7:
Object Orientation in C++ II
Class Constructors
In the previous program, we declared our Box objects, Box1 and Box2, and then laboriously
worked through each of the data members for each object, in order to assign an initial value to
it. This is unsatisfactory from several points of view. First of all, it would be easy to overlook
initializing a data member, particularly in the case of a class which had many more data
members than our Box class. Initializing the data members of several objects of a complex class
could involve pages of assignment statements. The final constraint on this approach arises
when we get to defining data members of a class that don't have the attribute public - we
won't be able to access them from outside the class anyway. There has to be a better way, and
of course there is - it's known as the class constructor.
What is a Constructor?
A class constructor is a special function in a class that is called when a new object of the class is
declared. It therefore provides the opportunity to initialize objects as they are created and to
ensure that data members only contain valid values.
You have no leeway in naming a class constructor - it always has the same name as the class in
which it is defined. The function Box(), for example, is a constructor for our Box class. It also
has no return type. It's wrong to specify a return type for a constructor; you must not even write
it as void. The primary function of a class constructor is to assign initial values to the data
elements of the class, and no return type is necessary or, indeed, permitted.
Try It Out - Adding a Constructor to the Box class
Let's extend our Box class to incorporate a constructor.
Example 7.1:
// Using a constructor
#include <iostream>
using namespace std;
class Box
{
public:
double length;
double breadth;
double height;
// Constructor definition
Box(double lv, double bv, double hv)
{
cout << endl << "Constructor called.";
length = lv;
// Set values of
breadth = bv;
// data members
height = hv;
}
FCS&E, GIK Institute Topi, Pakistan
Page 64
//
//
//
//
endl
"Volume of Box1 = " << volume;
endl
"Volume of CigarBox = "
CigarBox.Volume();
endl;
return 0;
}
How It Works
The constructor, Box(), has been written with three parameters of type double, corresponding to
the initial values for the length, breadth and height members of a Box object. The first
statement in the constructor outputs a message so that we can tell when it's been called. You
wouldn't do this in production programs, but, since it's very helpful in showing when a
constructor is called, it's often used when testing a program. We'll use it regularly for the
purposes of illustration. The code in the body of the constructor is very simple. It just assigns the
arguments passed to the corresponding data members. If necessary, we could also include checks
that valid, non-negative arguments are supplied and, in a real context, you probably would want
to do this, but our primary interest here is in seeing how the mechanism works.
Within main(), we declare the object Box1 with initializing values for the data members length,
breadth, and height, in sequence. These are in parentheses following the object name. This
uses the functional notation for initialization, which can also be applied to initializing ordinary
variables of basic types. We also declare a second object of type Box, called CigarBox, which
also has initializing values.
The volume of Box1 is calculated using the member function Volume() as in the previous
example and is then displayed on the screen. We also display the value of the volume of
CigarBox. The output from the example is:
The first two lines are output from the two calls of the constructor, Box(), once for each object
declared. The constructor that we've supplied in the class definition is automatically called when
a Box object is declared, so both Box objects are initialized with the initializing values appearing
in the declaration. These are passed to the constructor as arguments, in the sequence that they are
written in the declaration. As you can see, the volume of Box1 is the same as before and
FCS&E, GIK Institute Topi, Pakistan
Page 65
CigarBox
has a volume looking suspiciously like the product of its dimensions, which is quite a
relief.
The Default Constructor
Try modifying the last example by adding the declaration for Box2 that we had previously:
Box Box2;
Here, we've left Box2 without initializing values. When you rebuild this version of the program,
you'll get the error message:
error C2512: 'Box': no appropriate default constructor available
This means that the compiler is looking for a default constructor for Box2, either one that needs
no arguments, because none are specified in the constructor definition, or one whose arguments
are all optional, because we haven't supplied any initializing values for the data members. Well,
this statement was perfectly satisfactory in Ex6_02.cpp, so why doesn't it work now?
The answer is that the previous example used a default constructor that was supplied by the
compiler, because we didn't supply one. Since in this example we did supply a constructor, the
compiler assumed that we were taking care of everything and didn't supply the default. So, if you
still want to use declarations for Box objects which aren't initialized, you have to include the
default constructor yourself. What exactly does the default constructor look like? In the simplest
case, it's just a constructor that accepts no arguments, it doesn't even need to do anything:
Box()
{}
// Default constructor
// Totally devoid of statements
// Constructor definition
Box(double lv, double bv, double hv)
{
cout << endl << "Constructor called.";
length = lv;
// Set values of
breadth = bv;
// data members
height = hv;
}
// Default constructor definition
Box()
{ cout << endl << "Default constructor called."; }
// Function to calculate the volume of a box
double Volume()
{
FCS&E, GIK Institute Topi, Pakistan
Page 66
//
//
//
//
//
// Define Box2
// members in
// terms of Box1
endl
"Volume of CigarBox = "
CigarBox.Volume();
endl;
return 0;
}
How It Works
Now that we have included our own version of the default constructor, there are no error
messages from the compiler and everything works. The program produces this output:
All that our default constructor does is to display a message. Evidently, it was called when we
declared the object Box2. We also get the correct value for the volumes of all three Box objects,
so the rest of the program is working as it should.
One aspect of this example that you may have noticed is that we now know we can overload
constructors just as we overloaded functions. We've just run an example with two constructors
that differ only in their parameter list. One has three parameters of type double and the other has
no parameters at all.
Assigning Default Values in a Constructor
When we discussed functions in C++, we saw how we could specify default values for the
parameters to a function in the function prototype. We can also do this for class member
functions, including constructors. If we put the definition of the member function inside the class
definition, we can put the default values for the parameters in the function header. If we only
Page 67
include the prototype of a function in the class definition, the default parameter value should go
in the prototype.
If we decided that the default size for a Box object was a unit box with all sides of length 1, we
could alter the class definition in the last example to this:
class Box
{
public:
double length;
double breadth;
double height;
// Constructor definition
Box(double lv = 1.0, double bv = 1.0, double hv = 1.0)
{
cout << endl << "Constructor called.";
length = lv;
// Set values of
breadth = bv;
// data members
height = hv;
}
// Default constructor definition
Box()
{ cout << endl << "Default constructor called."; }
// Function to calculate the volume of a box
double Volume()
{
return length * breadth * height;
}
};
If we make this change to the last example, what happens? We get another error message from
the compiler, of course. We get these useful comments:
warning C4520: 'Box': multiple default constructors specified
error C2668: 'Box::Box': ambiguous call to overloaded function
This means that the compiler can't work out which of the two constructors to call - the one for
which we have set default values for the parameters or the constructor that doesn't accept any
parameters. This is because the declaration of Box2 requires a constructor without parameters,
and either constructor can now be called in this case. The immediately obvious solution to this is
to get rid of the constructor that accepts no parameters. This is actually beneficial. Without this
constructor, any Box object that is declared without being explicitly initialized will automatically
have its members initialized to 1.
Try It Out - Supplying Default Values for Constructor Arguments
We can demonstrate this with the following simplified example:
Example 7.2:
// Supplying default values for constructor arguments
#include <iostream>
using namespace std;
class Box
{
public:
double length;
double breadth;
Page 68
double height;
// Constructor definition
Box(double lv=1.0, double bv=1.0, double hv=1.0)
{
cout << endl << "Constructor called.";
length = lv;
// Set values of
breadth = bv;
// data members
height = hv;
}
// Function to calculate the volume of a box
double Volume()
{
return length * breadth * height;
}
};
int main(void)
{
Box Box2;
// Declare Box2 - no initial values
cout << endl
<< "Volume of Box2 = "
<< Box2.Volume();
cout << endl;
return 0;
}
How It Works
We only declare a single uninitialized Box variable, Box2, because that's all we need for
demonstration purposes. This version of the program produces the following output:
This shows that the constructor with default parameter values is doing its job of setting the
values of objects that have no initializing values specified.
You shouldn't assume from the above example that this is the only, or even the recommended,
way of implementing the default constructor. There will be many occasions where you won't
want to assign default values in this way, in which case you'll need to write a separate default
constructor. There will even be times when you don't want to have a default constructor
operating at all, even though you have defined another constructor. This would ensure that all
declared objects of a class must have initializing values explicitly specified in their declaration.
Page 69
Now the values of the data members are not set in assignment statements in the body of the
constructor. As in a declaration, they are specified as initializing values using functional notation
and appear in the initializing list as part of the function header. The member length is initialized
by the value of lv, for example. This can be rather more efficient than using assignments as we
did in the previous version. If you substitute this version of the constructor in the previous
example, you will see that it works just as well.
Note that the initializing list for the constructor is separated from the parameter list by a colon
and that each of the initializers is separated by a comma. This technique for initializing
parameters in a constructor is important, because, as we shall see later, it's the only way of
setting values for certain types of data members of an object.
Having the possibility of specifying class members as private also enables you to separate the
interface to the class from its internal implementation. The interface to a class is composed of the
public members and the public member functions in particular, since they can provide indirect
access to all the members of a class, including the private members. By keeping the internals of
a class private, you can later modify them to improve performance, for example, without
necessitating modifications to the code that uses the class through its public interface. To keep
them safe from unnecessary meddling, it's good practice to declare data and function members of
a class that don't need to be exposed as private. Only make public what is essential to the use
of your class.
Try It Out - Private Data Members
We can rewrite the Box class to make its data members private.
// A class with private members
FCS&E, GIK Institute Topi, Pakistan
Page 70
#include <iostream>
using namespace std;
class Box
// Class definition at global scope
{
public:
// Constructor definition
Box(double lv=1.0, double bv=1.0, double hv=1.0)
{
cout << endl << "Constructor called.";
length = lv;
// Set values of
breadth = bv;
// data members
height = hv;
}
// Function to calculate the volume of a box
double Volume()
{
return length * breadth * height;
}
private:
double length;
double breadth;
double height;
};
int main(void)
{
Box Match(2.2, 1.1, 0.5); // Declare Match box
Box Box2;
// Declare Box2 - no initial values
cout << endl
<< "Volume of Match = "
<< Match.Volume();
// Uncomment the following line to get an error
// Box2.length = 4.0;
cout <<
<<
<<
cout <<
endl
"Volume of Box2 = "
Box2.Volume();
endl;
return 0;
}
How It Works
The definition of the class Box now has two sections. The first is the public section containing
the constructor and the member function Volume(). The second section is specified as private
and contains the data members. Now the data members can only be accessed by the member
functions of the class. We don't have to modify any of the member functions - they can access all
the data members of the class anyway. However, if you uncomment the statement in the function
main(), assigning a value to the member length of the object Box2, you'll get a compiler error
message confirming that the data member is inaccessible.
A point to remember is that using a constructor or a member function is now the only way to get
a value into a private data member of an object. You have to make sure that all the ways in
which you might want to set or modify private data members of a class are provided for through
member functions.
FCS&E, GIK Institute Topi, Pakistan
Page 71
We could also put functions into the private section of a class. In this case, they can only be
called by other member functions. If you put the function Volume() in the private section, you
will get a compiler error from the statements that attempt to use it in the function main(). If you
put the constructor in the private section, you won't be able to declare any members of the
class.
The above example generates this output:
This demonstrates that the class is still working satisfactorily, with its data members defined as
having the access attribute private. The major difference is that they are now completely
protected from unauthorized access and modification.
If you don't specify otherwise, the default access attribute which applies to members of a class is
private. You could, therefore, put all your private members at the beginning of the class
definition and let them default to private by omitting the keyword. However, it's better to take the
trouble to explicitly state the access attribute in every case, so there can be no doubt about what
you intend.
Of course, you don't have to make all your data members private. If the application for your
class requires it, you can have some data members defined as private and some as public. It
all depends on what you're trying to do. If there's no reason to make members of a class public,
it is better to make them private as it makes the class more secure. Ordinary functions won't be
able to access any of the private members of your class.
Just to show how it looks, this has been written as a member function definition which is external
to the class. We've specified it as inline, since we'll benefit from the speed increase, without
increasing the size of our code too much. Assuming that you have the declaration of the function
in the public section of the class, you can use it by writing this statement:
len = Box2.GetLength();
All you need to do is to write a similar function for each data member that you want to make
available to the outside world, and their values can be accessed without prejudicing the security
of the class. Of course, if you put the definitions for these functions within the class definition,
they will be inline by default.
Page 72
We now want to create another Box object, identical to the first. We would like to initialize the
second Box object with Box1.
};
int main(void)
{
Box Box1(78.0, 24.0, 18.0);
Box Box2 = Box1;
// Initialize Box2 with Box1
cout <<
<<
<<
<<
endl
"Box1 volume = " << Box1.Volume()
endl
"Box2 volume = " << Box2.Volume();
How It Works
This example will produce this:
Page 73
Clearly, the program is working as we would want, with both boxes having the same volume.
However, as you can see from the output, our constructor was called only once for the creation
of Box1. But how was Box2 created? The mechanism is similar to the one that we experienced
when we had no constructor defined and the compiler supplied a default constructor to allow an
object to be created. In this case, the compiler generates a default version of what is referred to as
a copy constructor.
A copy constructor does exactly what we're doing here - it creates an object of a class by
initializing it with an existing object of the same class. The default version of the copy
constructor creates the new object by copying the existing object, member by member.
This is fine for simple classes such as Box, but for many classes - classes that have pointers or
arrays as members for example - it won't work properly. Indeed, with such classes it can create
serious errors in your program. In these cases, you need to create your own class copy
constructor.
Destructor:
A destructor is a special member function of a class that is executed whenever an object of it's
class goes out of scope or whenever the delete expression is applied to a pointer to the object of
that class.
A destructor will have exact same name as the class prefixed with a tilde (~) and it can neither
return a value nor can it take any parameters. Destructor can be very useful for releasing
resources before coming out of the program like closing files, releasing memories etc.
Example 7.4:
#include <iostream>
using namespace std;
class CRectangle {
int width, height;
public:
CRectangle (int a,int b)
{ width = a;
height = b;
}
int area ()
{return (width * height);}
~CRectangle () {
cout<<"deleting object..."<<endl;
Page 74
}
};
int main () {
CRectangle rect (3,4);
CRectangle rectb (5,6);
cout << "rect area: " << rect.area() << endl;
cout << "rectb area: " << rectb.area() << endl;
return 0;
}
Note:
Before doing your lab exercises run the examples.
Exercises will be provided by the instructors in the lab.
Page 75
Lab 8
Friend Function & Classes, This Pointer, and static Variables
Friend Classes
C++ provides the friend keyword to do just this. Inside a class, you can indicate that other classes (or
simply functions) will have direct access to protected and private members of the class. When granting
access to a class, you must specify that the access is granted for a class using the class keyword:
friend class aClass;
Note that friend declarations can go in either the public, private, or protected section of a class--it
doesn't matter where they appear. In particular, specifying a friend in the section marked protected
doesn't prevent the friend from also accessing private fields.
Here is a more concrete example of declaring a friend:
class Node
{
private:
int data;
int key;
// ...
friend class BinaryTree; // class BinaryTree can now access data directly
};
Now, Node does not need to provide any means of accessing the data stored in the tree. The BinaryTree
class that will use the data is the only class that will ever need access to the data or key. (The BinaryTree
class needs to use the key to order the tree, and it will be the gateway through which other classes can
access data stored in any particular node.)
Now in the BinaryTree class, you can treat the key and data fields as though they were public:
class BinaryTree
{
private:
Node *root;
Page 76
Friend Functions
Similarly, a class can grant access to its internal variables on a more selective basis--for instance,
restricting access to only a single function. To do so, the entire function signature must be replicated
after the friend specifier, including the return type of the function--and, of course, you'll need to give
the scope of the function if it's inside another class:
friend return_type class_name::function(args);
For instance, in our example Node class, we would use this syntax:
class Node
{
private:
int data;
int key;
// ...
friend int BinaryTree::find(); // Only BinaryTree's find function has access
};
Now the find function of BinaryTree could access the internals of the Node class, but no other function
in BinaryTree could. (Though we might want a few others to be able to!)
FCS&E, GIK Institute Topi, Pakistan
Page 77
Note that when friends are specified within a class, this does not give the class itself access to the friend
function. That function is not within the scope of the class; it's only an indication that the class will grant
access to the function.
Functions which are friends of a class and are defined within the class definition are also by default
inline.
Friend functions are not members of the class, and therefore the access attributes do not apply to
them. They are just ordinary global functions with special privileges.
Let's suppose that we wanted to implement a friend function in the Box class to compute the surface
area of a Box object.
//Friend function
friend double BoxSurface(Box aBox);
};
// friend function to calculate the surface area of a Box object
double BoxSurface(Box aBox)
{
return 2.0*(aBox.length*aBox.breadth +
aBox.length*aBox.height +
aBox.height*aBox.breadth);
}
int main(void)
{
Box Match(2.2, 1.1, 0.5); // Declare Match box
FCS&E, GIK Institute Topi, Pakistan
Page 78
Box Box2;
endl
"Surface area of Box2 = "
BoxSurface(Box2);
endl;
return 0;
}
How It Works
We declare the function BoxSurface() as a friend of the Box class by writing the function prototype with
the keyword friend at the front. Since the BoxSurface() function itself is a global function, it makes no
difference where we put the friend declaration within the definition of the class, but it's a good idea to
be consistent when you position this sort of declaration. You can see that we have chosen to position
ours after all the public and private members of the class. Remember that a friend function isn't a
member of the class, so access attributes don't apply.
The definition of the function follows that of the class. Note that we specify access to the data members
of the object within the definition of BoxSurface(), using the Box object passed to the function as a
parameter. Because a friend function isn't a class member, the data members can't be referenced just
by their names. They each have to be qualified by the object name in exactly the same way as they
might in an ordinary function, except, of course, that an ordinary function can't access the private
members of a class. A friend function is the same as an ordinary function except that it can access all the
members of a class without restriction.
The example produces this output:
This is exactly what you would expect. The friend function is computing the surface area of the Box
objects from the values of the private members.
Page 79
// Length of a box
// Breadth of a box
// Height of a box
};
int main(void)
{
Box Box1(3.3, 1.2, 1.5);
// Declare box1
// Declare box2
if(Box1.compare(Box2))
{
FCS&E, GIK Institute Topi, Pakistan
Page 80
When the above code is compiled and executed, it produces following result:
Constructor called.
Constructor called.
Box2 is equal to or larger than Box1
Note that s_nID has kept its value across multiple function calls.
Page 81
The static keyword has another meaning when applied to global variables it changes them
from global scope to file scope. Because global variables are typically avoided by competent
programmers, and file scope variables are just global variables limited to a single file, the static
keyword is typically not used in this capacity.
Because s_nValue is a static member variable, s_nValue is shared between all objects of the class.
Consequently, cFirst.s_nValue is the same as cSecond.s_nValue. The above program shows that the
value we set using cFirst can be accessed using cSecond!
Although you can access static members through objects of the class type, this is somewhat misleading.
cFirst.s_nValue implies that s_nValue belongs to cFirst, and this is really not the case. s_nValue does not
belong to any object. In fact, s_nValue exists even if there are no objects of the class have been
instantiated!
Page 82
Consequently, it is better to think of static members as belonging to the class itself, not the objects of
the class. Because s_nValue exists independently of any class objects, it can be accessed directly using
the class name and the scope operator:
class Something
{
public:
static int s_nValue;
};
int Something::s_nValue = 1;
int main()
{
Something::s_nValue = 2;
std::cout << Something::s_nValue;
return 0;
}
In the above snippet, s_nValue is referenced by class name rather than through an object. Note that we
have not even instantiated an object of type Something, but we are still able to access and use
Something::s_nValue. This is the preferred method for accessing static members.
This initializer should be placed in the code file for the class (eg. Something.cpp). In the absense of an
initializing line, C++ will initialize the value to 0.
Page 83
Because s_nIDGenerator is shared by all Something objects, when a new Something object is
created, its constructor grabs the current value out of s_nIDGenerator and then increments
the value for the next object. This guarantees that each Something object receives a unique id
(incremented in the order of creation). This can really help when debugging multiple items in an
array, as it provides a way to tell multiple objects of the same class type apart!
Static member variables can also be useful when the class needs to utilize an internal lookup
table (eg. to look up the name of something, or to find a pre-calculated value). By making the
lookup table static, only one copy exists for all objects, rather than a copy for each object
instantiated. This can save substantial amounts of memory.
Note:
Before doing your lab exercises run the examples.
Exercises will be provided by the instructors in the lab.
Page 84
Lab 9
Operator Overloading
Operator Overloading in C++
In C++ the overloading principle applies not only to functions, but to operators too. That is, of operators
can be extended to work not just with built-in types but also classes. A programmer can provide his or
her own operator to a class by overloading the built-in operator to perform some specific computation
when the operator is used on objects of that class. Is operator overloading really useful in real world
implementations? It certainlly can be, making it very easy to write code that feels natural (we'll see
some examples soon). On the other hand, operator overloading, like any advanced C++ feature, makes
the language more complicated. In addition, operators tend to have very specific meaning, and most
programmers don't expect operators to do a lot of work, so overloading operators can be abused to
make code unreadable. But we won't do that.
Page 85
The assignment operator can be overloaded similarly. Notice that we did not have to call any accessor
functions in order to get the real and imaginary parts from the parameter other since the overloaded
operator is a member of the class and has full access to all private data. Alternatively, we could have
defined the addition operator globally and called a member to do the actual work. In that case, we'd
also have to make the method a friend of the class, or use an accessor method to get at the private data:
friend Complex operator+(Complex);
Complex operator+(const Complex &num1, const Complex &num2)
{
double result_real = num1.real + num2.real;
double result_imaginary = num1.imag + num2.imag;
return Complex( result_real, result_imaginary );
}
Why would you do this? when the operator is a class member, the first object in the expression must be
of that particular type. It's as if you were writing:
Complex a( 1, 2 );
Complex a( 2, 2 );
Complex c = a.operator=( b );
when it's a global function, the implicit or user-defined conversion can allow the operator to act even if
the first operand is not exactly of the same type:
Complex c = 2+b;
//if the integer 2 can be converted by the Complex class, this expression is valid
By the way, the number of operands to a function is fixed; that is, a binary operator takes two operands,
a unary only one, and you can't change it. The same is true for the precedence of operators too; for
example the multiplication operator is called before addition. There are some operators that need the
first operand to be assignable, such as : operator=, operator(), operator[] and operator->, so their use is
restricted just as member functions(non-static), they can't be overloaded globally. The operator=,
operator& and operator, (sequencing) have already defined meanings by default for all objects, but their
meanings can be changed by overloading or erased by making them private.
Another intuitive meaning of the "+" operator from the STL string class which is overloaded to do
concatenation:
string prefix("de");
string word("composed");
string composed = prefix+word;
Using "+" to concatenate is also allowed in Java, but note that this is not extensible to other classes, and
it's not a user defined behavior. Almost all operators can be overloaded in C++:
+
~
*
,
&
++
--
<<
>>
==
!=
&&
||
+=
-=
/=
%=
^=
&=
|=
*=
<<=
>>=
[]
()
->
->*
new
delete
Page 86
The only operators that can't be overloaded are the operators for scope resolution (::), member
selection (.), and member selection through a pointer to a function(.*). Overloading assumes you specify
a behavior for an operator that acts on a user defined type and it can't be used just with general
pointers. The standard behavior of operators for built-in (primitive) types cannot be changed by
overloading,
that
is,
you
can't
overload
operator+(int,int).
The logic(boolean) operators have by the default a short-circuiting way of acting in expressions with
multiple boolean operations. This means that the expression:
if(a && b && c)
will not evaluate all three operations and will stop after a false one is found. This behavior does not
apply
to
operators
that
are
overloaded
by
the
programmer.
Even the simplest C++ application, like a "hello world" program, is using overloaded operators. This is
due to the use of this technique almost everywhere in the standard library (STL). Actually the most basic
operations in C++ are done with overloaded operators, the IO(input/output) operators are overloaded
versions of shift operators(<<, >>). Their use comes naturally to many beginning programmers, but their
implementation is not straightforward. However a general format for overloading the input/output
operators must be known by any C++ developer. We will apply this general form to manage the
input/output for our Complex class:
friend ostream &operator<<(ostream &out, Complex c)
//output
{
out<<"real part: "<<real<<"\n";
out<<"imag part: "<<imag<<"\n";
return out;
}
friend istream &operator>>(istream &in, Complex &c)
{
//input
Notice the use of the friend keyword in order to access the private members in the above
implementations. The main distinction between them is that the operator>> may encounter unexpected
errors for incorrect input, which will make it fail sometimes because we haven't handled the errors
correctly. A important trick that can be seen in this general way of overloading IO is the returning
reference for istream/ostream which is needed in order to use them in a recursive manner:
Complex a(2,3);
Complex b(5.3,6);
cout<<a<<b;
Lab 10:
FCS&E, GIK Institute Topi, Pakistan
Page 87
Inheritance
One of the most important concepts in object-oriented programming is that of inheritance.
Inheritance allows us to define a class in terms of another class, which makes it easier to create
and maintain an application. This also provides an opportunity to reuse the code functionality
and fast implementation time.
When creating a class, instead of writing completely new data members and member functions,
the programmer can designate that the new class should inherit the members of an existing class.
This existing class is called the base class, and the new class is referred to as the derived class.
The idea of inheritance implements the is a relationship. For example, mammal IS-A animal, dog
IS-A mammal hence dog IS-A animal as well and so on.
Where access-specifier is one of public, protected, or private, and base-class is the name of a
previously defined class. If the access-specifier is not used, then it is private by default.
Consider a base class Shape and its derived class Rectangle as follows:
#include <iostream>
using namespace std;
// Base class
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// Derived class
class Rectangle: public Shape
{
public:
Page 88
int getArea()
{
return (width * height);
}
};
int main(void)
{
Rectangle Rect;
Rect.setWidth(5);
Rect.setHeight(7);
// Print the area of the object.
cout << "Total area: " << Rect.getArea() << endl;
return 0;
}
When the above code is compiled and executed, it produces following result:
Total area: 35
public
yes
yes
yes
protected
yes
yes
no
private
yes
no
no
A derived class inherits all base class methods with the following exceptions:
Type of Inheritance:
FCS&E, GIK Institute Topi, Pakistan
Page 89
When deriving a class from a base class, the base class may be inherited through public,
protected or private inheritance. The type of inheritance is specified by the access-specifier as
explained above.
We hardly use protected or private inheritance but public inheritance is commonly used. While
using different type of inheritance, following rules are applied:
Public Inheritance: When deriving a class from a public base class, public members of
the base class become public members of the derived class and protected members of
the base class become protected members of the derived class. A base class's private
members are never accessible directly from a derived class, but can be accessed through
calls to the public and protected members of the base class.
Protected Inheritance: When deriving from a protected base class, public and
protected members of the base class become protected members of the derived class.
Private Inheritance: When deriving from a private base class, public and protected
members of the base class become private members of the derived class.
Multiple Inheritances:
A C++ class can inherit members from more than one class and here is the extended syntax:
class derived-class: access baseA, access baseB....
Where access is one of public, protected, or private and would be given for every base class
and they will be separated by comma as shown above. Let us try the following example:
#include <iostream>
using namespace std;
// Base class Shape
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// Base class PaintCost
class PaintCost
{
public:
int getCost(int area)
{
return area * 70;
FCS&E, GIK Institute Topi, Pakistan
Page 90
}
};
// Derived class
class Rectangle: public Shape, public PaintCost
{
public:
int getArea()
{
return (width * height);
}
};
int main(void)
{
Rectangle Rect;
int area;
Rect.setWidth(5);
Rect.setHeight(7);
area = Rect.getArea();
// Print the area of the object.
cout << "Total area: " << Rect.getArea() << endl;
// Print the total cost of painting
cout << "Total paint cost: $" << Rect.getCost(area) << endl;
return 0;
}
When the above code is compiled and executed, it produces following result:
Total area: 35
Total paint cost: $2450
Lab 11:
FCS&E, GIK Institute Topi, Pakistan
Page 91
Page 92
CTriangle triangle;
CPolygon * ptr_polygon1 = &rectangle;
CPolygon * ptr_polygon2 = ▵
ptr_polygon1->setup(2,2);
ptr_polygon2->setup(2,2);
cout << rectangle.area () << endl;
cout << triangle.area () << endl;
return 0;
}
As you can see, we create two pointers (ptr_polygon1 and ptr_polygon2) that point to the objects
of class CPolygon. Then we assign to these pointers the address of (using the reference
ampersand sign) the objects rectangle and triangle. Both rectangle and triangle are objects of
classes derived from CPolygon.
In the cout statement we use the objects rectangle and triangle instead of the pointers
ptr_polygon1 and ptr_polygon2. We do this because ptr_polygon1 and ptr_polygon2 are of the
type CPolygon. This means we can only use the pointers to refer to members that CRectangle
and CTriangle inherit from Cpolygon.
If we want to use the pointers to class CPolygon then area() should be declared in the class
CPolygon and not only in the derived classes CRectangle and Ctriangle.
The problem is that we use different versions of area() in the derived classes CRectangle and
Ctriangle so we cant implement one version of area() in the base class CPolygon. (If they were
the same we had no problem.)
We can fix this by using virtual members.
Virtual Members
A virtual member is a member of a base class that we can redefine in its derived classes. To
declare a member as virtual we must use the keyword virtual.
Lets change our previous example:
#include <iostream>
using namespace std;
class CPolygon
{
protected:
int width, height;
public:
void setup (int first, int second)
{
width= first;
height= second;
FCS&E, GIK Institute Topi, Pakistan
Page 93
}
virtual int area()
{
return (0);
}
};
class CRectangle: public CPolygon
{
public:
int area()
{
return (width * height);
}
};
class CTriangle: public CPolygon
{
public:
int area()
{
return (width * height / 2);
}
};
int main ()
{
CRectangle rectangle;
CTriangle triangle;
CPolygon polygon;
CPolygon * ptr_polygon1 = &rectangle;
CPolygon * ptr_polygon2 = ▵
CPolygon * ptr_polygon3 = &polygon;
ptr_polygon1->setup(2,2);
ptr_polygon2->setup(2,2);
ptr_polygon3->setup(2,2);
cout << ptr_polygon1->area () << endl;
cout << ptr_polygon2->area () << endl;
cout << ptr_polygon3->area () << endl;
return 0;
}
Because of the change adding area() as a virtual member of CPolygon now all the three
classes have all the same members (width, height, setup() and area().)
A class that declares or inherits a virtual function is called a polymorphic class.
Page 94
an abstract concept, it could be a circle, a square, etc, etc. You could say in C++ that class
CShape is an abstract base class (ABC) and class circle (etc) could be a derived class.
As we look at the C++ language we could say that an abstract base class has one or more pure
virtual member functions.
In the example above we put an implementation (return (0);) in the virtual member function
area(). If we want to change it into a pure virtual member function we use =0; instead of the
return (0). So the class will look like this:
class CPolygon
{
protected:
int width, height;
public:
void setup (int first, int second)
{
width= first;
height= second;
}
virtual int area() = 0;
};
This pure virtual function area() makes CPolygon an abstract base class. But you have to
remember the following: by adding a pure virtual member to the base class, you are forced to
also add the member to any derived class.
So our example should now look like this:
#include <iostream>
using namespace std;
class CPolygon
{
protected:
int width, height;
public:
void setup (int first, int second)
{
width= first;
height= second;
}
virtual int area() = 0;
};
class CRectangle: public CPolygon
{
public:
int area(void)
{
return (width * height);
}
};
FCS&E, GIK Institute Topi, Pakistan
Page 95
Note: there is also an extra void in the derived classes CRectangle and CTriangle.
Using a unique type of pointer (CPolygon*) we can point to objects of different but related
classes. We can make use of that. For instance: we could implement an extra function member in
the abstract base class CPolygon that can print the result of the area() function. (Remember that
CPolygon itself has no implementation for the function area() and still we can use it, isnt it
cool.) After implementation of such a function the example will look like this:
#include <iostream>
using namespace std;
class CPolygon
{
protected:
int width, height;
public:
void setup (int first, int second)
{
width= first;
height= second;
}
virtual int area(void) = 0;
void onscreen(void)
{
cout << this->area() << endl;
}
};
Page 96
Lab 12:
File Handling in C++
FCS&E, GIK Institute Topi, Pakistan
Page 97
This code creates a file called example.txt and inserts a sentence into it in the same way we are used to do
with cout, but using the file stream myfile instead.
But let's go step by step:
Open a file
The first operation generally performed on an object of one of these classes is to associate it to a real file.
This procedure is known as to open a file. An open file is represented within a program by a stream object
(an instantiation of one of these classes, in the previous example this was myfile) and any input or output
operation performed on this stream object will be applied to the physical file associated to it.
In order to open a file with a stream object we use its member function open():
Page 98
All these flags can be combined using the bitwise operator OR (|). For example, if we want to open the
file example.bin in binary mode to add data we could do it by the following call to member function
open():
ofstream myfile;
myfile.open ("example.bin", ios::out | ios::app | ios::binary);
Each one of the open() member functions of the classes ofstream, ifstream and fstream has a default mode
that is used if the file is opened without a second argument:
For ifstream and ofstream classes, ios::in and ios::out are automatically and respectively assumed, even if
a mode that does not include them is passed as second argument to the open() member function.
The default value is only applied if the function is called without specifying any value for the mode
parameter. If the function is called with any value in that parameter the default mode is overridden, not
combined.
File streams opened in binary mode perform input and output operations independently of any format
considerations. Non-binary files are known as text files, and some translations may occur due to
formatting of some special characters (like newline and carriage return characters).
Since the first task that is performed on a file stream object is generally to open a file, these three classes
include a constructor that automatically calls the open() member function and has the exact same
parameters as this member. Therefore, we could also have declared the previous myfile object and
conducted the same opening operation in our previous example by writing:
Page 99
To check if a file stream was successful opening a file, you can do it by calling to member is_open() with
no arguments. This member function returns a bool value of true in the case that indeed the stream object
is associated with an open file, or false otherwise:
Closing a file
When we are finished with our input and output operations on a file we shall close it so that its resources
become available again. In order to do that we have to call the stream's member function close(). This
member function takes no parameters, and what it does is to flush the associated buffers and close the file:
myfile.close();
Once this member function is called, the stream object can be used to open another file, and the file is
available again to be opened by other processes.
In case that an object is destructed while still associated with an open file, the destructor automatically
calls the member function close().
Text files
Text file streams are those where we do not include the ios::binary flag in their opening mode.
These files are designed to store text and thus all values that we input or output from/to them can
suffer some formatting transformations, which do not necessarily correspond to their literal
binary value.
Data output operations on text files are performed in the same way we operated with cout:
// writing on a text file
#include <iostream>
#include <fstream>
using namespace std;
int main () {
ofstream myfile ("example.txt");
if (myfile.is_open())
{
myfile << "This is a line.\n";
FCS&E, GIK Institute Topi, Pakistan
Page 100
Data input from a file can also be performed in the same way that we did with cin:
// reading a text file
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main () {
string line;
ifstream myfile ("example.txt");
if (myfile.is_open())
{
while (! myfile.eof() )
{
getline (myfile,line);
cout << line << endl;
}
myfile.close();
}
else cout << "Unable to open file";
return 0;
}
This is a line.
This is another line.
This last example reads a text file and prints out its content on the screen. Notice how we have used a new
member function, called eof() that returns true in the case that the end of the file has been reached. We
have created a while loop that finishes when indeed myfile.eof() becomes true (i.e., the end of the file has
been reached).
bad()
Returns true if a reading or writing operation fails. For example in the case that we try to write to a file
that is not open for writing or if the device where we try to write has no space left.
FCS&E, GIK Institute Topi, Pakistan
Page 101
fail()
Returns true in the same cases as bad(), but also in the case that a format error happens, like when an
alphabetical character is extracted when we are trying to read an integer number.
eof()
Returns true if a file open for reading has reached the end.
good()
It is the most generic state flag: it returns false in the same cases in which calling any of the previous
functions would return true.
In order to reset the state flags checked by any of these member functions we have just seen we can use
the member function clear(), which takes no parameters.
Page 102
The following example uses the member functions we have just seen to obtain the size of a file:
// obtaining file size
#include <iostream>
#include <fstream>
using namespace std;
int main () {
long begin,end;
ifstream myfile ("example.txt");
begin = myfile.tellg();
myfile.seekg (0, ios::end);
end = myfile.tellg();
myfile.close();
cout << "size is: " << (end-begin) << " bytes.\n";
return 0;
}
size is: 40 bytes.
Page 103
Page 104