[Imperative Programming + C] Bài 6 – Struct & Typedef

Thông thường thì bước đầu tiên khi chúng ta bắt tay vào việc xây dựng một chương trình phần mềm sẽ là xác định đối tượng dữ liệu cần quản lý. Hay nói một cách khác là chúng ta cần định nghĩa một kiểu dữ liệu mô phỏng lại thông tin cần quản lý của

Thông thường thì bước đầu tiên khi chúng ta bắt tay vào việc xây dựng một chương trình phần mềm sẽ là xác định đối tượng dữ liệu cần quản lý. Hay nói một cách khác là chúng ta cần định nghĩa một kiểu dữ liệu mô phỏng lại thông tin cần quản lý của các thực thể trong cuộc sống.

Ví dụ điển hình là khi xây dựng một trang blog cá nhân đơn giản chúng ta đã định nghĩa kiểu dữ liệu Article gồm các trường id, title, content, v.v… để mô tả các bản ghi bài viết.

Một chương trình viết trong C cũng như vậy, và C có cung cấp một vài cú pháp để người viết code có thể tự định nghĩa kiểu dữ liệu riêng, có ý nghĩa với bối cảnh của chương trình đang xây dựng.

Định nghĩa Struct

Chúng ta sẽ bắt đầu với từ khóa struct, khá giống với class trong JavaScript ở cấp độ sử dụng cơ bản. Ở đây chúng ta sẽ có một đoạn code ví dụ cách định nghĩa một kiểu dữ liệu tổ hợp tên là book.

#include<stdio.h>structbook{int   id;char  title[100];};intmain(){structbook   laotzu ={.id =0,.title ="Tao Te Ching"};// - - - - - - - - - - - - - - - - - -printf("n @Id: %i", laotzu.id);printf("n Title: %s", laotzu.title);return0;}
gcc main.c -o main
main

 @Id: 0
 Title: Tao Te Ching

Thao tác định nghĩa và sử dụng một struct thực sự rất quen thuộc và có nhiều điểm tương đồng với class trong JavaScript, đặc biệt là ở đoạn truy xuất các phần tử đóng gói bên trong bằng phép thực thi .

Điểm duy nhất mà mình thấy có phần hơi rườm rà đó là ở vị trí type-hint chúng ta đang phải viết nguyên cả cụm từ struct book để biểu thị cho kiểu dữ liệu của biến laotzu.

Sử dụng Typedef

Code đẹp và dễ đọc đã, mọi thứ logic khác sẽ để dành tìm hiểu sau. Mình có Google thêm về cách tạo ra tên tham chiếu cho các kiểu dữ liệu sẵn có thì C lại có cung cấp thêm một từ khóa typedef (type define). Từ khóa này sẽ giúp chúng ta tạo ra một tên gọi thay thế alias cho bất kỳ kiểu dữ liệu nào.

#include<stdio.h>struct_book{int   id;char  title[100];};typedefstruct_book   book;intmain(){
   book laotzu ={.id =0,.title ="Tao Te Ching"};// - - - - - - - - - - - - - - - - - -printf("n @Id: %i", laotzu.id);printf("n Title: %s", laotzu.title);return0;}

Cú pháp định nghĩa của struct còn cho phép định nghĩa kiểu dữ liệu không tên giống với class của JavaScript; Do đó code định nghĩa structtypedef ở phía trên còn có thể viết ngắn gọn như thế này.

typedefstruct{int   id;char  title[100];} book;

Việc sử dụng typedef thực sự rất linh động, chúng ta thậm chí còn có thể đặt tên cho các kiểu pointer để có giao diện viết code tương đồng với JavaScript.

#include<stdio.h>typedefchar*   String;intmain(){
   String message ="Just be";printf("%s", message);return0;}
gcc main.c -o main
main

Just be

Phép thực thi &

Phép thực thi này đáng lẽ nên được xếp cùng nội dung của bài viết về khái niệm biến con trỏ pointer. Tuy nhiên thì ở thời điểm đó mình rất muốn duy trì mọi thứ đơn giản để tập trung vào tư duy quản lý bộ nhớ đệm. Và ở thời điểm hiện tại thì đây lại là công cụ rất cần thiết để làm việc với struct.

Phép thực thi & có thể gọi tên là phép lấy địa chỉ address operator – sẽ nhận vào một biến đứng sau đó và trả về địa chỉ của vùng bộ nhớ mà biến đó đang trỏ tới.

#include<stdio.h>intmain(){int age =1001;int* ageRef =&age;// - - - - - - - - - - - - - - - - - -printf("n Reference: %p", ageRef);printf("n Value: %i",*ageRef);return0;}
gcc main.c -o main
main

 Reference: 000000000061fde4
 Value: 1001

Khi sử dụng struct để truyền vào một sub-program, thì một biến kiểu struct lại có phương thức hoạt động giống với các biến kiểu đơn giản như int, char, v.v… Chương trình con sẽ nhận được một struct bản sao của struct được truyền vào; Và code bên trong sub-program sẽ không thể thay đổi được nội dung của struct ban đầu.

#include<stdio.h>typedefstruct{int   id;char  title[100];} book;voidupdateid(book copy){
   copy.id =1;}intmain(){
   book laotzu ={.id =0,.title ="Tao Te Ching"};// - - - - - - - - - - - - - - - - - -updateid(laotzu);printf("Updated Id: %i", laotzu.id);// - - - - - - - - - - - - - - - - - -return0;}
gcc main.c -o main
main

Updated Id: 0

Để chương trình con updateId có thể hoạt động được thì chúng ta có thể sửa lại sub-program này một chút và trả về struct bản sao để thay thế struct ban đầu.

#include<stdio.h>typedefstruct{int   id;char  title[100];} book;

book updateid(book copy){
   copy.id =1;return copy;}intmain(){
   book laotzu ={.id =0,.title ="Tao Te Ching"};// - - - - - - - - - - - - - - - - - -
   laotzu =updateid(laotzu);printf("Updated Id: %i", laotzu.id);// - - - - - - - - - - - - - - - - - -return0;}
gcc main.c -o main
main

Updated Id: 1

Hoặc một cách làm khác, đó là chúng ta có thể truyền địa chỉ tham chiếu của struct ban đầu vào chương trình con updateId. Và như vậy, code bên trong sub-program này sẽ có thể thay đổi nội dung của struct ban đầu. Tuy nhiên thao tác truy xuất tới các trường dữ liệu thông qua một biến con trỏ khác sẽ có cách viết hơi rườm rà một chút.

#include<stdio.h>typedefstruct{int   id;char  title[100];} book;voidupdateid(book* reference){(*reference).id =1;}intmain(){
   book laotzu ={.id =0,.title ="Tao Te Ching"};// - - - - - - - - - - - - - - - - - -updateid(&laotzu);printf("Updated Id: %i", laotzu.id);return0;}
gcc main.c -o main
main

Updated Id: 1

Ở đoạn (*reference).id thường được viết lại thành reference->id. Cách viết sử dụng ký hiệu mũi tên -> trông ngắn gọn hơn và thường được sử dụng nhiều hơn từ kết quả mà mình Google được. Tuy nhiên trong code ví dụ mình muốn mô tả logic hoạt động thông thường như khi chúng ta sử dụng các biến pointer trong những bài trước đó. 😄

Struct in JavaScript

Trong JavaScript, các object thuần dữ liệu không có chứa các phương thức là dạng triển khai cao hơn của struct. Tuy nhiên khi truyền một object vào một sub-program thì JavaScript sẽ mặc định là truyền địa chỉ tham chiếu của object đó chứ không tự động tạo ra một object bản sao như cách xử lý struct mặc định của C.

classBook{constructor(givenId, givenTitle){this.id = givenId;this.title = givenTitle;}}functionupdateId(reference){
   reference.id =1;}voidfunctionmain(){var laotzu =newBook(0,"Tao Te Ching");updateId(laotzu);
   console.log(laotzu.id);}();

Mỗi một ngôn ngữ và môi trường lập trình đều có những đặc thù riêng và mình cảm thấy cách xử lý mặc định của C khi truyền một struct vào một sub-program cũng rất dễ hiểu. Tuy nhiên logic xử lý tự động tạo ra bản sao dữ liệu như thế này có phần hơi bất đồng bộ so với StringArray của chính môi trường C. Do đó nên mình đã tìm hiểu thêm một chút thông tin và chọn ra một quy ước convention về cách đặt tên và sử dụng struct để phù hợp với logic sử dụng của mình.

Mục tiêu của convention này là để đồng bộ logic xử lý chung khi truyền các kiểu dữ liệu phức hợp vào các sub-program trong C. Đó là thao tác truyền một cấu trúc dữ liệu phức hợp như String, Array, hay một struct nào đó, sẽ luôn luôn mặc định là truyền vào địa chỉ tham chiếu từ một pointer. Như vậy chương trình con có thể quyết định lựa chọn:

  • Chỉnh sửa nội dung của cấu trúc dữ liệu ban đầu thông qua địa chỉ tham chiếu được truyền vào.
  • Hoặc chủ động tạo ra một cấu trúc dữ liệu bản sao kèm theo những cập nhật thay đổi để trả về.
#include<stdio.h>typedefchar*   String

typedefstruct{int      id;
   String   title;} book;typedef   book*   Book;

Book construct(int id, String title){
   book new ={.id = id,.title = title
   };
   Book reference =&new;return reference;}voidupdateid(Book reference){
   reference->id =1;}intmain(){
   Book laotzu =construct(0,"Tao Te Ching");updateid(laotzu);// - - - - - - - - - - - - - - - - - -printf("Updated Id: %i", laotzu->id);return0;}

Khi sử dụng convention này, các kiểu dữ liệu đại diện sẽ đều được đặt tên với chữ cái đầu viết hoa để biểu thị cho kiểu biến con trỏ pointer và cần được khởi tạo qua các trình khởi tạo construct như trong code ví dụ trên. Cũng khá thuận tiện và đằng nào thì khi chúng ta viết một chương trình bất kỳ cũng đều sẽ cần tạo ra các thư viện cung cấp các sub-program tiện ích cho mỗi kiểu dữ liệu; Thêm một chương trình construct nhỏ thì cũng không có gì quá bất tiện. 😄

(Chưa đăng tải) [Imperative Programming + C] Bài 7 – Header & …

Nguồn: viblo.asia

Bài viết liên quan

Thay đổi Package Name của Android Studio dể dàng với plugin APR

Nếu bạn đang gặp khó khăn hoặc bế tắc trong việc thay đổi package name trong And

Lỗi không Update Meta_Value Khi thay thế hình ảnh cũ bằng hình ảnh mới trong WordPress

Mã dưới đây hoạt động tốt có 1 lỗi không update được postmeta ” meta_key=

Bài 1 – React Native DevOps các khái niệm và các cài đặt căn bản

Hướng dẫn setup jenkins agent để bắt đầu build mobile bằng jenkins cho devloper an t

Chuyển đổi từ monolith sang microservices qua ví dụ

1. Why microservices? Microservices là kiến trúc hệ thống phần mềm hướng dịch vụ,