Pointers
193 In this context * is known as a de-referencing or indirect value operator. This operator will give the value stored at a particular memory location (in other words it will give the value stored at a particular address). It is the complement of the ‘&’ operator. Hence, *p means value at p (which holds address of marks). The output of the program is: Address is : 006AFDF4 Value at that address is : 70 To summarize: •
we can say that * means 'value at the address'.
•
Both the operators * and & can be considered as pointer operators.
The figure below should make the concept of the program very clear to you:
Everything is stored in memory. So what about the pointer itself. A pointer stores an address; but where does it store it? The answer can be found in the above figure. The pointer itself has a memory address where it stores the address of the variable that it points to. Confusing? Basically, the pointer has an address where it stores another address. In the earlier program, if you type the following code: cout<<&p; You will get the result as 006AFDF0. That is the address of the pointer itself.
Pointers
194 This brings to view another point. The difference between pointers and a general variable is that address is primary for pointers. The value stored at the address is secondary. For other variables, the value is primary while the address where the value is stored is secondary. If you want to find the value of ‘marks’ you would just write: cout<
Be careful with Pointers This section deals with some of the mistakes that can be committed using pointers. 1.) Make sure that your pointer variable points to the correct type of data. If you declare a pointer of type integer then make sure that the address it holds contains an integer. float y = 6.6; int * p; p = &y; float.
// y is a float quantity // p points to an integer type // Wrong - p is supposed to point to an integer but y is a
2.) Be careful while making multiple pointer declarations. Suppose you want to declare two pointers: p1 and p2 that point to an integer data type. You might be tempted to write the declaration as: int * p1, p2; Seems right, doesn't it? This declaration is wrong. This will declare p1 as a pointer pointing to integer type. The compiler will consider p2 as an integer and not a pointer. Since * precedes p1, the compiler considers only p1 as a pointer. Hence be careful while declaring pointers.
Pointers
195 3.) A pointer should store an address. Consider an extract from a program given below: int *p; *p = 82;
// marks is a pointer that points to data type long. //wrong
Note the point that we have not yet stored any memory address in marks. When a pointer is created the compiler will allot memory to hold an address. It will not allocate memory to hold the data to which the address points. Perhaps it's a bit confusing. The problem with our program is that we are supposed to store an address in p. After storing an address in p you can then store some value at that address. What we have done is that we haven't assigned any address to p. Without an address you can't store a value. Hence the value 82 cannot be placed anywhere. A pointer which is not initialized is called a ‘dangling pointer’. 4.) Pointers are not the same as integers. Pointers hold a memory address and hence they cannot be multiplied or divided. Pointer addition will be discussed later. 5.) Not advisable to assign a memory address on your own (unless you are really sure of what you are doing). Consider the following: int * p; p = 006AFDF4;
//Correct //wrong
A pointer stores a memory address. So you may think that the second line is correct. We have assigned a hexadecimal number to p (which is a pointer). The problem is that the compiler doesn't know that 006AFDF4 is a memory address. You may get an error message saying ‘data type mismatch’. Instead you can write it as: p = (int * ) 0x006AFDF4;
// This is correct.
This is correct because we are forcibly telling the compiler that this is a memory address (you’ll learn about forcing in the section on ‘casting’). 6.) Some programmers prefer to initialize a pointer to NULL. NULL is a predefined constant with a value of 0. When initialized to null, the memory address stored in the pointer will be: 0x00000000. The code below will compile but it is an erroneous code: int *p; int a=5; cout<<*p;
Pointers
// value displayed is 910569528
196 In this code, the pointer ‘p’ hasn’t been assigned the address of ‘a’. ‘p’ will have some random memory location and reading the value at this location produces a garbage value. Programmers thus prefer to use: int *p=NULL; int a=5; cout<<*p; This code will generate an error when it is run in your computer because ‘p’ is pointing to the location 0x00000000. 7.) Potential hazards of using uninitialized pointers: There are instances when a programmer declares a pointer and forgets to initialize it. Later in the code the programmer uses this pointer and this can lead to serious errors. Since the pointer has some unknown address value, when we dereference such a pointer we cannot predict what will be the value. A bigger problem might arise if you attempt to change the value stored at that address. For ex: int *p; *p=5; In this case, the value at an unknown location will get changed to 5. This particular memory location could actually be some other variable in your program. Thus indirectly you would have changed the value of another variable and it will become difficult to trace the problem.
You may be wondering why we need to tell the compiler as to what type of data a pointer is going to point to? A pointer just holds an address, so why do we need to specify the data type pointed to? The problem will occur when you make use of the dereferencing operator to get the value stored at the address. If a pointer points to a character, then the compiler knows that when we dereference the pointer it should only read one byte. If the pointer points to a short int the compiler knows that it has to read 2 bytes and so on. If we didn’t specify what data type the pointer will point to, then the compiler has no idea as to how many bytes it should read when dereferenced. And if we never dereferenced a pointer then we’d never know what is stored at a particular memory address! What’s the difference between: int *ptr; and int* ptr;
Pointers
197 It is a matter of choice. Some programmers prefer the first one while you may frequently encounter the second type of declaration. The advantage of using the first method is that you are less likely to commit the mistake of declaring a pointer and an integer in one statement. Ex: if you want to declare to pointer ptr1 and ptr2, it is less likely to type int *ptr, ptr2; Pointers Use whichever method you are comfortable with.
198
More Pointers - II
The following topics are covered in this section: • •
Arithmetic operations on Pointers Pointers and Arrays
Arithmetic operation on Pointers You can't perform multiplication and division on pointers but you can do addition and subtraction on pointers. Suppose that p1 is a pointer that points to an integer and the address is 100 (yes, addresses will usually be a larger hexadecimal number but assume 100 as a memory address for simplicity). Also assume that an integer occupies 4 bytes. Now if we say: p1++ ; what would the above expression do? The value of p1 (i.e. the address pointed to by p1) gets changed. The address becomes 104 not 101 because an integer occupies 4 bytes. If an integer occupied 2 bytes then the result would be address 102. Similarly if you do: p1--; the address of p1 will be 98. Hence, when a pointer is incremented it points to the memory location of the next element of its data type. Similarly you could say the following: p1 = p1 + 10; This makes p1 point to the tenth element of p1's data type beyond the current position. If p1 points to a character type then p1 + 10 would move the pointer 10 bytes. Suppose p1 were a integer then p1 would move 20 bytes (if int occupies 2 bytes). Can we add two pointers? Addition of two pointers is not allowed. It doesn’t make logical sense to add two pointers because you won’t know what the new address will be. You’ll get a compiler error if you attempt to perform such an operation. But subtraction of two pointers is allowed since this will give you the number of elements lying between the two addresses. You can use pointers to find the size occupied by a data type (i.e. without using the sizeof operator) as shown below:
Pointers
199 long int i; long int *ptr; ptr=&i; cout<
//ERROR
would cause an error saying that a pointer can only be subtracted from another pointer (or you’d get the message: Illegal pointer subtraction). Assignment Operator with pointers: Whenever we make use of pointers in a program we should be careful about what we are dealing with (i.e. are we wanting to refer to the memory location or do we want to use the value stored at that memory location). Consider two pointers, p1 and p2 (both pointing to integer data types). p1=p2; *p1=*p2; In the first case, both p1 and p2 will now point to the same memory location. But in the second case only the value stored at the location pointed by p1 will change (i.e. the address will not change). Similarly,
if (p1 == p2) and if (*p1 == *p2)
are completely different. In the first case we are checking whether both pointers contain the same address while in the second case we check whether the value contained at the memory location pointed by them is equal.
Pointers
200
Pointers and Arrays Arrays can be accessed using pointers instead of their index numbers. Consider the following program: int main( ) { int marks[3]; int* p; p = &marks[0]; // Pointer points to the first element of array. marks[0]=58; marks[1]=61; marks[2]=70; cout<
Pointers
201 Well, a pointer of type integer (like ‘p’ that was declared above) can point to any integer variable. ‘p’ can point to any other variable (as long as the variable is an integer type). But the array is constant. Whenever you use the array name, it will only refer to the same array. This means that the array is similar to a constant (it remains the same) and in fact it is sometimes called a constant pointer. Thus in the previous example: p = marks; is correct because pointer ‘p’ can take different values but marks = p; is invalid because ‘marks’ is like a constant pointer (value cannot be changed). We refer to an array value by indexing: marks[2]=70; using pointer we can write it as: *(p+2)=70; but even indexing in pointers is allowed: p[2]=70; is also a valid statement. Remember: *(p+2) is 70 but *p + 2 will not be 70. Since * has a higher operator precedence than +, the result of *p + 2 will be 58 + 2 (assuming that *p has a value 58). When using pointers to access array elements ensure that you use the parentheses. Similarly it is not that only the pointer should be called by using the * operator. Since an array is also like a pointer, the following: marks[1] = 61; is the same as: *(marks+1) = 61;
Pointers
202 Beware: Don’t confuse the de-reference operator (*) with the same * that is used to declare a pointer. Though they both seem the same, they are totally different in function; one is to declare a pointer and the other is to access the value held at that particular address. int j; int* p = &j; j = 5; cout<<*p; The above piece of coding is correct because we are initializing the pointer ‘p’ to the address of the variable j. Try this: int* p=5; Absolutely wrong! Cannot assign a value to ‘p’. Consider a modification of the above program. #include
Pointers
203 We had discussed earlier about passing arrays to functions earlier and you can use any one of the following methods: void disp(int a[ ] ) or: void disp(int a[3] ) Now you should have understood as to how the above two methods really work. In effect we are actually passing the address of the array to the function. Thus you could also pass a pointer to a function (of course the pointer should have the address of the array). See the example below: #include
Pointers
204 Instead of: clear(p,3); you could even have used: clear(marks,3); This would have also worked perfectly well because ‘p’ and marks mean the same thing. Another modification you could have done is to change the function syntax to: void clear(int m[ ],int size) { for (int i=0;i
Pointers
205
Pass by value and pass by reference (and reference variables) Pass by value and Pass by Reference We have seen as to how to pass values to a function through arguments. Actually there are two ways to pass values to a function through arguments. These two methods are explained below with examples.
Pass By Value: Example:
void check (int x) {//body of function } int main ( ) { int b = 10; check (b); }
In this function, ‘x’ is a parameter and ‘b’ (which is the value to be passed) is the argument. In this case the value of ‘b’ (the argument) is copied in ‘x’ (the parameter). Hence the parameter is actually a copy of the argument. The function will operate only on the copy and not on the original argument. This method is known as PASS BY VALUE. So far we have dealt only with pass by value (except when passing arrays to functions). // Pass by value illustration #include
Pointers
206 You can see that the value of ‘num’ is unchanged. The function ‘square’ works only on the parameter (i.e. on x ). It does not work on the original variable that was passed (i.e it doesn't work on ‘num’).
The diagram makes it quite clear as to what happens when we call square(num). The following initialization takes place: int x = num; and the function square( ) operates only on a copy of num.
Pass By Reference In pass by reference method, the function will operate on the original variable itself. It doesn't work on a copy of the argument but works on the argument itself. Consider the same square function example: // Illustration of pass by reference #include
207 } int main ( ) { int num = 10; square(&num); cout<<" Value of num is "<
Pointers
208 This is the pass-by-reference method which was used in C. In C++ there is a different approach. Of course you can use the above method, but C++ has its own special way.
C++ style of Pass by Reference In the C style we have seen that you need to make use of & and lots of * symbols to pass-byreference. In C++ we make use of the ‘reference parameter’ (or reference variable) and can avoid the use of so many symbols. All you need to do is put & before your function's parameter. Example: void square (&x) Whatever operation is done on x will actually affect the original calling argument. Basically ‘x’ can be said to be an implicit pointer. The difference is that you needn't use *x to operate on the parameter. Consider the same example: // Pass by reference C++ style example void square ( int &x ) { x = x * x; }
// x becomes a reference parameter //no need to write *x = (*x) * (*x); as we did earlier
int main ( ) { int num =10; int answer; square(num); earlier cout<<" Value of num is "<
// No need to say &num as we did // Value of a is 100
The line x = x * x ; actually operates on ‘num’ and not on a copy of ‘num’. When the C++ compiler sees that a function has been declared with its arguments having ‘&’, it knows that the function is a pass-by-reference type.
Pointers
209
More about reference variables Reference variables are not applicable only to function parameters. You can have them in other places of the program as well. Consider the following program: #include
int x; int &ref = x; //ref is a reference variable x=5; cout<
•
•
•
Usually reference variables will only be used with respect to functions (using them as shown in the program above can lead to confusion). Passing by reference is especially useful in passing structures and objects to functions. In the case of structures, if you use pass-by-value, memory space will be used up to create a copy of the structure within the function. Passing the structure by reference will avoid this overhead. In the case of classes, if you pass an object by pass-by-value you will be invoking an extra destructor (because a copy of the object is made). In passing an object by reference we can avoid this problem (this will be discussed later). References can also be returned by functions (dealt with in operator overloading and in streams).
Pointers
210 Can arrays be passed by value? When we pass arrays to a function we are actually passing the address of the first element of the array. This is as good as passing a pointer to the function. Thus the function will be directly operating on the original array and not on a copy of the array. There is no way of creating a function which when called will operate on a copy of the array (unless you create a new array within the function and manually copy the elements from the original array to the new array).
Test yourself on Pass by Reference This section is intended to make sure that you've understood pass by reference. Write a C++ program to get two numbers from the user and swap the values using a function. You have to do it using reference parameters. The solution will be as follows: #include
Pointers
211
More on references •
Reference variables are aliases (an alternate name to a variable). int x; int &ref=x;
//we tell the compiler that ‘ref’ is another name for ‘x’
Working on ‘ref’ is the same as working on ‘x’ and vice-versa. But since a reference variable is an alias, we should initialize the alias. int x; int &ref;
//ILLEGAL reference variable should be initialized.
This makes sense because an alias should be an alternate name for something (and we need to tell the compiler what that something is).
Pointers
212 •
Non-const references should be initialized using L-values (i.e. using something which is permitted on the left hand side of an assignment expression). int &ref=5;
//ERROR
This is an error because ‘5’ is an integer constant and it is not an L-value (we cannot use 5 on the left side of an assignment). Logically speaking also this isn’t possible because a reference variable is an alias (you use an alias instead of another name). One might argue, “instead of the constant 5, I intended to use the term ref”. But when we think from the viewpoint of the compiler, we can understand the complications involved if this were permitted. Let’s say this statement was legal. Now (though the programmer might not purposely do it), there is nothing to stop the programmer from coding: int &ref=5; ref=10;
//ERROR (but let’s assume this is permitted)
What now; where is the new value of 10 supposed to be stored? (there is no location for it). But if we use a const-reference: const int &ref=5;
//Permitted
the compiler won’t complain. The reason is that now we tell the compiler, “This is a const reference and in case I attempt to assign any values to it then you can flag an error”. The compiler feels reassured and it will permit the use of ‘ref’ instead of 5. Note: For the above purpose, it would be more clearer and effective to use const int ref=5; (since we are not really making use of any functionality of a reference variable). •
We have seen that passing by reference is similar to passing a pointer (as was done in C programming). In fact reference variables and pointers are quite similar. You might wonder, “why not use pointers itself?”
When passing a pointer, the function definition will be cluttered with a lot of dereferencing. Passing pointers (C style) void square (int *x) { *x = (*x) * (*x); }
Passing by reference void square (int &x ) { x = x * x; }
The code is clean and reader-friendly when passing by reference.
Pointers
213 •
We can use the ‘const’ keyword in reference parameters to ensure that the function doesn’t modify the argument. For example: //Function won’t modify the argument void print(const int &x) { cout<
//compiler error
is an error. The compiler will complain saying, “you are attempting to modify the value of the constant reference ‘x’”. •
Same address: int x; int &ref=x; cout<
//address of ‘x’ //address of ‘ref’
Both addresses will be the same (which is why working on ‘x’ is the same as working on ‘ref’).
Pointers
214 •
We can return references from functions but beware.
int& dummy( ) { int x=5; return x; } int main ( ) { cout<
Pointers
215
More Pointers - II The following topics are covered in this section: • • •
Returning pointers from functions Run Time and compile time Dynamic Allocation
Returning pointers from functions You can return pointers (or arrays) from functions. The syntax to declare a function which will return a pointer is: return-data-type* function-name (pararmeters); For example: int* create ( ); This is to declare a function called create ( ) that will return a pointer to an integer. Check out the example given below: #include
216 The output is: The marks are : 80 80 80 As you can see, we return ‘pt’ (which is a pointer to an integer) from the function. This is like returning an address from the function. There is nothing special about returning pointers from functions but you have to be careful with the syntax for the function header.
Run-time and Compile-time The concept of OOPs (Object Oreinted Programming) makes emphasis on making decisions during run-time rather than at compile-time. Run-time is the time when someone runs your program. Compile-time is the time at which you compile the program. To differentiate between run-time and compile-time let's take a real life example. Let us suppose that tonight you decide you are going to walk to office tomorrow. You decide that you will start from home at 7:30am. You have decided these two things at night itself. This is similar to compile-time. Decision is taken before the time of action. Now in the morning, you start from home as decided at 7:30. You walk and on the way someone, your girlfriend or boyfriend, comes in a car. They stop beside you and offer to give you a lift to your office. You had initially decided to walk but suddenly change your mind. Why walk all the way to office? Maybe I could walk tomorrow. You step into the car and reach office. This was a run-time decision. You decided on the spot during the time of action. Coming back to C++, run-time decisions provide more flexibility to adjust. As you can see, in real-life, most of us prefer to take decisions depending on the situation that arises instead of fixing a plan earlier. We take decisions on the spot instantaneously and in C++ this can be illustrated with an example. For example: in a program that makes use of an array for storing a series of numbers, you may declare an array of 20 elements. But sometimes the user may want to enter more elements. Hence you may declare an array of 200 elements. The compiler will allocate memory space for all the 200 elements during compile time itself. But your user may not always make use of the 200 elements. Sometimes it may be 10 sometimes 50. By declaring 200 elements the space allocated to the unused elements is a waste. It would be better if we could decide on the size of the array during run-time (i.e. when the user runs your program rather than fixing the size when you write and compile the program). According to the user’s preference the program could allot the required space for the array. This is a compile-time decision and this can be implemented through pointers, as we shall see later.
Pointers
217
Dynamic Allocation Whenever you declare variables required amount of space is allocated for them in the memory. But there are certain instances when we might not be able to predict how much space might be required when the program executes; it may depend on the user. Suppose that you want to write a program to get the marks from the user and display the entered values. You do not know how many marks the user will enter (it could be for 2 subjects or for 8 subjects). So, you might write the following code: # include
Pointers
218 also illustrate the difference between compile-time and run-time. So, how to overcome this problem? Simple. You could give the variable ‘size’ some value to start with. Replace: cout<<"Enter the size of the array : "; cin>>size; with: size=4; Now everything should be fine. Is it so? Try it and you’ll get the same compiler error. The compiler reads and stores the value of 4 for ‘size’ but it still will not substitute that value in: int marks[size]; The compiler assumes that ‘size’ is a variable (since it was declared like that) and since a variable’s value can change in the program, it will not compile the code. One way to correct this is by declaring the maximum size of the ‘marks’ array by saying: int marks[4]; //program will compile There is no need for the variable ‘size’ and the user can enter only a maximum of 4 values because that is the space allocated for the array ‘marks’. Another way to correct the program is to declare the variable ‘size’ as a constant. const int size=4; Now you can use: int marks[size]; The reason that this is valid is because the compiler makes note of the fact that ‘size’ is a constant and has been given a constant value 4. Since this value will never change in the program and space will be allocated for 4 elements in the array ‘marks’. C programmers made use of macros (instead of ‘const’): #define SIZE 4 to define constants. When the compiler comes across the term ‘SIZE’ anywhere in the program it will simply substitute the value of 4 for ‘SIZE’. But whatever you do, the compiler limits you to fixing the size of the array at compile-time. Can you decide the array size at run-time? Dynamic allocation comes to the rescue.
Pointers
219 Dynamic allocation means allocating (or obtaining) and freeing memory at run-time. There are certain cases where run-time decisions are better. For example, deciding the size of an array. Similarly you can free up allotted memory in your program when you don’t need a particular array by deleting the entire array itself. The two operators used for this purpose are: ‘new’ and ‘delete’. ‘new’ is used to allocate memory while ‘delete’ is used to free the allocated memory (memory which was allocated by ‘new’). The free memory available is sometimes referred to as the heap. Memory that has been allocated by ‘new’ should be freed by using ‘delete’. If you don't use ‘delete’ then the memory becomes a waste and cannot be used by the system. This is called memory leak (i.e. when allocated memory is never returned to the heap). You could go on taking memory from the heap till it gets exhausted. This will lead to memory leak and can cause problems to your program and to other programs which attempt to take memory from the heap. The syntax is: data-type * name = new data-type; delete name ; Beware: Both data types should be the same. Example: int * p = new int; delete p; Remember: delete p; means that the data pointed to by ‘p’ is deleted. The pointer ‘p’ will not get deleted. The following coding is correct: int m = 20; int *p = new int; *p=5; cout<<*p<
Pointers
220 int main ( ) { int size,i; cout<<"Enter the size of the array : "; cin>>size; int *marks = new int[size]; cout<<"\nEnter the marks: "; for (i = 0; i
Pointers
221
More Pointers - IV
The following topics are covered in this section: • •
Pointers to Functions Some pointer declarations
Pointers to Functions You can even create pointers that point to functions. The primary use of such pointers is to pass functions as arguments to another function. You have to be very careful while declaring pointers to functions. Take note of the position of the parentheses: return-data (*pointer-name) (arguments); We’ll write a simple program to add two numbers. For this purpose we shall define a function called sum( ). This function sum ( ), will be passed to another function called func( ) which will simply call the sum ( ) function. #include
Pointers
222 declares a pointer to a function that has a return value of integer and takes two arguments of type integer. sum( ) is a function with return data type of integer and also with two arguments of type integer. Hence the pointer ‘p’ can point to the function sum ( ). Thus we assign the address of the function sum ( ) to ‘p’: p = sum; We’ve also defined another function called as ‘func ( )’ which takes two integer arguments and a third argument which is a pointer to a function. The idea is to pass the function sum ( ) to the function ‘func( )’ and call the sum ( ) function from func ( ). func(5,6,p); will call the func ( ) function and it will pass pointer ‘p’ to func ( ). Remember that p is a pointer to sum ( ). Hence in reality we are actually passing a function as argument to another function. Let’s expand the program one step further by creating another function called product( ) which will return the product of the two arguments. #include
Pointers
223 The output is: The result is : 11 The result is : 30 The program is very similar to the previous one. I just wanted to illustrate that you can pass any function to func ( ) as long as it has a return type of integer and it has two integer arguments. Practical use: You might be wondering why we’d need to pass functions as arguments to other functions. There are instances when you might create generic functions, which the user can tailor to suit his/her needs. For example: Signal handling functions usually contain a function as one of their arguments. These functions are used in a program to react to certain external events- for example if the user presses CONTROL+C keys, the default action is for the program to terminate. But you might write a program in which you want to print “YOU CAN’T TERMINATE THIS PROGRAM” every time the user presses CONTROL+C. To perform this there are standard signal handling functions. These functions generally take the following 2 arguments: 1.) The signal for which you want your program to react (CONTROL+C is just one of the possible signals your program might receive). 2.) The function which has to be performed when your program receives that signal. The function might be something like: int sighandler (int sig, pointer-to-function); All the user needs to do is decide what signal he wants to handle and then decide on the function he wants to perform in case that signal is received (there’s no need to rewrite the entire signal handling function for his application; which would be time consuming. Programming is all about reusing code rather than rewriting). Can you think of any other simpler way in which a generic function like sighandler can be created?
Pointers
224
Pointer Declarations Pointer declarations are a bit confusing. Read through the various declarations described below to clear your doubts: Declaration
What it means
int *p;
pointer to an integer
void p(char *str);
function that accepts a pointer to a character
int (*p)(char *str);
pointer to a function that accepts pointer to character as argument and returns an integer.
int *p(char *str);
p is a function that accepts pointer to character as argument and returns a pointer to integer.
int *p[5];
p is an array of pointers to integers
int (*p)[5];
p is pointer to an integer array of 5 elements
void (*p)(char (*str)[] );
pointer to function that accepts pointer to character array
The list can be made more complicated but this should be sufficient to understand how pointers are generally declared. A couple of declarations are pretty interesting: int *p[5];
-
p is an array of pointers to integers
int (*p)[5];
-
p is pointer to an integer array of 5 elements
The subscript operator has a higher precedence over * in the above declaration. So if we say: int *p[5]; the compiler will create an array of pointers. To forcefully evaluate *p first, we use the parentheses and declare: int (*p)[5]; Now the compiler considers ‘p’ as a pointer and so this means that ‘p’ is a pointer to an array of integers.
Pointers
225
More Pointers - V
The following topics are covered in this section: • • • • •
Pointers to Structures Multiple indirection Pointers to constants Constant Pointers Void Pointers
Pointers to Structures This is similar to creating pointers of other data types but you should know how to access structure elements through pointers. struct phonebook { int pin; int tel; }; int main( ) { phonebook record[2]; phonebook *p; p=record; //Points to record[0] p->pin=60004; p->tel=23451; p=p+1; //Now points to record[1] p->pin=50023; p->tel=89732; p=record; cout<
Pointers
226 The output is: The pincode is : 60004 The tel. no. is : 23451 The pincode is : 50023 The tel. no. is : 89732 You’ll notice that to access the individual elements we have made use of different operators. When you use pointers, you should not use the dot operator. Instead we make use of the arrow operator (->). p->pin=60004; Actually, the dot operator (or the member operator) can be used but you have to be careful about operator precedence. To use the member operator we’ll have to dereference the pointer and then use it. The following expression: *p.pin would be wrong. The dot operator is a post-fix operator and it has higher precedence over the dereferencing operator (which is a pre-fix operator). So to set it right we will have to use: (*p).pin Thus we could also have used the following code in our program: cout<
Pointers
227
Pointer to Pointer (Multiple Indirection) In C++ you can create a pointer that will point to another pointer. In fact you can even create a pointer that points to a pointer that points to a pointer. But here we shall only deal with the case of a pointer that points to another pointer (the same concept can be extended to other cases). The figure below should make the concept clear. Let ‘marks’ be an integer variable (let us assume that an integer occupies 4 bytes of memory). int *p; int marks=80; p=&marks; int **p2; p2=&p; Thus p2 is a pointer which points to a pointer of type integer (i.e. p2 points to ‘p’).
The pointer p2 can be used for two purposes: • •
p2 can be used to refer to the address of ‘p’ p2 can also be used to access the value of marks.
If you want to access the value of marks through p2 then you can type: cout<<**p2; //result will be 80 (which is stored in ‘marks’)
Pointers
228
Pointer to constants: When a pointer points to a constant term, the pointer cannot modify the value of the constant term. The declaration of a pointer to a constant is as follows: const data-type *pointer-name; Example: const int num=20; const int *ptr; ptr = # •
The address of a constant cannot be assigned to a pointer unless the pointer is declared as pointing to a constant. The following is incorrect: const int num=20; int *ptr; ptr=# //Error (pointer not declared as pointing to a constant)
•
A pointer to a constant cannot be assigned to a non-constant pointer.
const int num=20; const int *ptr; ptr=# int *ptr2; ptr2=ptr; //Error because ptr2 is a pointer to a non constant •
The code fragment below is correct: const int num=20; const int *ptr; ptr=# const int *ptr3; ptr3=ptr; //Correct because ptr3 is a pointer to a constant
Thus you can use pointers to constants as function parameters if you do not want the function to modify the argument. ¾ The following is fine: const const ptr ptr++;
int int =
num=20; *ptr; #
//no problem
The pointer ‘ptr’ itself is not a constant (which means that we can change the value held in ptr at any time).
Pointers
229
Constant Pointers: A pointer, which is declared to be a constant, cannot be changed after initialization. Thus it will always retain the same value. Syntax for declaration: data-type *const pointer-name = address; The constant pointer has to be initialized and this value will be retained throughout the program. For example: int num = 20; int *const ptr = # //constant pointer initialized to address of num. You cannot try to increment the pointer: ptr++; because ptr is a constant pointer and it’s value cannot be changed. You’ll get a compiler error saying “L-value is a constant”. But: cout<<*(ptr+1); is correct. Why? In this case we are only displaying the value stored at the next memory location; we are not trying to change the value held in ‘ptr’ (of course, this will produce some garbage value). The following code fragment is incorrect: int num=20; int *const ptr; //has to be initialized here itself ptr=#
Void Pointers If a pointer is declared as pointing to void then it can point to any data type. The syntax for declaring a void pointer is: void *pointer-name; For example: int num=20; double db=1; void *ptr; ptr = # ptr = &db;
Pointers
230 In the above code fragment the void pointer ‘ptr’ is first assigned the address of an integer and then assigned the address of a double quantity. Though you can assign any data type you cannot attempt to access the value using the dereferencing operator. The following code will produce errors: int num=20; double db=1; void *ptr; ptr = # ptr = &db; cout<<*ptr; //Error – cannot use * to access value stored. Also you cannot try to assign a void pointer to a pointer of some other data type. Pointer arithmetic is also not allowed in void pointers. If a function can operate on any kind of data type then the void pointer can be used (for example: the operator ‘new’ when overloaded will have a void pointer as its argument because this operator can work with any data type).
Pointers
231
More Pointers - VI
The following topics are covered in this section: • •
Pointers to characters Pointers Demystified (2-D arrays and pointers)
Pointer to Characters: Check out the following code: int main( ) { char *name = "Tintin"; cout<
232 When individual characters are assigned to a character array, it is the responsibility of the programmer to include the null character. In the above example we haven’t assigned a null character and thus the resultant output will contain garbage values: Tintin¦¦8_e Strings are constants and you cannot modify them after initializing them. Thus the following code will produce an error: char *name = "Tintin"; *name="h";
//COMPILER error
The reason is because the right hand side is a char[2] (the letter h and the null character). In other words you cannot assign a string to a character (*name can only hold a character since it is a pointer to a character data type). So, we could try the following: char *name = "Tintin"; *name=’h’;
//Run Time error
Now the compiler is satisfied with what you’ve done. Why? Because ‘name’ points to a character and you’ve asked the compiler to store a character at that memory location. The two types match and the compiler gives the green flag. But when you execute the program, it will crash because of a run-time error. Why? Because a string in memory is a constant and you are not supposed to change the value. To prevent such bugs from creeping into your code, it is a good idea to use the keyword const: const char *name = "Tintin"; *name='x';
//Compiler error
Now you’ll get a compiler error because the compiler has been informed that ‘name’ is pointing to a constant and thus it shouldn’t be able to modify the data it holds.
Pointers
233
Pointers Demystified: Before getting into pointers lets refresh our memory on a few important concepts: •
Variables are actually named memory locations.
•
When we ask for a variable value, the program has to access the memory address and retrieve the value stored there.
•
The same concept applies to arrays as well.
•
Arrays are stored contiguously (i.e. in consecutive memory locations).
Now, let’s go a bit deeper into pointers now that we are familiar with the basic concepts. We’ll again take up our discussion on pointers and arrays. Consider the following code snippet: short int marks[5]; //some assignments cout<
Pointers
234 Case I Sometimes, when you are dealing with arrays it might be a good idea to use the pointers to access elements rather than using array indexing. The pointer method might improve the performance. Let’s take a simple example: int main( ) { short int marks[5]={80,70,60,75,90}; int i; short int *ptr; ptr=marks; cout<
80
70
60
75
90
Using pointers:
80
70
60
75
90
Rather than the output we need to focus on the difference between the 2 methods used. In the first method we used array indexes to display the value of each element: for (i=0;i<5;i++) { cout<<"\t"<
Pointers
235 In this way the program calculates the address and retrieves the value stored at that location. You might wonder what’s the problem in this? Let’s take a look at the second method, using pointers: short int *ptr; ptr=marks; for (i=0;i<5;i++) { cout<<"\t"<<*(ptr++); } ‘ptr’ is a pointer which initially holds the address of the first element of the array marks. The first time the program enters the for loop, it will display the value of the first element of the array. Then ‘ptr’ is incremented. Incrementing ‘ptr’ is equivalent to: ptr = ptr + sizeof(short int) Each time the program executes the loop it has to move the pointer to the next element using the above equation. The difference in array indexing and pointer referencing lies in the 2 equations. In the array indexing method the program has to perform a multiplication whereas in the case of pointers this is not required. By using the second method we can improve performance (speed of execution) because multiplication needn’t be performed (and multiplication is generally a time consuming operation for computers). In small programs you may not notice much of a difference but when you are dealing with larger data types this could cause a significant improvement. Case II But this doesn’t mean that using pointers instead of array indexing will always improve performance. It all depends on the situation (just try to think of the problem from the compiler’s point of view). For example, let’s say we have an array: int salary[10]; int id; Later in some part of the code we have the statement: cout<<”Enter the employee ID:”; cin>>id; cout<<”Salary of that employee is:”<
Pointers
236 The following: marks[2]; and *(marks+2); are actually the same. When we say marks[2] the compiler would internally convert it into *(marks + 2). The following code snippet should clarify your doubts: int weights[4]={10,20,30,40}; int *ptr=&weights[0]; cout<
Two-dimensional arrays and pointers: Let us say that we’ve declared a 2-D array: int marks[4][2]; Though we feel that this array is similar to a tabular structure, we don’t have tables in memory. Array elements are stored contiguously in memory (irrespective of whether it is a one dimension or multi-dimensional array). We refer to the 2 dimensions as rows and columns (the first square bracket denotes the row and the second denotes the column number) but as far as the computer is concerned, all the elements are just stored continuously in memory. So, how would the array marks[][] be stored in memory?
Pointers
237
Thus, the first row elements are stored first, followed by the second row elements and so on. When we refer to an element as: marks[3][1] the program has to calculate the address of the element to retrieve the value. The address of marks[3][1] = base address of the 2-D array + offset This is similar to what we saw for 1-D arrays. Base address of the 2-D array = address of marks[0][0] and offset = (number of columns * element’s row number) + element’s col. number In our case: offset (in terms of the number of elements) = (2 * 3) + 1 To obtain the offset in terms of bytes, just multiply the above value by sizeof(short int). The concept might seem confusing at first but once you substitute some values you should be able to grasp the idea. So, how do we refer to 2-D array elements using pointers? A 2-D array is a pointer to an array of 1-D arrays. If we declared an array as: marks [4][5] then when we say mark[0] we are referring to the first row. mark[1] will point to the second row and so on. Let’s take this one at a time. A 2-D array is a pointer to an array of 1-D arrays. In our example, each row of the array contains 5 elements. These 5 elements form the set of 1-D arrays. Thus each 1-D array will make up a row of our original array. If we use: marks[0]
Pointers
238 it is equivalent to marks and it contains a set of elements (the elements are the individual rows: marks[0], marks[1] etc.). marks[0] is an array with elements marks[0][0], marks[0][1], marks[0][2]…marks[0][4]. We finally arrive to the conclusion that a 2-D array is a pointer to an array of 1-D arrays. For a normal 1-D array, marks [2] = *(marks + 2) To refer to an element in a 2-D array, say: marks [2][3] we can use the notation: *(marks[2] + 3) This tells the program to take the address of row2 and add 3 elements to it. But we’ve seen that marks[2] = *(marks +2). Thus: marks[2][3] = *( *(marks + 2) + 3 ) The double asterisk confirms what we stated at the beginning. It denotes that our 2-D array is in fact a pointer to a pointer. Try out the following code snippet: int marks[4][2]={60,75, 80,65, 90,95, 87,76}; cout<
Pointers
239
Core Concepts of OOP
Object Oriented Programming (OOP) Introduction: Introduction: Structured programming can be roughly divided into two categories: Procedural Object Oriented Structured programming is basically dividing a program into smaller modules that are easier to manage. All of what we have covered up to this point is part of procedural programming. In this the programmer divides the program into smaller functions (rather than putting the entire code within one function). Each function would have its own variables and sharing of variables between various functions is also possible (using global variables). What we will see henceforth is related to object oriented programming (OOP). Large programs were difficult to handle in procedural programming and OOP was developed to reduce complexity of software development. In C++, OOP is implemented using classes. The main concepts in OOP are: Data Abstraction Data encapsulation (hiding) Polymorphism Inheritance Data Abstraction: The fundamental data types (like int, float, double, char etc.) might suffice for simple programs. It is difficult for a programmer to always think in terms of the fundamental data types alone. For example, what data type would you use if you want to model a car in a computer application? A car has many properties and each one will be of a different data type (the speed would be an integer, the colour would be a string and so on). We could use a structure to solve our problem but structures also have their limitations. When you create a structure, the user (another programmer who might use your structure or you yourself) can directly manipulate with the member data of the structure. Let’s say that we have created a structure called ‘car’ which has 2 data types for speed and color. You can create an instance of the structure car (say ferrari) and then assign the value for ‘speed’ as 99999. Or you could also set the colour as ‘xyzer’. This would lead to incorrect data and you wouldn’t want the user to
Classes & OOP
240 enter such values. Thus a structure wouldn’t permit us to control the way data is stored or accessed. A user can simply modify anything by directly accessing it. Wouldn’t it be more convenient if we could have some control over our data type? Whenever you create such a complex data type, there will also be some operations that you would want to perform on that data type. In C++, these operations (which are implemented through functions) and the data can be bound together as a class. A C++ class is a data type that the programmer defines. Data abstraction refers to the capability of a programmer to create new, user-defined data types. Classes are also called abstract data types (ADTs or user-defined data types) because they are created using the process of abstraction. So, what is the process of abstraction? Let us consider a simple example of a person. Every person in this world has many attributes (like age, date of birth, name, gender, marital status, salary, number of children, hobbies, strengths, weaknesses, diseases, nationality etc.). If we are designing a patient database application for a hospital, we might not require storing attributes like salary, strengths and weaknesses of a person. So while designing this application we will only choose the particular features of the patient that we are interested in (like age, date of birth, nationality, disease history, gender etc.). Now if we have to design another system for a corporation, we will consider the person as an employee. An employee database system wouldn’t need to store information like disease history, haemoglobin level etc. In this case we would only be interested in date of birth, salary, qualification etc. In both cases, our requirement is to store detail information about a person. In one case we have a patient and in one case we have an employee. Depending on our application we selected what we wanted. Abstraction is the process in which we selectively choose what is needed for our application discarding the unnecessary attributes (based on the requirements). You’ll appreciate ADT as you progress through this chapter. Data Encapsulation: Data encapsulation/ data hiding is an important feature of object oriented programming. The mechanism of hiding data is to put them in a class and make them private. The data is now hidden and safe from any accidental manipulations, i.e. no function (from outside the class) can change the member data. Actually there are two things you can hide: implementation of functions (to the user it doesn’t matter as to how you’ve implemented a particular function) and data. In procedural programming it is possible to only hide the implementation details but you cannot hide/ protect data. OOP lets you achieve this. Why do we need to really hide anything? Who are we hiding the data from? These questions will be answered later in this chapter. A simple example is the case of the ‘car’ we considered earlier. We don’t want the user to directly access ‘speed’ and modify it. By making ‘speed’ private, we prevent the user from doing this.
Classes & OOP
241 Polymorphism: Polymorphism means having many forms. Polymorphism can be seen frequently in the English language. There are many English words, which have a different meaning depending on the context of use. The statements “close a book”, “close the file”, “close the door” and “close the deal” all make use of the verb ‘to close’ but the meaning of each statement depends on the context. Another example is the sentence, “I’ve cracked the exam”. The meaning of cracked in this case is different from the crack used in a sentence like, “The pot cracked”. In both sentences the word is the same but its interpretation varies depending on the context. In the same way you can think of many programming examples. For instance, consider the + operator. When it is used on numbers it will act as an addition operator, adding two numbers mathematically and giving the result. When the + acts on two strings, the result will be the concatenation of the two strings (we’ll take a look at string objects which permit us to use + later). For example: “new” + “delhi” = “newdelhi”. Thus, though the operator is the same (+), it can perform different actions depending on the type of the operands. This is a simple example of polymorphism. A few other examples of polymorphism are:
The << and >> operators are bit-shifting operators and they are also used for displaying information on the screen or for storing values in a variable (the circumstance decides whether they are used to shift bits or as input and output operators). The division operator (/) when operating on two integers will produce an integer as the result of division. But if one of the operands are floating point numbers then the result will also be a floating-point number. In all these cases, the same operator performs different functions depending on the situation. This is called polymorphism. Inheritance: Just as the name implies, inheritance refers to children inheriting property from their parents. In C++, the parents are called the parent classes and the children are called the derived (or child) classes. The idea of inheritance is to prevent classes from being redefined over and over again. If a programmer has already created a class and you want to make use of the same class with some additional features, then you needn’t rewrite the entire class description again. Instead, you can derive a class from the original one (hence all the existing features of the class will be available in your class also) and you can add the extra features you need to your class. This is called re-usability of code. Instead of re-writing, we can re-use through the concept of inheritance. Let’s take the example of animals: a lion belongs to the cat family; the cat family comes under the mammals’ category and the mammals category will come under the general group called animals. Using inheritance, if a lion is being described then only the unique features of a lion need to be defined (you needn’t define the features of animals, mammals and cats). Thus the class ‘lion’ will be inherited from the class ‘cat’ which will in turn be inherited from the class ‘mammals’ which is inherited from ‘animals’.
Classes & OOP
242 OOP Languages: Object Oriented Programming did not originate in C++. In fact it was already existing and OOP was combined with C programming to develop C++. A few of the OOP languages are:
Simula Modula SmallTalk Ada C++ Java Some of these languages are said to be ‘pure OOP’ while others are ‘hybrid OOP’. ‘Pure OOP’ means that everything in a program has to be tied with classes (and you cannot use separate functions). Java is an example of pure OOP. C++ comes under hybrid OOP because you can use OOP as well as the normal C style coding (involving separate functions and data).
A closer look into OOP: The world can be considered to consist of many objects. Objects will have attributes and behaviours. A water-heater is a simple example of an object. It has certain attributes or properties (like colour, size, maximum and current temperatures etc.) and there are certain behaviours associated with the water-heater (like switching on the heater, increasing the temperature or heating for a specified time interval, switching off the heater etc.). These are actions that can be performed on the heater. Or in other words they are actions which can modify certain properties of the heater (for instance by switching on the heater the current temperature of the heater will change). A car is another example of an object. It has a lot of attributes such as fuel capacity, current speed, top speed, number of wheels, type of gearbox etc. There are also a lot of operations which you can perform on this object. For example: you can accelerate the car, apply brakes etc. The attributes of a car will have some values at any given instance of time. Once the car is in motion, you can say that at a particular time the speed of the car is 30 km/hr (thus current speed will be 30km/hr). Similarly, the color of the car is red or the car has four wheels. The values for the attributes at any given instant of time define the state of the object. There are
Classes & OOP
243 two types of states an object can have: static and dynamic. Some attributes of the car will not change over a period of time. The number of wheels in the car is always going to be four (unless you are making a new prototype!). The colour of the car would also remain the same for a long time. These attributes contribute to the static state of the car. The current speed of the car is a dynamic property which will change frequently depending on the actions performed upon the car. In OO terminology you will encounter the following terms frequently: State Behaviour Identity Behaviour of an object refers to the set of operations (or actions) that can be performed on an object. Every object will have some attribute that can be used to uniquely identify the object. For example let’s take the example of a car as an object. All cars have colour as an attribute. But can you distinguish two cars based on their colours? Definitely not. But you can distinguish two cars based on their registration number. Hence registration number is the attribute which can be used to uniquely identify a car. If you take a banking example then the account number is a unique way to identify an account (no two accounts can have the same account number). An object will have two parts: 1. Interface 2. Implementation In a car, the interface is the acceleration and braking actions which can be performed on the car (there are many more but lets just limit ourselves to these two actions). The driver is going to be the user of the car. When the driver presses the accelerator pedal, there are a lot of things that happen within the car which actually cause the rpm (rotations per minute of the wheel) to increase. Is the driver concerned about what actually happens within the engine? No. The driver just wants the car to accelerate on pressing the pedal and is least bothered about the underlying mechanisms used by the manufacturers to achieve this. He doesn’t care about how the engine is designed or as to how the piston is moving to achieve acceleration. All he knows (and wants to know generally) is that the car should accelerate when he presses the pedal. These internal mechanisms are called implementation details in OOP terminology. One of the central features of OOP is to separate the interface from the implementation. The person using an object should not know/worry about the implementation. This is what is termed encapsulation.
Classes & OOP
244
Classes and Objects in C++: In C++ classes are used implement OOP. A class will contain two types of members: member data and member functions. The member functions can be used to operate on the member data within the class. The data members correspond to the attributes while the member functions correspond to the behaviour. Instead of the term ‘function’, some programmers use the term ‘method’. The term ‘class’ and ‘object’ might seem confusing at first. Basically you cannot directly use a class (we need to create an instance of the class and we call this an object). In our fundamental data types we have int, double, char etc. But are we using them directly? For example, do we say: int = 5; No. If we were to do this then we would never be able to create different integer variables. We create an instance of an integer when we say: int x = 5; Since classes are also data types (user defined data types), they also follow the same principle. You have to create instances of a class to do anything useful. An object is an instance of a class, i.e. only when you define an object, will the compiler allocate memory for the object. Class is like a model (or a template) from which you can create many objects of the same type. A template can be compared to the plan of a building. When the plan is drawn, we have not yet allocated the area on land for construction. We only know about how the building structure will be. But when construction work begins, the area will be allocated. Similarly, the compiler allocates memory space for every object that is created. This is why a class is called an abstraction (in other words a class is a generality while an object is a specific instance of the class). Let’s say we have a class called student, with the attributes: id name age We can create two students by saying:
student Watson, Hastings;
Now, Watson and Hastings are 2 students. Each of them will have an id, name and age (we can modify their attributes separately). You will be able to distinguish between a class and an object clearly when we write a few programs. Everything in a class (data and functions) is private, protected or public. They are called access-specifiers (because they decide how the class members can be accessed).
Classes & OOP
245 private: As the name suggests, whatever is in the private area of a class can only be accessed from within the class. If the data is made private then it can be accessed only through member functions of the class. If a function is made private then it can be called from within another member function. Data/function is made private by default (i.e. if you don’t mention any specifier). protected: The specifier ‘protected’ is used in inheritance and will be dealt with later. public: Public members of a class are accessible from outside the class. Hence when objects are created, they can access the public members directly. Usually, data is made private while the functions are made public (this is done to ensure data encapsulation). These public functions can operate on the private data. The syntax for a class is: class name-of-class { private : data-type variable-name; public : functions; };
//these are private //these are public // End of class- make a note of the terminator.
It is not a must that data should be private and functions should be public. You can have private functions as well public data. Remember: No member of your class can be declared as ‘register’ or ‘extern’. Note: Before getting into classes, you should know that there are two types of programmers who work with classes: the class designer and the class user. The designer (or creator) creates a class and the user makes use of the class in his/her programs. The term ‘user’ usually represents the person who will use an application developed by a programmer. But if the term ‘user’ is used while explaining classes, then it refers to a ‘class user’ (a class user is another programmer). In our example codes, we will be the designers as well as the users of the class (of course if you provide the code to someone else, then we will be considered the designers while they will be the users). A class creator will try to hide from the user whatever is not required by the user (why provide more options to the user and lead to complications!). Hide whatever is possible so that the class user cannot tamper with things that they are not supposed to use.
Classes & OOP
246
Demonstration of a Class A program to demonstrate Classes Before getting into the nuances of classes let’s take a look at a few simple examples to illustrate classes and objects. Let’s say that we want to create a timer, something like a stopwatch. We should be able to set the timer to a start value, pause it, stop it or start it. Thinking in terms of OOP we would have one member data: •
count (let’s keep it as an integer)
Being the first example, we’ll implement some simple functions: •
initialize( )
•
display( )
•
increment( )
The function names are self-explanatory and the functions would operate on the member data (i.e. someone who uses our timer shouldn’t be able to change the value of count directly. If this were allowed then the user might set count to a negative value or might misuse it). The user of our timer object should be able to access our timer in a controlled manner. #include
Classes & OOP
247 int main( ) { Timer t1,t2; t1.display( ); t1.initialize( ); t1.display( ); t1.increment( ); t1.display( ); t2.initialize( ); t2.increment( ); t2.increment( ); t2.display( ); return 0; } When you run the program you may get an output similar to this: Seconds remaining:4289044 Resetting timer! Seconds remaining:0 Seconds remaining:100 Resetting timer! Seconds remaining:200 Since this is our first program in classes, we’ll dissect it line-by-line.
Dissection of the program
Explanation for each part
class Timer {
Begin the declaration of our class called ‘Timer’.
private: int count;
public: void reset( ) { cout<<"timer!";
As part of data hiding, we’ve put the member data (count) within the private section of the class. Only the public functions of the class can access ‘count’.
The keyword public indicates that everything following this label is part of the public section of the class.
count=0; } initialize( ) is a member function of class Timer which will be used for resetting the value of count to void initialize( ) { cout<<"timer!"; count=0; }
zero. Since it is present in the public section, anyone using our class can call this function (and since this function is part of the class, it is permitted to access the member data ‘count’).
Classes & OOP
248 void display( ) { cout<<"remaining:"<
We’ve defined 2 more functions display ( ) and increment ( ) which also access the member data
void increment( ) { count=count+100; } };
‘count’. Signals the end of our class declaration. Next comes the main( ) function in which we’ve created two objects. ‘t1’ is an object or an instance of
int main( )
the class Timer. ‘t2’ is another object (i.e. we now have two timer objects in our program). Every
{
instance of a class will have its own copy of the Timer t1,t2;
member data. This is similar to how we used to create variables from the fundamental data types (ex: int x, y; creates two integer variables). All public members of the class can be called using the dot operator. Thus we call the member function
t1.display( );
display using the dot operator. This would display the value of the t1’s variable ‘count’. Since it hasn’t been initialized, you’ll find a garbage value on your screen.
t1.initialize( ); t1.display( );
t1.increment( ); t1.display( );
t2.initialize( ); t2.increment( ); t2.increment( ); t2.display( ); return 0; }
Set member data count to 0.
We’ve incremented the value of count from 0 by 100. Thus the last display would yield: Seconds remaining:100
These will all act on the ‘count’ of object t2. Thus at the end of our program: count (t1) will be 100 count (t2) will be 200
End of the program.
Remember: When we call member functions using the object t1 we are only manipulating the ‘count’ of t1. The object t2 remains unaffected.
Classes & OOP
249 Remember: Only functions belonging to the class can access the private members of the class. This example should have clarified your doubts about a class and an object. We create objects from a class. The class acts like a general framework from which we can create many objects. Each object will have the same member functions and data but the value for the data can be different. If we apply what we learnt about OOP earlier we can state this in another way: Each object that we create will have its own state. In real life this corresponds to having two timers which are identical in functionality but independent of each other (each one can have a different time). Try adding the following statement to the code above: cout<
cannot access private member declared in class 'Timer'
Let’s modify our class by adding a public member data: class Timer { private: int count; public: int dummy; void initialize( ) { cout<<"timer!"; count=0; } }; int main( ) { Timer t1,t2; t1.dummy=90; cout<
Classes & OOP
250 Note: You generally won’t find programs with a function like display ( ) which we’ve used above. Instead of this we would generally define a function through which the user (i.e. the programmer who would later use our class) can retrieve the value of the member data. So, instead of the function: void display( ) { cout<<"remaining:"<
t1.set_count(-200);
and the compiler would faithfully obey (it’s like creating a digital clock which can be accidentally/ intentionally set to negative time!). To prevent this then we have to test the value of the argument passed within the member function set_count( ).
Classes & OOP
251 Remember: In real application programming, a class designer would avoid using I/O within member functions (i.e. it is preferable to avoid using cout or cin within member functions). This task is left to the user of the class (of course the class designer should provide some member functions through which the user can set member data). In this book you may find cout statements within member functions; these are primarily used to illustrate concepts and how C++ works. Some important points about classes: You cannot access private members of a class directly. Private members can be accessed only through the public member functions. An object in the program can directly access only the public members through the dot operator (the dot operator is also called the member operator). Private members cannot be accessed directly by an object. Whenever we call a member function, we actually say that ‘we are passing a message to the object’. This is another term frequently used in OOP. Can a function be private? Yes. Nothing prevents you from doing so. Once you declare a function as private, it cannot be accessed using the dot operator. You can only call the private function from within a public function. But you might be wondering why would need to create a private function? This topic will be dealt with an example after learning about constructors and destructors. Remember: By default, all data and functions in a class are made private. To make something public you have to explicitly specify the access specifier in your class declaration. It is a good practice to explicitly state which members are private and public in your class (as done in the above program).
Classes & OOP
252
Constructors In our first example in creating a class Timer, we used a member function called initialize( ). This was used to set the starting value of count to some legal value. void initialize( ) { cout<<"timer!"; count=0; } Basically, our idea was that whenever someone creates a Timer object the starting value of count shouldn’t have a garbage value. So we defined a member function which would assign values to our member data. Anyone who uses our class is expected to call the initialize( ) function first before using the Timer object. But there is a problem with this; when a user creates a Timer object he/she may forget to call the initialize function. This would create a lot of problems in the code later. Wouldn’t it be wonderful if we could initialize the member data of an object just like we initialize variables of fundamental data types? Remember: Initialization and assignment are different. Initialization is done at the time of defining the variable. This is discussed at the end of this section. Variables of fundamental data types can be initialized (at the time of declaration). For example: int variable = 5; This line declares ‘variable’ to be an integer and assigns the value of 5 to ‘variable’. Similarly, how about initializing an object? Constructors serve this purpose. When an instance of a class (or an object) is created, a special member function for that class called a ‘constructor’ is invoked. You can declare your own constructor but if you don’t then the compiler will provide a ‘default constructor’ for the class. A constructor is a member function with same name as the class. It is usually used for the initialization of an object and it is executed at the time of creation of the object. Constructors cannot return any data. Basically, objects are created to model real life entities and when an object is created, you will want some particular data (or properties) of the object to be initialized. The idea is to prevent an object from having an uncertain or undetermined state at the time of its creation. So, all initializations of member data are done using the constructor. A constructor is just like a function except that you won’t have a return data type and also the name of the constructor should be the same as the name of the class. General syntax for a constructor is: constructor-name { //body of constructor } Another form that can be used is: constructor-name ( ) : member-data (value) {} Let's take a look at a simple program to illustrate the use of constructors.
Classes & OOP
253 class counter { private : int count; public : counter ( ) { count=0; }
// Constructor sets member data count to zero.
//you can define more functions }; int main ( ) { counter c1; return 0; } When the compiler reads the line counter c1; it goes to the public part of the class and sees the constructor used. Since counter c1; has no arguments the compiler makes use of the constructor without parameters. Hence the count of c1 is initialized to zero. This is what we wanted to do: Create an object and at the time of creation set the member data to a particular value. Instead of zero you can even give a value. For example: counter ( ) { count=7; } If you use the above constructor, then the value of the member data count is initialized to 7. The alternate form for this constructor is: counter ( ) : count (7) {} So, what’s the difference in the two forms? This form makes use of an initializer list (i.e. count(7) forms the initializer list over here) to initialize the values while the constructor body is empty. In the other method we initialize the members within the constructor body. Remember: In the strictest sense, the first form: counter ( ) { count=7; } isn’t really an initialization (it is only an assignment). We’ll discuss this later.
Classes & OOP
254 Beware: When using an initialization list the order in which the members are initialized depends on the order in which you declare them within the class. The first member in the class will be the first member to be initialized. If your initializations depends on the sequence then be careful while using initializer lists. In the case of assigning values within the constructor body, the order only depends on the order in which you write the statements within the body (and not on the declaration order). This is an apt juncture at which we can discuss about initialization and assignment. Before going into classes, let’s consider our primitive (or fundamental) data types. int amount; amount = 92; In this case we have declared (and defined) a variable called ‘amount’. In the second statement, we assign a value of 92 to this variable. Now take a look at the following: int amount = 92; In this case we have initialized the variable ‘amount’ with a value of 92. Initialization is done at the time of declaration. You might recall that: int amount=92; and int amount(92); are equivalent. Now let’s come back to our discussion of constructors. counter ( ) { count=7; } We are actually assigning a value to the member data count. This isn’t an initialization (the member ‘count’ was first defined and then later we are assigning a value of 7 to the member). But when you use an initializer list: counter ( ) : count (7) {} the member data ‘count’ is initialized with a value of 7. Generally, using an initializer list is more efficient than assignment but in the case of simple member data types this may not really matter. We’ll take a look at this topic again later. If we do not provide a constructor, the compiler will provide its own default constructor (but this default constructor will not do anything useful - i.e. it will not initialize our member data). The compiler will not provide its default constructor if we provide our own constructor. A constructor with no parameters is also called a default constructor.
Classes & OOP
255 Constructors with Parameters (Parameterized Constructors): Constructors are like functions and they can also take parameters just like other functions. An example is shown below: class counter { private : int count; public : counter (int x) : count (x) {} // Some other functions in the class }; int main ( ) { int y; counter c1(2); cout<< "What value of count do you want initialise it to?"; cin>>y; counter c2(y); return 0; } The statement: counter c1(2); means that the constructor with one parameter is invoked. The argument passed to the parameter is 2. This value is now assigned to the ‘count’ of object ‘c1’. count (x) is equal to saying count = x. Hence ‘count’ of object c1 is two. You could also write the constructor as: counter (int x) { count=x; } Similarly,
counter c2 (y);
means that ‘count’ of c2 will be initialized to the value of y (i.e. the value the user types in). You could also perform some validations within the constructor to ensure that the user doesn’t initialize the member data to an invalid value. Remember: Constructors cannot return values.
Classes & OOP
256 Overloaded Constructors: Constructors being similar to functions can also be overloaded. To overload a constructor the parameter type or the number of parameters in the constructors should be different. It is possible that in our program we might want to have one constructor with no arguments and one parameterized constructor to initialize the member data to some other values. This is illustrated below: class rect { private: int length, breadth; public: rect( ) //constructor with no parameter { length=breadth=0; } rect(int x, int y) { length=x; breadth=y; }
//constructor with parameters
}; int main( ) { rect a; rect b(1,2); return 0; }
//executes ‘no parameter constructor’ //invokes the parameterized constructor
The above program will create an object ‘a’ whose length and breadth will be initialized to 0. The object ‘b’ will have its length and breadth initialized to 1 and 2 respectively. Another way to write the above program would be as follows: class rect { private: int length, breadth; public: rect(int x=0, int y=0) { length=x; breadth=y; } }; int main( ) { rect a; rect b(1,2); return 0; }
Classes & OOP
//if no argument is passed, x=0 and y=0.
257 The result will be the same as earlier. In this program we do not explicitly specify the default constructor. We make use of a single constructor to deal with both situations. If the program creates an object without any arguments then the constructor function will be executed with the default parameter values (specified to be 0 in this case). If an argument is passed to the constructor, then the parameter will be assigned the values of the argument.
Default Constructors and Arrays of Objects: Just like creating an array of integer data type, we can also create an array of objects belonging to a particular class. When you create an array of objects the class should have a no argument constructor (in other words the class should have a default constructor). Consider the following program: # include
Classes & OOP
258 int main( ) { rect ob[3]; return 0; }
//can be compiled without error
In the above program there is a default constructor provided by the compiler itself. But usually it is better to provide our own default constructor (so that we can initialize the data) as below: class rect { private: int length, breadth; public: rect( ) { length=0; breadth=0; }
//default constructor
}; int main( ) { rect ob[3]; return 0; }
//can be compiled
Let’s recall the main points about default constructors: If no constructor is provided, the compiler provides a default constructor (but this constructor does nothing). If any constructor (even if only a parameterized constructor) is provided, the compiler will not provide a default constructor. A constructor with no parameters is called a default constructor. A constructor with default arguments is also a default constructor. For example: rect(int x=0, int y=0) //if no argument is passed, x=0 and y=0. { length=x; breadth=y; } This is a default constructor.
Classes & OOP
259
Classes continued The following topics are covered in this section: •
Scope Resolution Operator
•
Destructor
•
Dynamically creating objects
•
Objects in Functions (returning and passing them)
•
Initializing an object using an existing object
Scope Resolution Operator (::) In the earlier section on classes, we have defined the member functions within the class itself. Hence there was no need to declare the function. But is it possible to define the member function of a class outside the class? We have already discussed inline functions. Even in classes you can explicitly declare functions to be inline using the ‘inline’ keyword. Remember: When you declare and define a function within the class it is made an inline function. If you define a function outside a class (using scope resolution operator), it will be treated like a normal function (not inline). Usually when using classes, any function that is just one or two lines long will be defined within the class itself. Longer functions are defined outside the class. Member functions can be defined outside the class by using the scope resolution operator. Using this we can declare a function within the class and define it outside the class. Let’s take a look at the syntax: return-data-type name-of-class-to-which-it-belongs :: function-name (parameters) Consider the example below: class Timer { private: int count; public: void display( ); //declared inside the class void increment( ); Timer( ); }; //Function defined outside the class using scope resolution operator void Timer::display( ) { cout<<"remaining:"<
void Timer::increment( ) { count=count+100; }
Classes & OOP
260 Timer::Timer( ) { cout<<"timer!"; count=0; } int main( ) { Timer t1; t1.display( ); t1.increment( ); t1.display( ); return 0; } Everything in the above program is same except that we have defined all the member functions outside the class. For this purpose we use the scope resolution operator to tell the compiler to which class the function belongs to. The statement void Timer::display( ) tells the compiler that the function ‘display’ belongs to the class ‘Timer’ and has no return data type (void). Similarly we have used the scope resolution operator to define the function increment( ). In this example we’ve also defined the constructor outside the class: Timer::Timer( ) { cout<<"timer!"; count=0; } Since a constructor cannot return any value we don’t specify any return data type (not even void should be used as return data type). Remember: Though the functions are defined outside the class, they still belong to the class. The scope resolution operator is used to say that this function belongs to this class. Programmers will usually define functions outside the class because they want to separate the implementation from the interface. When a programmer creates a class and supplies it for others to use he may not give away the implementation details to the public. Instead he’ll only provide the users with the interface and declaring functions outside a class aids a programmer in achieving this. We’ll discuss how this can be achieved later.
Destructor: When an object is created the constructor is invoked (or called). What happens when an object is no longer needed? The complement of the constructor is called. As the name implies, the destructor will destroy the object that was created. For a constructor we use the same name as the class name and for a destructor also we will use the same class name. But to differentiate between the constructor and destructor, C++ makes use of the tilde (~) symbol for the destructor. class class-name { class-name( ) //constructor {} ~class-name ( ) //destructor {} }; The important features of a destructor are: A class can have only one destructor function. Destructor cannot have any arguments. It cannot return anything.
Classes & OOP
261 If a destructor is not explicitly specified by the programmer, there is no problem. In some cases a destructor needs to be specified. So, what are the cases where we need to bother about the destructor? The most common example is when you have allocated some memory using the ‘new’ operator. Memory allocated using ‘new’ should always be freed using ‘delete’ before the termination of the program. Thus in this case we will specify the appropriate ‘delete’ statement in the destructor to ensure that the memory is freed up. Let’s see a simple program to understand a destructor: #include
Dynamically creating objects: In the chapter on pointers we discussed how we can allocate memory dynamically. This concept can be extended to allocate memory for user-defined data types also (like objects). C programmers would be familiar with the dynamic allocation operators ‘malloc’ and ‘free’ (the C++ equivalent are ‘new’ and ‘delete’). One of the most significant features of ‘new’ and ‘delete’ is that these operators are aware of constructors and destructors. Let’s try out a simple example: class car { private: int speed; char color[20]; public: car( ) //Constructor { cout<<"a Car"; } ~car( ) //Destructor {
Classes & OOP
262 cout<<"the Car"; } }; int main( ) { car *mycar=new car; delete mycar; return 0; } The output will be: Creating a Car Destroying the Car As you can deduce from the output, creating an object using ‘new’ ensures that the constructor for that object is called. Similarly delete calls the destructor of the object. This won’t happen if we were to use ‘malloc’ and ‘free’.
Objects in Functions: Objects can be used in functions just like other data types are used. Objects can be passed as argument to a function and an object can be returned from the function. These two topics will be dealt below separately. Passing Objects to Functions: An object can be passed to a function as shown in the program below: #include
Classes & OOP
263 The output will be: New rectangle created The area of the rectangle is : 20 Rectangle destroyed Rectangle destroyed ‘ob’ is an object of type ‘rect’. It is initialized using the parameterized constructor. The function calculate can accept arguments of type ‘rect’ (i.e. it can be passed ‘rect’ objects). We have created only one object (ob) but the destructor has been executed twice; why? When we say: calculate(ob); ‘ob’ will be passed by value to the function ‘calculate’ (i.e. the function will not directly work on the object ‘ob’. It will only work on a copy of the object ‘ob’). Since it works on a copy of the object, a temporary object of type ‘rect’ will be created when calculate ( ) function is executed. When the program exits the calculate ( ) function, the temporary object has to be destroyed and this is why the destructor has been invoked twice in the above program (once for destroying the temporary object and once for destroying the object ‘ob’). You might wonder why the constructor is also not executed twice? When a temporary object is created the constructor is not invoked. When a temporary object is created the program wants to maintain the state of the temporary object exactly the same as the original object (i.e. the member data values of the original and temporary object should be the same). Constructors are usually used for initializing and if the temporary object gets initialized then the values in the temporary object will be different from the original. So the constructor is not executed for a temporary object creation. This topic is discussed again in the “Copy Constructors” section. Returning Objects from Functions: What happens while passing an object to a function will happen even when you return an object from a function (i.e. a temporary object is created while returning an object also). class rect { private: int length, breadth; public: rect(int x, int y) { cout<
Classes & OOP
264 int main( ) { rect ob(9,9); ob=dummy_func( ); ob.area( ); return 0; } The output is: New rectangle created New rectangle created Rectangle destroyed Rectangle destroyed The area of the rectangle is : 25 Rectangle destroyed The header of dummy_func( ) is: rect dummy_func( ) This signifies that dummy_func( ) is a function which has no parameters and returns an object of type ‘rect’. Based on the output we can say that the destructor is invoked one extra time (just as it was done when we passed an object to a function). In this case the destructor is invoked due to the statement: ob=dummy_func( ); While returning an object the program will create a temporary object (which gets destroyed at the end). This extra temporary object can create problems if you make use of dynamic memory allocation. We’ll revisit this topic after we learn copy constructors.
Initializing an object using an existing object: In fundamental data types, the following is possible: int age1 = 10; int age2 = age1; We’ve used one variable to initialize another. The same is possible with objects also but care has to be taken. Consider the following example: #include
Classes & OOP
265 //Constructor can be defined outside the class using scope resolution operator rect::rect(int x, int y) { cout<
Classes & OOP
266
Classes Explanation A Practical use of classes: The Problem: Living forms (plants, animals and humans) haven’t been fully understood by man. There are many things in life that are very complex and the most complex of all is the human being. A simpler life form would be the bacteria. Nowadays, researchers are trying to mimic or imitate the behaviour of living organisms (like bacterial movement). Consider the problem of optimization. Optimization means finding the best point in a given area. How do we decide which point is best? As a simple example, consider an equation with a single variable ‘x’. Let us assume that you have to find the lowest value for that equation (the equation is also called as a function). In this case the best value means the lowest value. Let the equation be: cost = 10*x + 100 …given that x is greater than 0 and can only take positive integer values between 1 and 100. What value of x do you think gives the least cost? If x=1 then cost=110; if x=2 then cost=120 and so on… It is quite clear that x=1 will give the least cost. Hence the best point (or optimum value) is x=1. That was very easy. The equation was very simple, involving just one variable. In real application you would have equations involving more than one variable and you will also have many conditions (the only condition we used was that x should lie between 1 and 100). You can extend the problem to the case of two variables.
Let’s assume that we have to find the least value for a given equation that has two variables (x and y). Let the limits of x and y be between 1 to 100 (and assume that they can take only integer values). The solution space (or the area in which you have to find the best value) will be as below:
100
Maxi limit
Solution Space
50 Yaxis
0
Classes & OOP
50 X axis
100
267 It is clear from the above graph that there will be 100*100 (=10,000) possible coordinates (in other words 10,000 possible combined values for x and y). The simplest way to find the minimum value for a function involving x and y would be to substitute all the 10,000 combinations, find the cost for each combination and then choose the minimum value. This seems to be a straightforward and simple method, but you can imagine the computational time needed when the solution space increases in size. And if you include floating point values, you are going to end up with a major problem. This is why we need techniques for optimization. We need to use algorithms that can determine the best point in a given solution space as quickly as possible (i.e. the algorithm should not calculate values for each and every combination). This is where bacterial movement comes into the picture. You might have heard of the E.Coli bacteria. This bacterium lives in the human body and has a particular method of movement. It keeps searching for areas where there is more food (or nutrition). Assume that the bacteria is initially at some place in your intestine. It will take a small step in one particular direction. If the new place has more nutrition than the previous position, then it will continue to move in the same direction. Otherwise, it will take a different direction and proceed. Suppose the new position has more nutrition, it will take another step in the same direction. Again if the next position is favourable, it will proceed in the same direction. This movement is a very simplified version of the actual movement (there are more complicated steps involved in bacterial movement but we will not deal with them here). Now you can apply the same bacterial movement algorithm to the problem of optimization. The advantage is that you won’t be computing values for each and every point in the solution space (i.e. we will program in such a way that after a particular number of iterations the program will terminate). There are many other organisms in nature which have different ways of movement and this could also be applied to the problem of optimization (in fact algorithms relating to ants, bees and snakes have been developed already). So, where do we make use of classes and objects? Bacteria is a general category and it forms the class. All types of bacteria will have some stepsize (stepsize refers to the length of the stride taken by the bacteria in moving once) and some particular direction as well (bacteria are in three-dimensional space and they are free to take any direction). Thus, these features are common to all bacteria. We can then model bacteria as a class. This class could contain the following functions: •
• • •
To initialize the starting position of the bacteria. Every bacterium when it is created will have a particular position in 3 dimension. This has to be specified by three variables: x,y and z. To generate the direction in which the bacteria should move (in mathematics this is called the unit vector). To find the new position after the bacteria moves. An algorithm to decide whether to move to the new position or not (move only if new place is better than the present one).
Every instance created from the class ‘bacteria’ is an object. Each object will have a different starting position (decided randomly) and each bacteria will move in different directions (i.e. each one will try to find the best point).
Classes & OOP
268 Thus, we can create 10 objects of type ‘bacteria’ and make each bacteria search the solution space. After some particular number of iterations, we can stop the bacterial movement and find out the positions of each of the objects.
Demonstration of the Bacteria Class We shall create a class called ‘bacteria’. This class will model a real life bacteria and we shall assume that the bacteria is going to move only on a 2-D (two-dimensional) area (i.e. we will only bother about the x and y axis). Of course you can extend it to 3-D as well. To keep track of the position, we shall make use of two variables x[2] and y[2].
x[0] and y[0] will be used to store the starting position. Once the bacteria moves, the new position will be stored in x[1] and y[1]. For the next movement, x[1] and y[1] will become the starting point and so we set: x[0]=x[1] and y[0]=y[1] Another data member is the variable ‘stepsize’. This determines the length of each stride of the bacteria. Higher the value, the more distance the bacteria will cover each time. The other two variables needed are ‘ai’ and ‘bi’. This is used to specify the direction of the movement (those of you who have learnt about vectors will know about the properties of a unit vector). Anyway a unit vector is needed to decide the direction of movement.
Classes & OOP
269 The program written below is meant to illustrate the use of comments as well. Many programmers have different styles of commenting but you should always make use of comments throughout your program. //************************************************************** ** //Aim :Illustrate the movement of a bacteria based on food available * // The class bacteria models a general bacteria * //Author :Santhosh * //Written on :12 Feb 2004 //Revised on :16 Feb 2004 * // (modified the unit_vec( ) function) * //**************************************************************** class bacteria { protected: float x[2],y[2],food[2]; int stepsize; float ai,bi;
//To store the old and new position //Unit vector
public: bacteria( ) {
//Constructor with no arguments
x[0]=rand( ); y[0]=rand( ); stepsize=1; food[0]=x[0]+y[0]; ai=bi=food[1]=0; x[1]=y[1]=0; } void disp_final( ); void disp_initial( ); void unit_vec( ); void calculate( ); void move( ); int test( ); };
Classes & OOP
//To display new position //Display initial position //To generate direction //To move the bacteria
270 void bacteria::unit_vec( ) { ai = rand( ); bi = rand( ); float mag = (ai*ai) + (bi*bi); mag = sqrt(mag); ai = ai/mag; bi = bi/mag; } void bacteria::calculate( ) { x[1]=x[0]+(stepsize*ai); y[1]=y[0]+(stepsize*bi); food[1]=x[1]+y[1]; } int bacteria::test( ) { if (food[1]>food[0]) bacteria { return 1; } else return 0; } void bacteria::move( ) { x[0]=x[1]; y[0]=y[1]; food[0]=food[1]; }
Classes & OOP
//only if food increases we want to move
271 void bacteria::disp_initial( ) { cout<
Classes & OOP
272 The output would be: The original position of bacteria is : 41 , 18467 The new position of bacteria is : 41.2325 , 18468 The original position of bacteria is : 41.2325 , 18468 The new position of bacteria is : 42.0056 , 18468.6 The original position of bacteria is : 42.0056 , 18468.6 The new position of bacteria is : 42.3698 , 18469.5 The test( ) function is used to ensure that the bacteria moves to positions with more food. If the amount of nutrition at the new place is less, then we will find a new unit vector and search in another position. In the above program the food will always keep increasing in the new position (because there is no provision provided for generating a negative unit vector). In real applications you would have to provide means for doing that as well. Also, the food function here is simply ‘x’ coordinate plus ‘y’ coordinate; in real applications you will have complicated equations. The output may appear a little weird. What does 42.3698 , 18469.5 mean? Actually we are working with vectors; thus the output is as good as saying: 42.3698 x + 18469.5 y 42.3698 units on the x axis and 18469.5 units on the y axis.
This is a very simple illustration. In reality, the bacteria would probably have to move around thousands of times and it has to move in some logical manner. Over here we make the bacteria move in a completely random manner. And you will also need to use a lot more bacteria objects to search for the best point (one bacteria object will not be sufficient to search a huge area).
Classes & OOP
273 The use of Inheritance: We’ve modeled a general class called ‘bacteria’ but the problem is that certain types of bacteria have different methods for movement (for example: E.Coli and TB bacterium move differently). E.Coli and TB are bacteria but they have different movements. Now what can we do? Should we declare another class called ‘ecoli’ and ‘tb’? Should we again declare the coordinate variables x,y,z and the member functions in each of these classes? And if we should do that, what is the purpose of having a general ‘bacteria’ class? The idea of having a general bacteria class serves two purposes: •
•
First of all, we are modeling the real world (we have different types of bacteria, each with some unique features). But all bacteria have certain common features and it is these common features that we declare in the general bacteria class. It is easier to think and program based on real life objects (this is the basis of OOP). Secondly, we can use the concept of inheritance to solve our problems. Using this we needn’t redefine all the variables and functions that are present in the ‘bacteria’ class again.
The ecoli and TB will also be modeled as classes but they will be derived from the general ‘bacteria’ class. In this way the two classes will inherit the properties of the ‘bacteria’ class. This is known as inheritance. Thus now you can create objects of type ‘TB’ and ‘ecoli’ as well. Inheritance will be dealt with in Chapter 10. Suppose another programmer in future wants to add another type of bacteria, he can simply derive a new class from the existing ‘bacteria’ class (thus saving time and reusing the code).
Classes & OOP
274
Data Encapsulation
Let us assume that you have created a new library that you want to distribute to other programmers. For example, working with images (bmp, gif or jpg) isn’t that easy in C++. You have to write methods (functions) for reading an image, identifying what type it is, displaying the image etc. There are many image-processing applications where the first step is to read an image. The simplest application is Optical Character Recognition (OCR). In OCR we write a program that will be able to recognize hand-written letters or numbers. For instance, if you write 8, the computer should be able to recognize that an 8 has been written (no matter how bad the handwriting is). Whatever you write on paper will be scanned into the system and stored in the form of an image file. A programmer developing an OCR application could either start from scratch or he could focus on his problem (i.e. OCR). By starting from scratch, he has to write a lot of coding just for the purpose of reading an image. Instead if he can get access to some good Image processing library (which will contain files for reading images), he could simply use the #include directive and concentrate on the OCR algorithm. So, let’s say that you have created a library for the purpose of image processing. You will make use of the concept of classes. You will create a class called ‘Image’, which contains member functions for reading an image, identifying the type of image etc. You will also have some member data like the height and width of the image. When you distribute your library to others, you give them the opportunity to create objects (or instances) of your ‘Image’ class. You will also have provided a little help file describing the various functions that are available for use. Note: You will only describe the use of the various functions (ex: the function syntax and what it will do). You will not be providing an insight into the implementation aspect of the function (i.e. you will not be explaining the function definition). Now, the OCR person will start coding his application. There is a chance that he might use the same member data identifier ‘length’ and ‘width’ in his program. Imagine the consequence if ‘length’ and ‘width’ are not made private within your class. There is a good chance that the user will fiddle around with the data (knowingly or unknowingly) and their values will get
Classes & OOP
275 altered by the user (which is what you don’t want). The result is that your functions, which operate on these member data, will produce some ambiguous results. By hiding your data, you prevent such problems from happening because the user will never be able to access or modify your class’ private members. The idea of encapsulation is to prevent users from having access to everything. They should be able to access members that are needed for their use. In larger classes there may be many members (data and functions) which are not required for someone who is going to use your class. These members should be made private. Remember: Usually the class declarations are written in a *.h header file (this is called as the interface) while the definitions for the member functions are written in a *.cpp source file (this will be the implementation). Thus even if the implementation is changed the interface remains unaffected (more about this in Multiple file programming). Thus to summarize: •
Data encapsulation means hiding the implementation of a class from the user. We restrict the user in such a way that the user can perform only limited operations on the private part (and that is only through the member functions). The user cannot access any of your private members directly.
•
Another advantage of hiding the implementation is that you (the class designer) may decide to change the process of implementation. You might have discovered some better and effective method for doing the same purpose. By hiding the implementation, all you need to do is to change the function definition. The user will never have to change his code (because you have only changed the implementation, which the user has nothing to do with). The code written by the user will still run even though you have changed the implementation.
Classes & OOP
276
More on Classes and Objects The idea of OOP is to write programs which will mirror the real world. Whenever you write a program using classes you have to decide as to what are the members of the class? To do these take the real-world entity that you are going to model. For example: Let us model a class based on a cricket player (say a batsman). Our problem is that we want to write a program to maintain records of different batsman (particularly we need to keep track of their highest and lowest scores). Every batsman will have the following attributes (or properties): • • • • • • •
A name A player number Date of Birth Native place Best score (or highest score) Worst score Physical description (like hair colour etc.)
We are not interested in storing information about all the attributes and hence we must isolate the attributes that are relevant to our problem. Considering our problem we will only need to use the name, player number, best score and worst score properties. Next we have to decide on the methods (or functions) that are required in the class. The following will be needed: • • • •
Initialize the member data. A display function to display the player details (i.e. display the member data values) Function to update the best score after every match. Function to update the worst score after every match.
Classes are sometimes modeled diagrammatically as shown below:
In this example, ‘batsman’ is the class and any instance created from this class is an object (i.e. if you create an object having values for each of the properties then that object is said to be an
Classes & OOP
277 instance of the class). For example: if we create an object called ‘ob1’ with the following member values: Name: Sachin Number: 10 Highest score: 200 Lowest score: 5 then ‘ob1’ is an instance of the class (or entity) ‘batsman’. This should clarify any doubts you might be having about the difference between a class and an object. Similarly whatever real life object you consider, you can model them into classes depending on the problem.
Private Member functions: Usually member data are made private while functions (or methods) are made public. There might be instances where you might want to make certain functions private (i.e. you may not want the user to directly access these functions). Private functions can only be called from within public member functions. These functions are also called ‘helper functions’ Why do we need them? Let’s take the example of the class ‘batsman’. After every match the user will enter the batsman’s new score and then he will have to call two functions to update the batsman’s record (i.e. the user has to call update_best ( ) and update_worst ( )). It is unnecessary to bother the user with this kind of a double function call. Why should the user access these two functions directly? Instead of this, we could define another function called update ( ) which will call update_best ( ) and update_worst ( ). In this way the user needs to only call one function after every match. The idea of classes is to restrict user access. We don’t want the user to access data or functions unnecessarily. So, we will make update_best ( ) and update_worst ( ) as private functions while update ( ) will be a public function.
Classes & OOP
278 // To illustrate the use of helper functions (private functions) #include
Classes & OOP
279 void batsman::update_worst(int z) { if (z
Classes & OOP
280
Friends and more! The following topics are covered in this section: • • • •
Friend Function Friend Class Static Members Constant Objects
Friend Function:
The private member data of a class can be accessed only by the class' member functions. Well, there is one exception. A friend function will be friendly with a class even though it is not a member of that class. By the term friendly we mean that the friend function can access the private members of the class. Check out the example below: #include
Classes & OOP
281 The output is: Enter the color : red Enter the speed : 345 The color of the car is : red The speed of the car is : 345 First of all we've created a class named car. It has two private data: color and ‘speed’ and one public function ‘input’. It also has a friend function called display ( ). Next comes the definition of display ( ): int display (car x) The parameter specifies an object of type ‘car’. The function has been defined to output: x.color; x.speed; The idea is that you will pass an object of type ‘car’ to this function and the function will display the corresponding color and speed values for that particular object. In the main ( ) part, an object called ‘mine’ has been created whose private data are set by the user through the input ( ) function. Next the friend function has been called: display(mine); Remember: Friend functions are not members of any class but they can access private data of the class to which they are a friend. Beware: Since they are not members of any class, you should not call them using the dot operator. You might be wondering what's the big point of friend functions. In the above example, we could have made ‘display’ as a member function of the class instead of declaring it as a friend function. What's the use? A friend function can be friendly to 2 or more classes. The friend function does not belong to any class, so it can be used to access private data of two or more classes. No other function can do that! #include
Classes & OOP
282 { life=1; } friend void check(bacteria, virus); }; class virus { private: int life; public: virus( ):life(0) {} friend void check(bacteria, virus); }; void check (bacteria b,virus v) {
if (b.life= =1 || v.life= =1) { cout<<"\nSomething is alive"; } if (b.life = = 1) { cout<<"\nA bacteria is alive."; } if (v.life = = 1) { cout<<"\nA virus is alive."; } } int main( ) { bacteria fever; virus cholera; check(fever, cholera); return 0; } In the second line of the program we have the statement: class virus;
Classes & OOP
283 This is a forward declaration of the class ‘virus’. This is done because when the compiler reads through the lines in the ‘bacteria’ class, it encounters the word ‘virus’ in the friend function declaration. Until this point it doesn't know what ‘virus’ is because we will be defining the ‘virus’ class later on. So we tell the compiler in advance that ‘virus’ is a class by declaring it in the starting itself. If you don't declare it, you will get errors. Just try it out. One more note: You should declare the friend function in both the classes where you want it to be a friend. In this program we want to check whether any organism is alive at present by testing the value of ‘life’. Of course you can write individual member functions in each class but the use of a friend function makes it simpler and easier to understand the logic.
Friend Classes Just like functions are made friends of classes, we can also make one class to be a friend of another class. In this way, the friend class will have access to all the private members of the other class. #include
Classes & OOP
284 int main( ) { add ad; support sup; sup.sum(ad); return 0; } The output will be: The sum of the 2 members is : 8 In this program, the class ‘support’ is a friend of the class ‘add’. Thus the class ‘support’ can access all the private members of the class ‘add’ (i.e. ‘x’ and ‘y’). Friend classes are rarely used.
Static Class Members A class is just an empty area. No area is allocated when you create a class. So, only when you create an object of that class will the compiler allocate space to the object. Again, this means that: private: int x; does not allocate space for an integer x. When you create objects belonging to this class, required space is allocated for holding the integer. Each new object that you create belonging to this class will have its own version of the integer ‘x’. The keyword ‘static’ helps you to create a single copy for all objects belonging to the same class. #include
Classes & OOP
285 int bacteria::count=5; // memory allocated for ‘count’ over here int main( ) { bacteria b; bacteria plague; b.modify( ); plague.modify( ); plague.modify( ); return 0; } The output is: The new count is : 6 The new count is : 7 The new count is : 8 The integer ‘count’ is declared to be static. But again outside the class we say: int bacteria::count=5; This is done so that the compiler will allocate space for the integer ‘count’. If ‘count’ were not static, when would the compiler allocate space to it? It would allocate space when an object is created. But in the case of static variable members in a class, we will create it only once and so we have to ensure that the compiler creates that one instance of the variable. In this case we’ve initialized it to a starting value of 5. If you simply type: int bacteria::count; ‘count’ would be automatically initialized to 0. Since ‘count’ is static, each time you change the value of ‘count’, all the objects belonging to that class will make use of the new value. So far we’ve seen static member data but you can also make functions static in a class. Just precede the function by the keyword static. Static functions will be useful to operate on static member data.
Classes & OOP
286
Constant Object Classes are user-defined types and objects can also be made constant using the ‘const’ keyword. When an object is made constant there are some peculiar properties that you should take note of. A constant object is created to avoid making changes to any of the values of the member data (that is the purpose of constant in general and so is the case with constant objects). 1.) Constant objects cannot access member functions that are not constant. #include
Classes & OOP
287 2.) So, the question arises, what are constant functions? Constant functions can be called by constant objects. The compiler doesn’t know when a function modifies data values and when a function doesn’t. When you make a function constant, the compiler believes that the function will not change any of the member data values. To create a constant function you have to simply add the keyword ‘const’ at the end of the function. class bacteria { private: int life,count; public: bacteria( ) { life=0; } void display( ) const { cout<
Classes & OOP
288 3.) Can a constant member function never modify any member value? Again there is an exception to this. If a member data is declared as ‘mutable’, then the constant member function can modify this member data. You might wonder what purpose this would solve? There might be a case where you want to keep count of something for all objects (irrespective of whether they are constant objects or not). In this case, a constant object should also be able to access such member data. Let’s take the same case of the bacteria class. We shall make the member data ‘life’ mutable. class bacteria { private: mutable int life; public: bacteria( ) { life=0; } void display( ) const { life=life+1; cout<
Classes & OOP
289
Copy constructors, pointers to objects The following topics are covered in this section: • • • •
Pointers to Objects this Pointer How objects are stored in memory Copy Constructors
Pointers to Objects You can create pointers to objects and to access the member functions you will have to use the arrow operator just like you would do for structures. class bacteria { private: int life; public: bacteria( ) { life=1; } void display( ) { cout<<"Life is : "<
Classes & OOP
290
How are objects stored in memory? We know that classes are just a framework- they do not occupy memory space. Instances of the class (objects) will occupy memory space. But a class has member data as well as member functions. It is obvious that every instance of the class should have its own copy of the member data; but is it necessary that every object should have a copy of all the member functions? The member data values will vary from object to object but the member functions are going to be the same. Member functions are implemented by taking advantage of this fact and hence only a single copy of the member functions is stored in memory. For example: #include
Classes & OOP
291
Now an interesting question arises. If all objects are going to access the same member functions how does the function operate on the correct object? How does the function know on which object it has to act upon? How is it that when h1 calls set_temp(), the member ‘temp’ of h1 gets set? This indicates that there must be a way in which the member function can identify who called it? The member function set_temp( ) should be able to differentiate between the objects h1, h2 and h3. To achieve this we pass a special member to the function (which the function uses to identify who called it). This member is the ‘this’ pointer.
‘this’ Pointer We’ve seen that objects can call member functions (public functions) by simply using the dot operator. But the member functions are not called as we might think. The address of the object (i.e. the object calling the function) is passed to the member function. This is passed as an argument to the member function. You might be thinking, "We never mentioned any argument in my member function. Where does this argument go?" Actually this is a kind of hidden argument (something that isn’t explicitly stated). Where does this argument go? The hidden argument is the address of the object and hence it has to go to a pointer. What is the name of the pointer? This pointer is called as ‘this’ pointer because it will point to the address of the object which called the member function (it will point to this object or in other words the present object). In an earlier example (the one with a class called ‘car’), we came across the function: void input( ) { cout<<"\nEnter the color : "; cin>>color; cout<<"\nEnter the speed : "; cin>>speed; } This is a member function belonging to the class called ‘car’. ‘color’ and ‘speed’ are member data of that class.
Classes & OOP
292 In reality, the above function is equivalent to: void input( ) { cout<<"\nEnter the color : "; cin>>this->color; cout<<"\nEnter the speed : "; cin>>this->speed; } In fact the compiler will convert our code into something similar (since it has to use the ‘this’ pointer to access the member data of the correct object). The ‘this’ pointer is useful when a member function of a class has to pass the address of the current object to another part of the program (or another function). Beware: The ‘this’ pointer is not present in static member functions. It is also not present in friend functions (because friend functions are not members of a class).
Copy Constructors It’s time to go back to the problem we discussed earlier (the mystery of the default copy constructor). A copy constructor is invoked when you initialize a new object of a class using an existing object. This will happen when: •
You pass a copy of an object as argument to a function (i.e. when passing by value).
•
When you return an object from a function
•
Or initialize an object during declaration using another object.
If we don’t specify a copy constructor, the compiler already has a default copy constructor. This default copy constructor will simply perform a bit-by-bit copy of the original object to the new object (i.e. it will blindly copy everything to the new object). This can lead to problems (especially if you use the ‘new’ and ‘delete’ operators in the constructor and destructor of the class). An example was discussed earlier in this chapter (using the ‘rect’ class). We’ll start by considering the same example: class rect { private: int length, breadth; public: rect( ) { cout<
Classes & OOP
293 length=breadth=0; } rect(int x, int y); void area( ) { cout<
Classes & OOP
294 Let’s try to map the output with the code we wrote: rect ob1(10,9); rect ob2=ob1; rect ob3(ob1); rect ob4; ob4=ob3; We’ve created 4 objects and we have 4 destructors but only 2 constructors. Why? ob1 is created using the parameterized constructor. No problems here since we are calling the constructor directly. ob2 and ob3 do not call the constructor. They are initialized using ob1 and in these 2 cases it is the default copy constructor that is called. ob4 is created using the no argument constructor. Thus we’ve called only 2 constructors and the problem is because of our default copy constructor. What we need to do is to provide our own copy constructor. A copy constructor for the above program would be defined as shown below: rect(const rect &rc) { cout<
Classes & OOP
295 As you can see, the copy constructor is called twice (for ob2 and ob3). That’s good but you might ask, “Why should I worry about this copy constructor?”. We’ll take another example. Let us suppose that we are using the ‘new’ operator in the constructor of the class and ‘delete’ is used in the destructor of the class. We also have a function whose argument is an object of that class. While calling this function, we will pass an existing object to the function. The function will create a temporary object (a copy of the object) but it will not invoke the constructor for this new object. Hence the ‘new’ operator (present in the constructor) is not invoked and an exact replica of the passed object is made. During destruction, the temporary object will invoke the destructor (which has the ‘delete’ operator and it will delete the portion of memory allocated to the original object). When the original object is destroyed, it will also invoke the destructor and this will again try to free the same memory. This can lead to serious problems. To solve this problem we have to define our own copy constructor. Let ‘bacteria’ and ‘virus’ be two classes. Each one will have a variable called ‘life’ created dynamically using the ‘new’ operator. We shall also define a friend function to which we will pass objects of both ‘bacteria’ and ‘virus’. #include
Classes & OOP
296 { life=new int; cout<<"\nCreated virus"; *life=0; } friend void check(bacteria, virus); ~virus( ) { delete life; cout<<"\ndestroyed virus"; } }; void check (bacteria b, virus v) { if ( (b.life[0]==1) || (v.life[0]==1) ) { cout<<"\nSomething is alive"; } } int main( ) { bacteria fever; virus cholera; check(fever,cholera); return 0; } The output will be: Created Bacteria Created virus Something is alive destroyed bact. destroyed virus And then an error is generated when using VC++ (because the same memory area is being deleted twice)…. In Visual C++ compiler it generates an error during run time and other compilers will also produce something similar (or in the worst case your program could freeze). Anyway, this can lead to drastic effects so you have to program in such a way that this problem does not occur. We’ll add our own copy constructors to both the classes.
Classes & OOP
297 Add the following to the bacteria class: bacteria(const bacteria &ba) { cout<
Classes & OOP
298 life[0]=ba.life[0]; life[1]=ba.life[1]; This ensures that the values will be the same. Now when the temporary object is destroyed, the destructor will be called and memory will be freed. When the original object is destroyed, the destructor is called again and memory (a different area) is freed up. Hence there is no clash of memory. Another question is why do we use const in: bacteria(const bacteria &ba) A copy constructor is used only for the purpose of copying; to initialize a new object based on an existing one. In this case you don’t want the copy constructor to modify the existing object (so we use ‘const’). Note: The above example was purely meant for explaining the idea of copy constructors. You wouldn’t really be creating a variable like ‘life’ dynamically. Another solution: Instead of using the copy constructor we could have taken a more simpler approach as well. The problem arose because we were passing 2 objects to the friend function by value. If we passed them by reference then the compiler would never need to create temporary objects. What should we do to achieve this? First declare the friend function as: friend void check(bacteria &, virus &); and then define it as: void check (bacteria &b, virus &v) { if ( (b.life[0]==1) || (v.life[0]==1) ) { cout<<"\nSomething is alive"; } } Try the program and you’ll get what we actually wanted: Created Bacteria Created virus Something is alive destroyed virus destroyed bact.
Classes & OOP
299 Beware: The copy constructor will not work when you use the assignment operator (=) to assign one object to another. In such cases, you have to overload the = operator to avoid the problem (operator overloading is discussed in the next chapter). If our above program had: int main( ) { bacteria fever; bacteria ecoli; ecoli=fever; return 0; }
//RUN-TIME ERROR
we’ll again get into memory problems because now we are using the assignment operator to copy ‘fever’ to ‘ecoli’. This leads to the same problem we had with copy constructors (both fever and ecoli will now access the same memory location for ‘life’ and both will try to free the same memory location). This just proves the point that when you perform such assignments, the copy constructor does not come into play. Remember: bacteria ecoli=fever; is different from bacteria ecoli; ecoli=fever; The first case is an initialization using an exiting object (copy constructor comes into action) but in the second case we are performing an assignment (assignment operator comes into action).
Classes & OOP
300
Miscellaneous Topics on Classes
The following topics are covered in this section: • • • • •
Returning Objects revisited The keyword 'explicit' Constructor using initializer list Declaration and definition revisited Recap
Returning Objects revisited (return value optimization): Returning an unnamed object (instead of a named object) might help the compiler to optimize the code to improve efficiency. Method I (copy constructor invoked)
Method II Returning an unnamed object
class base { private: int val; public: base(int x=0) { val=x; cout<
class base { private: int val; public: base(int x=0) { val=x; cout<
Classes & OOP
301 OUTPUT: OUTPUT: Constructor; value:5 Constructor; value:6 Copy constructor Destructor; value:6 Destructor; value:6 Destructor; value:5
Constructor; value:5 Constructor; value:6 Destructor; value:6 Destructor; value:5
In the first method we create an object in the function and return it: base btemp(val+1); return btemp; In this case, a constructor and destructor are called for objects ‘b1’ and ‘btemp’. In addition a copy constructor and destructor are called on a temporary object when we return an object from the function: return btemp; In the second method we return an unnamed object directly: return base(val+1); Here neither is an additional destructor called nor is the copy constructor invoked. This is called return value optimization (wherein the compiler is able to avoid the creation of a temporary object; thus improving efficiency). Note: The compiler tries its best to optimize code using various techniques (so that it can reduce work for the processor which in turn helps improve efficiency of our program). Return value optimization is one such optimization technique.
The keyword ‘explicit’ Let’s consider a simple program using classes. The class ‘circle’ has 2 constructors: a single argument constructor and a no argument constructor. #include
Classes & OOP
302 circle(int x) { cout<<"Single argument constructor invoked"; cout<<"is:"<
Classes & OOP
303 normal scenario). If we didn’t have a single-argument constructor, the compiler would not permit this. For example if our constructor were: circle(int x, int y) instead of circle(int x) then the compiler would give an error message saying “conversion from `int' to non-scalar type `circle' requested” So, what should we do to prevent this implicit conversion from taking place? Should we avoid using a single argument constructor? C++ provides a keyword called ‘explicit’. Instead of the constructor: circle(int x) { cout<<"Single argument constructor invoked"; cout<<"is:"<
When we have a single argument constructor, the compiler might try to perform an implicit conversion to convert an object into an object of the required type. This can be prevented by using the keyword ‘explicit’ in the constructor.
Classes & OOP
304
Constructor using initializer list: The following code: int main( ) { const int x; return 0; } will not compile because a ‘const’ has to be initialized. The compiler error will be: ‘x’ : const object must be initialized if not extern The next code snippet will also produce a compiler error: int main( ) { int x; int &ref; return 0; } This time we have created a reference variable but haven’t initialized it. The error message will be: 'ref' : references must be initialized Keeping these two facts in mind, let’s go back to initializer lists. The constructor for the class can be written as: counter ( ) { count=7; } or we could use the form: counter ( ) : count (7) {} The second method makes use of an initializer list (i.e. count(7) forms the initializer list over here) to initialize the values while the constructor body is empty. The first form isn’t really an initialization; it’s just an assignment. The first form is equivalent to writing: int count; count = 7; Thus the variable ‘count’ is first created and later assigned a value of 7. Initialization means giving a value to the variable at the time of creation; i.e. int count=7; is an initialization.
Classes & OOP
305 To prove that counter ( ) { count=7; } is just an assignment, let’s write a small program. #include
Classes & OOP
306 void display( ) { cout<<"count is:"<
All const and reference member data types have to be initialized using an initializer list. Anything within the body of a constructor is only an assignment and not an initialization. If one class contains another class (i.e. one object contains an object of another type), then you’ll have to initialize the other object in the initializer list.
Beware: When using an initialization list the order in which the members are initialized depends on the order in which you declare them within the class (does not depend on the order specified in the initializer list). The first member in the class will be the first member to be initialized. If your initializations depends on the sequence then be careful while using initializer lists.
Classes & OOP
307
Declaration and Definition revisited: Now we are in a better position to understand these 2 terms (which were introduced in the first chapter itself). Declaration: We tell the compiler that “this is the name of an object of this type which I might use later in my program.” Example: class virus; //class declaration We tell the compiler beforehand that ‘virus’ is a class. int getHealth ( ); //function declaration extern int error_no; //declaration of error_no The last example is used when you are writing code in multiple files (where one part of the code uses variables defined in another file). This will be dealt with later. But the point to note is that in the above 3 cases, we are only declaring something to the compiler (a function, a class and an object). The compiler does not allocate any memory space for them. Definition: Let’s start of with the examples: //Defining a class class virus { private: int life; //rest of the class }; //defining a function int getHealth ( ) { return health; } In a definition we provide the compiler with details. What about the next case? //defining and declaring int error_no; This is what we’ve been using frequently so far. This statement defines error_no (the compiler allocates storage space for it) and also declares error_no as type integer.
Classes & OOP
308
Recap •
The main concepts of OOP are: data abstraction, data encapsulation, inheritance and polymorphism.
•
Classes form the basis for OOP. Using classes, data and the functions that can operate on the data can be bundled together.
•
An object is an instance of a class.
•
The 3 access specfiers used in classes are private, protected and public. These specifiers determine as to who can access the class members.
•
The constructor is a special function invoked when an object is created and the destructor is invoked when the objected is deleted (or destroyed).
•
Every class is provided with a default constructor by the compiler if the programmer doesn’t define any constructor.
•
A constructor without parameters is a default constructor.
•
Constructors can be overloaded using different parameters.
•
When creating an array of objects, the class should have a default constructor.
•
When an object is passed to a function or when an object is returned from a function, a temporary object is created (the temporary object will only invoke the destructor but not the constructor).
•
Friend functions are not members of a class but they can access the private members of the class.
•
Objects can be made constant but they can only access constant functions and they cannot alter the value of member data (unless the member is declared to be ‘mutable’).
•
Static members can be accessed even without using an object.
•
Copy constructors should be defined in a class in case the dynamic memory allocation operators are used in the constructor and destructor.
•
Copy constructors are not invoked in assignments.
•
The ‘explicit’ keyword is used with constructors to prevent implicit conversions.
•
Initializer lists are used to initialize the member data of an object.
Classes & OOP
309
Chapters 7 and 8 (Classes and Pointers) Workshop Quiz
Q.) Predict the output: void change( int *b, int n) { int i; for( i = 0; i < n; i++) { *(b+i) = *(b+i) + 5; } } int main( ) { int a[]={ 2,4,6,8,10 }; int i; change(a,5); for( i = 0; i <= 4; i++) { cout<<“ ”<
2. Q.) do?
What will the program below void func(int *x, int *y) { int *t; t = x; x = y; y = t; } int main( ) { int a=2; int b=3; func(&a,&b); cout<
Answer.) 7
9
11
13
15
A.) I don’t think this should pose much of a problem. We are passing the address of the first element of an array to the function (through a pointer). Within the change ( ) function, we modify each element by adding 5 to it. Hence the output will be: 7
9
11
13
15
Answer.) Nothing On seeing a temporary variable and assignment statement, people jump to the conclusion that the above program will swap the values of two integers. But this is WRONG. The program actually does nothing. void func(int *x, int *y) { int *t; t = x; x = y; y = t; }
Within this function, all we are doing is assigning addresses (we are in no way altering ‘a’ or ‘b’). The output will be: 2 , 3 You could swap the values at a particular address (which is what we always do) but here we are simply using the addresses. Hence ‘a’ and ‘b’ are not swapped.
310 3. Q.) What is the value of ‘sum’ in the program below: int getNewValue(int x) { static int div=1; return(x/++div); } int main( ) { int a[ ]={12,24,45,0,6}; int i; int sum=0; for(i = 0; a[i]; i++) { sum+=getNewValue(a[i]); } cout<
Answer.) 25 There are a few important points to note in the above program. First of all you should note that we can pass individual array elements to functions as shown in the program above. Secondly, as long as the condition (i.e. the middle expression) in the ‘for’ loop is true (or non-zero), the loop will keep repeating. Thirdly, you should know about static variables and also about the prefix ++ operator. The ‘for loop’ will terminate for the case of a[3] because a[3] is 0. The value of sum will be: sum = (12/2) + (24/3) + (45/4) = 25 (because of integer division).
Answer.) Returns the number of characters in the string
4. Q.) What is the use of this function? int len(char *str) { int length=0; while(*str++!='\0') { length++; } return length; }
The use of *str++ might seem confusing. ++ has a higher precedence than * (but it’s better to use parentheses to make it clear- the parentheses have been dropped out here just to make the code appear more complex! Some C++ tests might present similar code fragments). Postfix ++ will increment its operand only after the assignment has been done (i.e. it will occur only in the next statement). Thus when * (the dereferencing operator) is applied, we will check whether the character is a null character or not and then we increment the value of ‘str’ (which means ‘str’ will now point to the next character in the string).
311 Answer.) Garbage values 5. Q.) Can you predict what will happen? int main( ) { int const max=5; int ar[max]={1,2,3,4,5}; int *ptr=&ar[0]; for (int i=0;i
A.) If you are expecting that the output will be 6,7,8,9,10 then you are wrong. Why? ptr was initialized to the address of ar[0]. But within the ‘for’ loop we have kept on incrementing ‘ptr’. Each time the corresponding value at the location is incremented by 5. Once the first loop is complete, ptr is now pointing to the next location beyond ar[4]. Thus in the second loop we are actually displaying garbage values (instead of getting the display of 6,7,8,9,10). If the 2nd loop were: for (i=0;i
6. Q.) Will this compile? void print(int &x) { cout<
print(y); the code might compile on some compilers. But this statement will produce a compile-time error because the compiler wouldn’t be able to decide which print( ) function to call. Some compilers will flag an error saying “parameter list of print(int) is not sufficiently different from print(int &)”. And this is logically true since there is no way in which the compiler will know which function to call.
312 7. Q.) Is there an error? What would be the output? class dummy { private: int data; public: int get_data( ) { return data; } }; int main( ) { dummy d1; cout<
Answer.) Output will be a garbage value The code will compile and execute even though there is no constructor explicitly defined for the class dummy. The compiler will provide with a default constructor. But some beginners are tempted into believing that a default constructor will initialize all member data. This is not true. The above program will display some undefined (garbage) value. To initialize member data correctly, the class designer should define a constructor and not rely on the implicitly created default constructor. Answer.) Compile Error A.) The compiler will complain at the statement: increment(y)=2;
8. Q.) Will the following code compile? If it saying that increment(y) is not an L-value. The will, what is the output? function increment( ) returns an integer which we can use as an R-value, not an L-value. An int increment(int &x) L-value is one which can be used on the left { side of an assignment expression. This in turn x++; means that an L-value should refer in someway return x; to a memory location where a new value can be } stored (after the assignment). Thus in the above code fragment, the statement: int main ( ) { int y=5; increment(y)=2; cout<