C and OOPs
C and OOPs
C and OOPs
Table of Contents
1. Some features of C and Introduction to the language C++ - I. 28) Basic data types. - Literals (integer, float, Character) - Special character sets. Qualifiers. - unsigned - long - short Variables declaration and definition and usage. Recursive function. Scope rules and Storage specifiers. - automatic - static - external - register. Operators - arithmetic - relational - logical Type casting. Control flow Branching and looping statements. - if statement - switch statement - for statement - while statement - do..while statement The break and continue statements.
(1
2.Some features of C and Introduction to the language C++, II. (29 44) Array types. Ways of initializing the arrays. Multidimensional arrays. Pointer types. Pointer Arithmetic. Relationship between Arrays and Pointers. Structures. Enumerated Constants. Reference Types. Difference between a Pointer and a Reference. Unions.
3
3. Object Oriented Programming Introduction (45 55) Procedural, Structural and Object-Oriented Programming logic. Different implementation of OOP. Introduction to C++. C++ and OOP. - Data abstraction. - Data encapsulation and data hiding. - Reusability and inheritance. - Polymorphism. Differences between C and C++. - Additional keywords in C++. - Comments. - Variable declarations. The scope resolution operator. Default arguments. The new and delete operators. The cin and cout objects.
4. Classes and Objects (56 68) Structures Revisited. Introduction to class. How to define a user-defined variable class. How to create variables (objects) of class. Access specifiers Private, Public and Protected. Member functions of the class. Passing and returning objects. Pointers to objects. Array of objects. Initializing the array of objects. The special this pointer.
5. Constructor and Destructor Functions (69 76) Automatic initialization of data members - constructors Constructor with one argument, multi arguments Using the constructor Destructor Default constructor and destructor
6. Operator Overloading (77 97) Introduction to Operator Overloading. Operator Overloading Fundamentals.
4
Implementing the operator functions. Rules for overloading the operators. Pointer oddities (assignment) and Operator Overloading. Copy Constructor (to solve the problem of initialization). Conversion functions. - Conversion from basic to user-defined variable. - Conversion from one user-defined to basic variable. - Conversion from one user-defined object to another. . Conversion function in source class. . Conversion function in the destination class.
7. Function Overloading and References (98 108) Function overloading. Need to overload functions. Precautions to be taken while overloading functions. Call by reference- definition and usage. Passing and returning reference variables. Why using references is a must in some occasions. Reference oddities.
8. Inheritance I (109- 119) Reusability. Inheritance concept- single inheritance. Defining a derived class. Private, Public and Protected derivations. The access specifiers. Using the derived class.
9. Inheritance II (120-132) Hiding overloaded functions. Constructor and destructor in derived class. Object initialization and conversion. Nested classes (Container classes). Multilevel inheritance. Multiple inheritance. Problems with multiple inheritance. Multiple inheritance with a common base class. Virtual base class. Abstract class. Pointers to objects. Virtual function. Pure virtual function.
5
10 Additional features of C++. (133- 166)
Static member and Static functions. Friend functions. Friend functions and Operator Overloading. Friend classes. Granting friendship to few member functions of the class. Necessity of friends. Overloading the extraction and insertion Operators. Inline functions. - global inline functions - class inline functions. Preprocess or Directives - #define and #undefined directives - #ifdef, #elif, #else and #endif directives. - #error directive. Input and Output. - the overloaded insertion and extraction operators. - istream and ostream objects other than cout and cin. - Manipulators. Introduction to template functions. Introduction to template classes.
The C language has, over the past few years, became one of the most popular programming language. This rapid popularity, amongst other reasons, can be attributed to the sheer power and simplicity of the languages. The C++ became still popular because it maintains the simplicity of the C Programming language and also makes it more powerful by supporting the Object Oriented Programming concepts. This chapter introduces several features of these languages. If you already know C or even if you have not learnt the language earlier, this chapter prepares you to explore the language C++.
1.1.
LITERALS (CONSTANTS)
Constants are data storage locations whose address is not accessible for the user. Their value is not changed during the course of the program. Literal constant : represented by their value. Symbolic constants : represented by their symbols / names. e.g. sum = num1 + 10; // 10 is a literal constant. // sum is a symbolic constant.
7 Constants as well as variables have types associated with them. The following table summarizes the data types , their sizes and their range, for a 16bit machine. Type Unsigned short int. Short int. Unsigned long int. Long int. int. Unsigned int. Char Float Double Size in Bytes 2 2 4 4 2 2 1 4 8 Range 0 to 65,535 -32,768 to 32767 0 to 4,294,967,295 -2,147,483,648 to 2,147,483,647 -32,768 to 32,767 0 to 65,535 256 character values 1.2e-308 to 3.4e38 2.2e-308 to 1.8e308
1.2.
INTEGER LITERAL
Integer are numbers without fractional parts.
e.g. 20 // Decimal 024 // Octal 0x14 // Hexadecimal To denote long, unsigned, suffixes such as L, U, l, u can be used. e.g. 128U, 1028u, 1L, 8LU, 71l( 71 and small L) .
1.3.
FLOATING LITERAL
They can be written in common decimal as well as scientific notation( floating point representation). By default it is of type double. F, L are applied only to common decimal notation. e.g. 3.1415F , 1.0E-3, 12.345L, 2. , 3E1, 1.0L
1.4.
BOOLEAN CONSTANTS
1.5.
CHARACTER LITERAL
Character literals are characters , digits and some special symbols, which are enclosed in single quotes and require a single byte of memory. e.g. a, 2, ., (blank) -uses a byte.
1.6.
When normal characters are preceded with a \ (back slash) , they work in a different way. These are called escape sequences. e.g. \n new line \t horizontal tab \v vertical tab \ double quotes. They can also be represented using literal constants . e.g. \14 instead of \n for newline.
1.7.
STRING LITERAL
An array of constant characters is represented as strings. Zero or more characters are represented within double quotation marks. These are terminated by a null character. The compiler does this. e.g. -- null string
1.8.
QUALIFIERS
All variables by default are signed ( the sign bit is also stored as a part of the number ). If it is known beforehand that the value of a variable will not be signed, then the keyword unsigned can precede a variable, thus declaring it an unsigned variable. This is called as a qualifier. e.g. unsigned int num = 341; unsigned arr = 20; Note that in the second example the data type is omitted, in which case it defaults to being an integer.
There are many occasions when values to be stored in the variable exceeds the storage space allocated to them. For instance, an integer on a 16-bit processor is allocated 2 bytes, which can store at the most 65535, if the variable is unsigned. If a bigger number is to be stored in a variable , then the qualifier long can be used. e.g. long int num = 1234354l; long emp = 1234321L; unsigned long int ulinum = 0; unsigned long ulnum; Note that the two qualifiers with one variable declaration can also be given. In this case the last two variables can have double the amount of storage and are unsigned. Also note that the letter L or l succeeds long constants. Similarly, there is another qualifier short, which gives storage equal to, or less than an integer, depending upon the implementation.
10 e.g. short int i; Some implementations have the qualifier signed. All the variables by default are signed, therefore this qualifier is not of much use.
1.9.
VARIABLES
Information stored in a variable can change in the course of the program. The type used in the definition describes the kind of information the symbol can store. Variables are addressable.
11 cout is equivalent to printf() in C. It prints the literals or values of variables.*/ If we want to find the power of some number or if we want to change the number we have to type the whole sentence again. We can generalize this operation by using 2 variables as follows. e.g. int val = 4, pow =10, cnt ; for ( cnt =1; cnt <= pow; ++cnt ) { res = res*val; }
This is flexible compared to previous method but not reusable. By using functions we can make them reusable too. e.g. int pow( int val , int exp ) { for ( res =1 ; exp>0 ; --exp ) {
You can call this function any number of times with different arguments. Variables can alternatively be called as Objects. There are two terms associated with a variable : 1. r-value ( read value) : Data. This can be a literal or variable name. 2. l-value (location value) : Address in the memory location where data is stored.
12 e.g. ch = ch1 - 0; where ch is l-value,ch1 is r-value symbol,0 is r-value literal. You can have the same symbol in both l-value and r-value. e.g. ch = ch - 0; 0 = 1; // Error, cannot have a literal as l-value.
salary + extra = newsal // Error, l value must be an // addressable variable and not an expression. Objects or variables can have only a single location. So, they cant be defined twice. However, they can be declared twice. Definition sets memory aside for a variable where as declaration just informs the compiler that this variable is defined somewhere in the program. Some variables may be needed in two or more files. In such cases we can define it in one file and declare it in all the files that use this variable. e.g. // file fileno1.cpp string filename; : : // file fileno2.cpp extern string filename; : : The keyword extern informs the compiler that the filename is defined somewhere outside the existing file. If you need to declare many variables in different files you can declare once in a header file and include it in files where the declarations are needed.
13 int month; When more than one variable of same type are needed a comma can be used to declare multiple variables. e.g. double sal , wage; int m , d , y , k,a; // Can span multiline too.
If the variables are declared globally, they are initialized to zero by default constructors( functions that are used to initialize the variables) that are inbuilt . If they are declared locally we have to initialize them as they may have some junk value stored in them. e.g. int x; // Global , initialized to zero. void main() { int i; } You can initialize the variables in the following ways. e.g. int ival =100; int ival(100); double sal =10.0, wage = sal + 10; int mon = 06, day = 19, year =1975; // Local , not initialized;
In C++, special constructors are inbuilt that support initialization. e.g. int ival = int(); // Sets ival = 0 ;
double dval = double() ; // Sets dval to 0.0; An object can be initialized with an arbitrary complex expression. e.g. double pr =199.99. disc = 0.16;
Recursion is a process by which a function invokes itself with a condition for its safe exit. It is best suitable for a recursive problem. A typical example is the factorial problem, the programs without and with recursive functions are shown below. void main() { long factorial ( int num); // Forward declaration of //a function int num; printf(\n Enter a number:); scanf(5d,&num); printf( Factorial of 5d is %ld,num,factorial(num)); } long factorial(int num) // Non recursive { long f=1;
The storage class determines the life of a variable in terms of its duration or its scope. There are four storage classes : automatic static external register
1.13.1
Automatic Variables Automatic variables are variable which are defined within the functions. They lose
their value when the function terminates. It can be accessed only in that function. All variables when declared within the function are , by default, are automatic. However, we can explicitly declare them by using the keyword automatic.
cout << Value of i before incrementing is :<< i; i = i + 10; cout <<\n<< Value of i after incrementing is :<<i; } void main() { print();
16 print(); print(); } Output: Value of i before incrementing is : 0 Value of i after incrementing is : 10 Value of i before incrementing is : 0 Value of i after incrementing is : 10 Value of i before incrementing is : 0 Value of i after incrementing is : 10
1.13.2. Static Variables Static variables have the same scope s automatic variables, but , unlike automatic variables, static variables retain their values over number of function calls. The life of a static variable starts, when the first time the function in which it is declared, is executed and it remains in existence, till the program terminates. They are declared with the keyword static. e.g. void print() { static int i =0;
cout << Value of i before incrementing is :<< i; i = i + 10; cout <<\n<< Value of i after incrementing is :<<i; } void main() { print(); print(); print(); } Output:
17 Value of i before incrementing is : 0 Value of i after incrementing is : 10 Value of i before incrementing is : 10 Value of i after incrementing is : 20 Value of i before incrementing is : 20 Value of i after incrementing is : 30 It can be seen from the above example that the value of the variable is retained when the function is called again. It is allocated memory and is initialized only for the first time.
1.13.3.
External Variables Different functions of the same program can be written in different source files and
can be compiled together. The scope of a global variable is not limited to any one function , but is extended to all the functions that are defined after it is declared. However, the scope of a global variable is limited to only those functions, which are in the same file scope. If we want to use a variable defined in another file, we can use extern to declare them. e.g. // FILE 1 g is global and can be used only in main() and // // fn1(); int g = 0; void main() { : : }
void fn1() { : : }
// FILE 2 If the variable declared in file 1 is required to be used in file 2 then it is to be declared as an extern. extern int g = 0;
1.13.4. Register Variable Computers have internal registers, which are used to store data temporarily, before any operation can be performed. Intermediate results of the calculations are also stored in registers. Operations can be performed on the data stored in registers more quickly than on the data stored in memory. This is because the registers are a part of the processor itself. If a particular variable is used often for instance, the control variable in a loop, can be assigned a register, rather than a variable . This is done using the keyword register. However, a register is assigned by the compiler only if it is free, otherwise it is taken as automatic. Also, global variables cannot be register variables.
e.g. void loopfn() { register int i; for(i=0; i< 100; i++) { cout << i; } }
1.14. OPERATORS
19 The variables, which are declared and defined, are the operands, which are operated upon by the operators. Operators specify what operations are to be performed on the operands. The language offers a range of operators, ranging from arithmetic, relational and logical operators to bit-wise logical , compound assignment and shift operators. 1.14.1. Assignment Operators The equal (=) sign is used for assigning a value to another. The left hand side has to be a variable (lvalue, which excludes constants, expressions, functions etc.). e.g. x = 10; y = x; 1.14.2. Arithmetic Operators We can classify the arithmetic operators as UNARY and BINARY operators.
1.14.2.1. Unary Operators Unary operators are those, which operate on a single operand.
Unary Minus Operator( Negation) This operand can be used to negate the value of a variable. It is also used to specify a negative number, here a minus(-) sign is prefixed to the number. e.g.
The value of y now becomes 5 and 'a' becomes -10. However, the language does not offer any unary + operator.
Increment and Decrement Operators. The operator for increment is ++ and decrement is . These operators increase or decrease the value of a variable on which they are operated, by one(1). They can be used as prefix or postfix to the variable, and their meaning changes depending on their usage. When
20 used as prefix, the value of the variable is incremented/ decremented before using the expression. But when used as postfix, its value is first used and then the value is incremented or decremented. e.g. --x; x--;
In the above example it does not matter whether the decrement operator is prefixed or suffixed. It will produce the same result. However, in the following example it does make a difference : int a =0, b=10; a = ++b; is different from a=b++; In the first case, the value of a after the execution of this statement will be 11, since b is incremented and then assigned. In the second case, the value of a will be 10 , since it is assigned first and then incremented. The value of b in both the cases will be 11. The unary operators have a higher precedence than the binary arithmetic operators.
1.14.2.2. Binary Operators There are five binary operators. These operators, require two operands. + - * / % e.g. z = x + y; f = 10 / 3; f = 10 % s; x = a + b c / d * e; x = ((a + (b c) / d )* e; for addition. for subtraction. fro multiplication. for division . for modulus.
The second example will give the quotient of 10 divided by 3, where as the third example will give the remainder after division.
21
The expressions in the fourth example is evaluated according to the precedence of operators, which is as follows: %, /, * on the same level, evaluated from left to right. +, on the same level, evaluated from left to right.
Arithmetic expressions can also contain parenthesis to override the precedence, as shown in the last example.
1.14.3. Compound Assignment Operators Apart from the binary and the unary arithmetic operators, we also have compound assignment operators. These are +=, -=, *=, /=, %=. Using these operators, the expression x = x + 5; can also be written as x += 5; This helps in writing compact code.
1.14.4. Relational Operators A relational operator is used to make comparison between two values. All these operators are binary and require two operands. There are the following relational operators : == != > >= < <= e.g. number < 6 ch != a total == 1000 equal to not equal to greater than greater than or equal to less than less than or equal to
Note that no space is given between the symbols. All these operators have equal precedence amongst themselves and a lower precedence than the arithmetic operators.
22
1.14.5. Logical Operators We say any expression that evaluates to zero is a FALSE logic condition and that evaluating to non-zero value is a TRUE condition. Logical operators are useful in combining one or more conditions. The following are the logical operators : && || ! AND OR NOT
The first two operators are binary, whereas the exclamation(!) is a unary operator. It is used to negate the condition. e.g. x<0 && y>10 evaluates to true if both conditions are true x < 0 || z = = 0 evaluates to true, if one of the conditions is true
!(x == 0)
The unary negation operator(!) has a higher precedence amongst the these, followed by the and (&&) operator and then the or(||) operator, and are evaluated from left to right.
1.14.6. Bit-wise Operators Some applications require operations to be done on different bits of a byte separately. Bit-wise operators offer a facility to do just that. There are several bit-wise operators :
Unary Operator : Ones Complement Operator(`) This operator is a unary operator that causes the bits of its operand to be inverted, i.e. 1 becomes 0 and 0 becomes 1. for instance, to see the largest possible number, which can be stored in an unsigned integer, a zero can be assigned to it. All the bits in this word will be zeros. When the ones complement operator is used on this word, all the bits will be inverted to ones, giving the largest possible number. The program to show this conversion is below : main()
23 { unsigned u = 0;
Binary logical bit-wise operators There are three logical bit-wise operators : & | ^ and or exclusive or
These are binary operators. The operations are carried out independently on each pair of the corresponding bits of the operands. i.e. the bit 1 of operand 1 is logically operated with the bit 1 of operand 2. the operations of these operators are summarized in the following table:
BIT2 0 1 0 1
BIRT1 | BIT2 0 1 1 1
BIT1 ^ BIT2 0 1 1 0
x = a & b; y = a ^ b; z = a | b; x &= y;
24 y |= z;
The result of x is 0x01, since a=0 0 0 0 0 0 0 1 & & & && & & & b=0 0 0 0 0 1 0 1
x=0 0 0 0 0 0 0 1
There are two shift operators : left shift ( <<) and right shift (>>). These are binary operators. The format is operand >> number or operand << number
The first operand is the value, which is to be shifted. The second is the number of bits by which it is shifted. The left shift operators shift number bits to the left, whereas the right shift operators shift number bits to the right. The leftmost and the rightmost bits are shifted out and are lost.
The char y( which is allocated a byte of space on a 16-bit machine) will have its bits shifted to the right three places. The value of y which was 0x80(1000 0000) is changed to 0xFo(1111 0000) , after y = y>>3.
25 The languages follow a standard precedence for basic operators. Precedence rules help in removing ambiguity of the order of operations performed while evaluating an expressions. Also important for this purpose is the associativity of the operators,
associativity defines the direction in which the expression is evaluated when the operator is involved. The precedence and associativity of the operators is summarized below: Operator () [] -> ! ~ ++ -- (type)* sizeof * / % + << >> == != & ^ |
&& || ? : = / += -= etc.
Associativity left to right right to left left to right left to right left to right left to right left to right left to right left to right
left to right left to right right to left right to left left to right
Operators on the same line have the same precedence; rows are in order of decreasing precedence, so, for example, *, /, % all have the same precedence which are higher than that of + and on the next line.
26
1.14.9. The sizeof() operator This is a pseudo-operator given by the language, which returns the number of bytes taken up by a variable or data type. The value returned by this operator can be used to determine the size of a variable. sizeof(int); // returns 2 on a 16bit machine
1.15.
this case, the operands are converted before evaluation, to maintain compatibility of the data types. The following table shows the conversion :
Operand1
operand 3
Result
char
int
int
int
long
long
int
double
double
int
float
float
int
unsigned
unsigned
long
double
double
float
double
27 ch = ch + 32;
Here, ch will be converted to an integer, since there is an integer(32) in the expression. The result will be an integer. e.g. float f = 10.0; int i = 0; i = f / 3; In this expression , the constant 3 will be converted to a float and then floating point division will take place, resulting in 3.33333, but since the lvalue is an integer the value will be automatically truncated to 3 and the fraction part is lost. ( implicit conversion from float to int ).
Implicit type conversions, as allowed by the language, can lead to errors creeping in the program if care is not taken. Therefore, explicit type conversions may be used in mixed mode expressions. This is done by type-casting a value of a particular type, into the desired type. This is done by using the (type) operator as follows: (type) expression expression is converted to the given type by the rules stated above. e.g. float a = 10.0, b = 3.0, c; c = a / b; This expression will result in 3.333333 being stored in c. If the application requires integer division of two floating point numbers, then the following expression with the type cast can be used: c = (int)a / (int)b; or c = (int) (a / b);
28 The control flow statements are used when it is required that the flow of the program is to be changed after taking some decision. This control flow statement thus specify the order in which the computations are carried out. The language offers for, while, do-while, ifelse, else-if, switch, goto as control statements. We can classify them as branching and looping structures.
1.17.1. Branching Statements The control flow statements used for branching are if-else, else-if and switch.
1.17.1.1. The if-else and else-if statements. The syntax of if else statement is : if(expression 1) { statements; } else { statements; }
If the expression1 is evaluated to be true the statements under it are executed otherwise if it is wrong the statements written after the keyword else are executed. e.g. if( ch < 0 || ch > 9) { cout << Not a Number); } else // in case of a single statement the braces can be avoided. cout<< This is a number;
A single if(expression) without an else can also be used for checking a single condition.
29 A multi-way decision making segment can be written by using if statements inside the else part, which gives the else-if construct. e.g. if( option == 1) cout<< 11111; else if( option == 2) cout<< 22222; else if( option == 3) cout<< 33333; else cout << invalid ; They can be nested as well: e.g. if(expression 1)
{ if ( expression 2) { : : } else {
} } else { if ( expression 2) { : :
30 } else {
} }
1.17.1.2. The Conditional Expression Operator An alternate method to using a simple if-else construct is the conditional expressions operator, ?: A conditional expression operator is a ternary operator, it has three operand, whose general format is: expression1 ? expression2 : expression3
Here the expression1 is evaluated first, if it is true then the expression 2 is the value of the conditional operator, otherwise the expression 3 is the value. e.g. The if else construct
if(a>b) { z = a; } else { z = b; }
z = (a>b) ? a: b;
31 1.17.1.3. The Switch Construct The switch statement is a multi-way decision-making construct that tests whether an expression matches one of a number of constant values, and branches accordingly. The switch statement is another alternative to using nested if-else statements. The general format is as follows : switch(expression) { case value1 : statements; : : break; case value2 : statements; : : break; case value3 : statements; : : break;
default
: statements; : :
The expression must be declared in the parentheses, and the body of the switch statement must be enclosed in braces. The values with case should be constants. The expression is evaluated and compared with the values in cases, if it matches the statements under it are executed till it encounters a break statement. If the value does not match with any of the case statements then the statements under the default label are executed.
32 Omission of break takes control through the next case statements regarded whether the value of case matches or not. This can also be used constructively when same operation has to be performed on a number of cases. e.g. switch(ch) { case a : case e : case i : case o : case u : cout << Vowel; default : cout << Consonant ; }
In the above example if the value of ch is any of a,e,i,o,u then they are printed as vowels , otherwise they are printed as consonants.
The statements generally used for looping are for, do-while, while. The goto statement can be used for looping, but its use is generally avoided as it leads to haphazard code and also increases the chances of errors.
1.17.2.1. The for Loop This is a controlled form of loop. The general format is : for( initialize ; test ; update) { statements; : : }
33 The initialize part is executed only once, before entering the loop. If test evaluates to false then the next statement after for loop is executed. If the test evaluates to true then the updating takes place. e.g. for( int i = 0; i< 10; i++) // In C++ you can declare a variable here. { cout << i; } This will print from 0 to 10. The expressions in the for loop are optional but the semi-colons are necessary, i.e.,
for( ; ; ) { cout<<hello; } This is an infinite loop, with no expression. e.g. int i = 0 for(; i< 10; ) { cout << i; i++; } This is equivalent to our example, which printed from 0 to 9. More than one expression can also be inside the for loop by separating them using commas. e.g. for( int i = 0, int j=10; i< 10 && y>0 ; i++, y--) { cout << i; }
34 1.17.2.2. The while Loop The for loop is a form of a controlled loop. When the number of iterations to be performed are not known beforehand the while loop can be used. The general format is : while(condition) { statements; : }
The statements in the loop are executed as long as the condition is true . e.g. int x = 0;
1.17.2.3. The do..while Loop The general format is as follows : do { statements; : : } e.g. do { cout << x; } while( x < = 10); while(condition);
35
This works exactly like our previous example . the do.. while loop is exactly like while loop, with one difference. Here testing is done after executing the statements inside the loop and if the condition evaluates to true then we enter the loop again ,whereas in while only after the expression is checked we enter the loop. We can say do loop is executed at least once.
1.17.3. The break statement The break statement, which was already covered in the switch.. case , can also be used in the loops. When a loop statement is encountered in the loops the control is transferred to the statement outside the loop. When there are nested loops it breaks only from the current loop in which it was written.
1.17.4. The continue statement The continue statement causes the next iteration of the enclosing loop to begin. When this is encountered in the loop , the rest of the statements in the loop are skipped and control passes to the condition.
Let us see an example that accepts a variable amount of numbers from the keyboard and prints the sum of only positive numbers. e.g. void main() { int num, total = 0;
if(num == 0) break;
total+=num; }while(1);
1.17.5. The goto statement This statement can be used to branch to another statement of the program. This is rarely used as it violates the principle of structured programming. However we can use it when we want to exit from deeply nested loops. The general format is : goto label;
where label is a name(tag) followed by a colon. e.g. for( . . . ) { for( . . . ) { for( . . . ) { for( . . . ) { if( condition) goto cleanup; } } } } cleanup: statements;
37 : : A good programmer will use control statements effectively in order to avoid the usage of unconditional branching statement goto. This session introduced some features of the C and C++ languages. The data types offered, qualifiers to the data types, the scope rules and storage methods were also discussed in this session. The control flow required for any language to be structured was also discussed in detail. The language offers a variety of operators. The next session introduces some of the advanced topics.
SESSION 2
In this session we will learn about storing a set of like type data members. We will learn how to bundle related data members of may be different names under the same name. We will learn a few advanced topics of the language construct.
2.1.
ARRAY TYPES
An array is a collection of objects of a single data type. The individual objects are accessed by their position in the array. This way of accessing is called indexing or subscripting. e.g. int arr[10]; int ival = arr[2]; // Declares array of 10 integer objects // Assigns the value of third element in
arr[0] = ival;
element arr[0]
Dimension specifies the number of elements contained in the array. An array must be given dimension size greater than or equal to 1. The dimension value must be a constant expression. i.e., the value should be available at compile time. e.g. extern int getsize(); const int a = 10, b = 20; int c = 30; char c_arr[a]; int i_arr[b-3]; // valid // valid ,constant expression. Evaluates to 17 during compilation.
39 int i_arr[c]; // ERROR: non-constant variable. Though it is initialized, access to its value can only be accomplished runtime. int i_arr[get_size()]; // ERROR: non-constant expression
The elements of an array are numbered beginning with 0. For an array of 10 elements , the index values are 0 through 9.
2.2.
for(int i = 0; i < size ; i++ ) // You can declare a variable here in C++. { arr[i] = i; } } An array can be explicitly initialized as follows. e.g. int arr[3] = {0,1,2};
An explicitly initialized array need not specify size but if specified the number of elements provided must not exceed the size. If the size is given and some elements are not explicitly initialized they are set to zero e.g. int arr[] = {0,1,2}; int arr1[5] = {0,1,2}; // Initialized as {0,1,2,0,0}
40 const char a_arr1[] = {c.+,+} //size = 3; const char a_arr2[] = {c++} //size = 4 because of
An array cannot be initialized with or assigned to another array. We cannot have array of references. e.g. int ix,iy,iz; int *arr[] = {&ix,&iy,&iz}; //Can have array of pointers int &arr[] = {ix,iy,iz}; // ERROR: cannot have array of references. int arr2[] = {1,2,3}; // valid
//
arr1 = arr2;
//
To copy one array to another each element has to be copied using for structure. Any expression that evaluates into an integral value can be used as an index into array. e.g. arr[get_value()] = somevalue;
2.3.
MULTIDIMENSIONAL ARRAYS
Each dimension is specified in separate brackets e.g.
41 int arr[4][3];
This is a two-dimensional array with 4 as row dimension and 3 as a column dimension. Multidimensional array can be initialized as follows e.g. int arr[4][3] = {{0,1,2},{3,4,5},{6,7,8},{9,10,11}};
The nested brackets are optional. e.g. int arr[4][3] = { 0,1,2,3,4,5,6,7,8,9,10,11}; int arr[4][3] = {{0},{1},{2},{3}}; // First element of rows are 0,1,2,3 and remaining are considered zero. int arr[4][3] = {1,2,3}; // Initializes first three element of first row as 1,2,3, and remaining elements are considered Zero.
for(int i = 1;i< = 10;i++) for(int j = 1; j< = 10;j++) { arr[i][j] = i+j; } Instead of arr[1][2] if you give arr[1,2] is considered as arr[2].
2.4.
POINTER TYPES
i.exe
manipulation of that object. They are used in creating linked data structures like lists, trees and management of objects that are dynamically created during program execution.
Pointers are declared using the (*) operator. The general format is: type *ptrname; type can be of any data type and pointer name becomes the pointer of that data type. e.g. int *iptr; char *cptr; float *fptr;
The pointer iptr stores the address of an integer. In other words it points to an integer, cptr to a character and fptr to a float value.
Once the pointer variable is declared it can be made to point to a variable with the help of a address(reference) operator(&).
e.g. int num = 1024; int *iptr; iptr = # // iptr points to the variable num.
In the following example lptr1 is a pointer to long variable and lptr2 is a normal long variable.
43 You can declare a pointer variable in two ways. However, the second format is not recommended as it may lead to the confusion that when we declare variables like just shown above could be correct.
The pointer can hold the value of 0 , indicating that it points to no object or it can hold the address of same data type as mentioned earlier.
e.g. int ival = 1024; int *iptr1 = 0, *iptr2 = &ival; iptr1 = iptr2 // both address ival iptr2 = 0; // iptr2 now addresses to no object.
Pointers can never store a non-address value. e.g. iptr1=ival; // invalid, ival is not address.
A pointer of one type cannot be assigned the address value of the object of another type. e.g. double dval, *dptr = &dval; // allowed iptr = &dval ; //not allowed
However you can compare or interchange 2 types of address values using void pointer. e.g. void *vptr = iptr; vptr = dptr;
44
We cannot operate on an object pointed by void. They are only for transport of the variables. Once the pointer is defined. The variable it points to can be accessed by using the indirection operator (*) also called the dereferencing operator.
e.g. int ival1 = 1024, ival2 = 2084; int * iptr = &ival1; * iptr = ival2; * iptr = abs(*iptr); * iptr = * iptr + 1; // ival1 = ival2 // ival1 = abs(ival1) // ival1 = ival1 + 1;
You can also have second level of indirection i.e., you can have pointer to pointer to int. e.g. int **ptriptr = &iptr; We dereference it as follows. int *iptr3 = *ptriptr; // content is an address.
2.5.
POINTER ARITHMETIC
We can manipulate the pointers too. We can perform operations like addition,
subtraction, increment and decrement etc.,. Since the pointer variables contain address, adding or subtracting a number results in another address, which is at an offset from the original address. This may be memory address occupied by another variable. e.g. int i, j, k ; int *iptr = &i; *iptr = *iptr + 2; // I = I + 2; iptr = iptr + 2 // adds 2 to address.
45 int *iptrb = &arr[0]; int *iptre = &arr[10]; While ( iptrb != iptre) { Do something with the value if (*iptrb); ++iptrb; }
2.6.
Consider the following. int arr[] = {0,1,2,3,4,5,6,7,8,9}; If we write arr , it is considered as the address of the first element of an array. Hence, arr is equivalent to &arr[0]; arr[1] <==>arr+1; Similarly to access the value we can write , arr[0] or *arr; arr[1] or *(arr+1); Diff between *(arr+1 ) and *arr+1 *(arr+1) means the address of arr is increased by 1 and then the contents are fetched. *arr+1 means the contents are fetched from address arr and one is added to the content. The traversal of an array can be made either through subscripting or by direct pointer manipulation. e.g. void print(int *arr_beg, int *arr_end) { while(arr_beg ! = arr_end) { cout << arr_beg; ++arr_beg; } }
46
arr_end initializes element past the end of the array so that we can iterate through all the elements of the array. This however works only with pointers to array containing integers.
2.7.
STRUCTURES
A structure is a derived data type. It is a combination of logically related data items. Unlike arrays, which are a collection of like data types, structures can be made of members of unlike data type. The data items in the structures generally belong to the same entity , like information of an employee, players etc. The general format of structure declaration is: struct tag { type member1; type member2; type member3; : : }variables;
We can omit the variable declaration in the structure declaration and define it separately as follows :
47 tag variable; e.g. Structure definition. struct account { int accnum; char acctype; char name[25]; float balance; }; Structure declaration. struct account oldcust; [or] account newcust; We can refer to the member variables of the structures by using a dot operator (.). e.g. newcust.balance = 100.0 cout << oldcust.name; We can initialize the members as follows : e.g. account customer = { 100, w, David, 6500.00}; We cannot copy one structure variable into another. If this has to be done then we have to do memberwise assignment. We can also have nested structures as shown in the following example : struct date { int dd, mm, yy; }; struct account { int accnum; char acctype; char name[25];
48 float balance; struct date d1; }; Now if we have to access the members of date then we have to use the following method. account c1; c1.d1.dd=21; We can pass and return structures into functions . The whole structure will get copies into formal variable .
We can also have array of structures. If we declare array to account structure it will look like, account a[10];
Every thing is same as that of a single element except that it requires subscript in order to know which structure we are referring to.
We can also declare pointers to structures and to access member variables we have to use the pointer operator -> instead of a dot operator. account *aptr; cout << aptr->name;
A structure can contain pointer itself as one of the variables ,also called selfreferential structures. e.g. struct info { int i,j,k;
info *next; };
2.8.
ENUMERATED CONSTANTS
49 Enumerated constants enable the creation of new types and then define variables of these types so that their values are restricted to a set of possible values. e.g. enum Colour{RED, BLUE, GREEN, WHITE, BLACK};
Colour is the name of an enumerated data type. It makes RED a symbolic constant with the value 0, BLUE a symbolic constant with the value 1 and so on. Every enumerated constant has an integer value. If the program doesnt specify otherwise, the first constant will have the value 0, the remaining constants will count up by 1 as compared to their predecessors. Any of the enumerated constant can be initialised to have a particular value, however, those that are not initialised will count upwards from the value of previous variables. e.g. enum Colour{RED = 100, BLUE, GREEN = 500, WHITE, BLACK = 1000};
The values assigned will be RED = 100,BLUE = 101,GREEEN = 500,WHITE = 501,BLACK = 1000
e.g.
You can define variables of type Colour, but they can hold only one of the enumerated values. In our case RED,BLUE,GREEEN,WHITE,BLACK . You can declare objects of enum types.
num Days{SUN, MON, TUE, WED, THU, FRI, SAT}; Days day; Day = SUN; Day = 3; // error int and day are of different types Day = hello; // hello is not a member of Days.
Even though enum symbolic constants are internally considered to be of type unsigned int we cannot use them for iterations. e.g. enum Days{SUN, MON, TUE, WED, THU, FRI, SAT}; for(enum i = SUN; i<SAT; i++) //not allowed.
50
There is no support for moving backward or forward from one enumerator to another. However whenever necessary, an enumeration is automatically promoted to arithmetic type. e.g. if( MON > 0) { cout << ghjf; } int num = 2*MON; These are allowed.
2.9.
REFERENCE TYPES
The symbol & is interpreted as an address operator as well as AND operator. This operator is also used to declare a reference variable. A reference is referred to an alias, serves as an alternative name for the object. e.g. int ival = 1024; int &rval = ival; // rval is reference to ival A reference variable should be initialized at time of declaration itself. e.g. int &iref; // wrong //o.k.
Once a reference has been made to a variable , it cannot be changed to refer to another variable. e.g. void main() { int num1 = 10, num2 = 200; int &ref = num1; cout<< num1 << num2 << ref ; //10,200,10;
i.exe
51 ref = num2; cout << num1 << num2 << ref ; } It changes the contents of the original variable, which is not the desired action. Although a reference to a variable serves as a kind of pointer, we cannot initialize the address of an object to it. e.g. int ival = 1000; int &refval = &ival; //the error message by the //compiler would be- can not // convert int* to int. //200,200,200
However , we can define a pointer to a reference. e.g. int *pi = &ival; int *&ptr_ref = pi;
All the operations on the reference variables are actually applied to the object to which it refers. e.g. refval = refval + 2;// ival = ival + 2 ,1000 + 2 =1002 int ii = refval; int *pi = &refval // ii = 1002; //pi = &ival;
A constant reference can be initialized to an object of a different type as well as to non-addressable values, such as literal constants . e.g. double dval = 3.1415; const int &ir = 1024; const int &ir2 = dval; const double &dr = dval +1.0; The same initializations are not legal for non-constant references.
52 Internally, a reference maintains the address of the object for which it is an alias. In case of non-addressable values, such as literal constant and objects of different types, the compiler generates a temporary object that reference actually addresses but user has no access to these addresses. e.g. double dval = 1024; const int &ri = dval; The compiler transforms this as, int temp = dval; const int &ri = temp;
Now if we try to change ri, it actually changes temp and not dval as it is a constant We can initialise a reference to the address of a constant object. If we write, const int ival = 1000; int *&pi_ref = &ival; // we require constant reference. If we write, const int *&pi_ref = &ival;
/* this is reference to a pointer to an object of type int defined to be a constant. Our reference is not to a constant but rather to a non-constant, which addresses a constant object.*/
53 int temp = 0; const int &ri = temp; 2. The assignment of one reference with another changes the object being referenced and not the reference itself. e.g. int ival1 = 1000, ival2 = 2000; int *pi1 = &ival1, *pi2 = &ival2; int &ri1 = ival1, &ri2 = ival2; pi1 = pi2; ival1 remains unchanged but pi1 and pi2 now address the same object ival2. ri1 = ri2; ival1 becomes 2000. ri1 and ri2 still refer to ival1 and ival2 respectively.
2.11. UNIONS
A union is also like a structure , except that only one variable in the union is stored in the allocated memory at a time. It is a collection of mutually exclusive variables , which means all of its member variables share the same physical storage and only one variable is defined at a time. The size of the union is equal to the largest member variables. A union is defined as follows : union tag { type memvar1; type memvar2; type memvar3; : : }; A union variable of this data type can be declared as follows, Union tag variable_name; e.g. union utag
54 { int num; char ch; }; union tag filed; The above union will have two bytes of storage allocated to it. The variable num can be accessed as field.sum and ch is accessed as field.ch. At any time, only one of these two variables, can be referred to. Any change made to one variable affects another.
Thus unions use memory efficiently by using the same memory to store all the variables, which may be of different types, which exist at mutually exclusive times and are to be used in the program only once.
This session covered advanced concepts of the language like pointers, arrays, references, and relation between them . As can be seen pointers and references can be used to return more than one value from the functions, to speed the processing and to use memory more efficiently. The session also covered the concepts of structures and unions. The structures or records are the basic elements of a database. When they are used with pointers, arrays etc, complex database management can be realized. The unions are used to store variables of different types that are required at mutually exclusive times.
EXERCISES 1. Write a Program(WAP) to print a given integer in the reverse order. i.e. 12345 is printed as 54321 2. 3. 4. 5. WAP a program to print first N prime numbers where N is accepted from the user. WAP to print the sum of squares between 1 & N, where N is accepted from the user. WAP to convert a decimal number into its equivalent of any other base(2,8,16). WAP to accept an array of character strings and print them in descending order.
55
SESSION 3
Default arguments. The new and delete operators. The cin and cout objects.
In earlier sessions we learnt about language features which had nothing to do with Object Oriented Programming techniques. This session introduces the features of Object Oriented programming and also discusses other programming methods. We can also see features of C++ that are not present in C.
3.1
56 manner. The data is kept separate from the procedures, and the trick in programming was to keep track of, which functions called which functions, and which data was changed. To make sense of this potentially confusing situation, structured programming was created. Pascal is a language, which uses procedural method of programming.
The principal idea behind structured programming was as simple as the idea of divide and conquer. A computer program could be regarded as consisting of a set of tasks. Any task that was too complex to be simply described would be broken into a set of smaller component tasks, until the tasks were sufficiently small and self-contained to be easily understood. The language C uses structured programming approach.
Structured programming remains an enormously successful approach for dealing with complex problems. By the late 1980s, however, some of its deficiencies had become all too clear.
First, the separation of data from the tasks that manipulate the data became increasingly difficult to comprehend and maintain. It is natural to think of data (employee records, for example) and ways that data can be manipulated (sort, edit, and so on) as related ideas.
Second, programmers found themselves constantly reinventing new solutions to old programs. Reinventing in a way is opposite of reusability. Reusability involves building components that have known properties, and then plugging those components into a program, as the programmer needs them. This software concept is modeled after the hardware world; when an engineer needs a new transistor, she doesnt usually invent one but goes to big bin of transistors and finds one that fits her needs, or perhaps modifies the found transistor. No similar option existed for a software engineer until reusability was developed.
Object-oriented programming (OOP) attempts to meet these needs, providing techniques for managing enormous complexity, achieving reuse of software components, and coupling data with the tasks that manipulate the data.
57 The essence of object-oriented programming is to treat data and the procedures that act on the data as a single object a self-contained entity with an identity and certain characteristics of its own.
3.2
Object-oriented programming is not particularly concerned with the details of the program operation. Instead, it deals with the overall design of the program . The OOP features can be implemented in many languages that support them. C++ is one language that supports all the features of OOP. Other languages include Common Lisp object system, Smalltalk, Eiffel, Actor and the latest version of Turbo Pascal, Java. However, C++ is the most widely used object-oriented programming language.
3.3.
INTRODUCTION TO C++
C++ was developed by Bjarne Stroustrup at Bell Laboratories in 1983. Originally, it was called as C with class. C+ as an enhancement to the C language was developed primarily to facilitate managing, programming and maintaining large software projects.
The most important aspect of the C language is, probably, the flexibility to do whatever the programmer wants. The limits of the language are defined by the programmers imagination. Unfortunately, with very large projects in which many programmers use shared routines, this liberty can lead to what are called as side effects . This is one problem, which C++ attempts to resolve by restricting indiscriminate access. At the same time, C++, also attempts to keep the freedom and flexibility given by the language. All the keywords of C are keywords of C++ also. In addition we have some new ones too.
However, C++ is not merely an extension of the C language, where some new symbols have been added. The basic purpose of C++ language is to add features to the C language that supports the concepts of OOP.
58 There are several C++ products available Turbo C++, Zortech C++, AT&T C++, Sun C++ etc., Turbo C++ and Borland C++ are most widely used. AT & T however is the most advanced one.
3.4.
Before discussing these pillars let us know about some other terms associated with OOP.
The most important term is Abstraction. Abstraction means ignoring those aspects of a subject that are not relevant to the current purpose in order to concentrate more fully on those that are relevant. To an analyst who is as good as a subject expert it means choosing certain things or aspects of the system over others at a time. Most of the things in the real world are intrinsically complex, far more complex than one can comprehend at a time. By using abstraction one selects only a part of the whole complex instead or working with the whole thing. It does not mean ignoring the details altogether but only temporarily, the details are embedded inside and are dealt with at a later stage. Abstraction is used by OOP as a primary method of managing complexity. Association is the principle by which ideas are connected together or united. Association is used to tie together certain things that happen at some point in time or under similar conditions.
Communicating with messages is the principle for managing complexity, which is used when interfaces are involved to communicate between the entities. OOP uses classes and objects have attributes, which can be exclusively accessed by services of the objects. To change the attributes of the object (a data member of the object), a message has to be sent to the object to invoke the service. This mechanism helps in implementing encapsulation and data abstraction.
59 The property of being a self-contained unit is called encapsulation. The idea that the encapsulated unit can be used without knowing how it works is called data hiding.
Encapsulation is the principle by which related contents of a system are kept together. It minimizes traffic between different parts of the work and it separates certain specific requirements from other parts of specification, which use those requirements.
The important advantage of using encapsulation is that it helps minimize rework when developing a new system. The part of the work, which is prone to change, can be encapsulated together. Thus any changes can be made without affecting the overall system and hence changes can be easily incorporated.
As noted before, when an engineer needs to add resistor to the device she is creating, she doesnt typically build a new one from the scratch. She walks over to a bin of resistors, examines the bin of resistors, examines the colored bands that indicate the properties, and picks the one she needs. The resistor is a black box as far as the engineer is concerned that is, she doesnt care how it does its work as long as it conforms to her specifications. All the resistors properties are encapsulated in the resistor object they are not spread out through the circuitry. It is not necessary to understand how the resistor works to use it effectively, because its data is hidden inside the resistors casing.
C++ supports the properties of encapsulation and data hiding through the creation of user-defined types, called classes. Once created, a well-defined class acts as a fully encapsulated entity and can be used as a whole unit. The actual inner workings of the class should be hidden; users of well-defined class do not need to know how the class works, only how to use it.
Consider a car manufacturing company. When they want to build a new car, they have two choices. They can start from the scratch, or they can modify an existing model. Perhaps their former model is nearly perfect (say CAR1), but they would like to add a turbocharger and a six-speed transmission. The chief engineers would prefer to use the earlier model and make some changes to it than creating a new one (CAR2).
60
C++ supports the idea of reuse through inheritance. Through this concept, a new type can be declared that is an extension of the existing type. This new subclass is said to derive from the existing type and is sometimes called a derived type.
3.4.3. Polymorphism
The CAR2 in the above example may respond differently than the CAR1 when the car accelerates. The CAR2 might engage fuel injection and a turbocharger, for example, whereas the CAR1 simply get petrol into its carburetor. A user, however, does not have to know about these differences; the user simply presses the accelerator and the car responds with the correct function.
C++ supports this idea that different objects do the right thing through function polymorphism and class polymorphism. Poly means many, whereas morph means form. Thus, polymorphism refers to the same name taking many forms.
There are several differences between the C language and C++, which have nothing to do with OOP. Some of them are highlighted below.
3.5.1. Additional keywords in C++ class friend virtual inline const delete operator
The actual use and description of these additional keywords will be covered in their specific contexts.
3.5.2. Comments
61 Comments are integral part of any program . Comments help in coding, debugging and maintaining a program . The compiler ignores them. They should be used liberally in the program .
In C++, a comment starts with two forward slashes ( // ) and ends with the end of that line. A comment can start at the beginning of the line or on a line following the program statement. This form of giving comments is particularly useful for the short line comments. If a comment continues on more than a line, the two forward slashes should be given on every line.
The C style of giving a comment is also available in C++. This style ( /*.*/) is particularly useful when the comment spans more than a line. e.g. void main() { /* this is a good old style of giving a comment. It can be continued to next line and has to be ended with */
clrscr();
init()_; : : }
This declaration of variables in the C language is allowed only in the beginning of their block, prior to executable program statements. In C++ declaration of variables can be interspersed with executable program statements. The scope id variables, however, remains the same the block in which they are declared. e.g. void main()
62 { int x = 10;
int y = 0;
// variable declared
Although, a deviation from the old style of declaring all variables in the beginning of the block, this does save some amount of memory, i.e., a variable is not given a memory until the declaration statement. Also, since a variable can be declared just before using it is suppose to give a better control over variables.
Global variables are defined outside any functions and thus can be used by all the functions defined thereafter. However, if a global variable is declared with the same name as that of a local variable of a function , the local variable is the one in the scope when the program executes that function . The C++ language provides the scope resolution operator ( :: ) to access the global variable thus overriding a local variable with the same name. This operator is prefixed to the name of the global variable . The following example shows its usage.
The output of this program will be: Just writing global prints : 20 Writing ::global prints : 10
A default argument is a value that is automatically assigned to a formal variable, if the actual argument from the function call is omitted. e.g. void drawbox( int x1=1, int y1=1, int x2=25, int y2=80, int color=7); // Prototype
void main ( void ) { drawbox(10,1,25,80,14); // parameters passed drawbox(); } // uses default arguments
void drawbox( int x1, int y1, int x2, int y2, int color) { // Body of the function }
64 Function drawbox() draws a box around the edges of the coordinates passed as parameters. If these parameters are omitted, then the default values, as given in the declaration are passed.
When a function is declared, default values must be added from right to left. In other words, a default value for a particular argument cannot be given unless all default values for the arguments to its right are given. This is quite logical, since, if there are some arguments missing in the middle then the compiler would not know as to which arguments have been specified and which should be taken as default. e.g.
void drawbox( int x1=1, int y1=1, int x2=25, int y2=80, int color = 7); // Valid
void drawbox( int x1, int y1, int x2, int y2, int color = 7); // Valid
void drawbox( int x1=1, int y1=1, int x2=25, int y2=80, int color ); // Not Valid
Default arguments are useful when the arguments almost have the same value. They are also useful to increase the number of arguments to a function in an already working program. In this case, using default arguments mean that existing function calls need not be modified, whereas, new function calls pass more number of arguments.
The C language has defined library functions- malloc() and free() for dynamic allocation and de-allocation of memory. C++ provides yet another approach to allocate blocks of memory the new operator. This operator allocates memory for a given size and returns a pointer to its starting point. C++ also provides delete, to release the memory allocated by new. The pointer returned by the new operator need not be typecasted.
65 e.g. char arr[100]; // Compile_time allocation of an array char *arr; // Character pointer
In the above example, new returns a pointer to a block of size bytes. It is synonymous with declaring a character array. However, declaring an array is an example of static binding the array is built at the compile-time. This array remains in existence right from the beginning of the program to its end, even if not in use. Whereas , the array declared by new operator can be allocated memory only when required and can be released when over with, using the delete operator. This is an example of dynamic binding.
It is possible that the system may not have enough memory available to satisfy a request by the new operator. In this case, new returns a null pointer. Thus, the returned value from the new should always be checked before performing any operations on it, which otherwise may lead to an adverent system crashes.
Once the new operator allocates memory, it can be accessed using the indirection operator (*) or the array subscripts.
// A block of 5 doubles
char *cp;
66
ptr = new char [strlen(str)+1]; // Character array if( ptr != 0) { strcpy(ptr,str); : : } else { printf( not enough memory ); }
date *dp = new date; // This allocated a new structure // to the pointer variable dp printf( enter day ); scanf(%d,&dp->dd); : } NOTE : The cin and cout objects In C++ the keyword cin with the extraction operator >> is used to accept the input and the keyword cout with the insertion operator << is used to display string literals and values, in place of scanf() and printf() functions of C. However, here we do not need any type specifiers like %d , %f etc., to denote the variables type. cin and cout can be used in context with any basic data types.
67 e.g. int ivar; float fvar; char cvar; char name[10]; cin >> ivar ; cin >> fvar >> cvar >> name; cout<< ivar = << ivar << endl; cout<< fvar = <<fvar << cvar= << cvar << name;
It is clear in the above example, how we can accept and display variables. We can also concatenate various variables, using the insertion operator and extraction operator. While displaying, if you write some text in the quotes they are displayed as it is. Another manipulator endl can be used to end the line and get the cursor back to next line. We will study about these in detail in later sessions.
This session introduced the concept of Object-Oriented Programming. OOP improves upon the deficiencies of the traditional structured programming. Data abstraction, encapsulation and data hiding are some of the buzzwords of OOP. OOP, as seen, can be implemented in many languages. C++ is the most widely amongst them. It is based on the C language and is thus a complete superset of the original C language. Some additional features of C++ language were covered in this session. The actual implementation features are covered from next session onwards.
EXERCISES : 1. WAP to sort an array of strings. Use new and delete operators. 2. WAP to write a program to find the factorial of a number using recursion. If we do not accept the number from the user to find the factorial, then always print the factorial of 4. Use default argument methods.
68
SESSION 4
Object-oriented programming (OOP) is a conceptual approach to design programs. This session will continue our discussion on OOP and C++. Some of the features of OOP are Data encapsulation and data hiding, and these possible because of data abstraction. All these features have one thing in common the vehicle to implement them. This vehicle is class. It
69 is a new data type similar to structures. Since, the structure data type is a stepping-stone to this singularly important concept of C++, let us take another look at it.
4.1.
STRUCTURES
A structure is a user-defined data type, which may contain different data types as its members. Creating a structure is a two-part process. First, a structure template is defined. This template gives a lot of information to the compiler. For instance, How it is stored in the memory? How many member variables are there in this composite data element? What are the types of its member variables? For data types like int, float, double, char this information is built into the compiler. But for the user-defined data types like structures, it has to be provided to the compiler. This is given in the definition of the structure template. This template creates a new data type. Variables of this new data type can then be declared and used like basic data types. e.g.
struct Student { int Rollno; char Name[15]; int Marks[6]; float percent; };
The above example depicts structure called students with enclosed data variables. The keyword struct is used to define a structure template. Student is a name or tag. The variables of this type can be declared as follows : struct Student s1,s2; [or ] In C++ you can even omit the struct tag and declare the variables as, Student s1 = ( 100, Sanket , 20,10,30,40,50,60, 35.0}; Student s2,jack; Student *sptr = &s1;
70 Student s[100];
In C++ , functions also can be declared in a structure template. These functions are called as member functions. e.g. struct Student { int Rollno; char Name[15]; int Marks[6]; float percent;
There are three functions in the template given above. These functions have been incorporated in the structure definition to make it a fundamental unit. The building together of data and the functions that operate on that data is called as data encapsulation. But the problem is that the member variables of this structure can be accessed by functions other than the ones declared in the structure template. This direct access of member variables of a structure by functions outside the structure is not what OOP has to offer. The actual OOP methodology in this regard is implemented in C++ not by the structure data type, but by the data type class. The data type class is just like structures, but with a difference. It also has some access specifier, which controls the access to member variables of a class .
4.2.
INTRODUCTION TO CLASSES
Object-oriented programming (OOP) is a conceptual approach to design programs. It can be implemented in many languages, whether they directly support OOP concepts or not. The C language also can be used to implement many of the object-oriented principles. However, C++ supports the object-oriented features directly. All these features like Data
71 abstraction, Data encapsulation, Information hiding etc have one thing in common the vehicle that is used to implement them. The vehicle is class.
Class is a user defined data type just like structures, but with a difference. It also has three sections namely private, public and protected. Using these, access to member variables of a class can be strictly controlled.
4.3.
CLASS DEFINITION
The following is the general format of defining a class template: class tag_name { public : type member_variable_name; : type member_function_name(); : private: type member_variable_name; : type member_function_name(); : }; // Optional // Must
The keyword class is used to define a class template. The private and public sections of a class are given by the keywords private and public respectively. They determine the accessibility of the members. All the variables declared in the class, whether in the private or the public section, are the members of the class. Since the class scope is private by default, you can also omit the keyword private. In such cases you must declare the variables before public, as writing public overrides the private scope of the class. e.g. class tag_name {
72 type member_variable_name; : type member_function_name(); // private : public : type member_variable_name; : type member_function_name(); : }; // Must // private
The variables and functions from the public section are accessible to any function of the program. However, a program can access the private members of a class only by using the public member functions of the class. This insulation of data members from direct access in a program is called information hiding. e.g. class player { public : void getstats(void); void showstats(void); int no_player;
private : char name[40]; int age; int runs; int tests; float average; float calcaverage(void); };
73 The above example models a cricket player. The variables in the private section name, age, runs, highest, tests, and average can be accessed only by member functions of the class calcaverage(), getstats() and showstats(). The functions in the public section getstats() and showstats() can be called from the program directly , but function calcaverage() can be called only from the member functions of the class getstats() and showstats().
With information hiding one need not know how actually the data is represented or functions implemented. The program need not know about the changes in the private data and functions. The interface(public) functions take care of this. The OOP methodology is to hide the implementation specific details, thus reducing the complexities involved.
4.4.
language. Once a class is declared, an object of that type can be defined. An object is said to be a specific instance of a class just like Maruti car is an instance of a vehicle or pigeon is the instance of a bird. Once a class has been defined several objects of that type can be declared. For instance, an object of the class defined above can be declared with the following statement:
player Sachin, Dravid, Mohan ; [Or] class player Sachin , Dravid, Mohan ;
where Sachin and Dravid are two objects of the class player. Both the objects have their own set of member variables. Once the object is declared, its public members can be accessed using the dot operator with the name of the object. We can also use the variable no_player in the public section with a dot operator in functions other than the functions declared in the public section of the class. e.g. Sachin.getstats(); Dravid.showstats(); Mohan.no_player = 10;
74
4.5.
Class members can either be declared in public,protected or in the private sections of the class. But as one of the features of OOP is to prevent data from unrestricted access, the data members of the class are normally declared in the private section. The member functions that form the interface between the object and the program are declared in public section (otherwise these functions can not be called from the program ). The member functions which may have been broken down further or those, which do not form a part of the interface, are declared in the private section of the class. By default all the members of the class are private. The third access specifier protected that is not used in the above example, pertains to the member functions of some new class that will be inherited from the base class. As far as non-member functions are concerned, private and protected are one and the same.
4.6.
class template must define its return value as well as the list of its arguments. You can declare or define the function in the class specifier itself, in which case it is just like a normal function. But since the functions within the class specifier is considered inline by the compiler we should not define large functions and functions with control structures, iterative statements etc should not be written inside the class specifier. However, the definition of a member function differs from that of an ordinary function if written outside the class specifier. The header of a member function uses the scope operator (::) to specify the class to which it belongs. The syntax is: return_type class_name :: function_name (parameter list) : } e.g. void player :: getstats (void) { : {
75 }
This notation indicates that the functions getstats () and showstats() belong to the class player.
4.7.
When an object is passed by content , the compiler creates another object as a formal variable in the called function and copies all the data members from the actual variable to it. Objects can also be passed by address, which will be discussed later. e.g. class check { public : check add(check); void get() { cin >> a; } void put() { cout << a; } private : int a;
};
76
check check :: add ( check c2) { check temp; temp.a = a + c2.a; return ( temp); }
The above example creates three objects of class check. It adds the member variable of two classes, the invoking class c1 and the object that is passed to the function , c2 and returns the result to another object c3.
You can also notice that in the class add() the variable of the object c1 is just referred as a where as the member of the object passed .i.e. c2 is referred as c2.a . This is because the member function will be pointed by the pointer named this in the compiler where as what we pass should be accessed by the extraction operator .. we may pass more than one object and also normal variable. we can return an object or a normal variable from the function. We have made use of a temporary object in the function add() in order to facilitate return of the object.
4.8.
POINTERS TO OBJECTS
Passing and returning of objects is, however, not very efficient since it involves
passing and returning a copy of the data members. This problem can be eliminated using
77 pointers. Like other variables, objects of class can also have pointers. Declaring a pointer to an object of a particular class is same as declaring a pointer to a variable of any other data type. A pointer variable containing the address of an object is said to be pointing to that object. Pointers to objects can be used to make a call by address or for dynamic memory allocation. Just like structure pointer, a pointer to an object also uses the arrow operator to access its members. Like pointers to other data types, a pointer to an object also has only one word of memory. It has to be made to point to an already existing object or allocated memory using the keyword new. e.g. string str; string *sp; // Object // Pointer to an object
sp = &str;
sp = new string
A simple example of using pointers to objects is given below. class player { public : void getstats(void); void showstats(void); private : char name[40]; int age; int runs; int tests; float average; float calcaverage(void); };
void main() {
78 player Sachin;
4.9.
ARRAY OF OBJECTS
As seen earlier, a class is a template, which can contain data items as well as member functions to operate on the data items. Several objects of the class can also be declared and used. Also, an array of objects can be declared and used just like an array of any other data type. An example will demonstrate the use of array of objects. e.g. class student { public : void getdetails(); void printdetails(); private : int rollno; char name[25]; int marks[6]; float percent; }; void student :: getdetails() { int ctr,total;
79 cout << enter rollno; cin >> rollno ; cout << enter name; cin >> name; cout << enter 6 marks ; for( ctr = 1 ;ctr <= 6 ; ctr++ ) { cin >> marks[ctr]; total = total + marks[ctr]; } percent = total / 6; } void student :: printdetails () { cout << rollno << name << percent ; } void main() { student records[50]; int x=0; cout << How many students ; cin >> x;
for ( int i =1; i<= x; i++) { records[i].getdeatils(); } for ( int i =1; i<= x; i++) { records[i].printdeatils(); } }
80
As can be seen above, an array of objects is declared just like any other array. Members of the class are accessed, using the array name qualified by a subscript. The statement, records[y].printdetails(); invokes the member funs printdetails() for the object given by the subscript y. For different values of subscript, it invokes the same member function, but for different objects.
But this poses a big problem for the compiler: How can any given member function of a class knows which instance it is supposed to be working on ? In other words, up to now in a class member function you have simply been referring to the members directly without regard to the fact that when the instantiations occur each data member will have a different memory address. In other words, all the compiler knows is the offset of each data member from the start of the class.
The solution to this dilemma is that, in point of fact, each member function does have access to a pointer variable that points to the instance being manipulated. Fortunately this pointer is supplied to each member function automatically when the function is called, so that this burden is not placed upon the programmer.
This pointer variable has a special name this (reserved word). Even though the this pointer is implicitly declared, you always have access to it and may use the variable name anywhere you seem appropriate.
81
e.g. class try_this { public : void print(); try_this add(int); private : int ivar;
}; void print() { cout << ivar; cout << this -> ivar ; } The function print refers to the member variable ivar directly. Also, an explicit reference is made using the this pointer. This special pointer is generally used to return the object, which invoked the member function. For example,
t2 = t1.add(3); t2.print(); }
82 }
In the above example if ivar for t1 is 10 and value in v is 2, then the function add() adds them and ivar for t1 becomes 12 . We want to store this in another object t2, which can be done by returning the object t1 using *this to t2. The result of t2.print() now will be 12.
Dereferencing the Pointer this Sometimes a member function needs to make a copy of the invoking instance so that it can modify the copy without affecting the original instance. This can be done as follows :
In OOP emphasis is on how the program represents data. It is a design concept with less emphasis on operational aspects of the program. The primary concepts of OOP is implemented using class and objects. A class contains data members as well as function members. The access specifiers control the access of data members. Only the public members of the class can access the data members declared in private section. Once class has been defined, many objects of that class can be declared. Data members of different objects of the same class occupy different memory area but function members of different objects of the same class share the same set of functions. This is possible because of the internal pointer *this which keeps track of which function is invoked by which object.
EXERCISES: 1. Define a class to model a banking system. The function members should allow initializing the data members, a query to facilitate for account and a facility to deposit and withdraw from the account. WAP to implement the same. 2. Create a class called Time that has separate int member data for hours, minutes and seconds. Write functions for accepting time and displaying time.
83
SESSION 5
5.1
CONSTRUCTORS
By definition, a constructor function of some class is a member function that automatically gets executed whenever an instance of the class to which the constructor belongs comes into existence. The execution of such a function guarantees that the instance variables of the class will be initialized properly.
84 A constructor function is unique from all other functions in a class because it is not called using some instance of the class, but is invoked whenever we create an object of that class.
A constructor may be overloaded to accommodate many different forms of initialization for instances of the class. i.e. for a single class many constructors can be written with different argument lists .
5.2
e.g. class boxclass { public : boxclass ( int x1, int y1, int x2, int y2); void disp(void); private : int x1, y1; int x2, y2 ; }; boxclass::boxclass(int ax1,int ay1, int ax2, int ay2) { x1 = ax1 ; y1 = ay1 ; x2 = ax2 ; y2 = ay2 ;
85 }
5.2.1
Using the Constructor There are basically three ways of creating and initializing the object. The first way to
call the constructor is explicitly as : boxclass bigbox = boxclass ( 1,1,25,79); This statement creates an object with the name bigbox and initializes the data members with the parameters passed to the constructor function. The above object can also be created with an implicit call to the constructor : boxclass bigbox(1,1,25,79); Both the statements given above are equivalent. Yet, another way of creating and initializing an object is by direct assignment of the data item to the object name. But, this approach works if there is only one data item in the class. This is obvious because we cannot assign more than one value at a time to a variable. e.g. class counter { public : counter ( int c) // constructor. { count = c; }; private : int count; }; we can now create an object as, counter cnt = 0;
In the above example , object cnt is initialized by a value zero at the time of declaration. This value is actually assigned to its data member count. This is the third way to initialize an objects data member. Thus, all the following statements to initialize the objects of the class counter are equivalent: counter c1(20);
Once the constructor for a class has been declared and defined, the program must use it. In other words, objects cannot be declaring without initializing them. For instance, in the above example, an object of class counter has to be declared and initialized in one of the three ways given above. Uninitialized objects like : counter cx,cy; are not allowed. However, sometimes the data members of an object may not require initialization. May be they have to be accepted from the user later. C++ is not so rigid in its approach. It has defined means of circumventing such a problem. The key to defining an uninitialized object is to have a constructor with default arguments. e.g. class counter { public : counter ( int c = 0) // Constructor. { count = c; }; private : int count;
};
In the example above, if the actual argument is not given, then the value of the formal variable c in the constructor defaults to zero. Thus, declaration of objects of this class can be uninitialized as well.
87 counter c4,c5;
A constructor can be used to generate temporary instances as well , as given below : counter create( int cc) { return counter(c); }
An array of objects can be initialized at the time of declaration. A constructor has to be provided foe the same. The example given below uses the class employee to illustrate this. class employee { public : employee(char *n, int a, double s); private: char name[40]; int age; double salary; }; employee :: employee (char *n , int a, double s) { strcpy( name, n ); age = a; salary = s; } void main() { employee E[3] = { employee(sanat,25,9600), employee(sanket,35,12600), employee(priya,28,21600) }; }
88
5.3.
DESTRUCTORS
A destructor function gets executed whenever an instance of the class to which it belongs goes out of existence. The primary usage of a destructor function is to release memory space that the instance currently has reserved.
Syntax rules for writing a destructor function Its name is the same as that of the class to which it belongs, except that the first character of the name is the symbol tilde ( ~ ). It is declared with no return type ( not even void ) since it cannot ever return a value. It cannot be static, const or volatile. It takes no input arguments , and therefore cannot be overloaded. It should have public access in class declaration.
Generally the destructor cannot be called explicitly (directly) from the program. The compiler generates a class to destructor when the object expires. Class destructor is normally used to clean up the mess from an object. Class destructors become extremely necessary when class constructor use the new operator, otherwise it can be given as an empty function. However, the destructor function may be called explicitly allowing you to release the memory not required and allocate this memory to new resources, in Borland C++ version 3.1.
5.4.
supplies them for you. These functions have public access and essentially do nothing useful. If you were to write these functions yourselves, this would look like: class employee { public : employee() { }
89 ~employee(); { } };
Consider an example , to model a user-defined data type for strings. The object simulates a character array ( string ) using a character pointer and an integer variable for its actual size, which can be determined at its run-time. The object doesnt use a character array , since it may impose a limit on the number of characters that can be stored. e.g. # include <iostream.h > # include < string.h>
};
void main(void) { string s1( Hello student \n); string s2 = Welcome to C++ \n;
90 string s3 = string ( its fun \n); s1.putstr(); s2.putstr(); s3.putstr(); } string::string(char *s) { size = strlen(s); cptr = new char[size + 1]; strcpy(cptr,s); } string::~string() { delete cptr; }
The class defined in the above example contains a character pointer, which allocates memory at runtime, after determining the actual size required. This programme demonstrates the use of class along with the constructor and destructor to create a user defined data type String. The constructor function contains a default argument of null character, which will be assigned to the variable cptr in the absence of an actual parameter. The destructor uses the delete operator to release the memory allocated by the constructor .
A class can contain data members as well as function members. A member function is the only way to access the private section members of a class. For initializing the data members of the class, we may write a function just like any other function. But then, we have to invoke this function explicitly. C++ provides a special function called constructor. This function is called automatically when we create an object. It contains code to initialize the
91 member variables using the formal parameters if they are passed via objects. If we write such a function then we must pass parameters each time. To evade this problem we can write many constructors to give some flexibility to create objects with different parameter lists. We can also use default arguments in the constructor.
C++ also provides a special member function called destructor, which is called automatically when an object expires as per the scope rules. Both constructor and destructors do not have return types, not even void. The destructor cannot have any parameters as well.
EXERCISES 1. Create a class called Time that has a separate data members for day, month and year. A constructor should be used to initialize these members. Then write a function to add these dates and store the result in a third object and display it. 2. WAP to add co-ordinates of the plane. The class contains x and y co-ordinates. Create three objects. Use a constructor to pass one pair of co-ordinates and a function to accept the second pair. Add these variables of two objects and store the result in the third.
SESSION 6
OPERATOR OVERLOADING
92 During this session you will learn about: Introduction to Operator Overloading. Operator Overloading Fundamentals. Implementing the operator functions. Rules for overloading the operators. Pointer oddities (assignment) and Operator Overloading. Copy Constructor (to solve the problem of initialization). Conversion functions.
All computer languages have built in types like integers, real numbers, characters and so on. Some languages allow us to create our own data types like dates, complex numbers, co-ordinates of a point. Operations like addition, comparisons can be done only on basic data types and not on derived (user-defined) data types. If we want to operate on them we must write functions like compare (), add (). e.g. if (compare (v1, v2) = = 0) : : where v1 and v2 are variables of the new data type and compare () is a function that will contain actual comparison instructions for comparing their member variables. However, the concept of Operator Overloading, in C++, allows a statement like
if (v1 = = v2) : : where the operation of comparing them is defined in a member function and associated with comparison operator(==).
The ability to create new data types, on which direct operations can be performed is called as extensibility and the ability to associate an existing operator with a member function and use it with the objects of its class, as its operands, is called as Operator Overloading.
93 Operator Overloading is one form of Polymorphism ,an important feature of objectoriented programming .Polymorphism means one thing having many forms, i.e. here an operator can be overloaded to perform different operations on different data types on different contexts. Operator Overloading is also called operational polymorphism. Another form of polymorphism is function overloading.
6.1.
discreetly. The asterisk (*) is used as multiplication operator as well as indirection (pointer) operator. The ampersand (&) is used as address operator and also as the bitwise logical AND operator. The compiler decides what operation is to be performed by the context in which the operator is used.
Thus, the C language has been using Operator Overloading internally. Now, C++ has made this facility public. C++ can overload existing operators with some other operations. If the operator is not used in the context as defined by the language, then the overloaded operation, if defined will be carried out.
If x, y and z are integer variables, then the compiler knows the operation to be performed. But, if they are objects of some class, then the compiler will carry out the instructions, which will be written for that operator in the class.
6.2.
Where op is the symbol for the operator being overloaded. Op has to be a valid C++ operator, a new symbol cannot be used. e.g.
94 Let us consider an example where we overload unary arithmetic operator ++. class Counter { public : Counter(); void operator++(void); private : int Count; }; Counter::Counter() { Count = 0; }
c1++; ++c1; }
In main() the increment operator is applied to a specific object. The function itself does not take any arguments. It increments the data member Count. Similarly, to decrement the Counter object can also be coded in the class definition as:
In the above example , the compiler checks if the operator is overloaded and if an operator function is found in the class description of the object, then the statement to increment gets converted, by the compiler, to the following:
c1.operator++(); This is just like a normal function call qualified by the objects name. It has some special characters ( ++) in it. Once this conversion takes place, the compiler treats it just like any other member function from the class. Hence, it can be seen that such a facility is not a very big overhead on the compiler.
However, the operator function in the above example has a potential glitch. On overloading , it does not work exactly like it does for the basic data types. With the increment and decrement operators overloaded, the operator function is executed first, regardless of whether the operator is postfix or prefix.
If we want to assign values to another object in main() we have to return values to the calling function.
96
c1 = c2 ++; }
In this example , the operator function creates a new object temp of the class Counter, assigns the incremented value of Count to the data member of temp and returns the new object. This object is returned to main(). We can do this in another way by creating a nameless temporary object and return it.
class Counter { public : Counter(); // CONSTRUCTOR WITHOUT ARGUMENTS Counter( int c); // CONSTRUCTOR WITH 1 ARGUMENT Counter operator++(void);
};
97 Count = c; }
One change we can see is a constructor with one argument. No new temporary object is explicitly created. However return statement creates an unnamed temporary object of the class Counter initializes it with the value in Count and returns the newly created object. Hence one argument constructor is required.
Yet another way of returning an object from the member function is by using the this pointer. This special pointer points to the object, which invokes the function. The constructor with one argument is not required in this approach.
Consider a class COMPLEX for Complex numbers. It will have a real and an imaginary member variable. Here we can see binary operator overloaded and also how to return values from the functions.
Suppose that C1, C2 and C3 are objects of this class. Symbolically addition can be carried out as
C3 = C1 + C2;
The actual instructions of the operator are written in a special member function. e.g. COMPLEX COMPLEX :: operator+( COMPLEX C2) { COMPLEX temp; temp.real = real + C2.real;
The above example shows how Operator Overloading is implemented. It overloads + operator to perform addition on objects of COMPLEX class. Here we have overloaded a binary operator(+).
6.3.
This summarizes the most important points you need to know in order to do operator function overloading. The only operators you may overload are the ones from the C++ list and not all of those are available. You cannot arbitrarily choose a new symbol (such as @) and attempt to overload it. Start by declaring a function in the normal function fashion, but for the function name use the expression:
99 Operator op Where op is the operator to be overloaded. You may leave one or more spaces before op. The pre-defined precedence rules cannot be changed. i.e. you cannot, for example, make binary + have higher precedence than binary *. In addition, you cannot change the associativity of the operators. -> ! & * + ++ ++ The unary operators that you may overload are: indirect member operator not address dereference plus minus prefix increment postfix increment (possible in AT & T version 2.1) --postfix decrement prefix decrement (possible in AT & T version 2.1) ~ ones complement The binary operators that you may overload are: (), [], new, delete, *, / , %, + , - , <<,>>, <, <=, >, >=, ==,! =, &, ^, |, &&, ||, =, *=, /=, %=, +=, -, =, <<=, >>=, &=,! =, ^=, ','(Comma). The operators that can not be overloaded are: . .* :: ?: direct member direct pointer to member scope resolution ternary
No default arguments are allowed in overloaded operator functions. As with the predefined operators, an overloaded operator may be unary or binary. If it is normally unary, then it cannot be defined to be binary and vice versa.
100 However, if an operator can be both unary and binary, then it can be overloaded either way or both. The operator function for a class may be either a non-static member or global friend function. A non-static member function automatically has one argument implicitly defined, namely the address of the invoking instance (as specified by the pointer variable this). Since a friend function has no this pointer, it needs to have all its arguments explicitly defined). At least one of the arguments to the overloaded function explicit or implicit must be an instance of the class to which the operator belongs. Consider an example, which depicts overloading of += (Compound assignment), <, >, == (Equality),!=, + (Concatenation) using String class.
class String { public : String (); String ( char str [] ); void putstr(); String operator + (String); String operator += (String s2); int operator < (String s2); int operator > (String s2); int operator == (String s2); int operator != (String s2); private : char s[100]; };
String::String () {
};
101 String:: String( char str [] ) // CONSTRUCTOR WITH { strcpy(s,str) }; / ONE ARGUMENT
int String::operator < (String s2) { return (strcmp (s, s2.s ) < 0); } int String::operator > (String s2) { return (strcmp (s, s2.s ) > 0);
102 } int String::operator == (String s2) { return (strcmp (s, s2.s ) == 0); } int String::operator != (String s2) { return (strcmp (s, s2.s ) != 0); }
void main() { String s1 = welcome ; String s2 = to the world of c++; String s3;
s3 = s1 + s2;
cout << endl << s3 = ; s3.putstr(); String s4; cout <<endl<< *********************; s4 = s1 + = s2; cout << endl << s4 = ; s4.putstr(); String s5 = Azzzz ;
103 String s6 = Apple ; if( s5 < s6 ) { s5.putstr(); cout << < ; s6.putstr(); } else if( s5 > s6 ) { s5.putstr(); cout << > ; s6.putstr(); } else { s5.putstr(); cout << = ; s6.putstr(); } else { s5.putstr(); cout << < ; s6.putstr(); } } Output: S1 = welcome S2 = to the world of C++ S3 = welcome to the world of c++ ************************** S4 = welcome to the world of c++ if( s5 != s6 ) if( s5 == s6 )
104
6.4.
Consider an example , where the data members contain pointers and have been allocated memory using the operator new. In this case using an assignment operator to assign one object to another will result in the pointer variable being copied rather than the contents at the address. The following example explains this problem.
class String { public : String (char *s = ) { size = strlen(s); cptr = new char [size + 1]; strcpy(cptr,s); }; ~String() { delete cptr; // CONSTRUCTOR
} void putstr() { cout << cptr ; }; private : char *cptr; int size; }; void main() // FUNCTION TO PRINT STRING
105 { String s1(hello students ); String s2; s2 = s1; // Assignment s1.putstr(); s2.putstr(); }
The constructor function allocates a string and copies the contents of its formal variable in it. The assignment operator in main() assigns the object s1 to s2. the data members cptr and size , of object s1, gets assigned to s2. The output of the program is :
Why does the program give a null pointer assignment message ? After the program is over, the destructor function is called automatically, which releases the memory allocated by new to the data member cptr. But, after the assignment, the data member cptr of both the objects point to the same location in the memory. Thus, the delete operator called for the first object releases the memory and for the second call attempts to release the same memory location again, resulting in the error message.
The solution to this problem is to define an operator function for the assignment operator . It can be done as follows: String operator = (String s2) { delete cptr; size = strlen ( s2.cptr ); cptr = new char [size + 1];
On including this member function in the class definition of the above example , the program outputs the following : hello students hello students Hence, the operator function for assignment of an object to another of the same class removes the quirks associated with pointers as data members.
6.5.
COPY CONSTRUCTOR
Similar problems occur at the time of initialization of an object with another of the same class . Although initialization and assignment both assign values, initialization takes place only once when the object is created, whereas assignment can occur whenever desired in the program. Therefore, they differ conceptually.
The first statement declares an object s1 and passes a string as an argument to its constructor. The data members of s1 are initialized using the String. The second statement declares another object s2 and contains an object as its argument. Since there is no constructor having an object as its formal argument, the compiler itself initializes the second objects data members with those of the formal object . And since one of the members is a pointer, the problem of assignment arises here too. Overloading the assignment operator solves the problem of assignment. Defining a constructor function that takes object as its argument solves the problem of initialization. This constructor is also called copy constructor . It is called in three contexts :
When one object of a class is initialized to another of the same class. When an object is passed by value as an argument to a function.
All these situations result in a copy of one object to another. Hence , a copy constructor is very important if the class contains pointer as data member . A copy constructor for the above example is given below : String (const String &s2) { size = s2.size; cptr = new char[size + 1]; strcpy(cptr,s2.cptr); }
It is just like a normal constructor but the argument here is an object of the class . obviously this existing instance should never be modified by the copy constructor function , so the keyword const should be used in the function signature.
6.6.
CONVERSION FUNCTIONS
Conversion functions are member functions used for the following purposes: 1. Conversion of object to basic data type. 2. Conversion of basic data type to object. 3. Conversion of objects of different classes.
Conversions of one basic data type to another are automatically done by the compiler using its own built-in routines (implicit) and it can be forced using built-in conversion routines (explicit). However, since the compiler does not know anything about user-defined types (classes), the program has to define conversion functions. e.g. int i = 2, j =19; float f = 3.5;
i = f; // i gets the value 3 , implicit conversion f = (float) j; // f gets 19.00, explicit conversion
108 6.6.1. Conversion from Basic to User-Defined variable Consider the following example. class Distance { public : Distance(void) // Constructor with no { feet = 0; inches = 0.0; }; Distance(float metres) { float f; f = 3.28 * metres; feet = int(f); // Constructor with // one argument // argument
void display(void) { cout << Feet = << feet <<,; cout << Inches = << inches << endl; }; private : int feet; float inches; }; void main (void) { Distance d1 = 1.25; // Uses 2nd constructor Distance d2; float m; d2 = 2.0 ; // Uses 2nd constructor // Uses 1st constructor
109
cout << 1.25 metres is : << d1.showdist() ; cout << 2.0 metres is : << d2.showdist(); } Output : 1.25 metres is :FEET = 4 , INCHES = 1.199999 2.0 metres is :FEET = 6 , INCHES = 6.719999
The above program converts distance in metres ( basic data type) into feet and inches ( members of an object of class Distance ).
The declaration of first object d1 uses the second constructor and conversion takes place. However, when the statement encountered is d2 = 2.0; The compiler first checks for an operator function for the assignment operator. If the assignment operator is not overloaded, then it uses the constructor to do the conversion.
6.6.2. Conversion from User-Defined to Basic data type The following program uses the program in the previous section to convert the Distance into metres(float).
class Distance { public : Distance(void) // Constructor with no { feet = 0; inches = 0.0; }; // argument
Distance(float metres) {
f = 3.28 * metres; // one argument feet = int(f); inches = 12 * ( f feet); //conversion // Also used for };
// Conversion function
void display(void) { cout << Feet = << feet <<,; cout << Inches = << inches << endl; }; private : int feet; float inches; }; void main (void) { Distance d1 = 1.25; // Uses 2nd constructor Distance d2; float m; d2 = 2.0 ; // Uses 2nd constructor // Uses 1st constructor
cout << 1.25 metres is : << d1.showdist (); cout << 2.0 metres is : << d2.showdist (); cout << CONVERTED BACK TO METRES ; m = float ( d1 ); // Calls function explicitly.
cout << d2 = << m; } Output: 1.25 metres is :FEET = 4 ,INCHES = 1.199999 2.0 metres is :FEET = 6 ,INCHES = 6.719999
Actually, this conversion function is nothing but overloading the typecast operator float(). The conversion is achieved explicitly and implicitly. m = float (d1); is forced where as , in the second assignment statement m = d2; first the compiler checks for an operator function for assignment ( = ) operator and if not found it uses the conversion function. The conversion function must not define a return type nor should it have any arguments.
6.6.3. Conversion Between Objects of Different Classes Since the compiler does not know anything about the user-defined type, the conversion instructions are to be specified in a function. The function can be a member function of the source class or a member function of the destination class. We will consider both the cases.
Consider a class DistFeet which stores distance in terms of feet and inches and has a constructor to receive these. The second class DistMetres store distance in metres and has a constructor to receive the member.
112 class DistFeet { public : DistFeet(void) { feet = 0; inches = 0.0; }; DistFeet(int ft,float in) { feet = ft; inches = in }; // Constructor with no // argument
void ShowFeet(void) { cout << Feet = << feet << ,; cout << Inches = << inches << endl; }; private : int feet; float inches;
113 { metres = m ; } void ShowMetres(void) { cout << Metres = << metres << endl; }; // constructor 2.
float ffeet, inches; int ifeet; ffeet = 3.28 * metres; ifeet = int (ffeet); inches = 12 * (ffeet ifeet); return(DistFeet(inches,ifeet); };
private: float metres; }; void main (void) { DistMetres dm1 = 1.0; DistFeet df1;
114
In the above example, DistMetres contains a conversion function to convert the distance from DistMetres ( source class), to DistFeet ( destination class). The statement to convert one object to another df1 = dm1; calls the conversion function implicitly. It could also have been called explicitly as df1 = DistFeet(dm1);
6.6.3.2.Conversion function in the Destination Class class DistMetres { public: DistMetres(void) { metres = 0 ; // Constructor 1. }
115 private: float metres; }; class DistFeet { public : DistFeet(void) // Constructor1 with no { feet = 0; inches = 0.0; }; // argument
void ShowFeet(void) { cout << Feet = << feet << endl; cout << Inches = << inches << endl; };
};
void main (void) { DistMetres dm1 = 1.0; // Uses 2nd constructor // class DistMetres DistFeet df1; // Uses 1st constructor // class DistFeet
df1 = dm1 ;
dm1.ShowMetres(); df1.ShowFeet(); }
This program works same as previous function. Here constructor is written in the destination class. Also, we can see a new function GetMetres() . The function returns the data member metres of the invoking object. The function is required because the constructor is defined in the DistFeet class and since metres is a private member of the DistMetres class, it cannot be accessed directly in the constructor function in the DistFeet class.
Since you can use any of the above methods, it is strictly a matter of choice which method you choose to implement.
117
Basic to class
Constructor
Not Allowed
Class to Basic
Not Allowed
Conversion Function
Class to Class
Constructor
Conversion Function
This session covered yet another concept of OOP Polymorphism. It means one thing having many forms. It is very powerful, yet, simple concept which gives the C++ language a facility to redefine itself into a new language. There are two types of Polymorphismoperational and functional. This session covered operational polymorphism also called as operator overloading. We will see function overloading in future.
EXERCISES 1. WAP to add 2 complex number using OOT(Operator Overloading techniques). 2. WAP to add 2 times using OOT and display the resultant time in watch format. 3. WAP to add, subtract and multiply 2 matrices using OOT. Sort an array of objects. Each object has a string as a member variable. Overload >= or <= operators to compare the two strings. { make use of constructors and destructors whenever possible }
118
4. WAP to create a class called DATE . Accept 2 valid dates in the form of dd/mm/yyyy. Implement the following by overloading the operators and + . Display the result after every operation.
a) no_of_dasy = d1 d2, where d1 and d2 are DATE objects; d1 > = d2 ; and no_of_days is an integer. b) d1 = d1 + no_of_days - where d1 is a DATE object and no_of_days is an integer.
5. Modify the matrix program (program 3) slightly. Overload == operator to compare 2 matrices to be added or subtracted. i.e., whether the column of first and the row of second matrix are same or not. if(m1==m2) { m3=m1+m2; m4=m1-m2; } else display error;
SESSION 7
119 Function overloading. Need to overload functions. Precautions to be taken while overloading functions. Call by reference. Passing and returning reference variables. Why using references is a must in some occasions. Reference oddities.
The previous session covered operator overloading. Operator overloading is nothing but defining a member function, for a particular operator, which will be invoked when the operator is used with objects of that class. This is also called operational polymorphism. The second form of polymorphism in C++ is functional polymorphism also called function overloading.
7.1.
FUNCTION OVERLOADING
Function overloading is a form of polymorphism. Function overloading facilitates defining one function having many forms. In other words it facilitates defining several functions with the same name, thus overloading the function names. Like in operator overloading, even here, the compiler uses context to determine which definition of an overloaded function is to be invoked.
Function overloading is used to define a set of functions that essentially, do the same thing, but use different argument lists. The argument list of the function is also called as the functions signature. You can overload the function only if their signatures are different.
The differences can be 1. In number of arguments, 2. Data types of the arguments, 3. Order of arguments, if the number and data types of the arguments are same. e.g. int add( int, int ); int add( int, int, int );
120 float add( float, float ); float add( int, float, int ); float add(int,int,float);
The compiler cannot distinguish if the signature is same and only return type is different. Hence, it is a must, that their signature is different. The following functions therefore raise a compilation error. e.g. float add( int, float, int ); int add( int, float, int );
Consider the following example, which raises a given number to a given power and returns the result. long long_raise_to(int num, int power) { long lp = 1;
for( int i = 1; i <= power ; i++ ) { lp = lp * num ; } return lp; } double double_raise_to( double num, int power) { double dp = 1;
121 } void main(void) { cout<<2 raise to 8 is <<long_raise_to(2,8); cout<<2.5 raise to 4 is <<double_raise_to(2.5,4); } In C we would required to write 2 different function names as can be seen from the above example. These functions do identical tasks. However using function overloading techniques we can write the same program in C++ as shown: long raise_to(int num, int power) { long lp = 1; for( int i = 1; i <= power ; i++ ) { lp = lp * num ; } return lp; } double raise_to( double num, int power) { double dp = 1;
for( int i = 1; i <= power ; i++ ) { dp = dp * num ; } return dp; } void main(void) { cout<<2 raise to 8 is <<raise_to(2,8); cout<<2.5 raise to 4 is <<raise_to(2.5,4);
122 }
The main () function makes two calls to the function raise_to() once with 2 integers as arguments and again with an integer and a double. The compiler uses the context to determine which of the functions to invoke. Thus, first time it invokes first function and then the second one.
7.2.
functions need not be thought of, which often is a cumbersome process given that many times people run out of names. But, this facility should not be overused, lest it becomes an overhead in terms of readability and maintenance. Only those functions, which basically do the same task, on different sets of data, should be overloaded.
7.3.
CALL BY REFERENCE
Passing variables(parameters) to a function in C can be done in two ways pass by
value, also called as call by value and pass by address or also called as call by address. C++ however , gives one more way to call a function call by reference. In call by value , a copy of the actual contents of the variables is passed to the function. In this case, changes made to the formal variable , as they are called, are not reflected back in the calling function. Passing an address allows changes to be made directly to the memory, which are occupied by the actual variable.
However, a call by address requires the formal variable to be considered as pointer and thus the indirection operator has to be used with them. A reference on the other hand, is another name, for a previously defined variable. In other words, after a reference is defined for a particular variable, using its original name as well as the reference can refer to the variable. Hence, a reference is nothing but an alias.
Thus not passing a copy (call by value) saves time and is efficient. It does not have to create a temporary variable /. And not passing the address(call by address), eliminates the use
123 of pointers in the called functions. But, at the same time, the changes made to a formal variable , using its reference or alias can be reflected back in the calling function.
7.4.
C and C++ , both use the ampersand ( &) symbol as the address operator. It is also overloaded to perform binary AND operation on its operands. C++, once more overloads this operator and uses it to declare a reference variable . e.g. void main() { int num1 = 10, num2 = 200; int &ref = num1; cout << num1 = << num1 << endl; cout << num2 = << num2 << endl; cout << ref = << ref << endl; // & used only to // declare. ref = num2; cout << After change <<endl; cout << num1 = << num1 << endl; cout << num2 = << num2 << endl; cout << ref = << ref << endl; }
Output : num1 = 10 num2 = 200 ref = 10 After change num1 = 200 num2 = 200 ref = 200
124
A reference variable declared in a function should be initialized at the time of its declaration itself. Also, once a reference has been made to a variable, it cannot be changes to refer to another variable. The program given above shows the result of assigning another variable to a reference variable. the output of the program shows that assigning a variable to a reference variable, actually assigns its contents the original one. Hence , the reference to a particular variable may not be changed later in the program.
7.5.
Reference variables are particularly useful when passing to functions. The changes made in the called functions are reflected back to the calling function . The program uses the classic problem in programming, swapping the values of two variables. e.g. void val_swap(int x, int y) { int t; // Call by Value
t = *x; *x = *y; *y = t; } void val_swap(int &x, int &y) { int t; // Call by Reference
125 t = x; x = y; y = t; }
cout << Before call by value : ; cout << n1 = << n1 << n2 = << n2 << endl; val_swap( n1, n2 ); cout << After call by value : ; cout << n1 = << n1 << n2 = << n2 << endl;
cout << Before call by address : ; cout << n1 = << n1 << n2 = << n2 << endl; val_swap( &n1, &n2 ); cout << After call by address : ; cout << n1 = << n1 << n2 = << n2 << endl;
cout << Before call by reference: ; cout << n1 = << n1 << n2 = << n2 << endl; val_swap( n1, n2 ); cout << After call by value : ; cout << n1 = << n1 << n2 = << n2 << endl; } Output : Before call by value After call by value : n1 = 25 n2 = 50 : n1 = 25 n2 = 50 // x = 50, y = 25
You can see that the only difference in writing the functions in call by value and call by reference is while receiving the parameters where as in pass by address the function body has some changes, i.e. they use (*) indirection operator to manipulate the variables.
7.6.
Just as in passing the parameters by reference, returning a reference also doesnt return back a copy of the variable , instead an alias is returned. e.g. int &func(int &num) { : : return(num); } void main() { int n1,n2; : :
n1 = fn( n2); }
Notice that the function header contains an ampersand (&) before the function name. This is how a function is made to return reference variable. In this case, it takes a reference to
127 an integer as its argument and returns a reference to an integer. This facility can be very useful for returning objects and even structure variables.
7.7 .
becomes imperative. It uses the String class, which has already been covered earlier. class String { public : String (); String(char *s); ~String(); String operator=(String str); private : char *ptr; int size; }; String::String () { size = 0; cptr = 0; cout << Constructor = << cptr << endl; } //Constructor 1
//Constructor 2
128
String::~String() {
// Destructor
String::String operator=(String str) { delete cptr; size = strlen(str.cptr); cptr = new char[size+1]; strcpy(cptr,str.cptr); return(*this);
S2 = s1; } Output: Constructor = You are learning references Constructor = null Destructor = You are learning references Destructor = You are learning references Destructor = You are learning references Destructor = You are learning references
129 The example given above declares two objects s1 and s2 of class Sting. The declaration of the first object invokes the second constructor and the second object invokes the first constructor, whos argument being nothing prints null. There is just one assignment statement in the program, which assigns the contents in s1 to s2 using the operatoroverloaded function. The destructor function is called automatically by the compiler after this statement. However, it can be seen from the output that the destructor function is called four times for two objects. The assignment statement , s2 = s1; gets converted into s2.operator=(s1);
when the object is passed to the function it is stored in a temporary object and while returning with *this, the compiler creates an unnamed temporary object. Therefore the destructor function is invoked four times . the solution to this is to pass and return objects by reference. The operator function needs to be changed slightly as given below:
String &operator=(String &str) { delete cptr; size = strlen(str.cptr); cptr = new char[size+1]; strcpy(cptr,str.cptr); return(*this); }
Now the output will be : Constructor = You are learning references Constructor = null Destructor = You are learning references Destructor = You are learning references
7.8.
REFERENCE ODDITIES
130 A reference variable can refer to any integer variable, be it in an array or a member variable from structure or class. Reference variables can refer to constants as well. For instance, take a look at the program given below.
void func(int &intref) { ++intref; } void main() { long lint = 20; int sint = 10;
cout << Before incrementing << endl; cout << Small int = << sint << endl; cout << Long int = << lint << endl;
func(sint); func(lint);
cout << After incrementing << endl; cout << Small int = << sint << endl; cout << Long int = << lint << endl;
131
The program calls func() , which receives a reference to an integer, four times. First with an integer variable , which gets duly incremented, as can be seen from the output. It again calls the func() with long variable. The compiler does not give a type mismatch error but instead , it creates a temporary variable of the correct type and assigns the value of the variable lint to this variable. The func() increments the temporary variable and therefore as can be seen from the output , lint still has the same value. Similarly, the last two calls do not result in errors but temporary variables are created for them just like in the earlier case.
However, since a mismatch between the data types of the actual argument and the formal argument is shielded by these temporary reference variables, programmers prefer to use pointers, if a function has to change the value of an argument. Besides having the pointer notation also makes it obvious that the function is intended to make changes to the arguments. However, references are still used with structures , classes and objects extensively.
This session along with the previous session covered the most exciting feature of C++ polymorphism. This session also discussed yet another way of passing arguments to a function, by making a call by reference. A call by reference does not make a copy of the contents of the arguments nor does it uses the addresses; yet, it allows changes made in the called function to be reflected back in the calling function.
EXERCISES 1. WAP to create a class called as COMPLEX and implement the following by overloading the function ADD which returns a complex number. a) ADD(s1, s2) where s1 is an integer ( real ) and s2 is a COMPLEX number. b) ADD(s1, s2 )- where s1 and s2 are COMPLEX numbers.
132 2. Create a class DateClass with day, month and year as its members. Write an overloaded function for the minus sign. The first function returns the difference between two dates in terms of days. e.g. DateClass date1, date2; int diffdays; diffdays = date1 date2;
The second overloaded function is written which returns a date occurring a given number of days ago. e.g.
SESSION 8
133
INHERITANCE
During this session you will learn about : Reusability. Inheritance concept. How to define a derived class ? Private, Public and Protected derivations. The access specifiers. Using the derived class .
Object-oriented programming as seen in the preceding sessions emphasizes the data, rather than emphasizing algorithms. The previous sessions covered OOP features like extensibility, data encapsulation, information hiding, functional polymorphism and operational polymorphism. OOP, however, has more jargon associated to it, like reusability, inheritance. This session covers reusability and inheritance.
8.1.
REUSABILITY
Reusability means reusing code written earlier ,may be from some previous project or from the library. Reusing old code not only saves development time, but also saves the testing and debugging time. It is better to use existing code, which has been time-tested rather than reinvent it. Moreover, writing new code may introduce bugs in the program. Code, written and tested earlier, may relieve a programmer of the nitty-gritty. Details about the hardware, user-interface, files and so on. It leaves the programmer more time to concentrate on the overall logistics of the program.
8.2.
WHAT IS INHERITANCE?
Class, the vehicle, which is used to implement object-oriented concepts in C++, has given a new dimension to this idea of reusability. Many vendors now offer libraries of classes. A class library consists of data and methods to operate on that data, encapsulated in a
134 class . The source code of these libraries need not be available to modify them. The new dimension of OOP uses a method called inheritance to modify a class to suit ones needs. Inheritance means deriving new classes from the old ones. The old class is called the base class or parent class or super class and the class which is derived from this base class is called as derived class or child class or sub class. Deriving a new class from an existing one , allows redefining a member function of the base class and also adding new members to the derived class . and this is possible without having the source of the course definition also. In other words, a derived class not only inherits all properties of the base class but also has some refinements of its own. The base class remains unchanged in the process. In other words, the derived class is a type of base class, but with more details added. For this reason, the relationship between a derived class and its base class is called an is-a relationship.
Class A
Class B
Single Inheritance
Here class A is a base class and the class B is the derived class.
8.3.
A singly inherited derived class id defined by writing : The keyword class. The name of the derived class . A single colon (:). The type of derivation ( private , protected, or public ). The name of the base, or parent class. The remainder of the class definition.
135 e.g. class A { public : int public_A; void public_function_A(); private : int pri_A; void private_function_A(); protected : int protected_A; void protected_function_A(); }; class B : private A { public : int public_B; void public_function_B();
private : int pri_B; void private_function_B(); }; class C : public A { public : int public_C; void public_function_C(); private : int pri_C; void private_function_C(); };
136 class D : protected A { public : int public_D; void public_function_D(); private : int pri_D; void private_function_D(); }; A derived class always contains all of the member members from its base class . you cannot subtract anything from a base class. However, accessing the inherited variables is a different matter. It is also important to understand the privileges that the derived class has insofar as access to members of its base class are concerned. In other words, just because you happen to derive a class does not mean that you are automatically granted complete and unlimited access privileges to the members of the base class. to understand this you must look at the different types of derivation and the effect of each one.
8.4.
PRIVATE DERIVATION
If no specific derivation is listed, then a private derivation is assumed. If a new class is derived privately from its parent class , then : The private members inherited from its base class are inaccessible to new member functions in the derived class . this means that the creator of the base class has absolute control over the accessibility of these members , and there is no way that you can override this. The public members inherited from the base class have private access privilege. In other words, they are treated as though they were declared as new private members of the derived class, so that new member functions can access them. However, if another private derivation occurs from this derived class, then these members are inaccessible to new member functions. e.g. class base { private :
137 int number; }; class derived : private base { public : void f() { ++number; // Private base member not
accessible } };
The compiler error message is base :: number is not accessible in the function derived :: f(); e.g. class base { public : int number; };
class derived : private base { public : void f() { ++number; } }; // Access to number O.K.
prohibited. } }; The compiler error message is base :: number is not accessible in the function derived2 :: g();
Since public members of a base class are inherited as private in the derived class, the function derived :: f() has no problem accessing it . however, when another class is derived from the class derived , this new class inherits number but cannot access it. Of course, if derived1::g() were to call upon derived::f(), there is no problem since derived::f() is public and inherited into derived2 as private.
or there is another way. Writing access declaration does this. class base { public : int number; }; class derived : private base { public : base :: number ; void f()
139 { ++number; } }; class derived2 : private derived { public : void g() { ++number; } }; // Access to number O.K // Access to number O.K.
As you have just seen private derivations are very restrictive in terms of accessibility of the base class members . therefore, this type of derivation is rarely used.
8.5.
PUBLIC DERIVATION
Public derivations are much more common than private derivations. In this situation : The private members inherited from the base class are inaccessible to new members functions in the derived class. The public members inherited from the base class may be accessed by new members functions in the derived class and by instances of the derived class .
accessible } };
The compiller error message is base :: number is not accessible in the function derived::f(); Here, only if the number is public then you can access it. Note : However example 2 and 3 in the above section works here if you derive them as public.
8.6.
In the preceding example, declaring the data member number as private is much too restrictive because clearly new members function in the derived class need to gain access to it and in order to perform some useful work.
To solve this dilemma, the C++ syntax provides another class access specification called protected . here is how protected works :
In a private derivation the protected members inherited from the base class have private access privileges. Therefore, new member functions and friend of the derived class may access them.
In a public derivation the protected members inherited from the base class retain their protected status. They may be accessed by new members function and friends of the derived class .
In both situations the new members functions and friends of the derived class have unrestricted access to protected members . however, as the instances of the derived class are concerned, protected and private are one and the same, so that direct access id always denied. Thus, you can see that the new category of protected provides a middle ground between
141 public and private by granting access to new function and friends of the derived class while still blocking out access to non-derived class members and friend functions . class base { protected : int number; };
class derived : public base { public : void f() { ++number; } }; // base member access O.K.
8.7.
PROTECTED DERIVATION
In addition to doing private and public derivations, you may also do a protected derivation. In this situation :
The private members inherited from the base class are inaccessible to new member functions in the derived class. ( this is exactly same as if a private or public derivation has occurred.)
The protected members inherited from the base class have protected access privilege. The public members inherited from the base class have protected have protected access.
142 Thus , the only difference between a public and a protected derivation is how the public members of the parent class are inherited. It is unlikely that you will ever have occasion to do this type of derivation.
8.8.
1. If the designer of the base class wants no one, not even a derived class to access a member , then that member should be made private . 2. If the designer wants any derived class function to have access to it, then that member must be protected. 3. if the designer wants to let everyone , including the instances, have access to that member , then that member should be made public .
8.9.
SUMMARY OF DERIVATIONS
1. Regardless of the type of derivation, private members are inherited by the derived class , but cannot be accessed by the new member function of the derived class , and certainly not by the instances of the derived class . 2. In a private derivation, the derived class inherits public and protected members as private . a new members function can access these members, but instances of the derived class may not. Also any members of subsequently derived classes may not gain access to these members because of the first rule. 3. In public derivation, the derived class inherits public members as public , and protected as protected . a new member function of the derived class may access the public and protected members of the base class ,but instances of the derived class may access only the public members. 4. In a protected derivation, the derived class inherits public and protected members as protected .a new members function of the derived class may access the public and protected members of the base class, both instances of the derived class may access only the public members .
143 Derivation Type Base Class Member Private Private Public Protected Access in Derived Class (inaccessible ) Private Private
An instance of a derived class has complete access to the public members of the base class . assuming that the same name does not exist within the scope of the derived class , the members from the base class will automatically be used. Because there is no ambiguity involved in this situation, you do not need to use scope resolution operator to refer to this base class member.
// First checks class derived , then class base cout << d.get_number();
// Goes directly to class base cout<< d.base ::get_number(); } Output: 0 0. Using derived instances to access function members without the base class name and scope resolution operator, the compiler resolves the address by checking : Does the member function have scope in the derived class ? if so, use it ; else Does the member have scope in the parent class ? if so, use it ; else Does the member have scope higher up in the hierarchy ? if so use it; else Does the member have accessible cope higher up in the hierarchy? If so use it; else
145 Does the member exist in the file scope ? if so use it ; else generate a compilation error.
Inheritance is the most powerful use of class and is also, probably, the most important concept of OOP. It is the mechanism using which reusability of code is achieved. The facility to use the existing tested code reduces the time and cost of writing it all over again. This session introduced the concept of inheritance. The next session covers more suitable aspects of inheritance.
SESSION 9
Class inheritance has given a new dimension to the idea of reusability of code. The previous session introduced the concept of inheritance. This session discusses the more subtle aspects associated with class inheritance.
146
9.1.
We cannot overload a base class function by redefining it in a derived class with a different argument list. Consider examples to see if same function name exists in base as well as derived classes. e.g. class base { public : void f(int n ) { cout << n = << n; } };
class derived : public base { public :a void f(float n ) { cout << n = << n; } }; void main() { derived d;
d.f(1); }
Output : n = 1.000000
147
Even though we have passed an integer, the compiler uses the function of the derived class because it is the one, compiler knows about. If there is no convincible match within the scope of the derived class, the inherited members from the class will not be examined. The following example shows this. class base { public : void f(char *ptr) { cout << ptr = << ptr; } }; class derived : public base { public : void f(float n ) { cout << n = << n; } };
d.f(A); }
148 However, we can still access the hidden members from the base class using the scope resolution operator. So in the main if we call
d.base::f(A);
9.2.
If there are constructors involved in the base class and the derived class, the compiler automatically calls both of them. This happens because as soon as the derived class constructor gets control and establishes formal arguments, the base class constructor will be called immediately, i.e., before the derived class initialization list is honored.
Syntax in the derived class base/members initialization list : The base class name ( name of base constructor ) A list of arguments between parentheses, as in any normal function call. To explicitly invoke the base class default constructor , leave the argument list empty. Remember the base class constructor always is called first. class base { public : base(int n=0); ~base(); int get_base_number();
};
int base::get_base_number() { return base_number; } class derived : public base { public : derived(int b=0, int d =0): base(b),derived_number(d); ~ derived(); int get_derived_number(); private : int derived_number; }; derived::derived(int b=0, int d =0): base(b),derived_number(d) { cout << derived constructor ; } derived::~ derived() { cout << derived destructor ; }
cout < d= << d.get_base_number()<<,; cout << d.get_derived_number(); } Output: Base constructor Derived constructor D = 1,2 Derived destructor Base destructor As can be seen from the above example, the constructor for the base class is invoked before invoking the constructor for the derived class . This is like building the first storey of the building before proceeding for the second. With destructors, it is the other way round. Destructors for the derived class are invoked first, so that they can clean up the mess done by the constructor of the derived class . Then the destructor for the base class is called. It is like demolishing the top floor of a building before going for the lower one. A destructor for the derived class is to be defined only if its constructor allocates memory. Otherwise it can be just an empty function .
9.3.
OBJECT INITIALIZATION
An object of a derived class can be initialized to an object of a base class . If both the classes have same data members , then no specific constructor needs to be defined in the derived class . It uses the constructor of the base class . An object of a base class can be assigned to the object of the derived class , if the derived class doesnt contain any additional
151 data members . However, if it does , then the assignment operator will have to be overloaded for the same.
9.4.
OBJECT CONVERSIONS
Just like initialization , conversions are also done automatically when an object of a derived class is assigned to an object of the base class . However, the compiler resorts to a member-wise assignment in the absence of an overloaded function for the assignment operator .
9.5.
NESTED CLASSES
Many a times, it becomes necessary to have a class contain properties of two other classes. One way is to define a class within another that is a class with member classes also called nested classes. This has nothing to do with inheritance. Another way is multiple inheritance , which will be discussed later. e.g. class Aclass { public : Aclass(int pv) { private_variable_A = pv; } private : int private_variable_A; }; class Bclass { public : Bclass(int bpv, int apv): Aobj(apv) {
152 private_variable_B = bpv; } private : int private_variable_B; Aclass Aobj; // Declaring an object here. }; As can be seen , the class Bclass contains an object Aobj in its private section as one of its members. Also, it contains a constructor function with the same name , to which the two variables passed are bpv and apv. The variable bpv is used to initialize the private variable of Bclass. However, the constructor contains something after the colon. The part after the colon in the definition of a constructor is called as initialization section and the actual body of the constructor is called as assignment section . Initialization section initializes the base class members , whereas assignment section contains statements to initialize the derived class members .
As seen earlier , in case of the derived classes, the name of the base class constructor is written after colon in the initialization section. This base class constructor is called before the constructor in the derived class. However, this example does not contain a derived class. This is the example of the nested class . In this case, the name of the object of the member class Aclass is written after the colon. It tells the compiler to initialize the Aobj data member of Bclass with the value in apv. It is exactly like declaring an object of Aclass with the statement :
Aclass Aobj(apv);
The only change is that, it is written after the colon in the initialization section of the Bclass constructor. Its assignment section contains code to initialize its own members. The same constructor function can also be written as : Bclass (int bovine apv):Aobj(apv),private_variable_B = bpv;
9.6.
MULTILEVEL INHERITANCE
153 In multilevel inheritance there is a parent class , from whom we derive another class . now from this derived class we can derive another class and so on.
169
Class A
Class B
Class C
BSIT 34
170
9.7.
MULTIPLE INHERITANCE
Multiple inheritance , as the name suggests , is deriving a class from more than one class . The derived class inherits all the properties of all its base classes. Consider the following example :
Class A
Class B
Class C
class Bclass { : : };
The class Cclass in the above example is derived from two classes Aclass and Bclass therefore the first line of its class definition contains the name of two classes, both publicly inherited. Like with normal inheritance , constructors have to be defined for initializing the data members of all classes. The constructor in Cclass calls constructors for base classes. The constructor calls are separated by commas.
9.8.
class Bclass
172 { public : void put() { : } }; class Cclass : public Aclass , public Bclass { public : : : }; void main() { A objA; B objB; C objC; objA.put(); objB.put(); objC.put(); } The above example has a class C derived from two classes A and B. Both these classes have a function with the same name put(), which assume, the derived class does not have. The class C inherits the put() function from both the classes. When a call to this function is made using the object of the derived class , the compiler does not know which class it is referring to. In this case, the scope resolution operator has to be used to specify the correct object . Its usage is // From Class A // From class B // AMBIGUOUS RESULTS IN ERROR
173 very simple. The statement giving an error from the above example has to be replaced with the following :
9.9.
Inheritance is an important and powerful feature of OOP. Only the imagination of the person concerned is the limit. There are many combinations in which inheritance can be put to use. For instance, inheriting a class from two different classes, which in turn have been derived from the same base class . e.g.
base
Class A
Class B
derived
174 };
Aclass and Bclass are two classes derived from the same base class . The class derived has a common ancestor class base. This is multiple inheritance with a common base class . However, this subtlety of class inheritance is not all that simple. One potential problem here is that both, Aclass and Bclass, are derived from base and therefore both of them, contains a copy of the data members base class. The class derived is derived from these two classes. That means it contains two copies of base class members one from Aclass and the other from Bclass. This gives rise to ambiguity between the base data members. Another problem is that declaring an object of class derived will invoke the base class constructor twice. The solution to this problem is provided by virtual base classes.
175
class derived : public Aclass, public Bclass { : : }; This will resolve the ambiguity involved.
176
Abstract classes are the classes, which are written just to act as base classes. Consider the following classes.
class base { : : };
177
There are three classes Aclass, Bclass, Cclass each of which is derived from the class base. The main () function declares three objects of each of these three classes. However, it does not declare any object of the class base. This class is a general class whose sole purpose is to serve as a base class for the other three. Classes used only for the purpose of deriving other classes from them are called as abstract classes. They simply serve as base class , and no objects for such classes are created.
Pointers, as seen earlier, can be made to point to objects as well. The usage of pointer with classes is exactly like that of its usage with structures. As with structures the structure pointer ( -> ) can be used to access members of the class . The following example illustrates its usage. class Person { public : void getname() {
178 cout << enter name << endl; cin >> name; }
void putname() { cout << name = << name ; } private : char name[50]; };
Bill.getname(); Bill.putname();
179 Pointers to objects are generally used to form complex data structures like linked lists and trees. However, with inheritance, pointers can sometimes become problematic. The following example illustrates yet another problem with class inheritance .
class Shape { public : void print() { cout << I am a Shape << endl; } };
class Triangle : public Shape { public : void print() { cout << I am a Triangle << endl; } };
class Circle : public Shape { public : void print() { cout << I am a Circle << endl;
180 } };
Shape *ptr;
The example contains a base class called Shape. Two other classes Triangle and Circle are derived from the base class . The program declares three objects of the three classes. The
181 first part of the program calls print() function related to each of these classes. In the second part the program declares a pointer to the base class Shape and assigns address of each of the object declared earlier to this pointer.
What is the objective of assigning addresses of the derived classes to a pointer of the base class ? Further, how can the compiler allow such a thing ? Well, the compiler does not give an error on assigning address of a derived class to a pointer of the base class, because objects of the derived classes are type-compatible with pointers of base classes. This answers the second question. As far as the first question is concerned, the answer is polymorphism. That is, if the pointer points to the base class Shape, then the print() function from base class is invoked. Whereas, if the pointer is pointing to any of the derived class Triangle or Circle then the print()function from these respective classes is invoked. That is what polymorphism is all about giving different meanings to the same thing.
The output of the program is given below: I am a Shape I am a Triangle I am a Circle I am a Shape I am a Shape I am a Shape As can be seen, the first part works fine. But , for the second part, for each of the calls with different addresses, the function from the base class Shape is executed. What happens, here , is that the compiler ignores the contents of the pointer. Instead, to determine which function should be invoked, the compiler chooses the function from the same class as that of the pointer. But , this is not polymorphism. The keyword virtual again comes into rescue, but in a different context.
182
The keyword virtual was earlier used to resolve ambiguity for a class derived from two classes, both having a common ancestor. These classes are called virtual base classes. This time it helps in implementing the idea of polymorphism with class inheritance . The function of the base class can be declared with the keyword virtual. The program with this change and its output is given below.
class Shape { public : virtual void print() { cout << I am a Shape << endl; } };
class Triangle : public Shape { public : void print() { cout << I am a Triangle << endl; } };
Shape *ptr;
184 The output of the program is given below: I am a Shape I am a Triangle I am a Circle I am a Shape I am a Triangle I am a Circle
Now, the output of the derived classes are invoked correctly. When declared with the keyword virtual , the compiler selects the function to be invoked, based upon the contents of the pointer and not the type of the pointer. This facility can be very effectively used when many such classes are derived from one base class . Member functions of each of these can be ,then, invoked using a pointer to the base class .
As discussed earlier, an abstract class is one, which is used just for deriving some other classes. No object of this class is declared and used in the program. Similarly, there are pure virtual functions which themselves wont be used. Consider the above example with some changes.
class Shape { public : virtual void print() = 0; // Pure virtual function }; class Triangle : public Shape
185 { public : void print() { cout << I am a Triangle << endl; } }; class Circle : public Shape { public : void print() { cout << I am a Circle << endl; } }; void main() { Shape S; Triangle T; Circle C; Shape *ptr;
ptr = &T; ptr -> print(); ptr = &C; ptr -> print(); }
It can be seen from the above example that , the print() function from the base class is not invoked at all . even though the function is not necessary, it cannot be avoided, because , the pointer of the class Shape must point to its members.
Object oriented programming has altered the program design process. Exciting OOP concepts like polymorphism have given a big boost to all this. Inheritance has further enhanced the language. This session has covered some of the finer aspects of inheritance. The next session will resolve some finer aspects of the language.
EXERCISES: 1. Create a class drugs containing encapsulated data for medicine name, whether solid or liquid, price and purpose of use. From this class derive two classes, Ayurvedic and Allopathic. The class Ayurvedic should additionally store data on the herbs used, association to be used (whether honey or water). The class Allopathic should additionally include data on the chemicals used and the weight in milligrams. The classes should contain constructors and destructors. They should contain functions to accept data and display the data. The main() should test the derived classes.
187
SESSION 10
We have already seen the powerful features of OOP that make C++ such a strong language. In this session we will continue exploring some other features, which make this language more powerful.
188
For example, such a variable can be particularly useful if an object requires to know how many objects of its kind exist. class counter { public : counter (); int getcount(); private: static int count; }; counter::counter () { count++; }
counter c3;
counter c4,c5;
cout << Count = << c4.getcount() << endl; cout << Count = << c5.getcount() << endl; }
Output: Count = 2 Count = 2 Count = 3 Count = 5 Count = 5 // not 1 because 2 objects are already created.
In the above example, the class counter demonstrates the use of static data members. It contains just one data member count. Notice the initialization of this static class member. For some compilers it is mandatory. Even though the data member is in the private section it can be accessed in this case as a global variable directly, but has to be preceded by the name of the class and the scope resolution operator. This example contains a constructor to increment this variable. Similarly, there can be a destructor to decrement it.
You can use these to generate register numbers for student objects from a student class. Whenever an object is created, he will be automatically assigned a register number if the register
190 number is a static variable and a constructor is used to write an equation to generate separate register numbers in some order. You could initialize the variable to give the first register number and then use this in the constructor for further operations.
STATIC MEMBER. void main() { counter c1,c2; cout << Count = << counter :: getcount() << endl; cout << Count = << counter :: getcount() << endl;
counter c3; cout << Count = << counter :: getcount() << endl;
counter c4,c5; cout << Count = << counter :: getcount() << endl; cout << Count = << counter :: getcount() << endl; }
One of the main features of OOP is information hiding. A class encapsulates data and methods to operate on that data in a single unit . The data from the class can be accessed only through member functions of the class. This restricted access not only hides the implementation details of the methods and the data structure, it also saves the data from any possible misuse, accidental or otherwise. However, the concept of data encapsulation sometimes takes information hiding too far. There are situations where a rigid and controlled access leads to inconvenience and hardships.
For instance, consider a case where a function is required to operate on object of two different classes. This function cannot be made a member of both the classes. What can be done is that a function can be defined outside both the classes and made to operate on both. That is, a function not belonging to either, but able to access both. Such a function is called as a friend function. In other words, a friend function is a nonmember function, which has access to a classs private members. It is like allowing access to ones personal belongings to a friend.
Using a friend function is quite simple. The following example defines a friend function to access members of two classes. class Bclass; class Aclass { // Forward Declaration
};
};
194 return( ac.Avar + bc.Bvar); } void main() { Aclass aobj; Bclass bobj;
int total;
total = addup(aobj,bobj); }
The program defines two classes- Aclass and Bclass. It also has constructors for these classes. A friend function, addup(), is declared in the definition of both the classes, although it does not belong to either. This friend function returns the sum of the two private members of the classes. Notice, that this function can directly access the private members of the classes. To access the private members, the name of the member has to be prefixed with the name of the object , along with the dot operator.
The first line in the program is called forward declaration. This is required to inform the compiler that the definition for class Bclass comes after class Aclass and therefore it will not show any error on encountering the object of Bclass in the declaration of the friend function. Since it does not belong to both the classes , while defining it outside we do not give any scope resolution and we do not invoke it with the help of any object. Also , the keyword friend is just written before the declaration and not used while defining.
Sometimes friend functions can be avoided using inheritance and they are preferred. Excessive use of friend over many classes suggests a poorly designed program structure. But sometimes they are unavoidable.
195
Some times friend functions cannot be avoided. For instance with the operator overloading. Consider the following class that contains data members to simulate a matrix. Several operations can be performed on the matrices. One of them is to multiply the given matrix by a number( constant literal). There are two ways in which we can do this. The two ways are :
In the first case we can overload * to perform the operation and an object invokes this as the statement gets converted to : Mobj.operator*(num);
Where Mobj is an object of Matrix and num is a normal integer variable. What happens to the second one ? It gets converted by the compiler as :
num.operator*(Mobj); Let us see this program in detail. class Matrix { public: : : Matrix &operator*(int num); friend Matrix &operator*(int n, Matrix &m);
196 private: int mat[20][20]; int rows, cols; } Matrix Matrix::operator*(int num) { Matrix temp; temp.rows=rows; temp.cols=cols;
for(int i=1; i<=rows; i++) for(int j=1; j<=cols; j++) temp.mat[i][j]=mat[i][j]*num; return (temp); } Matrix operator*(int n, Matrix &m) { Matrix temp;
temp= m*n; return temp; } void main() { Matrix M1, M2, M3; int num; : : // accept matrix one and num
197 M2=M1*num; // calls member operator function. M3=num*M1; // calls friend function. }
Here when the compiler comes across the multiplication of an object by a number it invokes the operator member function. When is encountered before multiplication symbol as in the second call in the program, the compiler calls friend function . in friend function we have just reversed the arguments, which causes the member function to be invoked . Intelligent use of friend functions makes the code more readable.
198 class Bclass { public : : : void fn1(Aclass ac) { Bvar = ac. Avar; // Avar can be accessed. } private : int Bvar;
}; void main() { Aclass aobj; Bclass bobj; Bobj,fn1(aobj); } The program declares class Bclass to be a friend of Aclass. It means that all member functions of Bclass have been granted direct access to all member functions of Aclass.
If you want class A to grant friendship to one or more individual member functions of class B, then you must code the classes and their member functions in this manner:
199 Forward declare class A; Define class B and declare (not define) the member functions: Define class A in which you declare the friendship for the member functions of class B. Of course, you must qualify the names of these functions using the class name B and the scope resolution operator. e.g. class Aclass class Bclass { public : : : void fn1(); void fn3() { : : } private : int Bvar; // Cant define here Define the member functions of class B;
};
}; void classB:: fn1() { Bvar = Avar; } void classB:: fn2() { Bvar = variable +25; }
Well finish this session by showing how to overload the extraction and insertion operators. Here, you can accept the input and output the results of the user-defined variables or objects just like normal variables. Consider a class Complex that consists of two system defined
201 variables, both float to denote the real and complex part of a complex number. In general cases to accept these member variables, we need to write some function say getval() and invoke it using the object of the class say Comobj.getval() and similar method would be required to display them. Using operator overloading we can accept or display the user-defined object just like a normal variable . i.e. by overloading the extraction operator >> we can accept the complex object as, cin >> comobj; Similarly, by overloading the insertion operator we can display the member variables of the object as, cout << comobj; just as if it were a basic data type. Consider the example : class Complex { public: friend istream& operator >> (istream &is, Complex &c2) friend ostream& operator << (ostream &os, Complex &c2) private: float real, imaginary;
}; istream& operator >> (istream &is, Complex &c2) { cout << enter real and imaginary << endl; is >> c2.real >> c2.imaginary; return (is); }
202 { os << the complex number is <<endl; os << c2.real << +i<< c2.imaginary; return (os); } void main() { Complex c1,c2; cin >> c1; cout << c1; cin >> c2; cout << c2; }
The operator functions have to be declared friends , since they have to access the user class and the objects of istream and ostream classes that are system defined. Since these operator functions are friend functions, the two objects cin and cout are passed as arguments, along with the objects of the user-class. They return the istream and ostream objects so that the operator can be chained. That is the above two input statements can also be written as, cin >> c1 >> c2; cout << c1 << c2;
Imagine a c program, which reads disk records containing employee information. If this is a payroll application each employee record data is probably processed via a series of function calls. These function calls could be to print the entity data, to compute the salary, to compute the taxes to be withheld etc,. Each one of these calls inherently contains overhead that must be part
203 of your overall program. In other words it takes code space and time to push actual arguments onto the stack, call the function, push some return value onto the stack and finally pop all of those values.
C++ provides the inline functions, a mechanism by which these explicit function calls can be avoided.
An inline function by definition is a function whose code gets substituted in place of the actual call to that function.
Whenever the compiler encounters a call to that function it merely replaces it with the code itself thereby saving you all of the overhead. Such a function can be either a member of a class or a global function. Inline functions work best when they are small, straightforward bodies of code that are not called from too many different places within your program. The time saved will increase with the increase in number of calls.
Even if you request that the compiler make a function into an inline function, the compiler may or may not honor that request. It depends on the code of the function. For example, the compiler will not inline any function that contains a loop, static data member, or aggregate initializer list. If such cases case, a warning message will be issued.
The disadvantage with inline functions is that if the code itself ever needs to be modified, then all programs that use the these functions would then have to be recompiled. Furthermore, an inline function is a violation of implementation hiding.
204 Precede the return type of the function with the keyword inline. Write the function body (the definition, not just the declaration) before any calls to that function.
Here is a program that uses an inline function to compute and return the absolute value of its input argument. # include <stdio.h>
void main( ) { for (int i=-2 ; i<2 ; ++i) { int value = abs(i) ; printf (Absolute value of %+d = %+d\n,i, value); } }
The output is: Absolute value of 2 =+2 Absolute value of 1 =+1 Absolute value of +0 =+0 Absolute value of +1 =+1
205 When the call to the abs ( ) function is encountered, the compiler, instead of making a function call, generates this assembly code.
Here the member function integer :: store ( ) is an inline member function because it is completely defined within the context of the class definition. e.g. class integer { public : // This is an inline function
Functions inlined within the class definition are not evaluated by the compiler until the entire class scope has been processed. Thats why, if they are defined first, they can refer to
206 members defined later, or even compute the size of the class. The only problem with this class is that now it has been cluttered up with the actual definition of the integer::store() function.
A better approach is to move the function definition outside the class and use the scope resolution operator to give it class scope.
In order to make the inline request to the compiler, you must write the keyword inline in front of both the function declaration and its definition. e.g. class integer { public: inline void store (int) ;
The above example shows the correct way to define an inline function, the usage of the keyword inline in front of the declaration ( within the class definition ) is not always necessary. This is the case when the definition ( still preceded by the keyword inline) is encountered by the compiler before it is ever called. Now there is no problem because the inlining will still occur.
207
10.12.
preprocessor, which acts on the source code before the compilation process. The output from the preprocessor becomes the input to the compiler and is also called as extended source code. It performs file inclusions, macro substitution and conditional compilation etc.
10.12.1. The #define Directive The #define directive defines a macro which is a text string represented by a name. Whenever the preprocessor finds this name in the program, it is replaced by the text string it represents. The is an following example which defines a value and gives it a name PI.
// no semicolon given
The # define directive works strictly on the basis of substitution of a text string for the macro names.
208
This directive undefines a previously defined macro. For, example the following will give an error since PI is undefined.
#define PI 3.14285 void main() { cout<< PI; # undef PI; // no error here
10.12.3. The #ifdef, #elif, #else and #endif Directives These directives are used for conditional compilation. They are used in the following ways.
#ifdef condition statements; #endif; #ifdef condition statements; #else statements; #endif;
209
#ifdef condition1
#ifdef condition1 statements; #elif condition2 statements; #else statements; #endif; Just like #ifdef there is also #ifndef which negates the condition.
These directives control which portions of the code will be passed to the compiler. For example, these directives can be used for compiling the program written for two different machines. On MSDOS machines, the program can make use of certain MSDOS features. Otherwise, some other code depending on the machine is executed. e.g. void main() { #ifdef __MSDOS__ DOS-fn(); #else UNIX_fn(); #endif; }
This directive facilitates putting diagnostic messages into the preprocessor statements. Sometimes, working with conditional compilation, error messages need to be displayed. In this case the #error directive is very useful. The following example illustrates the usage of this directive.
#ifndef__LARGE__
#endif
The example shows a program, which has to be compiled only in the large model. If the compiler switch for a large model is not on, the preprocessor prints the given error message and stops compilation.
Most languages have statements to perform I/O . However in C and C++ we use functions to perform I/O. C++ also has its own I/O mechanism the cin and cout objects, declared in the header file iostream.h. On including iostream.h , the program gets the following 4 objects automatically: Cin This object corresponds to the standard input Stream, which by default is the keyboard. cout This object corresponds to the standard output stream ,which by default is the screen.
211
This object corresponds to the standard error Stream, which by default is the screen.
Output on the standard screen is done by using the cout object of the ostream class. cout << variable ;
variable can be any basic data type. In other words, the operator function for insertion operator is already overloaded for each of data types. Thus, if the variable is an integer, then the function with the corresponding signature is invoked. The prototype of this function would be :
ince we have been using this througout, no further discussion is needed on this topic. The following example shows various formats :
void main() { char fn[100] = Narayan; char * ln = Murthy; int age = 55; float salary = 100000.00;
cout << Name = << fn; cout << Sirname = << ln;
cout << \n Age = <<age; cout<< endl << Salary is << salary; } Output : Name = Narayan Sirname= Murthy Age = 55 Salary =100000
Similarly even cin is a object that accepts data in any data form and can be cascaded as well.
Apart from the cout, the ostream class provides some other functions for output. One of them is put(). It has the following prototype :
ostream &put(char);
This function prints only characters. e.g. cout.put(V); //Prints the character V
It can also be used like : cout.put(I).put(N).put(D).put(I).put(A); There is another function write() in this class. This function prints an entire string and has the prototype : ostream &write(char *,int);
213 The first argument gives the address of the character string to be printed. This function does not recognize the null character. Therefore if the second argument is more than the length of the string then the compiler raises an error. e.g. #include < iostream.h> #include < string.h > void main() { char *magic = ABRACADABRA; int len = strlen(magic); for( int sub = 0; sub < = len ; ++sub) { cout.write(magic,sub)<<\n; } Output : A AB ABR ABRA ABRAC ABRACA ABRACAD ABRACADA ABRACADAB ABRACADABR ABRACADABRA A function flush() can be used to flush the output buffer. sometimes it is necessary. Yet another function endl() prints a new line character.
214
cout<< ttttt << endl; cout<< I/O of the C++ is to be flushed <<flush;
They can be used with the insertion operator. They can also be used as follows:
flush(cout); endl(cout);
The overloaded function for insertion operator << of the ostream variables before displaying. The default formats are as follows:
char int
displayed as a character, uses one position. displayed as a decimal integer uses space just enough for the number and a minus sign if the number is negative.
float
displayed with 6 decimal places, except for the & double trailing zeroes. The field is just wide enough
for the number and a minus sign if number is negative. The number is in fixed-point notation or normalized form, whichever takes less space.
215 Character displayed in a field equal to the length of the Strings string. e.g. # include < iostream.h > void main() { char name[100] = IGLOOCAMALOO; char h = v; int posnum = 123; int negnum = -123; double d = 12345678.1234566; float f = 123.123;
cout << This is string : [ << name << ]\n; cout << This is character : [ << ch << ]\n; cout << Positive integer :[; cout<< posnum << ]\n; cout << Negative Integer :[; cout<< negnum << ]\n; cout<< Float :[ << f << ]\n; cout << Double : [ << d << ]\n; cout << integer division 10/3 = [; cout << 10/3 << ]\n; cout << floating Division 10.0 /3.0= [; cout << 10.0/3.0 << ]\n; }
216 Output : This is string : [ IGLOOCAMALOO] This is character : [V] Positive integer :[123] Negative Integer :[-123] Float :[123.123000] Double : [12345678.123457] integer division 10/3 = [3] floating Division 10.0 /3.0= [3.333333]
10.17. MANIPULATORS
As discussed earlier, there are several classes in the iostream.h header file. One of them is ios class. This class stores the format state. For instance, some bits define the base in which the numbers will be printed, some bits define the field width. This header file has functions called manipulators, using which the output format can be controlled. There are 3 manipulators dec(), oct(), hex() to set the base in which a given number will be printed. These manipulators are called in two ways :
hex(cout); // Pass the object name cout<< hex; // use the insertion operator .
But since these manipulators are not member functions of the class , it cannot be used as follows: cout.hex(); e.g. void main(void) { // Not allowed.
217 int i =110; cout << Default Base Decimal : << i << endl; cout << hex; // Print in hexadecimal cout << Hexadecimal : << i << endl;
cout << oct; // Print in Octal cout << Octal : << i << endl;
Output : Default Base Decimal : 110 Hexadecimal :6e Octal Decimal :156 :110
10.17.1. Changing Field width The printf() and scanf() functions from the standard library, use width specifier for controlling the width of its output . similarly, the ostream class also has a member function width() for the same. This function has the following prototype. int width(void); int width(int newsize);
218 The first form returns the current field width. The default is always zero. That is because , the cout object always uses space just wide enough to output the values and a default size if zero matches all. The second form can be used to change the current width setting. The argument passed to it is the new width to be set. It also returns the previous width. The width() function affects only the next item displayed. The filed width reverts to default afterwards. e.g. void main() { cout<< default width=<< cout.width()<<endl; cout<<[; cout.width(10); Set width to 10 cout<<A; cout<<]; } Output: Default width = 0; [ A]
By default, C++ pads spaces to extra places, if the width specified is more than the actual output . ostream contains a function fill() to set a new fill character. The function prototypes are : char fill(void) char fill(char c) //Returns current fill character //Set a new fill character.
219 e.g. void main() { int amt1 = 100, amt2= 12345; cout.fill(*); cout << Amount1 : [; cout.width(5); cout << amt1 <<]\n; cout << Amount 2 : [; cout.width(5); cout << amt2 << ]\n; } Output : Amount 1 : [**100] Amount 2 : [12345]
By default, C++ prints six decimal places after the decimal point, except for trailing zeroes. The function precision() can be used to change this default behavior. The function does not merely truncate the trailing digits, it rounds them up. This function has the following prototype :
220 e.g. void main() { float f1 = 123.43; float f2 = 10.0 / 3.0; float f3 = 10.0 / 6.0;
cout << Default f1 = [ << f1 <<]\n; cout << Default f2 = [ << f2 <<]\n; cout << Default f1 = [ << f3 <<]\n;
cout.precision(2);
cout << f1 = [ << f1 <<]\n; cout << f2 = [ << f2 <<]\n; cout << f3 = [ << f3 <<]\n; }
Output : Default f1 = [123.43] Default f2 = [3.333333] Default f3 = [1.666667] f1 = [123.43] f2 = [3.33] f3 = [1.67]
The ios contains a function setf() for controlling several formatting features. This class defines a set of enumerated constants that can be used as arguments to the setf() function. This function has the following prototypes :
The first prototype has only one argument. It can be any one of the following :
Enumerated Constant
Meaning
Showbase
Showpoint
Uppercase
showpos
222 These constants are enumerated constants, defined in the ios class definition and therefore have a class scope. They have to be prefixed by the name of the class with the scope resolution operator(::). e.g. cout.setf(ios::showpoint); The above example shows the trailing zeros and decimal point. The second prototype has two arguments. The first argument defines the bit settings that have to be changed. These arguments can be any of the following :
Enumerated Constants
Meaning
First Argument
Second argument
Float field
Fixed
Scientific
223
Adjustfield
Left
Right
Internal
e.g. void main() { int inum = 110; cout.setf(ios::showpos); cout << showpos : [ << inum << ]\n; cout << Hex : [ << hex << inum << ]\n; cout.setf(ios::showbase); cout << Base prefix :[<< inum << ]\n; cout.setf(ios::uppercase}; cout << Uppercase hex :[<< inum << ]\n; cout.setf(ios::left, ios:: adjustfield); //left justified. cout << dec << left justified :[; cout.width(6); cout << inum << ]\n; cout.setf(ios::right, ios:: adjustfield); //right // uppercase hex. // show base prefix. // show plus sign
Output: Showpos : [+110] Hex : [6e] Base prefix : [0x6e] Uppercase hex : [0x6e] Left justified: Right justified: e.g. void main() { float f = 10.0; cout << Float :[ << f << ]\n; cout.setf(ios::showpoint); // show trailing 0s and point cout << show point : [ << f << ]\n; cout.precision(2); cout << 2 decimals :[<<f<<]\n; cout.setf(ios::scientific, ios :: floatfield); cout << Scientific : [ << f << ]\n; } [+110] [+110]
225 output: Float : [+10] Showpoint : [+10.000000] 2decimals : [+10.00] scientific : [+1.00e+01]
These functions accept a character. The first form stores the character entered in ch and returns a reference to the istream object. The second form returns back the character entered. e.g. void main() { char ch1,ch2; cout << Enter a String : ; cin >> ch; while(ch != \n) { cout << ch;
226
The program given above will not work, since cin does not recognize white space characters and hence , will ignore the <return> character resulting in an infinite loop. The solution is to use get() as below: e.g. void main() { char ch1,ch2;
cin.get(ch); } }
The istream class defines two other functions getline() and an overloaded get() function for accepting strings. Their function prototypes are :
227 The first argument is the address of the location where the input string is to be placed. The second argument is the number of maximum characters to be read plus number. The third argument is the terminating character,. If omitted the function reads up to maximum characters specified or until new line character whichever is encountered first.
this function reads up to 49 characters or until new line character whichever is encountered first.
The difference between get() and getline() is that get() leaves the new line character in the input system, whereas getline() extracts it from the stream.
Function templates provide you with the capability to write a single function that is a skeleton, or template, for a family of similar functions.
228
In function overloading technique it relieves someone who is using your functions from having to know about different names for various functions that essentially do the same task. Unfortunately, overloaded functions are not the ultimate solution to the problem that is inherent when writing functions that are similar in their behavior.
Consider the following example: int max(int x, int y) { return ( x > y) ? x : y ; } float max(float x, float y) { return ( x > y) ? x : y ; } long max( long x, long y) { return ( x > y) ? x : y ; } char max(char x, char y) { return ( x > y) ? x : y ; }
void main() { cout << max( 1,2) << endl; cout << max( 4L,3L) << endl;
229 cout << max( 5.62,3.48) << endl; cout << max(A,a) << endl; } The output is : 2 4 5.62 a Even though function overloading is used, the problem with this example is that there is still too much repetitious coding. In other words, each function is essentially doing the same task. Now, instead of you having to write many functions, it would be nice if were to write only one function and can accommodate almost any type of input argument. How a Function Template solves this problem A function template solves the problem by allowing you to write just one function that serves as a skeleton, or, template, for a family of functions whose tasks are all similar.
This function template does not specify the actual types of the arguments that the function accepts; instead , it uses a generic, or parameterized type, as a place holder, until you determine the specific types. The process of invocation of these functions with actual parameter types is called the template instantiation. How to write a function template
A function template should be written at the start of the program in the global area, or you may place it into a header file. All function templates start with a template declaration.
The syntax is : The C++ keyword template A left angle bracket ( < )
230 A comma separates a list of generic types, each one. A generic type consists of two parts 1. the keyword class ( this usage of class has nothing to do with the key word class used to create user-defined type.) 2. a variable that represents some generic type, and will be used whenever this type needs to be written in the function definition. Typically the name T is used, but any valid C++ name will do. A right angle bracket ( > ).
e.g. template < class T> T max(char x, char y) { return ( x > y) ? x : y ; } void main() { cout << max( 1,2) << endl; cout << max( 5.62,3.48) << endl; cout << max(A,a) << endl; cout << max( 4,3) << endl; } The output is : 2 5.62 a 6
231 Each of first three max () function class causes a template function to be instantiated, first with an int, then with a double and then with a char. The last call using two integers does not cause another instantiation to occur because this call can use the instantiated function that was generated as a result of the first class that also uses two integers.
The template needs to have parameters, either a template type representing a type or a template non-type parameter representing a constant expression.
e.g. template < class Hello, const int size > Hello min ( const Hello ( & array)[size]) { Hello min_val = array [0];
for(int i=1; i<size; ++i) { if( array[i] < min_val) min_val = array[i]; } return ( min_val); } where Hello can be built in or user defined type and size is always constant and integer.
A template type specifier can be used for variable declaration ( in the above example , it is used to declare min_val), casts etc., just like normal variable.
we can have more than one template parameter type in the same template . e.g.
232 template < class T1, class T2, class T3> T3 min (T1 a , T2 b);
We can even overload function templates. e.g. template <class T> T min ( T *, int);
In addition to function templates, C++ also supports the concept of class templates. By definition, a class template is a class definition that describes a family of related classes. The philosophy is essentially the same as that of function templates, i.e., the ability to create a class (or structure) that contains one or more types that are generic, or parameterized.
A class template is defined in a manner similar to that of a function template. You start by writing a template declaration followed by the class definition: e.g.
The generation of a class from class definition is called template instantiation. We can declare variables of this class as follows:
In the first instantiation the entire template types i.e. variables of type T will be converted as integers. In the second case they are converted as double variables.
Of course, you may have more than one parameterized type between the angle brackets.
Template < class T1, class T2, class T3> class classname; Each parameter should be preceded with the keyword class.
// Error // O.K.
In this session we have seen how a static variable and static function combination can be used for variables, which are shared by all the objects and hence can be given a single
234 memory location and how objects are not required to invoke them. We also learnt how to share our private details with friends, which though should not be overused, but becomes extremely necessary sometimes. Using the manipulators and iostream objects we can enhance the way our output appears on the screen. Functions can be made as inline functions if they are small. These are substituted in place of function calls and are faster compared to calling and returning as compared to normal functions. We also learnt how the shortcomings of function overloading could be overcome using template functions.
A non-type parameter represents a constant value. Template < class T, const int buffer > class classname; The functions of the template classes obey the rules of the function templates. This session covered the subtle aspects of C++, which could not be classified under any
of the previous sessions. We discussed about friend functions, which though are very tempting, should not be overused. The concept of templates gives another dimension to overloading and reusability. We saw how friend functions along with operator overloading can make the objects look like normal variables and thus giving the language a more natural look. We also covered how to enhance the output of the program.
EXERCISES:
1. Write am program to accept details of N students. Generate the register number for them using the static variable and static functions. 2. An airlines company recruits female candidates for the post of airhostess. WAP to create a class named CANDIATE . The class should contain the candidates name, age, sex, weight, height and qualification. Include a constructor and a destructor for this class. Include a member function to accept the data and display the data. Create a class named
235 PERFECT to accept the exact requirements in terms of age, weight, height, and qualification from the company.
--sex and qualification should be same as that of PERFECT. Overload the = operator for this check.
--age ,weight and height should be less than or equal to the PERFECT. Overload the operator <= for this check.
Based on the return type of the above perform the following operations.
- if the first condition is -ve, then display the message ,"rejected". - if the first condition is +ve but the second is -ve , then display, "will be called later".
- if both the conditions are +ve ,the candidate is called for the first interview and display "called for the first interview.
* * * * *