Objects in C - Difference between C and Java

As we have already stated, C does not contain classes.This can limit the object-oriented programming that you are used to doing in Java. However, C does have a way to make a sort of complex variable type, called a struct (short for “structure”). C’s struct can be thought of as a class without any methods—it only contains fields. We will discuss how to use them here.

Note: If you want to use true object-oriented programming with C-like memory management, look into the C++ language. C++ was derived from C in the early ’80s. It contains real classes that are very similar to Java’s classes.

Defining a Basic struct

In its most basic form, a C struct looks much like you would expect. Here is a simple personnel record defined as a struct:

struct PersonnelRecord {
  char *familyName, *givenName; // strings
  unsigned long int idNumber; // big non-negative int  
}; 

This code might go near the top of the .c file, near the prototypes. A struct has some important differences from a Java-style class:

  • There are no visibility modifiers like public and private. All fields are inherently public.

  • There are no methods.

  • The definition must end with a semicolon.

However, structs are seldom defined as they are above, because of one annoying detail. You must always use the keyword struct when referring to this type, such as when declaring a new variable:

struct PersonnelRecord myRecord; // declares new object 

To get around this, we can use C’s typedef ability. The typedef was originally created to make custom variable types, that were aliases for better known types. For example, if you thought that using the keyword double was confusing, you could write code in which you define the word real to mean the same thing. Then, instead of declaring and using double variables, your code would instead use real variables, as if real were a standard C variable type.

We can take advantage of the typedef in order to use structs more naturally. In this example, we are not technically making a struct called PersonnelRecord. Rather we are making an anonymous struct with no name, but using typedef to make PersonnelRecord an alias for this anonymous struct:

typedef struct {                                 
   char *familyName, *givenName; // strings         
   unsigned long int idNumber; // big non-negative int                                            
} PersonnelRecord;                                

Now we can declare a new variable of this type without having to use the word struct:

PersonnelRecord myRecord; // declares new object 

When you declare a struct like this, it is automatically allocated from the stack. Like in Java, you can assign its fields using the . (“dot”) operator:

myRecord.familyName = "Lidell";
myRecord.givenName = "Alice";
myRecord.idNumber = 123456789;     

Like everything allocated from the stack, it will be recycled once the function it was defined in has completed. Therefore the above code is not that useful.

Managing ``struct`s from the Heap

If you want to allocate a struct from the heap, you must declare a pointer and call malloc(), like you did for arrays. This is shown here:

PersonnelRecord *myRecord; // make an object pointer\ 
myRecord = (PersonnelRecord*)malloc(sizeof(PersonnelRecord)); 
        // now allocate enough space from the heap 

This object will persist, as long as you need it. Of course you will have to free it when you are done with it, to avoid a memory leak:

free(myRecord); // back to the heap! 

Unfortunately, there is now another problem. The variable myRecord is a pointer, not a struct, and therefore it’s not valid to use the . operator on it to try to access a field. If you do so, you’ll get a compiler error telling you that record is not a proper structure containing fields. You need to use the * operator to first access the contents pointed to by the pointer, which is the actual struct. However, under the standard C order of operations, . will occur before *. That means that you need to use parentheses, like here:

(*myRecord).familyName = "Lidell"; // 1st *, then . 

To ease coding, there is another operator called the arrow operator, ->. The -> works just like a ., but it is used with pointers so that you don’t need to use parentheses. So this line does the same thing as the above one:

myRecord->familyName = "Lidell"; // no ()s needed! 

Practically speaking, you will see the -> operator in C code much more often than you will see the . operator. A struct that was allocated from the heap is just more useful than one that was allocated from the stack, and ones allocated from the heap almost always use ->.

Making “Methods”

A struct does not contain methods. Even so, C programmers often mimic them by making functions that are specially tuned to a particular struct.

A constructor can be made by creating a special function that takes values for all the fields and copying them into a newly allocated object:

// make a new PersonnelRecord, from given data
PersonnelRecord * makeNewRecord(char * familyName, char * givenName, unsigned long int idNumber) {
    // 1st allocate the new object                                     
    PersonnelRecord * newRecord;
    newRecord = (PersonnelRecord * ) malloc(sizeof(PersonnelRecord));
    // now fill it up
    newRecord -> familyName = malloc((strlen(familyName) + 1) *
      sizeof(char));
    strcpy(newRecord -> familyName, familyName); // copy family name
    newRecord -> givenName = malloc((strlen(givenName) + 1) * sizeof(char));
    strcpy(newRecord -> givenName, givenName); // copy given name
    newRecord -> idNumber = idNumber;
    return newRecord;
}                                                       |

This constructor has a very special feature: it allocates brand new strings and copies the old values into them, rather than using the strings directly. This is because strings in C are easily changeable. Making brand new strings helps to isolate this object’s data, so that it won’t be accidentally altered. The function starts by allocating enough space for the struct itself. Then it allocates enough space for the family name, and uses strcpy() to copy the family name argument into the family name field. Then it does the same thing with the given name. Finally it copies over the primitive ID number, and returns the brand new record.

We also need another function to handle deleting the PersonnelRecord when we’re done with it.This function needs to make sure that every part of the PersonnelRecord that was allocated from the heap is freed again.

Note: In C++, this function is called a destructor. A destructor is the opposite of a constructor, handling the tear-down when you’re all done with an object. Java handles all of this automatically.

// delete an old PersonnelRecord, returning it to the heap
void deleteRecord(PersonnelRecord * record) {
  free(record -> familyName); // free family name
  free(record -> givenName); // free given name
  free(record); // free structure itself
}

For other “methods”, just be sure to pass the object pointers as arguments. For example, here’s a function that creates a new string representing the PersonnelRecord, which functions kind of like a toString() function in Java:

// allocate & return string "familyName, givenName (ID#)"
char * makeStringFromRecord(PersonnelRecord * record) {
  // figure out length of ID# (log 10 + 1)
  int idLength = 1;
  if (record -> idNumber > 0) idLength = (int) log10(record -> idNumber) + 1;
  // figure out total length & allocate string
  int length = strlen(record -> familyName) + strlen(record -> givenName) +
    idLength + 6;
  char * string = (char * ) malloc(length * sizeof(char));
  // make string & return it
  sprintf(string, "%s, %s (%lu)", record -> familyName, record -> givenName,
    record -> idNumber);
  return string;
}

Here’s another function to compare two PersonnelRecords, which behaves similar to the compareTo() method inside Java’s Comparable interface. It would be useful if you needed to sort an array of these objects. It returns 0 if two records are equivalent, a negative number if record1 would sort before record2, and positive if it would sort after. It sorts the records by family name first, then given name if there was a tie, and finally by ID number if two people have the same names:

// compare 2 PersonnelRecords to see which is 1st
int compareRecords(PersonnelRecord * record1, PersonnelRecord * record2) {
  // 1st compare family names
  int diff = strcmp(record1 -> familyName, record2 -> familyName);
  if (diff) return diff; // return if !0
  // on tie, compare given names
  diff = strcmp(record1 -> givenName, record2 -> givenName);
  if (diff) return diff; // return if !0
  // finally compare ID #s
  if (record1 -> idNumber == record2 -> idNumber) return 0;
  else if (record1 -> idNumber < record2 -> idNumber) return -1;
  else return 1;
}

Licenses and Attributions


Speak Your Mind

-->