Diving a large source code into several files.
The series Learn C Language is almost ending. This is one of the last sections of the C-language learning series, and we’ll learn how to divide a large source code into several files. Once you acquire the skill, you’ll be able to code almost any kind of C-based program.
Hi everyone. This is one of the very last sections of Learn C Language series. It’s been a long journey, right? Since I’m handling many other series to keep track of my learning processes, including Linux Programming, Arduino, and Algebra, my learning journey will keep going. But this is one of my accomplishments to finish this series. Once I finish this one, I’ll start learning another. What I’m thinking of at this moment is learning Kotlin and modern Android programming since being an Android developer is one of my career goals at this moment. Also, I want to improve my programming skills by learning some advanced algorithms.
Anyways, let’s get started with this section!
This time, we’ll learn how to divide a large source code into several other files and make the program readable and manageable.
Image 01 is the source code we’ll divide. I know it’s a bit difficult to read through the file. But don’t worry, we’ll go through each part one after another by explaining what’s going on in each section.
Image 02 is the executed result. The reason the error is shown in “2 Micheal” is that id 2 is already registered by another name, Sarah.
Lines 5 to 10:
MAX_STUDENT: the max number of registrable students number
LENGTH: the max length of a student’s name
MESSAGE_LENGTH: the max length of the message that going to be printed out on the console
These elements are essential parts of the entire program and will be shared among them when the whole program is divided into several files.
Lines 13 to 16:
enum automatically allocates numbers.
MESSAGE_OK: 0
MESSAGE_ERROR: 1
Lines 19 to 22
Here is the structure that contains the id and name.
Lines 25 to 29
Global variables are declared here.
num: the number of students is going to be plugged into this variable.
student_database: the database where students’ info will be inserted.
Since student_database has MAX_STUDENT, it’s allowed to accommodate up to ten students.
Error: int-type error code will be inserted.
Lines 32 to 40
initDatabase: initializing the database.
add: registering int-type and char-type data.
student* get(int); student data’s pointer(address) will be returned.
showStudentData: print out student’s data.
showError: print out errors.
Lines 42 to 55
char names[][LENGTH] = { “John”,”Sarah”,”Micheal”,”Don” };
As it’s declared in LENGTH, you can register up to 50 students.
int ids[] = { 1,2,2,3 };
The 2 are duplicated to make an intentional error.
initDatabase();
initialize the database.
for (i = 0; i < 4; i++) { // data registration
add(ids[i], names[i]);
printf(“Registered: %d %s \n”, ids[i], names[i]);
showError();
}
The for-loop will rotate through ids and names and register each student to the database. If it encounters an error, it will display it in showError().
Finally, it prints out the registered data to the console with the for-loop.
initDatabase()
From here, let’s take a look at each method one by one, starting from initDatabase().
initDatabase() initializes the database.
for (i = 0; i < MAX_STUDENT; i++)
student_database[i].id = -1;
strcpy(student_database[i].name, “”);
}
The above codes will initialize id, name with the following parts:
student_database[i].id = -1;
strcpy(student_database[i].name, “”);
Error = MESSAGE_OK;
initialize the error message too.
num = 0;
The registered student’s number is also initialized.
add()
if (get(id) == NULL && num < MAX_STUDENT)
The if-statement checks if
the student data is registrable or not as well as num is smaller than MAX_STUDENT.
student_database[num].id = id;
If it’s registrable, it registers the id.
strcpy(student_database[num].name, name);
Also, it copies the received student name by strcpy.
Error = MESSAGE_OK;
Finally, MESSAGE_OK (meaning No Error) is inserted to Error.
return 1;
If the registration is successful, it returns 1.
Error = MESSAGE_ERROR;
If it fails, MESSAGE_OK (meaning Error occured) is inserted to Error.
get()
The for-loop rotates through if (student_database[i].id == id) and it returns the id if it matches it. If not, it returns NULL.
showStudentData()
showStudentData(student* data) receives data’s pointer.
Since it handles pointers, it uses the arrow operator (data->id, data->name).
showError()
showError() determines the executed result by switch-statement.
Divided files
From here, let’s take a look at how the long program (image 01) is divided into five different files.
Header files
There are two large categories:
dataOutput.h: handles data output to the console.
studentDatabase.h: handles the database.
Source files
main.c: the program’s main() is here.
dataOutput.c: corresponds to dataOutput.h
studentDatabase.c: corresponds to studentDatabase.h
main.c
main.c includes dataOutput.h and studentDatabase.h in lines 2 and 3.
studentDatabase.h
Here, enum, structure, and methods are initialized.
dataOutput.h
studentDatabase.h is included in line 4. And what needs your attention here is that header files also include each other, not just source files. And the reason it includes studentDatabase.h is that it accesses student*, which is the structure declared in studentDatabase.h.
studentDatabase.c
In this source file, methods are defined.
What needs your attention here is that static variables are not accessible from other source codes.
Just for a reference, in the example below (image 20), dataOutput.c accesses to studentDatabase.c’s Error variable but not be able to do the same with the static ones.
And define is also usable only within the source code.
define MESSAGE_LENGTH 256