Tôi không hiểu khái niệm vĩ mô tốt. Một vĩ mô là gì? Tôi không hiểu nó khác với chức năng như thế nào? Cả hàm và macro đều chứa một khối mã. Vậy macro và chức năng khác nhau như thế nào?
Tôi không hiểu khái niệm vĩ mô tốt. Một vĩ mô là gì? Tôi không hiểu nó khác với chức năng như thế nào? Cả hàm và macro đều chứa một khối mã. Vậy macro và chức năng khác nhau như thế nào?
Câu trả lời:
Tôi muốn thêm làm rõ sau đây sau khi quan sát mô hình bỏ phiếu phân cực cho câu trả lời này.
Câu trả lời không phải là văn bản giữ chính xác kỹ thuật và khái quát rộng rãi trong tâm trí. Đó là một nỗ lực khiêm tốn để giải thích bằng ngôn ngữ đơn giản, sự khác biệt giữa các macro và chức năng đối với người mới lập trình, mà không cố gắng hoàn thành hoặc chính xác (thực tế là nó không chính xác). Ngôn ngữ lập trình C đã ở trong tâm trí tôi khi soạn thảo câu trả lời, nhưng lời xin lỗi của tôi vì đã không đề cập rõ ràng và gây ra bất kỳ sự nhầm lẫn (tiềm năng) nào.
Tôi thực sự coi câu trả lời được chia sẻ bởi Jörg W Mittag . Thật là sâu sắc khi đọc nó (tôi biết ít hơn) và tôi đã nâng cấp nó ngay sau khi nó được đăng. Tôi mới bắt đầu trên Sàn giao dịch công nghệ phần mềm, và kinh nghiệm và các cuộc thảo luận cho đến nay vẫn thực sự sâu sắc.
Tôi sẽ để lại câu trả lời này ở đây vì nó có thể hữu ích cho những người mới phát triển phần mềm khác, cố gắng hiểu một khái niệm mà không bị sa lầy vào các tính chính xác kỹ thuật.
Cả macro và hàm đại diện cho đơn vị mã độc lập. Cả hai đều là công cụ hỗ trợ thiết kế mô-đun của một chương trình. Từ quan điểm của lập trình viên đang viết mã nguồn, chúng có vẻ khá giống nhau. Tuy nhiên, chúng khác nhau về cách chúng được xử lý trong vòng đời thực hiện chương trình.
Một macro được định nghĩa một lần và được sử dụng ở nhiều nơi trong một chương trình. Macro được mở rộng nội tuyến trong giai đoạn tiền xử lý. Do đó, về mặt kỹ thuật, nó không còn là một thực thể riêng biệt sau khi mã nguồn được biên dịch. Các câu lệnh trong định nghĩa vĩ mô trở thành một phần của hướng dẫn chương trình, giống như các câu lệnh khác.
Động lực đằng sau việc viết một macro là làm cho việc viết và quản lý mã nguồn dễ dàng hơn cho người lập trình. Các macro thường được mong muốn cho các tác vụ đơn giản hơn trong đó việc viết một hàm đầy đủ sẽ là một hình phạt chi phí / thời gian chạy hiệu năng. Ví dụ về các tình huống trong đó macro thích hợp hơn chức năng là:
Sử dụng các giá trị không đổi (như giá trị toán học hoặc khoa học) hoặc một số tham số cụ thể của chương trình.
In thông điệp tường trình hoặc xác nhận xử lý.
Thực hiện các tính toán đơn giản hoặc kiểm tra điều kiện.
Khi sử dụng macro, thật dễ dàng để thực hiện các thay đổi / chỉnh sửa ở một nơi có sẵn ngay lập tức ở mọi nơi mà macro được sử dụng trong chương trình. Yêu cầu biên dịch lại chương trình đơn giản để các thay đổi có hiệu lực.
Mặt khác, mã chức năng được biên dịch thành một đơn vị riêng biệt trong chương trình và được tải vào bộ nhớ trong khi thực hiện chương trình chỉ khi nó được yêu cầu. Mã chức năng giữ lại danh tính độc lập với phần còn lại của chương trình. Mã được tải sẽ được sử dụng lại nếu hàm được gọi nhiều lần. Khi gặp lệnh gọi hàm trong chương trình đang chạy, điều khiển được chuyển đến nó bởi hệ thống con thời gian chạy và ngữ cảnh của chương trình đang chạy (địa chỉ lệnh trả về) được giữ nguyên.
Tuy nhiên, có một hình phạt hiệu suất nhỏ cần phải gặp khi gọi hàm (chuyển ngữ cảnh, giữ nguyên địa chỉ trả về của các hướng dẫn chương trình chính, truyền tham số và xử lý giá trị trả về, v.v.). Do đó, việc sử dụng hàm chỉ mong muốn cho các khối mã phức tạp (đối với các macro xử lý các trường hợp đơn giản hơn).
Với kinh nghiệm, một lập trình viên đưa ra quyết định sáng suốt vì liệu một đoạn mã sẽ phù hợp như một macro hay chức năng trong kiến trúc chương trình tổng thể.
Thật không may, có nhiều cách sử dụng khác nhau của thuật ngữ "macro" trong lập trình.
Trong họ ngôn ngữ Lisp và các ngôn ngữ được truyền cảm hứng từ chúng, cũng như nhiều ngôn ngữ hiện đại hoặc lấy cảm hứng từ chức năng như Scala và Haskell, cũng như một số ngôn ngữ bắt buộc như Boo, macro là một đoạn mã chạy trong thời gian biên dịch (hoặc ít nhất là trước thời gian chạy để triển khai mà không cần trình biên dịch) và có thể chuyển đổi Cây cú pháp trừu tượng (hoặc bất cứ thứ gì tương đương trong ngôn ngữ cụ thể, ví dụ như trong Lisp, nó sẽ là S-Expressions) thành một thứ khác trong quá trình biên dịch. Ví dụ, trong nhiều triển khai Đề án, for
là một macro mở rộng thành nhiều lệnh gọi đến phần thân. Trong các ngôn ngữ được nhập tĩnh, macro thường an toàn kiểu, nghĩa là chúng không thể tạo mã không được gõ tốt.
Trong họ ngôn ngữ C, macro giống như thay thế văn bản. Điều đó cũng có nghĩa là họ có thể tạo ra mã không được gõ tốt, hoặc thậm chí không hợp pháp về mặt cú pháp.
Trong trình biên dịch macro, "macro" đề cập đến "hướng dẫn ảo", tức là hướng dẫn rằng CPU không hỗ trợ tự nhiên nhưng rất hữu ích và do đó trình biên dịch cho phép bạn sử dụng các hướng dẫn đó và sẽ mở rộng chúng thành nhiều hướng dẫn mà CPU hiểu .
Trong kịch bản ứng dụng, "macro" đề cập đến một loạt các hành động mà người dùng có thể "ghi lại" và "phát lại".
Tất cả chúng đều thuộc một số loại mã thực thi, có nghĩa là chúng có thể được xem như là các hàm. Tuy nhiên, trong trường hợp macro Lisp, ví dụ, đầu vào và đầu ra của chúng là các đoạn chương trình. Trong trường hợp của C, đầu vào và đầu ra của chúng là mã thông báo. Ba cái đầu tiên cũng có sự phân biệt rất quan trọng rằng chúng được thực thi tại thời điểm biên dịch . Trong thực tế, các macro tiền xử lý C, như tên của nó, thực sự được thực thi trước khi mã thậm chí đến trình biên dịch .
Trong họ ngôn ngữ C, một định nghĩa macro , một lệnh tiền xử lý, chỉ định một mẫu mã được tham số hóa được thay thế trong lệnh gọi macro mà không được biên dịch theo định nghĩa. Điều này có nghĩa là tất cả các biến miễn phí phải được ràng buộc trong ngữ cảnh của lệnh gọi macro. Đối số tham số với hiệu ứng phụ như i++
có thể được lặp lại, bằng cách sử dụng số nhiều của tham số. Sự thay thế văn bản của đối số 1 + 2
của một số tham số x
xảy ra trước khi biên dịch và có thể gây ra hành vi không mong muốn x * 3
( 7
io 9
). Một lỗi trong phần thân macro của định nghĩa macro sẽ chỉ hiển thị khi biên dịch tại lệnh gọi macro.
Các định nghĩa hàm đang quy định cụ thể với các biến tự do ràng buộc với bối cảnh của cơ quan chức năng; không gọi hàm .
Tuy nhiên, macro có vẻ tiêu cực cung cấp quyền truy cập vào cuộc gọi, số dòng và tệp nguồn, đối số dưới dạng chuỗi .
Trong các thuật ngữ trừu tượng hơn một chút, macro là cú pháp như một hàm là dữ liệu. Một hàm (trong bản tóm tắt) gói gọn một số biến đổi trên dữ liệu. Nó lấy các đối số của nó làm dữ liệu được đánh giá, thực hiện một số thao tác trên chúng và trả về một kết quả cũng chỉ là dữ liệu.
Một macro ngược lại có một số cú pháp không được đánh giá và hoạt động trên đó. Đối với các ngôn ngữ giống như C, cú pháp xuất hiện ở cấp mã thông báo. Đối với các ngôn ngữ có macro giống LISP, chúng nhận được cú pháp được biểu thị dưới dạng AST. Macro phải trả về một đoạn cú pháp mới.
Một macro thường đề cập đến một cái gì đó được mở rộng tại chỗ , thay thế "cuộc gọi" macro trong quá trình biên dịch hoặc xử lý trước bằng các hướng dẫn riêng lẻ trong ngôn ngữ đích. Trong thời gian chạy, nhìn chung sẽ không có dấu hiệu cho thấy macro bắt đầu và kết thúc ở đâu.
Điều này khác với chương trình con , là một đoạn mã có thể tái sử dụng được đặt riêng trong bộ nhớ, mà điều khiển được truyền khi chạy . "Các hàm", "thủ tục" và "phương thức" trong hầu hết các ngôn ngữ lập trình thuộc loại này.
Như câu trả lời của Jorg W Mittag thảo luận, các chi tiết chính xác khác nhau giữa các ngôn ngữ: trong một số ngôn ngữ, chẳng hạn như C, một macro thực hiện thay thế văn bản trong mã nguồn; trong một số, chẳng hạn như Lisp, nó thực hiện thao tác với hình thức trung gian như Cây Cú pháp Trừu tượng. Ngoài ra còn có một số vùng màu xám: một số ngôn ngữ có ký hiệu cho "hàm nội tuyến", được định nghĩa giống như một hàm, nhưng được mở rộng vào chương trình được biên dịch như macro.
Hầu hết các ngôn ngữ khuyến khích các lập trình viên suy luận về từng chương trình con một cách cô lập, xác định hợp đồng loại cho đầu vào và đầu ra và ẩn thông tin khác về mã gọi. Thường có một tác động hiệu năng để gọi một chương trình con mà macro không phát sinh và các macro có thể điều khiển chương trình theo cách mà chương trình con không thể. Do đó, macro có thể được coi là "cấp thấp hơn" so với chương trình con, vì chúng hoạt động trên cơ sở ít trừu tượng hơn.
Macro được thực thi trong quá trình biên dịch và hàm được thực thi trong thời gian chạy.
Thí dụ:
#include <stdio.h>
#define macro_sum(x,y) (x+y)
int func_sum(x,y) {
return x+y;
}
int main(void) {
printf("%d\n", macro_sum(2,3));
printf("%d\n", func_sum(2,3));
return 0;
}
Vì vậy, trong quá trình biên dịch, mã thực sự được thay đổi thành:
#include <stdio.h>
int func_sum(x,y) {
return x+y;
}
int main(void) {
printf("%d\n", (2+3));
printf("%d\n", func_sum(2,3));
return 0;
}