Recitation 4

These notes were prepared by Petros Komodromos.

Topics

  1. Inheritance: public, protected and private derivation
  2. Multiple inheritance
  3. Inheritance: constructors and destructors 
  4. Inheritance: redefining member functions 
  5. Virtual functions and polymorphism
  6. Abstract classes
  7. File streams

Appendix: Extra Material

  1. Namespaces 
  2. Assertions 
  3. C++ standard library string class
  4. Other topics

1. Inheritance: Public, Protected and Private Derivation

Inheritance is the ability to create a new class, called derived class, from an existing one, called base class. The derived class is also called subclass of the base class, which in turn is called superclass of the derived class. A derived class inherits all the data members and member functions of its superclasses, and, in general, it implements an is-a relationship. In contrast, a has-a relationship is implemented using a class object as a member of another class. New members can be defined in the derived class refining the definitions of its superclasses. Inheritance can be used to extend the capabilities of a base class. Classes can be derived by classes that are themselves derived and this process can be extended to an arbitrary number of levels of inheritance.

An object inherits data members and member functions from the superclass  of its class, and from all superclasses of that class. Member functions  of a derived class can only access members of its base class that are declared  in the public or protected part, i.e. private members cannot be accessed. The derived class methods invoked by a derived class object can access only the protected members that have been inherited from the base class of the particular object that invoked the member function (i.e. *this), or any other object of the derived class. The derived class member functions have also access to all private and protected, as well as public of course, members of its own class.

The derived member functions and data of a derived class object, can be accessed directly as if they were members of the derived class and not of the base class, as long as access is permitted. However, if the same name is used for both a member of the base class and a member of the derived class, the member of the derived class hides the corresponding member of the base class. By using the class scope resolution operator, the base class member, instead of the derived one, can be explicitly accessed. If the base class has a static member data then there will be only one instance of that regardless of how many subclasses have been derived from the base class and how many objects have been created.

To specify the base class of a derived class a colon is used after the name of the class, at its definition, followed by a specifier that defines the type of inheritance and the name of the superclass. The expression after the colon, which is used to specify the inheritance, is called class derivation list. To define a subclass using public derivation the keyword public should be used. In addition to public derivations, we can use protected and private class derivations. The following example shows a public derivation of a class.

class derivedClassName :  public  baseClassName {     ........... };

The colon (:) specifies that the derivedClassName class is derived from the baseClassName class. The derived class inherits the member variables and functions of the class from which it is derived, as well as the member variables and functions of all superclasses of that class. 

The access to specific member variables and functions of the superclasses of a class are specified according to the location where they have been defined (i.e. in which section of the class definition) in those superclasses, and the way that the subclasses are derived, i.e. which of the keywords public, protected, and private has been used during the derivations. The access to members of a superclass and the "cost" of that access do not depend on the depth of the inheritance tree.

With a public derivation of a subclass all member variables and functions of the superclass retain their status in the derived subclass, i.e. a public member remains public allowing (unlimited) access to everyone.

Using a private derivation, all public and protected members of the superclass become private in the derived class, i.e. a public member of the superclass within the derived class can only be accessed by the members of the derived class.

Finally, with protected derivation all the public parts of the superclass become protected in the derived class. A protected member data, or function, can be accessed only by member functions of the class and certain subclasses of the class, or by friend functions.

Therefore, when deriving a subclass we can reduce the access privileges using private or protected derivation. The access specifier (public, protected, and private) defines the type of access of the members inherited by the derived class from its base class. The access is specified based on which part (public, protected, and private) the declaration of a member data and function is declared. The protected part is used to allow access to the members declared in there only to members of that class and any subclasses.

The derived class has no access to the private members of the base class unless it is declared to be a friend of the base class. Note that friendship is not inherited, i.e. a subclass of a derived class that has been declared a friend of its own base class, is not considered a friend of its base class superclass. Friendship must be explicitly granted from each class to all classes that should have access privileges to that class.

The access level to a member that is inherited from a superclass can be adjusted to the access it has in the superclass, instead of following the rules based on the keyword used in the subclass derivation, by declaring the member in the public, or protected, access section accordingly using the name of the superclass followed by the class scope resolution operator and the name of the member. However, any attempt to change the access status of a base class member is invalid.

/* Example on public, protected and private derivation */

class Point { private:   double x; protected:   double y; public:   double z;   Point(double,double,double);  };

Point::Point(double xx=0, double yy=0, double zz=0) {    x = xx ;    y = yy ;    z=zz; }

                                            // Alternative derivations   class Voxel : public Point                            // all members retain their status                                                                    // in the derived class //class Voxel : protected Point                 // public members become protected                                                               // in the derived class //class Voxel : private Point         // all members become private in the derived class { private:   int color;

public:   Point::z;                     // e.g. declared in the public section to                                    // adjust the access of z to public   Voxel(double x, double y, double z, int col);   void print(); };

Voxel::Voxel() {    color=0; }

Voxel::Voxel(double x=0, double y=0, double z=0, int col) : Point(x,y,z) {    color = col; }

void Voxel::print() {                                  //  x is always not accessible since x is private    cout << y ;             // Accessible but if the derivation is private it                                    // becomes private in the derived class    cout <<  z ;                //  Accessible but it becomes whatever is                                      // the derivation in the derived class }

void main() {                 Voxel v(4,7,2,101110101);                 v.print(); }

2. Multiple Inheritance

A class may inherit the member variables and functions from more than one superclasses, i.e. a derived class may have multiple base classes. Then, all superclasses are defined after the colon separated by commas and with the indication of the type of derivation the should be used, i.e. one of the access specifiers: public, protected, or private. There is no limit on the number of the base classes that a derived class inherits from. The base class constructors are invoked in the order that appear in the class derivation list, while the destructors are invoked in the reverse order.

/* Example: on multiple inheritance */

class People {  public:    char first_name[20];    char last_name[20];    int age; };

class Student : public People {   public:    int student_id; };

class Staff {   private:    int social_sec_num; };

class Faculty : public People, protected Staff {  private:    int num_papers; ..... };

If  a member function is hidden by another member function we  can explicitly specify which one we want to be used by putting the name  of the class and two colons in front of the name of the member function.

className::functionName(...)

3. Inheritance: Constructors and Destructors

Although a derived class inherits the member data and functions of its base class, the constructors of the base class are not inherited. Therefore, the derived class needs to provide its own constructors which are called after the base class and member object constructors are called. When a derived class object is created, first a base class constructor is automatically invoked, typically to initialize the member data that correspond to the base class, and then the derived class constructor is called to initialize the additional data members of the derived class. The constructors of the superclasses/subclasses chain are executed in a top-down order, i.e. a base class constructor, if executed, is executed prior to the derived class construction.

A new set of constructors is typically provided for a derived class to make the proper initialization and, if necessary, call the proper superclass constructor with certain arguments. The derived class constructor is used to initialize the members that have been added by the derived class, while the superclass constructor(s) should take care of the corresponding classes data members.

A specific constructor of a superclass can be invoked by providing after the header of the constructor definition a colon followed by the name of the superclass (i.e. the constructor name) and the desired arguments with which it is to be called. This is calledmember initialization list and it can be used to pass arguments to the constructor of the base class. The order of the comma separated member initialization list does not affect the order of construction invocation. The order in which the constructors are invoked is: first, the base class constructor is called and in case of multiple inheritance the order is according to the order that has been used in the class derivation list. Next the constructors of member objects are called in the order in which the members have been defined in the derived class definition. Finally, the derived class constructor is called. A derived class constructor can invoke a constructor only of its direct superclass.

If the derived class has constructors but the base class has not, then the proper derived class constructor is called every time a derived class object is defined.

In the opposite case, i.e. the derived class having no constructors while the base class has, the base class must have a default constructor which is automatically invoked whenever a derived class object is defined.

A derived class constructor needs to explicitly invoke one of the base class constructors, if the base class has constructors but not a default constructor, i.e. a derived class constructor must explicitly invoke one of the base class’ constructors in its header. Alternatively, a default constructor can be provided for the base class. Then, if no base class constructor is explicitly invoked, the default constructor is automatically invoked whenever an object of the derived class is defined.

If a class is used as a base class only to be able to define the subclasses and there is no intention to have objects of that class, the constructors of the base class can be defined as protected, which restricts their access to the derived class (constructors). A class that has no actual objects, instances of itself, is called an abstract base class.

The following example demonstrates how the constructors of the class and its superclass are invoked and how a specific constructor of a superclass can be explicitly called with certain arguments.

/* Example on constructors calling other constructors */

class Point { private:     double x, y; public:   Point();   Point(double,double);  };

Point::Point() {    x = 0 ;   y = 0 ; }

Point::Point(double xx, double yy) {    x = xx ;   y = yy ; }

class Pixel : public Point { private:   int color; public:   Pixel();   Pixel(double x, double y, int col); };

Pixel::Pixel() {   color=0; }

Pixel::Pixel(double x, double y, int col) : Point(x,y) {   color = col; }

void main ( ) {   Point p(1.7,7.2);              //  The Point::Point(double xx=0, double yy=0)                                             // is called with (1.7,7.2)

  Pixel px1;                      //  The default constructors, Point::Point()                                          // and then  Pixel::Pixel(), are called

  Pixel px2(2.75, 8.23, 111000101);                        // The Point::Point(double xx, double yy) constructor and then                        // the Pixel::Pixel(double x, double y, int col) are called }

In the above example the expression: Point(x,y) after the parameters in the header of the Pixel constructor causes the invocation of a specific constructor of the class Point.

Similarly, the derived class, member object class and base class destructors are invoked as soon as the lifetime of an object (of the derived class) reaches its end. In contrast to the constructors that are invoked in a top-down order starting from the base class first, the destructors are called in the reverse order, invoking first the derived class destructor, and then, its member objects constructors, and, finally, the superclass constructor, etc. In addition, although constructors may not be virtual, destructors may be virtual allowing the invocation of the destructor of the class derived for the object pointed to by a pointer (of base class data type). The reverse order is used so as to ensure that the most recently allocated memory is the first to be released.

4. Inheritance: Redefining Member Functions

A member function of a superclass can be redefined (with the same name) in a subclass and, depending on which object invokes it, the corresponding one is invoked. When an object of the derived class is used to invoke a function, the search to find the member function starts from the derived class definition, which results in invocation of the derived class member function if it is available, unless a class scope resolution operator is used to explicitly specify whose class the member function should be called. A specific member function of a superclass can be called by using the name of the superclass followed by the class scope resolution operator before the name of the class to be invoked, since the member of the derived class hides the inherited member. The invocation, i.e. the explicit call of a member function of a superclass, can also be achieved from inside the body of the member function of the subclass. i.e. if the member function of a superclass is hidden by a member function of the subclass, the class scope resolution operator can be used to explicitly specify whose class the member function should be invoked.

The member functions of any superclass and subclass can be overloaded as any other set of functions in a certain scope. Note that the member functions of a base class and the member functions of its derived class do not all together make up a set of overloaded member functions, because the former are considered to be in the base class scope, while the latter in the derived class scope.

The previous example has been extended, as shown below, to show how to explicitly call a superclass member function.

/* Example on redefining and invoking member functions */

class Point { private:     double x, y; public:   Point();   Point(double,double);   void print(); };

Point::Point() {   x = 0 ;   y = 0 ; }

Point::Point(double xx, double yy) {   x = xx ;   y = yy ; }

void Point::print() {   cout << " (x,y) = (" << x << "," << y << ")  "  ; }

class Pixel : public Point { private:   int color; public:   Pixel();   Pixel(double x, double y, int col);   void print(); };

Pixel::Pixel() {   color=0; }

Pixel::Pixel(double x, double y, int col) : Point(x,y) {   color = col; }

void Pixel::print() {   this -> Point::print();                    // the member function print()                                                       // of class Point is called   cout << " color = " << color; }

int main ( ) {   Pixel px1;   cout << "\n Pixel px1:" ;   px1.print();                                     // the member function print()                                                        // of class Pixel is called   cout << endl;

  Pixel px2(2.75, 8.23, 111000101);   cout << " Pixel px2:" ;   px2.Point::print();                            // the member function print()                                                         // of class Point is called

  cout << endl;   return EXIT_SUCCESS; }

Output

 Pixel px1: (x,y) = (0,0)   color = 0  Pixel px2: (x,y) = (2.75,8.23)

5. Virtual Functions and Polymorphism

Virtual functions allow dynamic (or late) binding, which means that the selection of which function to call is done during execution, rather than during compilation. This provides the flexibility to perform the same kind of action on different types of objects as long as they are all instances of classes of or derived from, a superclass whoce function  is defined as virtual. The selection of which of the virtual functions to invoke is done at run-time. In contrast the resolution of a non-virtual function is done by the compiler during compilation and the process is called static binding. A non-virtual member function is invoked using implicitly the pointer this which is of a certain data type. If a pointer to a base class object is used to invoke the function, even if the pointer stores the address of a derived object, the base class member function will be called.

Polymorphism is the ability of having a member function, or an operator overloading function, that behave differently on different types of data. It is the ability of dynamic (i.e. run-time) binding of a pointer of a base class to a method, based on what is stored in the memory pointed to by the pointer and not the data type of the pointer. This is possible by the ability of a pointer to a base class to point not only to base class objects, but also to any object of any of its subclasses. In contrast, a pointer to a derived class object cannot point to a base class object unless explicit casting is used. The decision of which function is invoked is delayed until the run-time, instead of being made during compilation as in non-virtual functions. However, polymorphism which is a major characteristic of object-oriented programming can be used only when pointers (or references) are used and not actual objects.

A function is defined as virtual by preceding the return data type at the member function declaration of the base class with the keyword virtual, e.g. virtual void print(void); Declaring a member function of a class as virtual, the corresponding member functions in all that class’s subclasses are automatically considered to be virtual. However, the keyword virtual, although optional, is typically used also in the derived classes at the corresponding virtual function declarations to clarify the nature of the function. The keyword virtual should be used only in the function declarations and not at external definitions of the defined functions. The keyword virtual indicates that the selection of which function to invoke should be delayed until run time and be based on the data type of the object that is pointed to by the pointer that invoked the member function.

A derived class does not need to redefine a member function that has been indicated as virtual in its base class. In that case it inherits the member function from the base class. A virtual function that is redefined in a derived class must have the same signature as the base class function. Otherwise it will simply hide the base class function and compile-, rather than run-, time binding will be used. It is wrong to provide in a derived class a member function with the same signature as the virtual function declared in the base class but with different return data type. The only exception is to have as return data type the address or reference of a derived class object instead of a base class object.

/* Simple example on virtual functions */

#include <iostream.h> #include <stdlib.h>

class MyBase { public:

  void print()   {     cout << "\n Printing through the base class: MyBase" << endl;   }

  virtual void print(int i)   {     cout << "\n Printing through the base class: MyBase:"   << " i = " << i << endl;   } };  

class MyDerived : public MyBase { public:

  void print()   {     cout << "\n Printing through the derived class: MyDerived" << endl;   }

  virtual void print(int i)   {     cout << "\n Printing through the derived class: MyDerived:"   << " i = " << i << endl;   } };  

int main(void) {   MyBase b;   MyDerived d;

  b.print();   d.print();

  MyBase *pb=&d;   MyDerived *pd=&d;

  pb -> print();   pd -> print();

  pb -> print(1);   pd -> print(2);

  return EXIT_SUCCESS; }

Output

 Printing through the base class: MyBase  Printing through the derived class: MyDerived

 Printing through the base class: MyBase  Printing through the derived class: MyDerived

 Printing through the derived class: MyDerived: i = 1  Printing through the derived class: MyDerived: i = 2  

A pointer to an object of a certain class can point not only to any object of that class but also to any object of that class’ subclasses. Therefore, we may have an array of pointers to a base class which are used to point to objects of any of the base class’ subclasses. Having defined a virtual function, a pointer to the base class can be used to point to an object of the base or any of its subclasses object, and the decision which member function to invoke depends on the current contents of the pointer, i.e. to which class object it points to, rather than its (the pointer’s) data type. In contrast a pointer to a derived class cannot point to an object of the base class unless it is an explicit cast is used. If a pointer to a base class stores the address of a derived class object and both base and derived class have a non-virtual function, or data, with the same name, the base class member function, or data, is selected during static binding.

If the virtual member function is never expected to be used with an object of the base class, it can be specified as pure virtual function by providing instead of the body of the function an assignment to 0, e.g. virtual void print(void) = 0; Then, if the function is called run-time error will occur, since it is not intended to be invoked, but it is only provided to allow derived functions to define the actual functions, which will be called based on the contents of the memory pointed by the pointer that invokes the virtual function.

The class scope resolution operator may be used to disable the virtual mechanism and explicitly invoke the member function of a certain class. Such explicit invocation is resolved at compile, rather than at run, time. Declaring a pure virtual function results in no consideration of it during the virtual mechanism resolution, i.e. it cannot be invoked through the virtual mechanism, and specifies the class to be an abstract base class. However, a definition for a pure virtual function may be provided (i.e. the pure virtual function may be defined) and the function may be statically invoked (i.e during compile time).

If a pointer to a class is used to point to objects of subclasses of that class, the destructor of the class must be declared as virtual, to ensure that proper deallocations of memory occur when an object is deleted.

Although a constructor may not be declared as virtual, a destructor can be a virtual function. The reason for having a destructor declared as virtual is that if the dynamically allocated memory for a derived class is assigned to a pointer to a base class object, then the base class destructor will be called instead of the derived class destructor resulting in a memory leak. Therefore, it is good to declare as virtual the destructor of the base class if any virtual functions are used and especially when dynamic memory allocation is used. When the destructor is virtual then the order of destructor invocations starts with the derived class and continues with the destructors of its superclasses. Also a virtual function may not be static, since a virtual member function needs to be associated with a particular object of a class rather than the class as a whole.

Another use of the keyword virtual is to declare a base class as virtual. This is useful when a derived class inherits from multiple (direct) superclasses that happen to have already inherited from a common superclass higher in the class hierarchy. Then, the derived class inherits multiple times from the same (the common class to its superclasses) base class. To avoid this we can use the keyword virtual at the derivation of its superclasses, as shown below:

class MyBase { .......}; class MySuper1: public virtual MyBase { .......}; class MySuper2: public virtual MyBase { .......}; class MyDerived: public MySuper1, public MySuper2 { .......};

The use of virtual base class (as above), which is called virtual inheritance, allows the inheritance and sharing of a single base class sub-object instead of having unecessary multible copies of the base class whenever the base class occurs in the derivation hierarchy. Virtual inheritance avoids duplications of the base class sub-objects and ambiguities that rise with such duplicates. However, there is a performance and complexity impact when using virtual inheritance.

6. Abstract Classes

A class that is used as a general base class to derive other classes, without any instances of that class ever being created, is called an abstract class. A class can be made abstract by declaring one or more of its member functions of the class as a pure virtual function(s). This is achieved by setting to zero the declaration of the function. then, the member function will not be considered when a function of the same signature is called, rather one of the derived class functions will be called.

e.g.:  virtual void print(void) = 0;

An abstract class needs to have a derived class, i.e. it is invalid to define an object of an abstract class. A set of functions are typically defined aspure virtual functions in the base class to provide a common public interface for any current or future derived classes. A member function of an abstract base class is not ever intended to be called, as no instance of the abstract base class is ever anticipated. An attempt to define an object of an abstract base class results in a compile-time error.  

7. File Streams

Although the easiest way to read from a file or to write to a file is using redirection, the direct way to open a file and read from or write to it is using the file-handling library of C++. The declarations of the library are in the header file fstream.h, which must be included in a program so as to be able to use input and output streams to a file.

To create an input stream, i.e. open an input file for reading, the following definition should be used, which instantiates an ifstream object:

ifstream inputStreamName ("fileName");

Then, the inputStreamName can be used instead of the input operator cin to read from a file named fileName, instead from the standard input.

Similarly to write to a file, i.e open an output file for writing to it, the following definition should be used, which instantiates an ofstream object:

ofstream outputStreamName ("fileName");

Then, the outputStreamName can be used instead of the output operator cout to write to a file named fileName instead to the standard output.

After using the ifstream, or ofstream, object to read from, or write to, a file, the file should be closed when access to it is no longer needed. A file can be closed using the member function close(), i.e.     inputStreamName.close(); or  outputStreamName.close();

 EOF (end-of-file), which is a constant defined in the iostream.h header file can be used to read data until the end of file (EOF) is reached, by checking whether what was read is equal to EOF (machine dependent). EOF is entered in Unix workstations using <Control-d>.

You may optionally take a look to the following topics, which were not covered in the course.

A1. Namespaces

When several different libraries are used in a program there may be conflicts among identical global names of variables and functions. Namespace definitions can be used to reduce this problem by enclosing the source code (declarations and definitions) in certain namespaces. Each namespace has an associated namespace scope and contains the namespace members, which can be variables, class definitions, functions, etc., i.e. anything that could have been declared in the global scope. A namespace is defined using the keyword namespace followed by the name of the namespace and the declarations and definitions enclosed in curly braces. The definition of a namespace does not need to be contiguous, but it can be provided in several different points, even in different files.

To refer to a namespace member the qualified name notation indicating the namespace is required. The name of the namespace should be provided followed by the name of the member (variable or function) that is to be accessed. In addition, to be able to refer to a member of a namespace it is required to have earlier declared the namespace. Typically, the namespace declaration is provided in a header file which is included everywhere the namespace needs to be used. Since a member variable should be defined only once, the keyword extern should be used in the declaration of the member variables of a namespace. A member of the global namespace can be referred to by using the scope resolution operator (::) without a namespace preceding it. Therefore, it can be used to access global member that are hidden by local ones. Nested namespaces, i.e. defining a namespace within another namespace, are allowed. In that case more than one namespace names and scope resolution operators need to be used to specify the namespace scope.

Namespaces is a recent feature of C++ and not all compilers conform to the corresponding to namespaces C++ standard.

To avoid the need for typing of long names to specify the namespace scope there are two mechanisms that can be used the namespace aliases and theusing directives. However, not all compilers support these mechanisms according to the C++ standard.

Using a namespace alias we can associate a simpler name to an existing (and often long) namespace that we need to use. In particular, we can declare an alias (e.g. MYLIB) for a namespace with a long name (e.g. GraphicsDrawingFunctions) using the following declaration:

namespace MYLIB = GraphicsDrawingFunctions;

Then, to invoke the member function draw of the namespace we can use MYLIB::draw(), instead of GraphicsDrawingFunctions::draw().

The using directive can be used to access members of a namespace without the need to explicitly refer to the specific namespace, i.e. it provides unqualified access to the namespace. A namespace can become visible with a declaration in which the keyword using is used followed by the name of the namespace and the name of a member of the namespace we want to access. If no specific member is given then all members of the namespace become visible, i.e. are considered in the scope in the scope in which the using declaration is used. The next example demonstrates the use of a namespace named NameSpaceTest2 and its declaration (in the file test2.h) and definition (in the file test2.C).

test2.h

namespace NameSpaceTest2 {   extern int x;   extern void print(double d); }

test2.C

#include <iostream.h>

namespace NameSpaceTest2 {   int x = 22; }

namespace NameSpaceTest2 {   void print(double d)     {       cout <<"\n printing through NStest2::print(double d) "     << " d = " << d << endl;     } }  

test1.C

#include <iostream.h> #include <stdlib.h> #include "test2.h"

int x = 11;

void print(int i) {   cout <<"\n printing through ::print(int i):   i = " << i << endl; }

using NameSpaceTest2::print;

main() {   int x = 77;

  cout << "\n x = " << x << endl;   cout << " ::x = " << ::x << endl;   cout << " NameSpaceTest2::x = " << NameSpaceTest2::x << endl;

  print(3);   NameSpaceTest2::print(4);

  print(4.11);   NameSpaceTest2::print(4.22);

  return EXIT_SUCCESS; }  

Output

 x = 77  ::x = 11  NameSpaceTest2::x = 11

 printing through ::print(int i):   i = 3  printing through NStest2::print(double d)  d = 4

 printing through NStest2::print(double d)  d = 4.11  printing through NStest2::print(double d)  d = 4.22

A namespace without defining its name, called unnamed namespace, can be used to define members (functions, classes, and variables) only in a portion of a program without access from other files. An unnamed namespace is defined using the keyword namespace followed by curly braces where all definitions are located. Its members are visible only in that file (scope limited in that file) but have extent until the termination of the program. An unnamed namespace is equivalent to a static global member that is defined and used in one file but cannot be accessed from any other file although its extent lasts until the end of the program.

A special namespace named std has been used to declare and define all components of the C++ standard library. However, many compilers do not support this feature. All members of this namespace can become visible with the following statement:    using namespace std;

A.2. Assertion

Assertions are used in a program as conditions that must be true in order to ensure correctness of the program. They can be used as preconditions, postconditions, and invariants, to verify that a condition is true, e.g. in the entrance, exit, or anywhere within a function.

To use assertions the header file assert.h must be included and the preprocessor macro assert() can be used to check whether a condition is true. If the assertion fails the program terminates providing information about the error that occurred.

The following example demonstrates how assertions can be used to check whether a file has properly opened for reading. In this case, it was attempted to open a nonexistent file and the assert which checks whether the pointer to a file is not equal to null fails resulting in a program termination.

/* Example on the use of assertions */  

#include <iostream.h> #include <fstream.h> #include <stdlib.h> #include <assert.h>  

int main() {   char str1[]="existing";   system("ls>existing");   // Using system() an OS command can be executed   ifstream ifp1 (str1);   assert(ifp1);           // line 14   cout << "\n File " << str1 << " has been opened properly" << endl;

  char str2[]="nonexisting";   ifstream ifp2 (str2);   assert(ifp2);           // line 19   cout << "\n File " << str2 << " has been opened properly" << endl;

  return EXIT_SUCCESS; }    

Output

assertions.C:19: failed assertion ‘ifp2’ Abort

A.3. C++ Standard Library String Class

A string class, that has several convenient and object-oriented capabilities, is provided by the C++ Standard library. In order to use it, the string header file needs to be included, the following example shows how an object of this class can be defined and used, and how it can be combined with the more traditional C Standard library string which is represented as an array of characters.

/* Example on the C++ standard library string class */

#include <iostream.h> #include <iomanip.h> #include <cstring> #include <string>

int main(void) {   string str1("Testing"), str2;   string str3(str1);   char str4[] = "MIT";   const char *str5 = "";

  cout << "\n str1: " << setw(20) << str1 << "\t size = " << str1.size();   str1.empty() ?  cout << "\t (empty)" << endl : cout << endl ;

  cout << " str2: " << setw(30) << str2 << "\t size = " << str2.size();   str2.empty() ?  cout << "\t (empty)" << endl : cout << endl ;

  cout << " str3: " << setw(20) << str3 << "\t size = " << str3.size();   str3.empty() ?  cout << "\t (empty)" << endl : cout << endl ;

  cout << " str4: " << setw(20) << str4 << "\t size = " << strlen(str4);   strlen(str4) ?  cout << endl : cout << "\t (empty)" << endl ;

  cout << " str5: " << setw(20) << str5 << "\t size = " << strlen(str5);   strlen(str5) ?  cout << endl : cout << "\t (empty)" << endl ;

  if(str1==str3)     str2 = str1 + str4 ;   str2 += str3 ;   str2[10] = 't' ;

  cout << " str2: " << setw(15) << str2 << "\t size = " << str2.size();   str2.empty() ?  cout << "\t (empty)" << endl : cout << endl ;

  return 1; }

Output

 str1: Testing                   size = 7  str2:                           size = 0        (empty)  str3: Testing                   size = 7  str4:                  MIT      size = 3  str5:                           size = 0        (empty)  str2: TestingMITtesting         size = 17< /EM >

A.4. Linkage Specifications: extern "C"

An existing compiled C function may be incorporated in a C++ program and used if a declaration of the function with extern "C" preceding its return data type is provided. e.g.: extern "C" double fun(int, double); 

Command line arguments

To be able to use command-line arguments, i.e. provide arguments while executing a program main must be declared as: main(int argc, char *argv[ ]){........}, where argc is the number of command-line arguments and argv is an array of strings each of them corresponding to a command-line argument.