Cách tổ chức file C và C++ (phần 2)

Những bước cơ bản

Bây giờ bạn đã tin chắc rằng có những ưu điểm khi phân chia đề án của bạn thành nhiều file nhỏ hơn. Do đó, làm cách nào để thực hiện điều này? Mặc dù bạn có thể thực hiện bằng nhiều cách tùy ý, nhưng có một vài quy tắc cơ bản mà bạn nên tuân theo để chắc rằng nó hoạt động.

Đầu tiên, nhìn vào cách bạn phân chia các đoạn mã thành nhiều phần. Thông thường nó được phân chia vào trong các hệ thống con riêng biệt, hoặc các ‘module’, như là âm thanh nhạc, đồ hoạ, file điều khiển… Tạo những file mới với những tên có nghĩa sẽ giúp bạn biết loại mã bên trong khi nhìn lướt qua. Sau đó di chuyển toàn bộ mã bên trong theo các module bên trong file đó. Đôi khi bạn không có những module rõ ràng – vài người cho rằng nên có những cảnh báo về chất lượng thiết kế của bạn! Bạn cần có những tiêu chuẩn khác khi phân chia mã nguồn, như là cấu trúc nó theo tác dụng ở trên (thông thường những hàm “mục đích chung” có thể nằm trong chuỗi-điều khiển hoặc số-điều khiển). Và thỉnh thoảng 1 module có thể chia ra thành 2 hay nhiều file, với những lý do hợp lý.

Khi bạn phân chia nó thành nhiều file theo cách này, vấn đề tiếp theo cần được xem xét là những gì sẽ đến trong file header. Rất cơ bản, những mã mà bạn đặt vào đầu file nguồn như như thành phần không thể thiếu sẽ được chuyển vào các file header. Đó có thể là lý do tại sao chúng có tên gọi là file ‘header’.

Những dạng thường nằm trong file header:

  • Định nghĩa lớp hoặc cấu trúc
  • typedef
  • tên hàm
  • biến toàn cục (xem thêm bên dưới)
  • hằng số
  • #define macros
  • #pragma directives

(Hơn nữa, khi sử sụng C++, biểu mẫu <template> và các hàm bên trong thường nằm trong file header. Lý do sẽ được trình bày rõ ở phần sau).

Ví dụ, hãy nhìn vào những thư viện C chuẩn của bất kì trình biên dịch C hoặc C++. Stdlib.h là 1 ví dụ tốt; hãy mở nó ra bằng trình soạn thảo và xem nó. (Hoặc để tiết kiệm thời gian trong LS Visual C++ 6, gõ nó vào trong file nguồn, click chuột phải lên vàc chọn “Open Document “stdlib.h”‘). Bạn sẽ chú ý rằng chúng chỉ có vài hoặc toàn bộ những thành phần như trên, nhưng không có bất kì mã thực nào. Tương tự, bạn có thể thấy rằng bất kì định nghĩa biến toàn cục nào cũng có từ ‘extern’ phía trước. Điều này là quan trọng, ta sẽ biết nhiều hơn ở phần sau.

Nhìn chung bạn cần có 1 file header cho tất cả file nguồn. Có nghĩa là, một file SPRITES.CPP chắc chắn cần file SPRITES.H, file SOUND.CPP cần SOUND.H,… Giữ những cái tên phù hợp để bạn biết ngay những file header nào sẽ đi với file thông thường nào.

Những file header này trở thành phần chung giữa những hệ thống con. Bằng cách #include một header, bạn có thể truy xuất và toàn bộ những định nghĩa cấu trúc, tên hàm, hàng số… cho hệ thống con. Do đó, mỗi file nguồn sử dụng sprites cần phải #include “sprite.h”, mỗi file nguồn sử dụng âm thanh cần phải #include “sound.h”,… Chú ý rằng bạn nên dùng ngoặc kép hơn à dấu <> khi #include những file riêng. Dấu ngoặc kép cho trình biên dịch biết phải tìm kiếm header của bạn trong thư mục chương trình trước, rồi mới đến các header chuẩn của trình biên dịch.

Nên nhớ rằng, đối với trình biên dịch, hoàn toàn không có sự khác nhau giữa file header và file nguồn. (Ngoại trừ một vài trình biên dịch không biên dịch file header trực tiếp). Đơn giản hơn, chúng chỉ là những file văn bản đơn thuần chứa đầy dòng lệnh. Sự phân biệt chỉ là một quan niệm rằng lập trình viên phải dựa vào đó để giữ cấu trúc file hợp lý và đầy đủ. Ý tưởng chính là những header chỉ chứa những cái chung, và file nguồn chứ những thực thi thật sự. Bạn có thể áp dụng chúng ở bất cứ đâu khi làm việc với C hoặc C++, trong lập trình hướng đối tượng hay lập trình cấu trúc. Điều này có nghĩa là 1 file nguồn dùng 1 file nguồn khác thông qua header của file nguồn thứ hai.

Những cạm bẫy tìm ẩn

Trong những trường hợp đơn giản, bạn có thể tạo ra những chương trình làm việc hoàn chỉnh theo những hướng dẫn đó. Tuy nhiên có một vài chi tiết cần để ý, và nó thường là những chi tiết khiến những lập trình viên mới vào nghề khổ sở khi họ lần đầu tiên phân chia mã ra thành file header và file thông thường.

Theo kinh nghiệm của tôi, có 4 lỗi cơ bản mà người ta thường gặp phải:

  1. Những file nguồn không biên dịch nữa khi chúng không tìm thấy hàm hoặc biến cần thiết (Thường xuất hiện dòng lỗi tương tự như: “error C2065: ‘MyStruct’ : undeclared identifier” trong Visual C++)
  1. Phụ thuộc vòng tròn, khi những header xuất hiện khi cần #include lẫn nhau để làm việc một cách có dụng ý. Một Sprite chứa 1 con trỏ đến Creature, và 1 Creature có thể chứa 1 con trỏ đến Sprite. Bất kể bạn làm như thế nào, cả Creature lẫn Sprite đều phải khai báo trước tiên, và hệ quả là nó không làm việc khi dạng khác chưa được khai báo.
  1. Định nghĩa chồng khi 1 lớp hoặc cấu trúc được gọi 2 lần trong 1 file nguồn. Đây là lỗi thời gian biên dịch và thường xuất hiện khi gọi nhiều file header trong 1 file header khác, làm cho header được gọi 2 lần khi bạn biên dịch file nguồn (trong MSVC, lỗi này có dạng như “error CS2011: ‘MyStruct’ : ‘struct’ type redefinition).
  1. Trường hợp lặp lại các đối tượng đã được biên dịch tốt. Đây là lỗi liên kết, thường rất khó hiểu. (Trong MSVC, bạn có thể thấy cái gì đó như “”error LNK2005: “int myGlobal” (?myGlobal@@3HA) already defined in myotherfile.obj”).

Làm sao để sửa những trường hợp này?
(còn tiếp)

Nguồn: http://www.gamedev.net/reference/programming/features/orgfiles/page2.asp

Advertisements

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Đăng xuất / Thay đổi )

Connecting to %s