Books / C Language Primer for Java Developers / Chapter 8
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
andprivate
. All fields are inherentlypublic
. -
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;
}