Các preprocessor directive, xây dựng file header, Macro trong C

Nếu bạn không có làm việc với nhiều người, bạn không xây dựng các chương trình C/C++ gồm nhiều người, không xây dựng các chương trình C/C++ bởi nhiều modulus, nhiều file ghép lại. thì có lẽ điều sau đây, bạn chưa cần quan tâm lắm.

Để chúng ta có thể xây dựng lên một chương trình C/C++ lớn, bao gồm nhiều người cùng xây dựng, ... Những vấn đề sau, có lẽ sẽ là những vấn đề cần quan tâm tới

Trong C/C++, dấu thăng (#) được gọi là Preprocessor operator (Tạm dịch để hiểu là: Toán tử tiền xử lý)

Preprocessor directives gồm có: (Việt Nam thường được gọi là: Các chỉ thị tiền xử lý)


Code:

# (null directive)  #ifdef
#define #ifndef
#elif #include
#else #line
#endif #if
#error #undef
#pragma


Và một vài directive ở VC++ nữa là: #using, #import

Các bài sau, Dr sẽ giới thiệu sơ sơ về cách sử dụng Preprocessor directive, và ứng dụng cụ cụ thể như thế nào. OK?

#include

Đây là chỉ thị bao gộp. Thông thường khi bắt đầu học C/C++ bạn cũng đã biết đến directive này, với ý nghĩa bao gộp một tệp nào đó.




Syntax (Cú pháp):
#include <header_name>
#include "header_name"
#include macro_identifier



Ví dụ 1:



#include 
#include



Ví dụ 2:



#include "MyConio.h"
#include "MyConsoleIO"



Ví dụ 3:



#define MyHeaderFile "D:\BORLANDC\MYFOLDER\MyConio.H"
/*OK */
#include MyHeaderFile



Về cơ bản #include directive dùng để bao gộp một file code nào đó. Trước khi biên dịch, Compiler sẽ tìm file này, sau đó chèn vào file nguồn tại vị trí đặt #include. Nếu không tìm thấy thì sẽ báo lỗi: Unable to open include file "header_name"

Các vấn đề khác về #include sẽ được nói đến ở một phần khác.

 

#define



Code:

Syntax:
#define macro_identifier <token_sequence>


#define directive định nghĩa một macro. Macro cung cấp cho ta một kỹ thuật thay thế có hoặc không có 'bộ' chính thức, thay thế giống các tham số của hàm.

Nguyên bản: The #define directive defines a macro. Macros provide a mechanism for token replacement with or without a set of formal, function-like parameters.

Một token_sequence rỗng sẽ cho kết quả là macro identifier sẽ không được sử dụng trong mã nguồn.

Ví dụ:



#define Hi "Have a nice day!" // Thay thế chuỗi "Have a nice day!" bởi macro Hi

#define STDIO #include //Chú ý: Không được có khoảng trắng giữa các macro. Định nghĩa: #define STD IO #include là không được như muốn

#define EMPTY // Nếu trong mã nguồn có EMPTY, nó sẽ không được sử dụng.

#define max(a,b) ((a)>(b)?(a):(b))



Trích dẫn:

Chú ý: Không để các khoảng trắng như thế này max (a,b) để định nghĩa hàm max(a,b) như trên.
Nên đưa vào cặp () trong biểu thức để macro sử dụng được an toàn.
Đối của macro không khai báo kiểu, nên hàm macro được định nghĩa, có thể dùng với bất cứ kiểu dữ liệu nào


Một số tình huống với #define

Tình huống 1:



#define MAX 1000

printf("MAX"); // Sẽ in lên màn hình là MAX chứ không phải là 1000


Tình huống 2:



#define product(x,y) x*y

product(5+6,10); //sẽ cho kết quả là 5+6*10=65 chứ không phải là 110.



Định nghĩa một khối lệnh:
Nghiên cứu thông qua ví dụ sau:



#define HelloWorld {
                            clrscr();
                            printf("Hello world!");
                            printf("\nChao mung ban den voi cong dong C Viet");
                       }

//Cách sử dụng:
int main()
{
   HelloWorld
   return 0;
}



Hoặc ví dụ:



#define EXIT {printf("\The End"); exit(0);} // Tạo câu lệnh để thoát chương trình



Tình huống không tốt:
Nếu bạn không có cặp dấu {} bọc các khối lệnh lại, sẽ gặp phải tình huống không như muốn. Ví dụ:



#define EXIT printf("\The End"); exit(0);

Sử sụng: 
if(a==0) EXIT // Sẽ không cho kết quả như mong muốn. a!=0 nó vẫn thoát chương trình.
Câu lệnh trên sẽ hiểu là: if(a==0) printf("\The End"); exit(0);



Để dễ hình dung, bạn cứ hình dung về #define directive như sau:
#define A B // Thay thế B bởi A. Trong mã nguồn có A, thì compiler sẽ hiểu chỗ đó có nghĩa là B. A và B cách nhau bằng một khoảng trắng

 

#undef



Code:

Syntax:
#undef macro_identifier


Bạn có thể bỏ định nghĩa(undefine) một macro bằng sử dụng #undef directive. #undef bỏ đi bất cứ token_sequence trước đó từ macro_identifier; macro_definition đã bị quên, và macro_identifier là coi như chưa được định nghĩa. Không có macro_identifier mở rộng nào trong phạm vi của dòng chứa #undef.

Sau khi một macro_identifier đã được bỏ định nghĩa(undefined), nó có thể được định nghĩa lại với #define, sử dụng giống hoặc khác token_sequence.

Ví dụ:



#define BLOCK_SIZE 512 //Định nghĩa BLOCK_SIZE = 512
//Làm cái gì đó với BLOCK_SIZE đã được định nghĩa.

#undef BLOCK_SIZE

#define BLOCK_SIZE 256 //Định nghĩa lại BLOCK_SIZE, sau khi đã bỏ định nghĩa ở trên.





Trích dẫn:

Lưu ý:Các câu lệnh của preprocessor directive không có dấu kết thúc câu lệnh ( ; ) ở cuối



#undef



Code:

Syntax:
#undef macro_identifier


Bạn có thể bỏ định nghĩa(undefine) một macro bằng sử dụng #undef directive. #undef bỏ đi bất cứ token_sequence trước đó từ macro_identifier; macro_definition đã bị quên, và macro_identifier là coi như chưa được định nghĩa. Không có macro_identifier mở rộng nào trong phạm vi của dòng chứa #undef.

Sau khi một macro_identifier đã được bỏ định nghĩa(undefined), nó có thể được định nghĩa lại với #define, sử dụng giống hoặc khác token_sequence.

Ví dụ:



#define BLOCK_SIZE 512 //Định nghĩa BLOCK_SIZE = 512
//Làm cái gì đó với BLOCK_SIZE đã được định nghĩa.

#undef BLOCK_SIZE

#define BLOCK_SIZE 256 //Định nghĩa lại BLOCK_SIZE, sau khi đã bỏ định nghĩa ở trên.





Trích dẫn:

Lưu ý:Các câu lệnh của preprocessor directive không có dấu kết thúc câu lệnh ( ; ) ở cuối



 

#line




Syntax:
#line integer_constant  <"filename">



#line directive dùng để chỉ định line numbers trong một chương trình đối với việc tham chiếu chéo(cross-reference) và báo cáo lỗi (error reporting). Nếu chương trình của bạn có một phần nằm ở một vài file khác, thì #line directive rất hữu ích cho việc đánh dấu phần đó với line numbers từ file ghép lại.

Ví dụ 1:




/**
 ** This example illustrates #line directives.
 **/

#include 
#include 
#define LINE200 200
#line 100
void func_1()
{
   clrscr();
   printf("Func_1 - the current line number is %d\n",__LINE__);
}

#line LINE200
void func_2()
{
   printf("Func_2 - the current line number is %d\n",__LINE__);
}
int main(void)
{
   func_1();
   func_2();
   getch();

}



Ví dụ 2:

Bạn tạo một file test.h có nội dung như sau:



//This is header file: test.h.
// Path: D:\BORLANDC\INCLUDE
/***************************
 ***************************
 **************************/
int x, y;
int a;

//some thing



Rồi lưu nó vào thư mục "D:\BORLANDC\INCLUDE", với cái tên là test.h

Rồi thử compiling đoạn chương trình sau:



/**
 ** This example illustrates #line directives.
 **/

#include 
#line 5 "D:\BORLANDC\INCLUDE\test.h"
int main(void)
{
   a=5; // Báo lỗi: test.h(7,6):Undefined symbol 'a'
   return 0;
}



Khi đó sẽ nhận được thông báo lỗi: test.h(7,6):Undefined symbol 'a'

Để hiểu hơn về #line directive. Các bạn chạy thử 2 code trên rồi sẽ rõ hơn nhiều. Dr cũng không nắm rõ hết tác dụng của directive này, các bạn vui lòng bổ sung thêm nếu thấy nó có tác dụng.

 

#line (Bổ sung)

- Trước khi tìm hiểu về chỉ thị tiền xử lý này các bạn phải biết công dụng của Macro __LINE__ sau trong C (Macro này cho phép in ra số thứ tự của dòng được trình biên dịch nhận biết trong mã nguồn).

1. __LINE__ không có chỉ thị tiền xử lý :



int main(void) { // line đầu tiên là line 1
   printf("Dòng hiện tại là: %d", __LINE__); // line 2
   return 0; // line 3
}
// Kết quả : Dòng hiện tại là 2



2. __LINE__ với chỉ thị tiền xử lý #line :



#line 50 // Chỉ thị tiền xử lý ở đây nè T_T
int main(void) { // line 50
   printf("Dòng hiện tại là: %d", __LINE__); // line 51
   return 0; // line 52
}
// Kết quả : Dòng hiện tại là 51



- Mục đích sử dụng : Dành cho quá trình debug đặc biệt trong chương trình C

P/C : To DR, đã xài được trong C thì sang C++ cũng xài được, cái này khỏi nhắc chắc mọi người đều biết mà.

 



#if, #elif, #else, và #endif







Cảm ơn Solokop đã bổ sung nhé. Dr đã mạn phép sửa lại một chút ở bài viết của bạn. 


Bữa nào có điều kiện, Dr chỉ viết một tut về các Macros đã được định nghĩa sẵn của C và C++, như là macro __LINE__ đã nêu ở trên.

Trích dẫn:

Nhân đây Dr cũng lưu ý luôn:
- Các preprocessor directive này có thể sử dụng ở cả C++, chứ không riêng gì ở C.
- preprocessor directive, Dr giữ nguyên vì dịch sang tiếng Việt sẽ bị mất nghĩa nguyên bản của nó. Sau này ứng dụng nhiều sẽ thấy điều này.
- line numbers, dịch sang tiếng Việt sẽ dễ hiểu sai. Chính vì lý do đó, Dr tạm thời để nguyên bản mà không dịch


Ở bài này, chúng ta nghiên cứu đến tác dụng của các directive: #if, #elif, #else, và #endif.

Đây là các directive rất mạnh mẽ. Nhưng nếu các bạn đã có cơ bản kiến thức về lập trình thì lại rất dễ hiểu. Trước khi đi vào vấn đề chính, Dr xin giải thích một chút, để các bạn nhớ tốt hơn về chúng như sau:


#if          Hiểu là If,
#elif        Hiểu là Else If 
#else        Hiểu là else
#endif       Hiểu là End If


Bài viết ngay sau đây, Dr sẽ cụ thể về các directive này.

 

#if, #elif, #else, và #endif. (Tiếp theo)

Code:

Syntax
#if constant-expression-1

#elif constant-expression-2

.
.
.
#elif constant-expression-n

#else

#endif


Đây là các conditional directives (Các chỉ thị có điều kiện) nó làm việc cũng giống như các conditional operators (Các toán tử có điều kiện) thông thường của ngôn ngữ C. 

Nếu constant-expression-1 ==true thì toàn bộ nội dung của cú pháp trên sẽ được thay thế bởi (kể cả các khoảng trống). có thể là một lệnh preprocessor, một dòng mã nguồn thường, một macro, ...
Nếu constant-expression-1 == false thì sẽ bị bỏ qua, cấu trúc xét đến constant-expression-2 sau #elif, cứ như vậy cho đến kết thúc.

Cấu trúc được kết thúc bởi directive #endif.

Ví dụ: (Lấy luôn ví dụ trong các file header của Borland)


#if !defined(___DEFS_H)    // Nếu chưa định nghĩa ___DEFS_H
   #include <_defs.h>      // Bao gộp file _defs.h
#elif __STDC__             // Còn Nếu __STDC__ là true
    #define _Cdecl         // Định nghĩa _Cdecl
#else                      // Còn lại
    #define _Cdecl  cdecl  // Định nghĩa _Cdecl với nghĩa là cdecl
#endif                     //Kết thúc #If


Với ví dụ trên, có lẽ sẽ khiến nhiều người khó hiểu nhưng thực ra là không có gì cả. DR giải thích một cái, các bạn tự hiểu những cái khác nhé!

#if !defined(___DEFS_H) 
//___DEFS_H là một macro, do programmer đặt ra, sở dĩ nhó có nhiều dấu gạch dưới là để tránh việc trùng lặp với tên do người viết mã khác đặt ra. Với mã nguồn thì cái macro này chỉ có duy nhất một ý nghĩa đó là: Nhận dạng xem file _defs.h đã được bao gộp chưa mà thôi. Nếu bao gộp rồi thì thôi không bao gộp nữa!

Các bạn cứ hiểu từ từ, và đơn giản hóa vấn đề một chút, bạn sẽ hiểu hết ý nghĩa của nó.

__________________

Ở trên Dr mới nói đến đó là cú pháp đầy đủ của các directive conditational này. Có thể điều đó sẽ khiến cho nhiều người khó hiểu, Dr sẽ bổ sung thêm một chút:

Bạn có thể có các cấu trúc đơn giản như sau:

Cấu trúc đơn giản loại 1:



Syntax:
#if constant-expression-1
                  
#endif



Nếu constant-expression-1 đúng thì được đưa vào mã nguồn. Nếu sai thì bỏ qua

Ví dụ:



Example:
#if MAX>200
   printf("Đưa vào mã nguồn dòng này. Ứng với MAX>200");
#endif



Cấu trúc đơn giản loại 2:



Syntax:
#if constant-expression-1 
    
#elif constant-expression-2
    
#endif



Nếu constant-expression-1 đúng thì được đưa vào mã nguồn. Nếu sai thì xét constant-expression-2, nếu constant-expression-2 đúng thì được đưa vào mã nguồn. Nếu cả 2 cùng sai thì bỏ qua, nghĩa là không có thứ gì được đưa vào mã nguồn để biên dịch hết.

Ví dụ:



Example:
#if MAX>200
    printf("Đưa vào mã nguồn dòng lệnh này, Ứng với MAX>200");
    printf("\nDòng lệnh này, nếu có cũng ứng với điều kiện MAX>200");
#elif MAX<=200
     printf("Đưa vào mã nguồn dòng lệnh này, Ứng với MAX<=200");
#endif



Ngoài ra nó còn có các cấu trúc conditional directive lồng nhau, nó hoàn toàn không khác gì với các cấu trúc của câu lệnh điều kiện thông thường ở ngôn ngữ C mà bạn đã học. Chỉ có điều là nó không có cặp dấu {} để bọc khối lệnh mà khối lệnh, nhưng có thêm #endif để báo kết thúc #if directive conditional này. Rất đơn giản, phải không nào?

 

 



#ifdef và #ifndef









Code:

Syntax:
#ifdef identifier
#ifndef identifier


#ifdef và #ifndef là các chỉ thị dó điều kiện (conditional directives), cho bạn kiểm tra một identifier đã được định nghĩa hay chưa.

Nếu chưa được định nghĩa hoặc đã gỡ bỏ định nghĩa của identifier thì:
#ifdef identifier tương đương với #if 0
#ifndef identifier tương đương với #if 1

Nếu identifier đã được định nghĩa rồi, nó vẫn còn hiệu lực thì:
#ifdef identifier tương đương với #if 1
#ifndef identifier tương đương với #if 0

Trích dẫn:

Ghi chú: identifier nghĩa là một từ định danh nào đó. Ví dụ MAX, BORDER, ...

 



Post a Comment

Mới hơn Cũ hơn