[Imperative Programming + C] Bài 9 - Library và Header

    Yếu tố quan trọng cần tìm hiểu tiếp theo là cách phân chia code thành nhiều tệp để quản lý và viết test dễ dàng hơn. Vì vậy nên mình đã Google thử cách để tạo ra một thư viện kiểu như mấy cái stdio.h, stdlib.h, hay stdbool.h mà chúng ta đã sử dụng trước đó. Tiện thể thì kết quả là Google có gợi ý luôn chủ đề liên quan là thư viện tiêu chuẩn Standard Library của C.

Standard Library

    Câu chuyện ở đây là có vài cái trình biên dịch compiler khác nhau đang được sử dụng bởi cộng đồng C. Trong số đó thì cái gcc mà chúng ta đang sử dụng là rất rất phổ biến so với số còn lại. Tuy nhiên để đảm bảo có một sự nhất quán nền tảng giữa các compiler thì C đã đưa ra khái niệm thư viện tiêu chuẩn. Đó là một bộ các thư viện nhỏ cung cấp các chương trình con hỗ trợ chức năng để người viết code có thể sử dụng và xây dựng nên những chương trình của mình.

    Sau khi xem qua bộ thư viện tiêu chuẩn của C thì cảm nhận chung chung từ một newbie JavaScript là đang được ngược dòng lịch sử về thời điểm mà những người viết code có lẽ đều phải là các nhà khoa học máy tính. Các sub-program và các kiểu dữ liệu được định nghĩa với tên ở dạng viết tắt xoay quanh một từ khóa thể hiện chức năng chính.

    Ví dụ cụ thể là trình fprintf trong stdio.h có chức năng in f print f và tên gọi đầy đủ là file print format; Hay trình atoi trong string.h có chức năng là chuyển đổi a to i và có tên gọi đầy đủ là any to int.

    Ngoài thư viện tiêu chuẩn gồm các thư viện con ở trên thì các trình compiler khác nhau sẽ có cung cấp một vài thư viện riêng tùy thuộc vào môi trường ứng dụng mà compiler đó hướng tới. Các thư viện được cung cấp mặc định bởi các compiler có thể được sử dụng bằng cú pháp như sau:

# include <library.h>

    Còn các thư viện mà chúng ta tạo ra khi xây dựng các chương trình ứng dụng sẽ được sử dụng bằng cú pháp gần tương tự với chuỗi mô tả đường dẫn tương đối trỏ tới tệp thư viện tính từ vị trí của tệp code đang thực hiện #include.

# include "path/to/library.h"

Header File

    Thiết kế thư viện đơn giản nhất là một tệp có định dạng .h thường được gọi là tệp header. Ở đây chúng ta sẽ có một ví dụ nhỏ về một tệp math.h tự định nghĩa được đặt trong cùng thư mục với tệp main.c.

float sum (float $a, float $b) {
   return $a + $b;
}
# include <stdio.h>
# include "math.h"

void main () {
   float $a = 9.0;
   float $b = 1.8;
   float $result = sum ($a, $b);
   fprintf (stdout, "Sum of %f and %f is %f", $a, $b, $result);
}

    Lúc này khi chúng ta thực hiện thao tác chạy lệnh biên dịch thì tất cả nội dung code trong tệp math.h tự định nghĩa sẽ được copy/paste vào chính xác vị trí của dòng #include "math.h".

gcc main.c -o main
main

Sum of 9.000000 and 1.800000 is 10.800000

    Trên thực tế thì các thư viện của C thường được thiết kế ở dạng sử dụng đồng thời các tệp khai báo header.h và các tệp code chi tiết body.c. Trong đó thì các tệp header.h sẽ chỉ chứa code định nghĩa các kiểu dữ liệu, định nghĩa các giá trị hằng, và khai báo ngắn gọn về các chương trình con; Còn các tệp body.c sẽ chứa code định nghĩa chi tiết về các chương trình con đã được khai báo trong tệp header.h. Sau đó, tại bước thực hiện biên dịch chương trình thì chúng ta sẽ cần liệt kê tất cả các tệp body.c đã được sử dụng.

gcc main.c one.c two.c three.c ... -o main

    Mục đích của cách thực hiện này đó là các tệp header.h sẽ có thể được sử dụng làm chỉ mục để tổng quát nội dung của thư viện; Và các hằng số hay các kiểu dữ liệu tự định nghĩa sẽ có thể được sử dụng chung cho nhiều tệp body.c chứa code định nghĩa chi tiết các chương trình con. Trong khi đó thì các tệp body.c chứa code định nghĩa các chương trình con, do đã được tách nhỏ, nên sẽ có thể viết test riêng dễ dàng và quản lý thuận tiện hơn.

C Preprocessor

    Khi một chương trình mà chúng ta đang xây dựng được mở rộng và sử dụng nhiều thư viện khác nhau, có một vấn đề nho nhỏ có khả năng sẽ phát sinh. Đó là tất cả các định nghĩa hằng, kiểu dữ liệu, và các chương trình con ở cấp ngoài cùng của các thư viện sẽ đều mặc định được nhìn thấy bởi các phần code khác.

    Lúc này sẽ có khả năng xuất hiện nhiều hơn một đoạn code định nghĩa sử dụng cùng tên định danh, ví dụ như một tên hằng được định nghĩa trong hai thư viện. Chính vì vậy nên C còn cung cấp thêm các chỉ dẫn directive giúp chúng ta tạo ra các logic xử lý trước khi biên dịch - vì vậy nên các chỉ dẫn này còn được gọi là preprocessor.

    Code ví dụ dưới dây sẽ minh họa một thao tác kiểm tra xem một tên định danh infinity. Nếu infinity đã được định nghĩa bởi #define trong một tệp nào đó được #include trước tệp này thì sẽ không thực hiện thao tác #define lại ở đây.

# define infinity 1001
# include <stdio.h>
# include "infinity.h"

# if ! defined (infinity)
#    define infinity 81
# endif

void main () {
   fprintf (stdout, "Infinity: %i", infinity);
}

    Do ở tệp infinity.h thì tên định danh infinity đã được định nghĩa trước, vì vậy nên đoạn code kiểm tra tiền xử lý ở tệp main.c đã không định nghĩa lại thành 81. Và kết quả được in ra là 1001.

gcc main.c -o main
main

Infinity: 1001

    Nhắc tới chỉ dẫn #define, C có định nghĩa sẵn một số macro hỗ trợ truy xuất thông tin hệ thống như sau:

# include <stdio.h>

void main () {
   fprintf (stdout, "Date: %s \n", __DATE__);
   fprintf (stdout, "Time: %s \n", __TIME__);
   fprintf (stdout, "File: %s \n", __FILE__);
   fprintf (stdout, "Line: %d \n", __LINE__);
}
gcc main.c -o main
main

Date: Jun 13 2022 
Time: 20:19:39
File: main.c
Line: 7

    Như vậy là chúng ta đã điểm qua những yếu tố căn bản trong cú pháp Imperative Programming nói chung được C hỗ trợ và cả các nguồn tham khảo về bộ thư viện tiêu chuẩn của C. Về mặt nghiệm thu kiến thức thì trong Sub-Series này bạn có thể suy nghĩ đến việc viết một ứng dụng Console để người dùng có thể nhập liệu tương tác qua cửa sổ dòng lệnh; Hoặc tạm thời bỏ qua cho đến mini project của Sub-Series Procedural Programming. Mục đích chính của Sub-Series này là để bất kỳ newbie JavaScript nào như mình cũng có thể làm quen với việc sử dụng một ngôn ngữ định kiểu tĩnh và hiểu rõ hơn về các khái niệm giá trị value và địa chỉ reference.

    [Declarative Programming + Elm] Bài 1 - Hello, Elm !

    [Procedural Programming + Ada] Bài 1 - Giới Thiệu Ngôn Ngữ Ada

    Khá giống với Sub-Series HTML đầu tiên của Series Tự Học Lập Trình Web, Sub-Series về ngôn ngữ C vẫn sẽ được tiếp tục. Tuy nhiên, chúng ta nên ưu tiên cho các Sub-Series mới và việc đọc các bài viết tiếp theo về C nên đặt ở mức độ tham khảo, không bắt buộc.

    [Imperative Programming + C] Bài 10 - size_t Type

Nguồn: Viblo

Bình luận
Vui lòng đăng nhập để bình luận
Một số bài viết liên quan