91
Object Oriented Programming Using C++ X are assigned values of 5 and 6 respectively. In object Y they are assigned respective values of 8 and 10. Output : In object X Static int p = 5 int q = 6 In object X Static int p = 8 int q = 6 In object Y Static int p = 8 int q = 10 Operator Overloading C++ allows us to specify some new jobs for an already existing C++ operators. The mechanism of giving some extra jobs to an operator is known as operator overloading. Operator overloading provides a flexible option for the creation of new definitions for most of the C++ operators. Almost all the operators can be overloaded in C++, except the following: i) Class member access operators (., .*) ii) Scope resolution operator (::) iii) Size operator (sizeof) iv) Conditional operator (? :) When an operator is overloaded, its original meaning is not lost. Although the semantics of an operator can be extended, we cannot change its syntax, the grammatical rules that govern its use such as the number of operands, precedence and associativity. Defining Operator Overloading The additional task of an operator can be specified with the help of a special function, called operator function. The operator function must be a member of a class so that the additional task of the operator is limited by the concerned class. The general form of an operator function is : return type classname :: operator op(arglist) { Function body // task defined }
92
Object Oriented Programming Using C++ return type : is the type of value returned by the specified operation op : is the operator being overloaded The op is preceded by the keyword operator. Operator op is the function name. Operator function must be either a non static member function or a friend function. The two functions will be treated differently. A friend function will have one argument for unary operators and two for binary operators, while a member function has no arguments for unary operators. This is because the object used to invoke the member function is passed implicitly and therefore is available for the member function. This is not the case with friend function. Arguments may be passed either by value or by reference. Operator functions are declared in the class using prototypes as follows: vector operator+(vector); vector operator-(vector); friend vector operator+(vector, vector); friend vector operator-(vector); int operator ==(vector); friend int operator==(vector, vector)
// vector addition // unary minus // vector addition // vector minus // comparison // comparison
Vector is a data type of class and may represent both magnitude and direction. The process of overloading involves the following steps: 1. Create a class that defines the data type that is to be used in overloading operation. 2. Declare the operator function operator op() in the public part of the class. It may be either a member function of a friend function 3. Define the operator function to implement the required operations. Overloaded operator functions can be invoked by expressions such as op x or x op for unary operators and x op y for binary operators. op x (or x op) would be interpreted as operator op (x) for friend functions. Similarly, the expression x op y would be interpreted as either x.operator op(y) in case of member functions, or operator op(x, y) in case of friend functions. When both the forms are declared, standard argument matching is applied to resolve any ambiguity.
93
Object Oriented Programming Using C++ Overloading Unary Operators A unary operator is one which operates on a single operand. Example : overloading unary minus #include
// activates operator-() function
94
Object Oriented Programming Using C++ cout << “S : “; S.display(); } Output : S : 10 -20 S : -10 20
30 -30
The function operator –() takes no argument. It changes the sign of data members of the object S. Since this function is a member function of the same class, it can directly access the members of the object which activated it. Statement S2 = -S1 will not work, the function operator –() does not return any value. It can work if the function is modified to return an object. Using friend function friend void operator –(space &s);
// declaration
void operator –(space &s) { s.x = -s.x; s.y = -s.y; s.z = -s.z; }
// definition
Here the argument is passed by reference. It will not work if we pass argument by value because only a copy of the object that activated the call is passed to operator –(). Therefore, the changes made inside the operator function will not reflect in the called object. Overloading Binary Operaotrs Binary operators can be used to add (+), subtract (-), multiply (*) and divide (/) the objects. When we use binary operator for overloading it takes first argument (A) internally, and passes second argument (B) as an argument and return a value to store in C, where C may be object as well as a data member/ variable. If we use binary operator function as friend function; it takes two arguments. Example : overload + operator #include
95
Object Oriented Programming Using C++ float x, y; public: complex() { }; complex(float real, float imag) { x = real; y = imag; } complex operator +(complex); void display();
//constructor 1 // constructor 2
}; complex complex :: operator +(complex c) { complex temp; // temporary temp.x = x + c.x; // these are temp.y = y + c.y; // float additions return(temp); } void complex :: display () { cout << x << + j” << y ; } void main() { complex c1, c2, c3; c1 = complex(2.5, 3.5); c2 = complex(1.6, 2.7); c3 = c1 + c2; cout << “ C1 = “<
96
Object Oriented Programming Using C++ 3. It is a member function of complex. The function is expected to add two complex values and return a complex value as the result but receives only one value as argument. Where does the other value come from? C3 = C1 + C2; // invokes operator +() function INHERITANCE Inheritance is one of the most powerful features of objected oriented programming. It promotes software reusability by creating new base classes from existing classes. The new classes created, in addition to some additional features over and above the capabilities of the base class from which they are derived, posses all the attributes of the base class. When you derive a new class, it inherits all the attributes of the base class. You may incorporate additional attributes into the new class. But you do not have to touch the member functions of the base class. Each derived class itself becomes the base class for creation of further derived classes. Thus it is possible to create class libraries. Software groups can develop their own libraries and can use class libraries used by other groups. The C++ classes can be reused in several ways. Once a class has been written and tested, it can be adapted by other programmers to suit their requirements. This is basically done by creating new classes, reusing the properties of the existing ones. The mechanism of deriving a new class from an old one is called inheritance (or derivation). The old class is referred to as the base class and the new one is called the derived class or subclass. The derived class inherits some or all of the traits from the base class. A class can also inherit properties from more than one class or from more than one level. A derived class with only one base class, is called single inheritance and one with several base classes is called multiple inheritance. The traits of one class may be inherited by more than one class. This process is known as hierarchical inheritance. The mechanism of deriving a class from another ‘derived class’ is known as multilevel inheritance.
97
Object Oriented Programming Using C++
A
A
B
C Multiple Inheritance
B Single Inheritance
A
A
B
C
B
D
Hierarchical Inheritance
C
A Multileverl Inheritance
C
B
D
Hybrid Inheritance
98
Object Oriented Programming Using C++
A derived class can be defined by specifying its relationship with the base class in addition to its own details. The general form of defining a derived class is : class derived-class-name { ……………….. ………………. . ……………….. };
:
visibility-mode base-class-name
// // //
members of derived class
The colon indicates that the derived-class-name is derived from the base-class-name. The visibility mode is optional and, if present, may be either private or public. The default visibility-mode is private. Visibility mode specifies whether the features of the base class are privately derived or publicly derived. Examples class ABC : private XYZ { Members of ABC };
// private derivation
class ABC : public XYZ { Members of ABC };
// public derivation
class ABC : XYZ { Members of ABC };
// [private derivation by default
Points to Remember : 1. In private derivation, public members of base class becomes the private members of the derived class. class B class D : private B { { int a; int d; public : public: int b, c; display(); getdata(); }; mul(); };
99
Object Oriented Programming Using C++ Class D private : int d; int b, c; getdata(); mul(); public: display();
In public derivation, public members of a base class becomes the public members of the derived class. Class D private : int d; public: int b, c; getdata(); mul(); display();
2. In each case, no private members of a base class is accessible to the derived class. Example of Single Inheritance #include
100
Object Oriented Programming Using C++ }; class D : public B { int c; public : void mul(); void display(); }; void B :: get_ab() { a = 5; b = 10; int B :: get_a() { return a;
}
}
void B :: show_a() { cout << “a = “ << a;
}
void D :: mul() { c = b * get_a();
}
void D :: display() { cout <<” a = “ << get_a; cout <<” b = “ << b; cout <<” c = “ << c; } void main() { D d; d.get_ab(); d.mul(); d.show_a(); d.display(); d.b = 20; d.mul(); d.display(); } Output : A=5 A=5
a=5 b = 20
b = 10 c = 100
c = 50
101
Object Oriented Programming Using C++
The class D is a public derivation of the base class B. Therefore, D inherits all the public members of B and retians their visibility. Thus a public member of the base class B is also a public member of the derived class D. The private members of B cannot be inherited by D. The class D, in effect, will have more members than what it contains at the time of declaration Class D private : int c; public: int b; get_ab(); get_a() show_a(); mul(); display();
NOTE : Although the data member a is private in B and cannot be inherited, pbjects of D are able to access it through an inherited member function of B. Example : Single Inheritance : Private #include
// private derivation
102
Object Oriented Programming Using C++ void display(); }; void B :: get_ab() { cout << “Enter values for a and b : “; cin >> a >> b; } int B :: get_a() { return (a);
}
void B :: show_a() { cout << “a = “ << a;
}
void D :: mul() { c = b * get_a();
// ‘a’ cannot be used directly }
void D :: display() { show_a(); cout <<” b = “ << b; cout <<” c = “ << c; }
// outputs value of ‘a’
void main() { D d; // d.getab() ; wont work d.mul(); // d.show_a(); it wont work d.display()’ // d.b = 20; it wont work, b has become private d.mul(); d.display(); } Output : Enter values for a and b : 5 10 A=5 b = 10 c = 50 Enter values for a and b : 12 20 A = 12 b = 20 c = 240 Being private members of D, the members of B, get_ab() and show_a() can not be accessed directly through main().
103
Object Oriented Programming Using C++
Class D private : int c; int b; get_ab(); get_a() show_a(); public: mul(); display();
Making a Private Member Inheritable A private member of a base class can be inherited when declared as protected. A private member of a class when declared protected, it becomes accessible by the member functions within its class and any class immediately derived from it. It cannot be accessed by the functions outside these two classes. A class can now use all the three visibility modes: class alpha { private : ………. ………. protected : ………. ………. public : ………. ………. };
//optional // visible to member functions // within its class // visible to member functions // of its own and derived class // visible to all functions // in the program
When a protected member is inherited in public mode, it becomes protected in the derived class too and therefore is accessible by the member functions of the derived class. A protected member, inherited in the private mode derivation, becomes private in the derived class. Although it is available to the member functions of the derived class, it is not available for further inheritance (since private members cannot be inherited).
104
Object Oriented Programming Using C++ Class B not inheritable X
X not inheritable
Private Protected Public
Class D2 : private B Private
Class D1 : public B Private
Protected
Protected
Public
Public
Class X : public D1 : protected D2
Private Protected Public The keywords private, protected, and public may appear in any order and any number of times in the declaration of a class. For example class beta { protected : …………. public : ………….. private : …………. public : ………….. }; is a valid class definition.
105
Object Oriented Programming Using C++ It is possible to inherit a base class in protected mode (known as protected declaration). In protected derivation, both the public and protected members of the base class become protected members of the derived class. What are the various functions that can have access to these members? They could be: 1. A function that is a friend of the class. 2. A member function of a class that is a friend of the class. 3. A member function of a derived class. While the friend functions and the member functions of a friend class can have direct access to both the private and protected data, the member functions of a derived class can directly access only the protected data. However, they can access the private data through the member functions of the base class.
Visibility of inherited members Base Class Visibility
Derived Class Visibility Public Derivation
Private Derivation
Protected Derivation
Private
Not inherited
Not Inherited
Not Inherited
Protected
Protected
Private
Protected
Public
public
Private
Protected
Multilevel Inheritance Base Class
A
Grandfather
Intermediate Base Class
B
Father
Derived Class
C
Child
106
Object Oriented Programming Using C++
In the above figure class A servers as a base class for the derived class B, which in turn serves as a base class for the derived class C. The class B is known as intermediate base class since it provides a link for the inheritance between A and C. The chain ABC is known as inheritance path. A derived class with multilevel inheritance is declared as follows: class A { …………………. }; class B : public A { ……….}; class C : public B { ……….};
// Base class // B derived from A // C derived from B
This process can be extended to any number of levels; Example class student { protected: int roll_number; public : void get_number(int); void put_number(); }; void student :: get_number(int a) { roll_number = a; } void student :: put_number() { cout << “roll number : “ << roll_number ; } class test : public student // first level derivation { protected: float sub1, sub2; public : void get_marks(float, float); void put_marks(); } void test :: get_marks(float x, float y) { sub1 = x ; sub2 = y ; } void test :: put_marks() { cout << “Marks in sub1 = “ << sub1; cout << “Marks in sub2 = “ << sub2; } class result : public test { float total; public : void display(); };
// second level derivation
107
Object Oriented Programming Using C++ Polymorphism Before getting into this section, it is recommended that you have a proper understanding of pointers and class inheritance. If any of the following statements seem strange to you, you should review the indicated sections: Statement: Explained in: Int a::b(c) {}; Classes a->b Pointers class a: public b; Friendship and inheritance Pointers to base class One of the key features of derived classes is that a pointer to a derived class is typecompatible with a pointer to its base class. Polymorphism is the art of taking advantage of this simple but powerful and versatile feature, that brings Object Oriented Methodologies to its full potential. We are going to start by rewriting our program about the rectangle and the triangle of the previous section taking into consideration this pointer compatibility property: // pointers to base class
20
#include
10
using namespace std;
class CPolygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } };
class CRectangle: public CPolygon {
108
Object Oriented Programming Using C++
public: int area () { return (width * height); } };
class CTriangle: public CPolygon { public: int area () { return (width * height / 2); } };
int main () { CRectangle rect; CTriangle trgl; CPolygon * ppoly1 = ▭ CPolygon * ppoly2 = &trgl; ppoly1->set_values (4,5); ppoly2->set_values (4,5); cout << rect.area() << endl; cout << trgl.area() << endl; return 0; } In function main, we create two pointers that point to objects of class CPolygon (ppoly1 and ppoly2). Then we assign references to rect and trgl to these pointers, and because both are objects of classes derived from CPolygon, both are valid assignations.
109
Object Oriented Programming Using C++ The only limitation in using *ppoly1 and *ppoly2 instead of rect and trgl is that both *ppoly1 and *ppoly2 are of type CPolygon* and therefore we can only use these pointers to refer to the members that CRectangle and CTriangle inherit from CPolygon. For that reason when we call the area() members at the end of the program we have had to use directly the objects rect and trgl instead of the pointers *ppoly1 and *ppoly2. In order to use area() with the pointers to class CPolygon, this member should also have been declared in the class CPolygon, and not only in its derived classes, but the problem is that CRectangle and CTriangle implement different versions of area, therefore we cannot implement it in the base class. This is when virtual members become handy: Virtual members A member of a class that can be redefined in its derived classes is known as a virtual member. In order to declare a member of a class as virtual, we must precede its declaration with the keyword virtual: // virtual members
20
#include
10
using namespace std;
0
class CPolygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } virtual int area () { return (0); } };
class CRectangle: public CPolygon { public:
110
Object Oriented Programming Using C++
int area () { return (width * height); } };
class CTriangle: public CPolygon { public: int area () { return (width * height / 2); } };
int main () { CRectangle rect; CTriangle trgl; CPolygon poly; CPolygon * ppoly1 = ▭ CPolygon * ppoly2 = &trgl; CPolygon * ppoly3 = &poly; ppoly1->set_values (4,5); ppoly2->set_values (4,5); ppoly3->set_values (4,5); cout << ppoly1->area() << endl; cout << ppoly2->area() << endl; cout << ppoly3->area() << endl; return 0;
111
Object Oriented Programming Using C++
} Now the three classes (CPolygon, CRectangle and CTriangle) have all the same members: width, height, set_values() and area(). The member function area() has been declared as virtual in the base class because it is later redefined in each derived class. You can verify if you want that if you remove this virtual keyword from the declaration of area() within CPolygon, and then you run the program the result will be 0 for the three polygons instead of 20, 10 and 0. That is because instead of calling the corresponding area() function for each object (CRectangle::area(), CTriangle::area() and CPolygon::area(), respectively), CPolygon::area() will be called in all cases since the calls are via a pointer whose type is CPolygon*. Therefore, what the virtual keyword does is to allow a member of a derived class with the same name as one in the base class to be appropriately called from a pointer, and more precisely when the type of the pointer is a pointer to the base class but is pointing to an object of the derived class, as in the above example. A class that declares or inherits a virtual function is called a polymorphic class. Note that despite of its virtuality, we have also been able to declare an object of type CPolygon and to call its own area() function, which always returns 0. Abstract base classes Abstract base classes are something very similar to our CPolygon class of our previous example. The only difference is that in our previous example we have defined a valid area() function with a minimal functionality for objects that were of class CPolygon (like the object poly), whereas in an abstract base classes we could leave that area() member function without implementation at all. This is done by appending =0 (equal to zero) to the function declaration. An abstract base CPolygon class could look like this: // abstract class CPolygon class CPolygon { protected: int width, height;
112
Object Oriented Programming Using C++
public: void set_values (int a, int b) { width=a; height=b; } virtual int area () =0; }; Notice how we appended =0 to virtual int area () instead of specifying an implementation for the function. This type of function is called a pure virtual function, and all classes that contain at least one pure virtual function are abstract base classes. The main difference between an abstract base class and a regular polymorphic class is that because in abstract base classes at least one of its members lacks implementation we cannot create instances (objects) of it. But a class that cannot instantiate objects is not totally useless; We can create pointers to it and take advantage of all its polymorphic abilities. Therefore a declaration like: CPolygon poly; would not be valid for the abstract base class we have just declared, because tries to instantiate an object. Nevertheless, the following pointers: CPolygon * ppoly1; CPolygon * ppoly2; would be perfectly valid. This is so for as long as CPolygon includes a pure virtual function and therefore it's an abstract base class. However, pointers to this abstract base class can be used to point to objects of derived classes. Here you have the complete example: // abstract base class
20
113
Object Oriented Programming Using C++
#include
10
using namespace std;
class CPolygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } virtual int area (void) =0; };
class CRectangle: public CPolygon { public: int area (void) { return (width * height); } };
class CTriangle: public CPolygon { public: int area (void) { return (width * height / 2); } };
114
Object Oriented Programming Using C++
int main () { CRectangle rect; CTriangle trgl; CPolygon * ppoly1 = ▭ CPolygon * ppoly2 = &trgl; ppoly1->set_values (4,5); ppoly2->set_values (4,5); cout << ppoly1->area() << endl; cout << ppoly2->area() << endl; return 0; } If you review the program you will notice that we refer to objects of different but related classes using a unique type of pointer (CPolygon*). This can be tremendously useful. For example, now we can create a function member of the abstract base class CPolygon that is able to print on screen the result of the area() function even though CPolygon itself has no implementation for this function: // pure virtual members can be called
20
// from the abstract base class
10
#include
class CPolygon { protected: int width, height; public:
115
Object Oriented Programming Using C++
void set_values (int a, int b) { width=a; height=b; } virtual int area (void) =0; void printarea (void) { cout << this->area() << endl; } };
class CRectangle: public CPolygon { public: int area (void) { return (width * height); } };
class CTriangle: public CPolygon { public: int area (void) { return (width * height / 2); } };
int main () { CRectangle rect; CTriangle trgl; CPolygon * ppoly1 = ▭ CPolygon * ppoly2 = &trgl;
116
Object Oriented Programming Using C++
ppoly1->set_values (4,5); ppoly2->set_values (4,5); ppoly1->printarea(); ppoly2->printarea(); return 0; } Virtual members and abstract classes grant C++ the polymorphic characteristics that make object-oriented programming such a useful instrument in big projects. Of course, we have seen very simple uses of these features, but these features can be applied to arrays of objects or dynamically allocated objects. Let's end with the same example again, but this time with objects that are dynamically allocated: // dynamic allocation and polymorphism
20 10
#include
class CPolygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } virtual int area (void) =0; void printarea (void)
117
Object Oriented Programming Using C++
{ cout << this->area() << endl; } };
class CRectangle: public CPolygon { public: int area (void) { return (width * height); } };
class CTriangle: public CPolygon { public: int area (void) { return (width * height / 2); } };
int main () { CPolygon * ppoly1 = new CRectangle; CPolygon * ppoly2 = new CTriangle; ppoly1->set_values (4,5); ppoly2->set_values (4,5); ppoly1->printarea(); ppoly2->printarea(); delete ppoly1; delete ppoly2;
118
Object Oriented Programming Using C++
return 0; } Notice that the ppoly pointers: CPolygon * ppoly1 = new CRectangle; CPolygon * ppoly2 = new CTriangle; are declared being of type pointer to CPolygon but the objects dynamically allocated have been declared having the derived class type directly. Input/Output with Files C++ provides the following classes to perform output and input of characters to/from files: • • •
ofstream: Stream class to write on files ifstream: Stream class to read from files fstream: Stream class to both read and write from/to files.
These classes are derived directly or indirectly from the classes istream, and ostream. We have already used objects whose types were these classes: cin is an object of class istream and cout is an object of class ostream. Therfore, we have already been using classes that are related to our file streams. And in fact, we can use our file streams the same way we are already used to use cin and cout, with the only difference that we have to associate these streams with physical files. Let's see an example: // basic file operations
[file example.txt]
#include
Writing this to a file
#include
int main () {
119
Object Oriented Programming Using C++
ofstream myfile; myfile.open ("example.txt"); myfile << "Writing this to a file.\n"; myfile.close(); return 0; } This code creates a file called example.txt and inserts a sentence into it in the same way we are used to do with cout, but using the file stream myfile instead. But let's go step by step: Open a file The first operation generally performed on an object of one of these classes is to associate it to a real file. This procedure is known as to open a file. An open file is represented within a program by a stream object (an instantiation of one of these classes, in the previous example this was myfile) and any input or output operation performed on this stream object will be applied to the physical file associated to it. In order to open a file with a stream object we use its member function open(): open (filename, mode); Where filename is a null-terminated character sequence of type const char * (the same type that string literals have) representing the name of the file to be opened, and mode is an optional parameter with a combination of the following flags: ios::in Open for input operations. ios::out Open for output operations. ios::binary Open in binary mode. Set the initial position at the end of the file. ios::ate If this flag is not set to any value, the initial position is the beginning of the file. All output operations are performed at the end of the file, appending the ios::app content to the current content of the file. This flag can only be used in streams open for output-only operations. If the file opened for output operations already existed before, its previous ios::trunc content is deleted and replaced by the new one.
120
Object Oriented Programming Using C++ All these flags can be combined using the bitwise operator OR (|). For example, if we want to open the file example.bin in binary mode to add data we could do it by the following call to member function open(): ofstream myfile; myfile.open ("example.bin", ios::out | ios::app | ios::binary); Each one of the open() member functions of the classes ofstream, ifstream and fstream has a default mode that is used if the file is opened without a second argument: class default mode parameter ofstream ios::out ifstream ios::in fstream ios::in | ios::out For ifstream and ofstream classes, ios::in and ios::out are automatically and respectivelly assumed, even if a mode that does not include them is passed as second argument to the open() member function. The default value is only applied if the function is called without specifying any value for the mode parameter. If the function is called with any value in that parameter the default mode is overridden, not combined. File streams opened in binary mode, perform input and output operations independently of any format considerations. Non-binary files are known as text files, and some translations may occur due to formatting of some special characters (like newline and carriage return characters) Since the first task that is performed on a file stream object is generally to open a file, these three classes include a constructor that automatically calls the open() member function and has the exact same parameters as this member. Therefor, we could also have declared the previous myfile object and conducted the same opening operation in our previous example by writing: ofstream myfile ("example.bin", ios::out | ios::app | ios::binary); Combining object construction and stream opening in a single statement. Both forms to open a file are valid and equivalent. To check if a file stream was successful opening a file, you can do it by calling to member is_open() with no arguments. This member function returns a bool value of true 121
Object Oriented Programming Using C++ in the case that indeed the stream object is associated with an open file, or false otherwise: if (myfile.is_open()) { /* ok, proceed with output */ }
Closing a file When we are finished with our input and output operations on a file we shall close it so that its resources become available again. In order to do that we have to call the stream's member function close(). This member function takes no parameters, and what it does is to flush the associated buffers and close the file: myfile.close(); Once this member function is called, the stream object can be used to open another file, and the file is available again to be opened by other processes. In case that an object is destructed while still associated with an open file, the destructor automatically calls the member function close(). Text files Text file streams are those where we do not include the ios::binary flag in their opening mode. These files are designed to store text and thus all values that we input or output from/to them can suffer some formatting transformations, which do not necessarily correspond to their literal binary value. Data output operations on text files are performed in the same way we operated with cout: // writing on a text file
[file example.txt]
#include
This is a line.
#include
This is another line.
using namespace std;
int main () { ofstream myfile ("example.txt");
122
Object Oriented Programming Using C++
if (myfile.is_open()) { myfile << "This is a line.\n"; myfile << "This is another line.\n"; myfile.close(); } else cout << "Unable to open file"; return 0; } Data input from a file can also be performed in the same way that we did with cin: // reading a text file
This is a line.
#include
This is another line.
#include
int main () { string line; ifstream myfile ("example.txt"); if (myfile.is_open()) { while (! myfile.eof() ) {
123
Object Oriented Programming Using C++
getline (myfile,line); cout << line << endl; } myfile.close(); }
else cout << "Unable to open file";
return 0; } This last example reads a text file and prints out its content on the screen. Notice how we have used a new member function, called eof() that returns true in the case that the end of the file has been reached. We have created a while loop that finishes when indeed myfile.eof() becomes true (i.e., the end of the file has been reached). Checking state flags In addition to eof(), which checks if the end of file has been reached, other member functions exist to check the state of a stream (all of them return a bool value): bad() Returns true if a reading or writing operation fails. For example in the case that we try to write to a file that is not open for writing or if the device where we try to write has no space left. fail() Returns true in the same cases as bad(), but also in the case that a format error happens, like when an alphabetical character is extracted when we are trying to read an integer number. eof() Returns true if a file open for reading has reached the end. good() It is the most generic state flag: it returns false in the same cases in which calling any of the previous functions would return true.
124
Object Oriented Programming Using C++ In order to reset the state flags checked by any of these member functions we have just seen we can use the member function clear(), which takes no parameters. get and put stream pointers All i/o streams objects have, at least, one internal stream pointer: ifstream, like istream, has a pointer known as the get pointer that points to the element to be read in the next input operation. ofstream, like ostream, has a pointer known as the put pointer that points to the location where the next element has to be written. Finally, fstream, inherits both, the get and the put pointers, from iostream (which is itself derived from both istream and ostream). These internal stream pointers that point to the reading or writing locations within a stream can be manipulated using the following member functions: tellg() and tellp() These two member functions have no parameters and return a value of the member type pos_type, which is an integer data type representing the current position of the get stream pointer (in the case of tellg) or the put stream pointer (in the case of tellp). seekg() and seekp() These functions allow us to change the position of the get and put stream pointers. Both functions are overloaded with two different prototypes. The first prototype is: seekg ( position ); seekp ( position ); Using this prototype the stream pointer is changed to the absolute position position (counting from the beginning of the file). The type for this parameter is the same as the one returned by functions tellg and tellp: the member type pos_type, which is an integer value. The other prototype for these functions is: seekg ( offset, direction ); seekp ( offset, direction ); Using this prototype, the position of the get or put pointer is set to an offset value relative to some specific point determined by the parameter direction. offset is of the member type off_type, which is also an integer type. And direction is of type seekdir, which is an
125
Object Oriented Programming Using C++ enumerated type (enum) that determines the point from where offset is counted from, and that can take any of the following values: ios::beg offset counted from the beginning of the stream offset counted from the current position of the stream ios::cur pointer ios::end offset counted from the end of the stream The following example uses the member functions we have just seen to obtain the size of a file: // obtaining file size
size is: 40 bytes.
#include
int main () { long begin,end; ifstream myfile ("example.txt"); begin = myfile.tellg(); myfile.seekg (0, ios::end); end = myfile.tellg(); myfile.close(); cout << "size is: " << (end-begin) << " bytes.\n"; return 0; }
Binary files In binary files, to input and output data with the extraction and insertion operators (<< and ]>>) and functions like getline is not efficient, since we do not need to format any
126
Object Oriented Programming Using C++ data, and data may not use the separation codes used by text files to separate elements (like space, newline, etc...). File streams include two member functions specifically designed to input and output binary data sequentially: write and read. The first one (write) is a member function of ostream inherited by ofstream. And read is a member function of istream that is inherited by ifstream. Objects of class fstream have both members. Their prototypes are: write ( memory_block, size ); read ( memory_block, size ); Where memory_block is of type "pointer to char" (char*), and represents the address of an array of bytes where the read data elements are stored or from where the data elements to be written are taken. The size parameter is an integer value that specifies the number of characters to be read or written from/to the memory block. // reading a complete binary file
the complete file content is in memory
#include
ifstream::pos_type size; char * memblock;
int main () { ifstream file ("example.txt", ios::in| ios::binary|ios::ate); if (file.is_open()) { size = file.tellg(); memblock = new char [size]; file.seekg (0, ios::beg);
127
Object Oriented Programming Using C++
file.read (memblock, size); file.close();
cout << "the complete file content is in memory";
delete[] memblock; } else cout << "Unable to open file"; return 0; } In this example the entire file is read and stored in a memory block. Let's examine how this is done: First, the file is open with the ios::ate flag, which means that the get pointer will be positioned at the end of the file. This way, when we call to member tellg(), we will directly obtain the size of the file. Notice the type we have used to declare variable size: ifstream::pos_type size; This is the correct way of declaring size since ifstream::pos_type is the type returned by file.tellg(). But since this type is an integer type, it can be converted to an int. Therefore, we could also declare size as a variable of type int or some other integer type capable of holding the size of a file and call tellg as: int size; size = (int) file.tellg(); Once we have obtained the size of the file, we request the allocation of a memory block large enough to hold the entire file:
128
Object Oriented Programming Using C++
memblock = new char[size]; Right after that, we proceed to set the get pointer at the beginning of the file (remember that we opened the file with this pointer at the end), then read the entire file, and finally close it: file.seekg (0, ios::beg); file.read (memblock, size); file.close(); At this point we could operate with the data obtained from the file. Our programs simply announces that the content of the file is in memory and then terminates. Buffers and Synchronization When we operate with file streams, these are associated to an internal buffer of type streambuf. This buffer is a memory block that acts as an intermediary between the stream and the physical file. For example, with an ofstream, each time the member function put (which writes a single character) is called, the character is not written directly to the physical file with which the stream is associated. Instead of that, the character is inserted in that stream's intermediate buffer. When the buffer is flushed, all the data contained in it is written to the physical medium (if it is an output stream) or simply freed (if it is an input stream). This process is called synchronization and takes place under any of the following circumstances: •
• • •
When the file is closed: before closing a file all buffers that have not yet been flushed are synchronized and all pending data is written or read to the physical medium. When the buffer is full: Buffers have a certain size. When the buffer is full it is automatically synchronized. Explicitly, with manipulators: When certain manipulators are used on streams, an explicit synchronization takes place. These manipulators are: flush and endl. Explicitly, with member function sync(): Calling stream's member function sync(), which takes no parameters, causes an immediate synchronization. This function returns an int value equal to -1 if the stream has no associated buffer or in case of failure. Otherwise (if the stream buffer was successfully synchronized) it returns 0.
129