3.17 IMPLEMENTING OO BEHAVIOR IN C PROGRAMS


3.17 IMPLEMENTING OO BEHAVIOR IN C PROGRAMS

Yes, it is possible to capture, albeit in a somewhat limited sense, the three important elements of OO behavior-encapsulation, inheritance, and polymorphism-in purely C code. It is also educational to see how this can be done, in the same sense that it is educational to build a model airplane to gain insights into the workings of a real airplane. Actually, in their power and functionality the OO programs in C go beyond what would be suggested by this airplane-based analogy. Recent years have seen the emergence of a powerful package of freely available software called GNOME that is based on simulating object-orientation directly in C.GNOME, which stands for GNU Object Modeling Environment, is meant for designing graphical user interfaces for Linux/Unix based platforms.

Before showing how a limited version of object orientation can be captured directly in a C program, we need a data structure that can be used for a class. An obvious candidate for that is a C structure. But, of course, a structure in C does not directly provide us with a mechanism for data encapsulation, since all the data members of a structure object are directly accessible to the rest of the program wherever the object is in scope. Nonetheless, encapsulation with a structure can be achieved by adhering to the convention that a programmer shall access the members of a structure only through functions provided for that purpose. To illustrate this point, consider the following structure

      typedef struct {           char* name;           int age;      } User; 

We could now write the following functions for setting the values of the data members of User and for retrieving them:

      void setUserName( User* user, char aName[] ) {           user->name = malloc( sizeof( strlen( aName ) + 1 ) );           strcpy( user->name, aName );      }      char* getUserName( User* user ) { return user->name; }      void setUserAge( User* user, int yy ) { user->age = yy; }      int getUserAge( User* user ) { return user->age; } 

If we now insisted on following the convention that only these functions would be used for setting and/or retrieving the values of the members of User, we have achieved encapsulation. Remember, the purpose of encapsulation in OO is to permit a class to hold certain data members private so that objects belonging to other classes would not be able to access them directly, but only through functions designated for such purpose. Obviously, the approach to encapsulation shown here depends entirely on the cooperation of the programmer and would not be suitable for a general-purpose language for OO.

This brings us to the twin issues of inheritance and polymorphism. They can both be addressed by designating a parent structure as the first data member of a child structure. To illustrate, we will now show how one might program in C the parent-child class relationship of Figure 3.7. Here we wish for StudentUser to be a subclass of User. Using the same definition for User as given previously, we could now define StudentUser by


Figure 3.7

      typedef struct {           User user; /* (A) */           char** listOfCourses;           int numCourses;           int whatYear;      } StudentUser; 

Through user, defined as the first data member of type User in line (A), a studentUser object will inherit all of the data members of User. An object of type StudentUser could be stored in the memory in the manner depicted in Figure 3.8. Of course, the data members name and age would not be directly accessible though a pointer of type StudentUser* since they are not directly the data members of the StudentUser type. We have two options to reach those data members:

click to expand
Figure 3.8

      StudentUser* student;      . . . .      . . . .      student->user.name; or      StudentUser* student;      . . . .      . . . .      ( (User*) student )->name; 

In the first case, we access the member name through the data member user defined for the type StudentUser and in the second case we do the same by first casting the student pointer to type User* and reaching the name member directly. As depicted in the figure, both a StudentUser* pointer and the pointer obtained by casting it to User* type point to exactly the same location in the memory. Using the second approach, we can set and get the User members of a StudentUser object by using the setUserName, setUserAge, getUserName, getUserAge functions defined previously for the User type, as in the example below:

      StudentUser* trillianUser;      trillianUser = malloc( sizeof( StudentUser ) );      setUserName( (User*) trillianUser,      "Trillian" );      setUserAge( (User*) trillianUser, 38 );      getUserName( (User*) trillianUser );      getUserAge( (User*) trillianUser ); 

When different structure types are related in the manner shown above, in addition to inheritance they also exhibit polymorphic behavior, provided we use proper casting. Recall, in OO languages polymorphism basically means that we can pass a subclass type where a base class type is needed. In simulated OO in C, if we write a function such as

      int isSenior( User* usr ) {           if ( usr->age > 70 ) return 1;           else return 0;      } 

we can invoke this function with a StudentUser argument provided we cast the argument to its parent type, as in

      StudentUser* trillianUser;      . . . .      isSenior( (User*) trillianUser ); 

This means that all functions defined for the base-class type will work just the same for the subclass type. In addition, polymorphism implies the subclass type can be assigned to a base-class type and the resulting pointer down-cast to the subclass type. For simulated OO in C, these effects can be achieved if we remember to cast as shown below

      StudentUser* xenonStudentUser;      . . .      . . .      User* xenonUser = (User*)      xenonStudentUser;      . . .      . . .      StudentUser* p = (StudentUser*) xenonUser; 

In the source code shown below, we have pulled together the various explanations above into a single program. We start by defining a base type in line (A) and its subtype in line (B). Lines (C) through (G) define functions for the base type User, and lines (H) through (K) do the same for the subtype StudentUser. Note that in line (II), we need access to a base type data member through a subtype pointer.

In main, lines (L) and (M) then declare a base type pointer and a subtype pointer. The base type object declared in line (L) is exercised in the block of statements beginning at line (N). We do the same with the subtype object in the block of statements that begin in line (O). We apply the isSenior() predicate to the base type object in line (P) and to the subtype object in line (Q). Note the polymorphic behavior of this predicate in the invocation in line (Q). The last three statements in main show that the memory addresses of a subtype object, its base slice, and its first data member are all the same.

 
/* SimulatedOO.c */ #include <stdlib.h> #include <string.h> #include <stdio.h> /* base type: */ typedef struct { /* (A) */ char* name; int age; } User; /* subtype */ typedef struct { /* (B) */ User genericUser; char** listOfCourses; int numCourses; int whatYear; } StudentUser; /* function defined for the base type User */ void setUserName( User* user, char aName[] ) { /* (C) */ user->name = malloc( sizeof( strlen( aName ) + 1 ) ); strcpy( user->name, aName ); } /* function defined for the base type User */ char* getUserName( User* user ) { /* (D) */ printf( "\nName of user: %s\n", user->name ); return user->name; } /* function defined for the base type User */ void setUserAge( User* user, int yy ) { /* (E) */ user->age = yy; } /* function defined for the base type User */ int getUserAge( User* user ) { /* (F) */ printf( "Xs's age: %d\n", user->name, user->age ); return user->age; } /* function defined for the base type User */ int isSenior( User* usr ) { /* (G) */ if ( usr->age > 70 ) return 1; else return 0; } /* function defined for the subtype StudentUser */ void setListOfCourses( StudentUser* student, /* (H) */ char* listCrs[], int nCourses ) { int i; char** temp; student->numCourses = nCourses; temp = malloc( nCourses * sizeof( char* ) ); student->listOfCourses = temp; for (i=0; i<nCourses; i++) { *temp = malloc( sizeof( strlen( *listCrs ) + 1 ) ); strcpy( *temp, *listCrs ); temp++; listCrs++; } } /* function defined for the subtype StudentUser */ void printListOfCourses( StudentUser* student ) { /* (I) */ int i; char** temp; temp = student->listOfCourses; /* Here we access a field of the base type */ /* in a function defined for the subtype: */ printf( "\n%s's courses: \n", student->genericUser.name ); /* (I1) */ for (i=0; i<student->numCourses; i++) printf( "%s\n", *temp++ ); } /* function defined for the subtype StudentUser */ void setYear( StudentUser* student, int yy ) { /* (J) */ student->whatYear = yy; } /* function defined for the subtype StudentUser */ int getYear( StudentUser* student ) { /* (K) */ return student->whatYear; } int main() { User* zaphod; /* (L) */ StudentUser* trillian; /* (M) */ char* listCourses[] = {"physics", "chemistry", "algebra"}; int numCrs = sizeof( listCourses ) / sizeof( listCourses[0] ); zaphod = malloc( sizeof( User ) ); /* (N) */ setUserName( zaphod, "Zaphod" ); setUserAge( zaphod, 129 ); getUserName( zaphod ); /* (N1) */ getUserAge( zaphod ); /* (N2) */ trillian = malloc( sizeof( StudentUser ) ); /* (O) */ setUserName( (User*) trillian, "Trillian" ); setUserAge( (User*) trillian, 38 ); getUserName( (User*) trillian ); /* (O1) */ getUserAge( (User*) trillian ); /* (O2) */ setListOfCourses( trillian, listCourses, numCrs ); printListOfCourses( trillian ); /* (O3) */ printf( "\nZaphod is senior is %s\n", isSenior( zaphod ) ? "true" : "false" ); /* (P) */ /* polymorphism in action */ printf( "\nTrillion is senior is %s\n\n", isSenior( (User*) trillian ) ? "true" : "false" ); /* (Q) */ printf( "trillian object starts at address: %p\n", trillian ); /* (R) */ printf( "name field of trillian is at address: %p\n", &(trillian->genericUser.name) ); /* (S) */ printf( "trillian when cast to User* is at address : %p\n", (User*) trillian ); /* (T) */ }

The program produces the following output

 Name of user: Zaphod                                                       /* from line N1 */ Zaphod's age: 129                                                          /* from line N2 */ Name of user: Trillian                                                     /* from line 01 */ Trillian's age: 38                                                         /* from line 02 */ Trillian's courses:                                                        /* from line 03 */ physics chemistry algebra Zaphod is senior is true                                                   /* from line P */ Trillion is senior is false                                                /* from line Q */ trillian object starts at address: 0x8049b38                               /* from line R */ name field of trillian is at address: 0x8049b38                            /* from line S */ trillian when cast to User* is at address : 0x8049b38                      /* from line T */ 




Programming With Objects[c] A Comparative Presentation of Object-Oriented Programming With C++ and Java
Programming with Objects: A Comparative Presentation of Object Oriented Programming with C++ and Java
ISBN: 0471268526
EAN: 2147483647
Year: 2005
Pages: 273
Authors: Avinash Kak

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net