This is the third part of my Learning code by comparison series of learning C with explanations in Ruby.
It's always normal when writing a library, application, or other things in Ruby to use multiple files. It's common to do this with C too, but since we compile it's not as simple as our typical require
.
To start off our Ruby example we'll create a Person
module with common methods we'll want to include in our James
class. This would allow us to give James
extra methods that are unique to that class.
module Person
def full_name
"#{@first_name} #{@last_name}"
end
end
We'll save this to person.rb
. Now we'll create james.rb
and make use of Person
.
require './person'
class James
include Person
def initialize(first_name, last_name, age)
@first_name = first_name
@last_name = last_name
@age = age
end
def old_enough_to_drink?
@age > 18
end
end
james = James.new('James', 'Newton', 21)
james.full_name #=> "James Newton"
james.old_enough_to_drink? #=> true
One of the important lines to notice here is the require
where we're including Person
from person.rb
.
So, all in all; super easy. Now we move into C.
In C there is no way to just "include" another C file. Since C is compiled, we supply the extra C files to our compiler. But that itself isn't enough. We have to make use of "header" files to fill in some gaps.
In C we'll start with a very basic james.c
:
int main(int argc, const char *argv[])
{
return 0;
}
Now, we'll create a person.h
. This header file will define our struct
's and functions.
#include <stdio.h>
typedef struct {
char *first_name;
char *last_name;
int age;
} person_t;
You'll notice we're including stdio.h
. We're including this so we can make use of printf
.
Now we'll create a person.c
which will define a function to print out our persons information:
#include "person.h"
void person_print(person_t person)
{
printf("Name: %s %s\n", person.first_name, person.last_name);
printf("Age: %i\n", person.age);
}
Here we're including person.h
so we can make use of the definition of person_t
, and printf
.
Now we're going to want to make use of person_print
inside of james.c
, but we cannot include a C file. This is where how we compile starts to become important, which we'll touch on soon.
But first we need to add to james.c
to actually make the program do something:
#include "person.h"
int main(int argc, const char *argv[])
{
person_t james = {"James", "Newton", 21};
person_print(james);
return 0;
}
Now we need to do one more thing, and that is declare the person_print
method inside of person.h
:
// ...
extern void person_print(person_t person);
The reason we do this is because james.c
does not know our function exists. By adding a prototype declaration we're letting james.c
know that the function exists, just not what it does yet. This'll also allow us to use person_print
in more files if we wanted to, rather then having to redefine it multiple times.
Now, to compile our program.
Usually in my compile examples I just do gcc file.c
, but now that we're using multiple files we'll get a little more detailed.
We'll use the following command to compile:
$ gcc -o james james.c person.c -I .
In this command -o james
tells our compiler that the program will be compiled to the executable james
. The next two params, james.c person.c
tell the compiler the files to compile, and -I .
tells the compiler the "include search path" (or, where to find our person.h
).
After compiling there will be an executable file named james
. Compiling and running will yield these results:
$ gcc -o james james.c person.c -I .
$ ./james
Name: James Newton
Age: 21