Professional Documents
Culture Documents
CS 102 LAB Manual2 PDF
CS 102 LAB Manual2 PDF
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 ............................................................................................................................................................ 75
Friend Function & Classes, This Pointer, and static Variables .................................................................... 75
Lab 9 ............................................................................................................................................................ 84
Operator Overloading ................................................................................................................................. 84
Lab 10: ......................................................................................................................................................... 86
Inheritance .................................................................................................................................................. 87
Lab 11: ........................................................................................................................................................ 90
Introduction to Polymorphism and Abstract Base Classes ......................................................................... 91
Lab 12: ......................................................................................................................................................... 96
File Handling in C++..................................................................................................................................... 96
LAB 1
Review of CS101L & Arrays (1-D and 2-D)
1-Dimensional Arrays:
Array basics
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.
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;
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: #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:
11: cout<<"\n" <<age;
12: return 0;
13: }
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:
Example 1.4:
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: cout<<"Age at index[0]= "<<age[0]<<endl;
11: cout<<"Age at index[1]= "<<age[1]<<endl;
12: cout<<"Age at index[2]= "<<age[2]<<endl;
13: cout<<"Age at index[3]= "<<age[3]<<endl;
14: return 0;
15: }
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: #include <iostream.h>
2:
3: int main()
4: {
5: short age[4];
6: short same_age[4];
7: age[0]=23;
8: age[1]=34;
9: age[2]=65;
10: age[3]=74;
11:
12: same_age=age;
13:
14: cout<<"The age at index[0]"<<same_age[0];
15: cout<<"The age at index[1]"<<same_age[1];
16: cout<<"The age at index[2]"<<same_age[2];
17: cout<<"The age at index[3]"<<same_age[3];
18: return 0;
19: }
Line (12) tries to copy the age array into the same_age array. What happened when you tried to
compile the program above?
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 re-
examined 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: #include <iostream.h>
2:
3: int main()
4: {
5: short age[4];
6: short same_age[4];
7:
8: age[0]=23;
9: age[1]=34;
10: age[2]=65;
11: age[3]=74;
12:
13: same_age[0]=age[0];
14: same_age[1]=age[1];
15: same_age[2]=age[2];
16: same_age[3]=age[3];
17:
18: cout<<"The age at index[0]= "<<same_age[0]<<endl;
19: cout<<"The age at index[1]= "<<same_age[1]<<endl;
20: cout<<"The age at index[2]= "<<same_age[2]<<endl;
21: cout<<"The age at index[3]= "<<same_age[3]<<endl;
22: return 0;
23: }
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.
Like printing arrays, there is no single statement in the language that says "copy an entire array into
another array". The array elements must be copied individually.
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: #include <iostream.h>
2:
3: int main()
4: {
5: short age[4];
6: short same_age[4];
7: int i, j;
8: age[0]=23;
9: age[1]=34;
10: age[2]=65;
11: age[3]=74;
12:
13: for(i=0; i<4; i++)
14: same_age[i]=age[i];
15:
16: for(j=0; j<4; j++)
17: cout<<"The age is = "<<same_age[j]<<endl;
18: return 0;
19: }
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.
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.
#include<iostream.h>
void main( )
{
int stud[4][2] ;
int i,j ;
}
There are two parts to the program—in 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 about—the zeroth
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 one-
dimensional 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.
The above format work also but it is more difficult to read as compared to the first one. It is
important to remember that while initializing a 2-D array it is necessary to mention the second
(column) dimension, whereas the first dimension (row) is optional.
Let see the arrangement of the above student arrays in memory which contains roll number and marks.
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];
for(i=0;i<2;i++)
for(j=0;j<3;j++)
cout<<arr[i][j] <<"\t";
cout<<"\n\n";
Example 1.10:
#include <iostream>
int main()
// A 2-Dimensional array
double distance[2][4];
cout<<"Enter values\n";
cout << "\nDistance [" << i << "][" << j << "]: " << distance[i][j]; //Printing Rows and Columns
return 0;
An array can be passed to a function as argument. An array can also be returned by a function.
To declare and define that a function takes an array as argument, declare the function as you
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;
int main()
{
const int numberOfItems = 5;
double distance[] = {44.14, 720.52, 96.08, 468.78, 6.28};
return 0;
}
Note:
Before doing your lab exercises run the examples.
Exercises will be provided by the instructors in the lab.
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 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 let’s 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.
A variable in a program is something with a name, the value of which can vary. The way the
compiler handles this is that it assigns a specific block of memory within the computer to hold the value
of that variable. The size of that block depends on the range over which the variable is allowed to vary.
For example, on 32 bit PC's the size of an integer variable is 4 bytes. On older 16 bit PCs integers were 2
bytes.
Example 2.1:
#include<iostream>
using namespace std;
int main()
{
float fl=3.14;
cout<<fl;
cin>>fl;
return 0;
}
The output for this program will be
At line (4) in the program above, the computer reserves memory for fl. Depending on the
computer's architecture, a float may require 2, 4, 8 or some other number of bytes. In our
example, we'll assume that a float requires 4 bytes.
1. The program finds and grabs the address reserved for fl--in this example 924.
2. The contents stored at that address are retrieved
The illustration that shows 3.14 in the computer's memory can be misleading. Looking at the diagram, it
appears that "3" is stored in memory location 924, "." is stored in memory location 925, "1" in 926, and "4"
in 927. Keep in mind that the computer actually converts the floating point number 3.14 into a set of ones
and zeros. Each byte holds 8 ones or zeros. So, our 4 byte float is stored as 32 ones and zeros (8 per byte
times 4 bytes). Regardless of whether the number is 3.14, or -273.15, the number is always stored in 4
bytes as a series of 32 ones and zeros.
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.
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
to print to the screen the integer value stored at the address pointed to by ptr;.
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;
How it Works:
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
5 15
758 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 15
758 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 10
758 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 secondvalue
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 20
758 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.
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;
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:
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 Arithmetic’s
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.
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
CS102L: Intensive Programming Lab
8:ptr1=&a;
9:ptr2=&b;
10:x=*ptr1 + *ptr2 - 6;
11:y=6 - *ptr1 / *ptr2 +30;
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.
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 b
30 6
ptr1 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 0XF… 6
ptr1 ptr2
Line 20 prints the same value as was printed by the Line 16, because values of the variable was
never changed, in fact ptr1’s value which was address of a was changed. Now Line 21 will print
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.
Sending Pointers as Arguments to Functions
When we pass pointers to some function, the addresses of actual arguments in the calling function are
copied into the arguments of the called function. This means that using these addresses we would have
an access to the actual arguments and hence we would be able to manipulate them. The following
program illustrates this fact. Try this out:
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) ;
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
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
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:
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;
}
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;
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
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 [offset of 5] = 0
*(a+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.
Lab 3:
Strings in C++
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,
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';
cout << "The name is " << L1 << L2 << L3 << L4 << L5;
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;
}
This would produce:
When studying arrays, we were listing the numeric members of the array between curly
brackets. Here is an example:
int Number[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
As done with other arrays, each member of this array of characters can be accessed using
its index. Here is an example:
Example 3.1:
#include <iostream>
using namespace std;
int main()
{
char Name[6] = { 'J', 'a', 'm', 'e', 's' };
cout << "The name is " << Name[0] << Name[1] << Name[2] << Name[3] << Name[4];
return 0;
}
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:
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:
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:
J a m e s \0
Like any other variable, before using a string, you must first declare it, which is done by
type the char keyword, followed by the name of the variable, followed by square brackets.
When declaring the variable, if/since you do not know the number of characters needed for
the string; you must still provide an estimate number. You can provide a value large
enough to accommodate the maximum number of characters that would be necessary for
the variable. For a person's name, this could be 20. For the title of a book or a web page,
this could be longer. Here are examples:
char Name[20];
char BookTitle[40];
char WebReference[80];
char WeekDay[4];
To request the value of an array of characters, use the cin extractor just like you would
proceed with any other variable, without the square bracket. Here is an example:
char WeekDay[12];
cout << "Enter today's name: ";
cin >> WeekDay;
To display the value of an array of characters, use the cout extractor as we have used it
with all other variables, without the square brackets. Here is an example:
Example 3.2:
#include <iostream>
int main()
{
char WeekDay[12];
char EndMe[] = "\n";
cout << "Enter today's name: ";
cin >> WeekDay;
cout << "Today is " << WeekDay;
cout << EndMe;
return 0;
Today is Thursday
C/C++ treats arrays of characters differently than it does the other arrays. For example, we
have learned to declare a two-dimensional array of integers as follows:
This variable is in fact two arrays and each array contains 6 integers. For a string, if you
want to declare a two-dimension array of characters, the first dimension specifies the
number of string in the variable. The second dimension specifies the number of characters
that each string can hold. Here is an example:
In this case, the StudentName variable is an array of 4 strings and each string can have a
maximum of 9 characters (+1 for the null-terminated character). To locate a string on this
variable, type the name of the array followed by its index, which is the index of the column.
This means that the first string of the StudentName array can be accessed with
StudentName[0]. The second would be StudentName[1], etc. This allows you to display
each string on the cout extractor:
Example 3.3:
#include <iostream>
Student Names
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'.
Earlier, we declared an array as follows:
int main()
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()
#include <iostream>
using namespace std;
int main()
{
double *Value;
Value = 12.55;
cout << "Value = " << Value;
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;
Strings as Variables
• To create a variable of type string, simply:
string fullName;
string oneString = “2 + 2 = ”;
oneString += “5”;
• For instance, what exactly does it mean if one string is “less than” another?
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.
string fullname;
String Processing
• In addition to giving us a new data type to hold strings, the string library offers many
• You can find most of them of them in the book, but here are a few useful ones.
• Example:
string s1 = “Super!”;
at(index)
• This method returns the character at the specified index. Indices start from 0.
• Example:
string n = “Vikram”;
string n = “Vikram”;
erase(index)
• This method removes all characters from the string starting from the specified index to
the end.
• Example:
os.erase(9);
find(str)
• This method returns the integer index of the first occurrence of the specified string
• Example:
//0 is output
find(str, index)
• This method returns the integer index of the first occurrence of the specified string
starting from the specified index.
• Example:
//5 is output
insert(index, str)
• Inserts the specified string at the specified index.
• Example:
replace(index, n, str)
• Removes n characters in the string starting from the specified index, and inserts the
specified
• Example:
transport.replace(0, 5, “Sail”);
substr(index, n)
• Returns the string consisting of n characters starting from the specified index.
• Example:
//outputs “Ship”
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];
Example 3.9:
#include <iostream>
#include <cstring>
int main()
int len;
char str[15];
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() 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];
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
N.B. If you fail to ensure that the source string is null-terminated, very strange and
sometimes very ugly things may result.
Strncat ()
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.
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);
N.B. If you fail to ensure that the source string is null-terminated, very strange and
sometimes very ugly things may result.
Strcmp ()
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.
diff will have a negative value after the following statement is executed.
diff will have a positive value after the following statement is executed.
diff will have a value of zero (0) after the execution of the following statement, which
compares s1 with itself.
Strncmp ()
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.
diff will have a negative value after the following statement is executed.
diff will have a positive value after the following statement is executed.
diff will have a value of zero (0) after the following statement.
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
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
struct employee
{char name[30];
int age;
float salary;
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;
struct x
{
int a;
int b;
int c;
};
main()
{ struct x z;
z.a = 10; // Assigning a value to a member through a structure variable
z.a++; // incrementing the member variable.
cout<<z.a; // Retrieving the value.
}
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;
strcpy(book1.title,”basic”);
strcpy(book1.author,”Balagurusamy”);
book1.pages=250;
book1.price=28.50;
int main ()
{
struct Library
{
int ISBN, copies, PYear;
char bookName[30], AuthorName[30], PublisherName[30];
};
Library libraryVariable;
LibraryVariable.ISBN = 1293;
strcpy (libraryVariable.bookName, "Network Security");
strcpy (libraryVariable.AuthorName, "Martin");
strcpy (libraryVariable.PublisherName, "Waley");
libraryVariable.copies = 4;
libraryVariable.PYear = 1998;
The value of a structure variable can be assigned to another structure variable of the same type, e.g:
Structures can be implemented with functions and arrays. Moreover, structures can be implemented as
the member of other structure. This is termed as structure within structure.
When a structure is decleared as the member of another structue, it is called Structure within a
structure. It is also known as nested structure.
cout<<universityVariable.libraryVariable.bookName;
cout<<universityVariable.city;
cout<<universityVariable.libraryVariable.bookName);
cout<<universityVariable.libraryVariable.ISBN);
Common Errors in Accessing Structures
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;
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.
Lab 5:
Structures II
A structure can be passed as a function argument just like any other variable. This raises a few
practical issues.
Where we wish to modify the value of members of the structure, we must pass a pointer to that
structure. This is just like passing a pointer to an int type argument whose value we wish to change.
If we are only interested in one member of a structure, it is probably simpler to just pass that
member. This will make for a simpler function, which is easier to re-use. Of course if we wish to
change the value of that member, we should pass a pointer to it.
When a structure is passed as an argument, each member of the structure is copied. This can prove
expensive where structures are large or functions are called frequently. Passing and working with
pointers to large structures may be more efficient in such cases
Example 5.1:
int main ()
{
struct { char name[20];
int age;
} record;
}
void display(char *name, int age)
{
cout<<"Name is "<<name <<"\nAge is "<<age;
}
Example 5.2:
#include<iostream>
struct record_format { char name[20]; int age;};
void display(struct record_format);
int main ()
{
struct record_format record;
strcpy(record.name, "Joe Brown");
record.age = 21;
display (record);
cout<<"name is "<<record.name<<"\nAge is "<<record.age;
return 0;
}
void display(struct record_format rec)
{cout<<"name is "<<rec.name<<"\nAge is " <<rec.age;
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:
We have a structure:
struct employee{
char name[30];
int age;
float salary;
};
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;
The members of a structure can be accessed using an arrow operator. The arrow operator->
(consisting of minus sign (-) followed by the greater than (>) symbol). Using the arrow
operator a structure member can be accessed by the expression sptr1->member-name Where sptr
holds the address of a structure variable, and member-name is the name of a member belonging to
the structure. For example, the values held by the members belonging to the structure emp1 are
given by the expression:
sptr1->name;
sptr1->age;
sptr1->salary;
Using the pointers the members of the structure variable emp1 can be initialized asfollow:
cout<<"\nEnter Name:";
gets(sptr1->name);
cout<<"\nEnter age:";
cin>>sptr1->age;
cout<<"\nEnter Salary:";
cin>>sptr1->salary;
//Printing Structure fields
cout<<"\nYou have entered:\n";
cout<<sptr1->name<<endl;
cout<<"\t"<<sptr1->age<<endl;
cout<<"\t"<<sptr1->salary<<endl;
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.
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.
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.
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 Bit n of ~x is the opposite of bit n of x
^ Bitwise Exclusive Or Bit n of x^y is 1 if bit n of x or bit n of y is 1 but not if both are 1.
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.
01001000 &
10111000 =
----------------
00001000
01001000 |
10111000 =
-----------------
11111000
Bitwise Exclusive OR (XOR): The exclusive-or operation takes two inputs and returns a 1 if either one or
the other of the inputs is a 1, but not if both are. That is, if both inputs are 1 or both inputs are 0, it
returns 0. Bitwise exclusive-or, with the operator of a carrot, ^, performs the exclusive-or operation on
each pair of bits. Exclusive-or is commonly abbreviated XOR.
01110010 ^
10101010
--------------
11011000
Suppose, we have some bit, either 1 or 0, that we'll call Z. When we take Z XOR 0, then we always get Z
back: if Z is 1, we get 1, and if Z is 0, we get 0. On the other hand, when we take Z XOR 1, we flip Z. If Z is
0, we get 1; if Z is 1, we get 0:
myBits ^ 0 : No change
myBits ^ 1 : Flip
Example 5.4:
#include <iostream>
#include <iomanip>
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;
}
Lab 6:
Object Orientation in C++ I
Data Types, Objects, Classes and Instances
So far, we've learnt that C++ lets you create variables which can be any of a range of basic data types:
int, long, double and so on. However, the variables of the basic types don't allow you to model real-
world objects (or even imaginary objects) adequately. It's hard to model a box in terms of an int, for
example; what we need is something that allows us to collect together the various attributes of an
object. In C++, we can do this very easily using classes.
You could define variables, length, breadth and height to represent the dimensions of the box and bind
them together as members of a Box class, as follows:
class Box
{
public:
double length;
double breadth;
double height;
};
This code actually creates a new type, called Box. The keyword class defines Box as such, and the
elements that make up this class are defined within the curly braces. Note that each line defining an
element of the class is terminated by a semicolon, and that a semicolon also appears after the closing
brace. The elements length, breadth and height are referred to as data members. At the top of the class
definition, you can see we have put the keyword public - this just means that the data members are
generally accessible from outside the class. You may, however, place restrictions on the accessibility of
class members, and we'll see how this works a bit later in this chapter.
With this definition of a new data type called Box, you can go ahead and define variables of this type just
as you did with variables of the basic types:
Box myBox; //Declare a variable myBox of type Box
Once we've defined the class Box, the declaration of variables of this type is quite standard. The variable
myBox here is also referred to as an object or an instance of the class Box.
With this definition of a new data type called Box, you can go ahead and define variables of this type just
as you did with variables of the basic types. You can then create, manipulate and destroy as many Box
objects as you need to in your program. This means that you can model objects using classes and write
your programs around them. So - that's object-oriented programming all wrapped up then?
Well, not quite. You see, object-oriented programming (OOP) is based on a number of foundations
(famously encapsulation, polymorphism and inheritance) and what we have seen so far doesn't quite fit
the bill. Don't worry about what these terms mean for the moment - we'll be exploring these ideas in
more detail as we learn more about what you can do with classes.
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++,
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
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.
Access Control in a Class
The keyword public looks a bit like a label, but in fact it is more than that. It determines the
access attributes of the members of the class that follow it. Specifying the data members as public
means that these members of an object of the class can be accessed anywhere within the scope of
the class object. You can also specify the members of a class as private or protected. In fact, if
you omit the access specification altogether, the members have the default attribute, private. We
shall look into the effect of these keywords in a class definition a bit later.
Remember that all we have defined so far is a class, which is a data type. We haven't declared any
objects of the class. When we talk about accessing a class member, say height, we're talking about
accessing the data member of a particular object, and that object needs to be declared somewhere.
Declaring Objects of a Class
We declare objects of a class with exactly the same sort of declaration that we use to declare objects
of basic types. We saw this at the beginning of the chapter. So, we could declare objects of our class,
Box, with these statements:
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.
Accessing the Data Members of a Class
The data members of objects of a class can be referred to using the direct member access
operator (.). So, to set the value of the data member height of the object Box2 to, say, 18.0, we
could write this assignment statement:
Box2.height = 18.0; // Setting the value of a data member
We can only access the data member in this way, in a function outside the class, because the
member height was specified as having public access. If it wasn't defined as public, this
statement would not compile. We'll see more about this shortly.
Try It Out - Your First Use of Classes
Let's have a go at creating a Box class and setting the values of it's data members. We'll try it out in
the following console application:
Example 6.1:
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
return 0;
}
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:
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.
Using Pointers to Objects
As you might expect, we can create a pointer to variable of a Box type. The declaration for this is just
what you might expect:
Box* pBox = &aBox
Assuming we have already declared an object, aBox, of type Box, then this line simply declares a pointer
to type Box and initializes it with the address of aBox. We could now access the members of aBox
through the pointer pBox, using statements like this:
(*pBox).length = 10;
The parenthesis to de-reference the pointer here are essential, since the member access operator takes
precedence over the de-reference operator. Without the parenthesis, we would be attempting to treat
the pointer like an object and to de-reference the member, so the statement would not compile.
This method of accessing a member of the class via a pointer looks rather clumsy. Since this kind of
operation crops up a lot in C++, the language includes a special operator to enable you to express the
same thing in a much more readable and intuitive form. It is called the indirect member access
operator, and it is specifically for accessing members of an object through a pointer. We could use it to
re-write the statement to access the length member of aBox through the pointer pBox:
pBox->length = 10;
The operator looks like a little arrow and is formed from a minus sign followed by the symbol for
'greater than'. It's much more expressive of what's going on, isn't it? We'll be seeing a lot more of this
operator as we go along and learn more about classes. We're now ready to break new ground by taking
a look at member functions of a class.
Member Functions of a Class
A member function of a class is a function that has its definition or its prototype within the class
definition. It operates on any object of the class of which it is a member, and has access to all the
members of a class for that object.
Example 6.2:
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
return 0;
}
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:
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:
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:
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:
Note:
Before doing your lab exercises run the examples.
Exercises will be provided by the instructors in the lab.
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;
// Constructor definition
Box(double lv, double bv, double hv)
{
cout << endl << "Constructor called.";
length = lv; // Set values of
breadth = bv; // data members
height = hv;
}
int main(void)
{
Box Box1(78.0,24.0,18.0); // Declare and initialize Box1
Box CigarBox(8.0,5.0,1.0); // Declare and initialize CigarBox
double volume = 0.0; // Store the volume of a box here
volume = Box1.Volume(); // Calculate volume of Box1
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
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; // Declare Box2 of type Box
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;
}
int main(void)
{
Box Box1(78.0,24.0,18.0); // Declare and initialize Box1
Box Box2; // Declare Box2 - no initial values
Box CigarBox(8.0,5.0,1.0); // Declare and initialize CigarBox
double volume = 0.0; // Store the volume of a box here
volume = Box1.Volume(); // Calculate volume of Box1
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
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 // 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
// 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;
}
// 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;
}
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.
Using an Initialization List in a Constructor
Previously, we initialized the members of an object in the class constructor using explicit
assignment. We could also have used a different technique, using what is called an initialization
list. We can demonstrate this with an alternative version of the constructor for the class Box:
// Constructor definition using an initialization list
Box(double lv=1.0, double bv=1.0, double hv=1.0): length(lv),
breadth(bv),
height(hv)
{
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
#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;
}
private:
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 Match(2.2, 1.1, 0.5); // Declare Match box
Box Box2; // Declare Box2 - no initial values
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.
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.
Accessing private Class Members
On reflection, declaring all the data members of a class as private might seem rather extreme.
It's all very well protecting them from unauthorized modification, but that's no reason to keep
their values a secret. What we need is a Freedom of Information Act for private members.
You don't need to start writing to your state senator to get it - it's already available to you. All
you need to do is to write a member function to return the value of a data member. Look at this
member function for the class Box:
inline double Box::GetLength(void)
{
return length;
}
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(); // Obtain data member length
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.
The Default Copy Constructor
Suppose we declare and initialize a Box object Box1 with this statement:
private:
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(78.0, 24.0, 18.0);
Box Box2 = Box1; // Initialize Box2 with Box1
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.
Note:
Before doing your lab exercises run the examples.
Exercises will be provided by the instructors in the lab.
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:
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.
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;
};
if(root->key == key)
return root->data;
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:
For instance, in our example Node class, we would use this syntax:
class Node
private:
int data;
int key;
// ...
};
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!)
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.
Try It Out - Using a friend to Calculate the Surface Area
We can see how this works in the following example:
Example 8.1
// Creating a friend function of a class
#include <iostream>
using namespace std;
private:
double length; // Length of a box in inches
double breadth; // Breadth of a box in inches
double height; // Height of a box in inches
//Friend function
friend double BoxSurface(Box aBox);
};
int main(void)
{
Box Match(2.2, 1.1, 0.5); // Declare Match box
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.
Placing friend Function Definitions Inside the Class
We could have combined the definition of the function with its declaration as a friend of the Box class
within the class definition - the code would run as before. However, this has a number of disadvantages
relating to the readability of the code. Although the function would still have global scope, this wouldn't
be obvious to readers of the code, since the function would be hidden in the body of the class definition,
and particularly since the function would no longer show up in the ClassView.
Every object in C++ has access to its own address through an important pointer called this pointer. The
this pointer is an implicit parameter to all member functions. Therefore, inside a member function, this
may be used to refer to the invoking object.
Friend functions do not have a this pointer, because friends are not members of a class. Only member
functions have a this pointer.
Let us try the following example to understand the concept of this pointer:
#include <iostream>
using namespace std;
class Box
{
public:
// Constructor definition
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
}
double Volume()
{
return length * breadth * height;
}
int compare(Box box)
{
return this->Volume() > box.Volume();
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // Declare box1
Box Box2(8.5, 6.0, 2.0); // Declare box2
if(Box1.compare(Box2))
{
When the above code is compiled and executed, it produces following result:
Constructor called.
Constructor called.
Box2 is equal to or larger than Box1
Static variables keep their values and are not destroyed even after they go out of scope. For example:
int GenerateID()
{
static int s_nID = 0;
return s_nID++;
}
int main()
{
std::cout << GenerateID() << std::endl;
std::cout << GenerateID() << std::endl;
std::cout << GenerateID() << std::endl;
return 0;
}
0
1
2
Note that s_nID has kept it’s value across multiple function calls.
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.
class Something
{
public:
static int s_nValue;
};
int Something::s_nValue = 1;
int main()
{
Something cFirst;
cFirst.s_nValue = 2;
Something cSecond;
std::cout << cSecond.s_nValue;
return 0;
}
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!
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.
Because static member variables are not part of the individual objects, you must explicitly define the
static member if you want to initialize it to a non-zero value. The following line in the above example
initializes the static member to 1:
int Something::s_nValue = 1;
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.
Why use static variables inside classes? One great example is to assign a unique ID to every instance of
the class. Here’s an example of that:
class Something
{
private:
static int s_nIDGenerator;
int m_nID;
public:
Something() { m_nID = s_nIDGenerator++; }
int Something::s_nIDGenerator = 1;
int main()
{
Something cFirst;
Something cSecond;
Something cThird;
1
2
3
Because s_nIDGenerator is shared by all Something objects, when a new Something object is
created, it’s 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.
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.
Complex c = a.Add(b);
This piece of code is not as readable as the first example though--we're dealing with numbers, so doing
addition should be natural. (In contrast to cases when programmers abuse this technique, when the
concept represented by the class is not related to the operator--ike using + and - to add and remove
elements from a data structure. In this cases operator overloading is a bad idea, creating confusion.)
In order to allow operations like Complex c = a+b, in above code we overload the "+" operator. The
overloading syntax is quite simple, similar to function overloading, the keyword operator must be
followed by the operator we want to overload:
class Complex
{
public:
Complex(double re,double im)
:real(re),imag(im)
{};
Complex operator+(const Complex& other);
Complex operator=(const Complex& other);
private:
double real;
double imag;
};
Complex Complex::operator+(const Complex& other)
{
double result_real = real + other.real;
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
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:
Complex a(2,3);
Complex b(5.3,6);
cout<<a<<b;
Note: Before doing your lab exercises run the examples.
Lab 10:
FCS&E, GIK Institute Topi, Pakistan Page 86
CS102L: Intensive Programming Lab
Inheritance
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.
A class can be derived from more than one classes, which means it can inherit data and functions
from multiple base classes. To define a derived class, we use a class derivation list to specify the
base class(es). A class derivation list names one or more base classes and has the form:
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>
// 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:
int getArea()
{
return (width * height);
}
};
int main(void)
{
Rectangle Rect;
Rect.setWidth(5);
Rect.setHeight(7);
return 0;
}
When the above code is compiled and executed, it produces following result:
Total area: 35
A derived class can access all the non-private members of its base class. Thus base-class
members that should not be accessible to the member functions of derived classes should be
declared private in the base class.
We can summarize the different access types according to who can access them in the following
way:
A derived class inherits all base class methods with the following exceptions:
Type of Inheritance:
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:
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>
}
};
// 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();
return 0;
}
When the above code is compiled and executed, it produces following result:
Total area: 35
Total paint cost: $2450
Lab 11:
Polymorphism
We have seen that it is possible to derive a class from a base class and that we can add
functionality to member functions.
One of the features of derived classes is that a pointer to a derived class is type-compatible with a
pointer to its base class. Polymorphism takes advantage of this feature.
#include <iostream>
using namespace std;
class CPolygon
{
protected:
int width, height;
public:
void setup (int first, int second)
{
width= first;
height= second;
}
};
int main ()
{
CRectangle rectangle;
CTriangle triangle;
ptr_polygon1->setup(2,2);
ptr_polygon2->setup(2,2);
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 can’t implement one version of area() in the base class CPolygon. (If they were
the same we had no problem.)
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.
#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()
{
return (0);
}
};
int main ()
{
CRectangle rectangle;
CTriangle triangle;
CPolygon polygon;
ptr_polygon1->setup(2,2);
ptr_polygon2->setup(2,2);
ptr_polygon3->setup(2,2);
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().)
At the design level, an abstract base class (ABC) corresponds to an abstract concept. For
instance: if you ask to draw a shape, I will probably ask what kind of shape. The term ―shape‖ is
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.
#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;
};
int main ()
{
CRectangle rectangle;
CTriangle triangle;
ptr_polygon1->setup(2,2);
ptr_polygon2->setup(2,2);
return 0;
}
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, isn’t 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;
}
};
int main ()
{
CRectangle rectangle;
CTriangle triangle;
ptr_polygon1->setup(2,2);
ptr_polygon2->setup(2,2);
ptr_polygon1->onscreen();
ptr_polygon2->onscreen();
return 0;
}
Lab 12:
File Handling in C++
These classes are derived directly or indirectly from the classes istream, and ostream. We have already
used objects whose types were these classes: cin is an object of class istream and cout is an object of class
ostream. Therfore, we have already been using classes that are related to our file streams. And in fact, we
can use our file streams the same way we are already used to use cin and cout, with the only difference
that we have to associate these streams with physical files. Let's see an example:
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():
Where filename is a null-terminated character sequence of type const char * (the same type that string
literals have) representing the name of the file to be opened, and mode is an optional parameter with a
combination of the following flags:
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:
Combining object construction and stream opening in a single statement. Both forms to open a file are
valid and equivalent.
To check if a file stream was successful opening a file, you can do it by calling to member is_open() with
no arguments. This member function returns a bool value of true in the case that indeed the stream object
is associated with an open file, or false otherwise:
This will open the file without destroying the current contents and allow you to append new data. When
opening files, be very careful not to use them if the file could not be opened. This can be tested for very
easily:
ifstream a_file ( "example.txt" );
if ( !a_file.is_open() ) {
// The file could not be opened
}
else {
// Safely use the file stream
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:
[file example.txt]
This is a line.
This is another line.
Data input from a file can also be performed in the same way that we did with cin:
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.
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.
ofstream, like ostream, has a pointer known as the put pointer that points to the location where the next
element has to be written.
Finally, fstream, inherits both, the get and the put pointers, from iostream (which is itself derived from
both istream and ostream).
These internal stream pointers that point to the reading or writing locations within a stream can be
manipulated using the following member functions:
These functions allow us to change the position of the get and put stream pointers. Both functions are
overloaded with two different prototypes. The first prototype is:
seekg ( position );
seekp ( position );
Using this prototype the stream pointer is changed to the absolute position position (counting from the
beginning of the file). The type for this parameter is the same as the one returned by functions tellg and
tellp: the member type pos_type, which is an integer value.
The other prototype for these functions is:
Using this prototype, the position of the get or put pointer is set to an offset value relative to some specific
point determined by the parameter direction. offset is of the member type off_type, which is also an
integer type.
And direction is of type seekdir, which is an enumerated type (enum) that determines the point from
where offset is counted from, and that can take any of the following values:
The following example uses the member functions we have just seen to obtain the size of a file: