[Khóa học C++] Bài 12 - Forward declarations and definitions

Share:

Mở đầu bài học với ví dụ sau

Trước khi bạn biên dịch chương trình này, bạn hãy thử đoán xem kết quả in ra màn hình là gì?
Bạn đang nghĩ về kết quả này ?
The sum of 3 and 4 is: 7

Chắc chắn bạn sẽ gặp một lỗi khi biên dịch, có thể là identifier not found hoặc một mã lỗi tương tự như vậy tùy theo trình biên dịch và IDE bạn dùng. Chương trình bị lỗi vì khi biên dịch, trình biên dịch sẽ đọc file một cách tuần tự. Khi trình biên dịch đọc tới dòng số 5 và gọi hàm add() trong hàm main(), trước khi tới dòng số 5, compiler rõ ràng là không biết add() là gì, và sẽ thông báo ra console một mã lỗi.

Nếu bạn đang dùng Visual Studio 2005 Express, bạn sẽ gặp thêm một lỗi nữa là redefinition, lỗi này đã được fix ở các bản Visual Studio cao hơn.

Lời khuyên cho bạn: Khi chương trình của bạn xuất hiện nhiều lỗi cùng một lúc, thì bạn phải sửa lỗi đầu tiên trước, sau đó biên dịch và cứ làm tiếp tục như vậy.

Quay lại chương trình trên, để sửa chương trình trên, bạn có 2 lựa chọn.

Cách 1: Define lại hàm add() trước khi nó được gọi

Ngay tại dòng số 10, chương trình gọi hàm add(3, 4), và dĩ nhiên, compiler đã "biết" hàm add() ngay từ dòng 3, nên chương trình này chạy sẽ không gây ra lỗi. Cách fix lỗi cực kì đơn giản, nhưng có một điều gì đó không ổn.

Bạn hãy tưởng tượng chương trình của bạn có 2 hàm A và B, trong hàm body của hàm A sẽ gọi hàm B, và trong body của hàm B sẽ gọi hàm A. Câu hỏi đặt ra là bạn sẽ define hàm nào trước để không gây ra lỗi?
Trong các chương trình lớn, bạn sẽ gặp rất nhiều trường hợp như trên, bạn không thể dùng cách 1 để giải quyết vấn đề này được. Bạn hãy đến với 2 khái niệm mới!

Function prototypes and forward declaration of functions

Cách 2: Sử dụng forward declaration.

Một forward declaration giúp compiler biết được sự tồn tại của một identifier (ở trong ví dụ trên identifier là hàm add()) trước khi identifier này thực sự được định nghĩa.

Để viết được một forward declaration cho một function, chúng ta sử dụng một câu lệnh được gọi là function prototype. Function prototype bao gồm tên hàm, giá trị trả về, các tham số của hàm (parameter), nhưng không có phần body của hàm, và kết thúc bằng dấu ;.

Và đây là function prototype của hàm add():
int add(int x, int y);

Bây giờ chúng ta sẽ sửa lại chương trình với forward declaration.

Trong chương trình trên, khi compiler biên dịch tới dòng số 7, sẽ gọi hàm add(), bởi vì đã có funtion prototype của hàm add() ở dòng số 3, nên compiler hiểu rằng hàm này có 2 tham số, kiểu trả về là kiểu integer. Và chương trình sẽ không báo lỗi.

Ngoài ra bạn có thể viết lại function prototype của hàm add() như sau:
int add(int, int);

Chúng ta không cần thiết phải viết tên của các parameter. Nhưng chúng tôi khuyên bạn nên dùng function prototype giống với function thực tế như trong đoạn code trên, để khi chúng ta nhìn vào prototype, chúng ta biết được function sẽ cần cụ thể những parameter như thế nào.

Forgetting the function body

Nếu bạn là một newbie, khi bạn viết một forward declaration cho một function, nhưng bạn lại quên định nghĩa nó như ở dòng 11 - 14 (bạn quên không viết những dòng này trong code) thì chương trình sẽ chạy như thế nào, hãy biên dịch ví dụ bên dưới.

Khi biên dịch chương trình, bạn sẽ gặp một lỗi có thể là unresolved external symbol, lỗi này nói cho chúng ta biết rằng, chương trình vẫn biên dịch được, nhưng lỗi gây ra ở quá trình linker.
Bạn hãy cẩn thận với điều này.

Other types of forward declarations

Bạn thường xuyên sử dụng forward declarations với function. Tuy nhiên, trong C++, forward declarations có thể được sử dụng với các identifier khác nữa, như biến, kiểu dữ liệu người dùng tự định nghĩa. Bạn sẽ được học chúng vào các bài trong tương lai.

Declarations vs. definitions

Trong C++, bạn thường nghe 2 từ là declaration và definition. Vậy chúng là gì, và bạn có thực sự hiểu rõ về chúng ?

Một definition thật sự thực thi (impelement) hoặc khởi tạo (instantiate) một identifier, điều này khiến cho RAM phải cấp phát bộ nhớ. Đây là ví dụ để bạn hiểu rõ.

Ở trong phần Forgetting the function body, bạn đã thấy rằng chương trình sẽ bị lỗi trong giai đoạn linker nếu thiếu definition. Cho nên, definition sẽ giải quyết lỗi ở linker.

Một luật nổi tiếng trong trong C++ được gọi là one definition rule (ODR), và ODR có 2 phần như sau:
  • Trong một file, một identifier chỉ có thể có duy nhất một deninition
  • Trong một chương trình, một object hay một function chỉ có thể có một definition, bởi vì trong một chương trình có thể có rất nhiều file. Tuy nhiên một số identifier như template function, inline function được "miễn nhiễm" với luật này. Bạn sẽ học ở các bài trong tương lai.
Việc vi phạm luật ODR sẽ gây ra các lỗi ở compiler hoặc linker.

Một declaration là một câu lệnh giúp compiler biết được sự tồn tại của một identifier (biến hoặc tên hàm) và kiểu của nó. Đây là một số ví dụ về declaration.

Declaration dùng để "thỏa mãn" compiler về sự tồn tại của identifier. Đây là lý do chỉ cần forward declaration thì chương trình sẽ biên dịch đúng (nhưng linker cần đúng thì phải có definition).

Bạn có thể để ý rằng int x; xuất hiện ở cả declaration và defination. Bởi vì trong C++, tất cả definition cũng sẽ là declaration. Cho nên int x; là một definition, và mặc định nó cũng sẽ là declaration. Vì thế, trong nhiều trường hợp, chúng ta chỉ cần definition là đủ.

Có một khái niệm khác là pure declarations mà bạn sẽ được học trong các bài tiếp theo.

Kết thúc



Không có nhận xét nào