從vue3開始vue引入了宏,比如defineProps、defineEmits等。我們每天寫vue代碼時都會使用到這些宏,但是你有沒有思考過vue中的宏到底是什么?為什么這些宏不需要手動從vue中import?為什么只能在setup頂層中使用這些宏?
要回答上面的問題,我們先來了解一下從一個vue文件到渲染到瀏覽器這一過程經歷了什么?
我們的vue代碼一般都是寫在后綴名為vue的文件上,顯然瀏覽器是不認識vue文件的,瀏覽器只認識html、css、jss等文件。所以第一步就是通過webpack或者vite將一個vue文件編譯為一個包含render函數的js文件。然后執行render函數生成虛擬DOM,再調用瀏覽器的DOM API根據虛擬DOM生成真實DOM掛載到瀏覽器上。
我們先來看看vue官方的解釋:
宏是一種特殊的代碼,由編譯器處理并轉換為其他東西。它們實際上是一種更巧妙的字符串替換形式。
通過前面我們知道了vue 文件渲染到瀏覽器上主要經歷了兩個階段。
第一階段是編譯時,也就是從一個vue文件經過webpack或者vite編譯變成包含render函數的js文件。此時的運行環境是nodejs環境,所以這個階段可以調用nodejs相關的api,但是沒有在瀏覽器環境內執行,所以不能調用瀏覽器的API。
第二階段是運行時,此時瀏覽器會執行js文件中的render函數,然后依次生成虛擬DOM和真實DOM。此時的運行環境是瀏覽器環境內,所以可以調用瀏覽器的API,但是在這一階段中是不能調用nodejs相關的api。
而宏就是作用于編譯時,也就是從vue文件編譯為js文件這一過程。
舉個defineProps的例子:在編譯時defineProps宏就會被轉換為定義props相關的代碼,當在瀏覽器運行時自然也就沒有了defineProps宏相關的代碼了。所以才說宏是在編譯時執行的代碼,而不是運行時執行的代碼。
我們來看一個實際的例子,下面這個是我們的源代碼:
<template>
<div>content is {{ content }}</div>
<div>title is {{ title }}</div>
</template>
<script setup lang="ts">
import {ref} from "vue"
const props=defineProps({
content: String,
});
const title=ref("title")
</script>
在這個例子中我們使用defineProps宏定義了一個類型為String,屬性名為content的props,并且在template中渲染content的內容。
我們接下來再看看編譯成js文件后的代碼,代碼我已經進行過簡化:
import { defineComponent as _defineComponent } from "vue";
import { ref } from "vue";
const __sfc__=_defineComponent({
props: {
content: String,
},
setup(__props) {
const props=__props;
const title=ref("title");
const __returned__={ props, title };
return __returned__;
},
});
import {
toDisplayString as _toDisplayString,
createElementVNode as _createElementVNode,
Fragment as _Fragment,
openBlock as _openBlock,
createElementBlock as _createElementBlock,
} from "vue";
function render(_ctx, _cache, $props, $setup) {
return (
_openBlock(),
_createElementBlock(
_Fragment,
null,
[
_createElementVNode(
"div",
null,
"content is " + _toDisplayString($props.content),
1 /* TEXT */
),
_createElementVNode(
"div",
null,
"title is " + _toDisplayString($setup.title),
1 /* TEXT */
),
],
64 /* STABLE_FRAGMENT */
)
);
}
__sfc__.render=render;
export default __sfc__;
我們可以看到編譯后的js文件主要由兩部分組成,第一部分為執行defineComponent函數生成一個 __sfc__ 對象,第二部分為一個render函數。render函數不是我們這篇文章要講的,我們主要來看看這個__sfc__對象。
看到defineComponent是不是覺得很眼熟,沒錯這個就是vue提供的API中的 definecomponent函數。這個函數在運行時沒有任何操作,僅用于提供類型推導。這個函數接收的第一個參數就是組件選項對象,返回值就是該組件本身。所以這個__sfc__對象就是我們的vue文件中的script代碼經過編譯后生成的對象,后面再通過__sfc__.render=render將render函數賦值到組件對象的render方法上面。
我們這里的組件選項對象經過編譯后只有兩個了,分別是props屬性和setup方法。明顯可以發現我們原本在setup里面使用的defineProps宏相關的代碼不在了,并且多了一個props屬性。沒錯這個props屬性就是我們的defineProps宏生成的。
我們再來看一個不在setup頂層調用defineProps的例子:
<script setup lang="ts">
import {ref} from "vue"
const title=ref("title")
if (title.value) {
const props=defineProps({
content: String,
});
}
</script>
運行這個例子會報錯:defineProps is not defined
我們來看看編譯后的js代碼:
import { defineComponent as _defineComponent } from "vue";
import { ref } from "vue";
const __sfc__=_defineComponent({
setup(__props) {
const title=ref("title");
if (title.value) {
const props=defineProps({
content: String,
});
}
const __returned__={ title };
return __returned__;
},
});
明顯可以看到由于我們沒有在setup的頂層調用defineProps宏,在編譯時就不會將defineProps宏替換為定義props相關的代碼,而是原封不動的輸出回來。在運行時執行到這行代碼后,由于我們沒有任何地方定義了defineProps函數,所以就會報錯defineProps is not defined。
現在我們能夠回答前面提的三個問題了。
作者:歐陽碼農
鏈接:https://juejin.cn/post/7335721246931189795
文鏈接:The Rust Programming Language
作者:rust 團隊
譯文首發鏈接:zhuanlan.zhihu.com/p/516660154
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
中間加了一些對于 JavaScript 開發者有幫助的注解。在學習 Rust 的代碼時,尤其是有一些經驗的開發者,一般都會去看一些相對簡單的庫,或者項目去學習。Rust 的原生語法本身并不復雜,有一定的 TypeScript 經驗的開發者,應該通過看一些教程,都能開始熟悉基本的語法。反而是宏相關內容,雖然就像本文寫的,大部分開發者不需要自己去開發宏,但是大家使用宏,和看懂別人代碼的主體邏輯,幾乎是繞不開宏的。雖然宏在 rust 里屬于“高級”內容,但是因為其和 rust 本身語法的正交性,Hugo 認為,反而應該早一些學習。并且,如果一個 JavaScript 開發者之前接觸過代碼生成器、babel,對這一章的內容反而會比較親切。
Derive 宏、attribute 宏特別像 rust 里的裝飾器。從作用上,和一般庫提供的接口來看,也特別像。所以如果之前有裝飾器的經驗的開發者,對這一章節應該也會比較親切。
整本書經常使用 println! 宏,但是還沒介紹宏這個機制。宏實際上是 rust 的一系列特性的合集:聲明式宏(declarative macro):使用 marco_rules!聲明的代碼和三種過程式宏(procedural macros):
我們一個個來討論這些內容,但是首先,我們看既然我們已經有了函數,我們為什么需要這些特性。
基本上,宏是指一些代碼可以生成另一些代碼,這一塊的技術一般稱為元編程(Hugo 注:代碼生成器也屬于這一類技術)。在附錄 C,我們討論了 derive 屬性,可以幫助你生成一系列的 trait。整本書我們也在用 println! 和 vec! 宏。這些宏在編譯時,都會展開成為代碼,這樣你就不需要手寫這些代碼。
元編程可以幫助你減少手寫和維護的代碼量,當然,函數也能幫助你實現類似的功能。但是,宏有函數沒有的威力。
函數必須聲明如慘的數量和種類。而宏,在另一方面,可以接收任意數量的參數:我們可以調用 println!("hello"),也可以調用 println!("hello {}", name)。并且,宏是在編譯階段展開了代碼,所以一個宏可以在編譯時為一個類型實現一個 trait。一個函數就不可以。因為函數是在運行時調用,而 trait 實現只可以發生在編譯時。(Hugo 注:JS 可以實現運行時生成 trait(這里只是套用 rust 的概念),當然,如果你需要的話。動態語言在某些場景能很簡單實現非常強大的功能。)
宏不好的地方在于,宏很復雜,因為你要用 rust 代碼寫 rust 代碼(Hugo 注:任何元編程都不是簡單的事兒,包括 JS 里的。)。因為這種間接性,宏的代碼要更難讀、難理解、難維護。(Hugo 注:個人學 rust,感覺最難不是生命周期,因為生命周期的問題,可以通過用一些庫繞過去,或者無腦 clone,如果是應用程序,則可以通過使用 orm 和數據庫來繞過很多生命周期的問題。反而是宏,因為稍微有點規模的代碼的,都有一大堆宏。宏最難的不是語法,而是作者的意圖,因為本質是他造了一套 DSL)
另一個和函數不一樣的地方是,宏需要先定義或者引入作用域,而函數可以在任何地方定義和使用。
在 Rust 中使用最廣泛的宏是聲明式宏。它們有時也被稱為 “macros by example”、“macro_rules!宏” 或者就是 “macros”。聲明式宏寫起來和 Rust 的 match 語法比較像。在第六章里講到,match 語法是一種流程控制語法,接收一個表達式,然后和結果進行模式匹配,然后執行匹配到結果的代碼。宏也會做類似的比較:在這種情況下,傳入的參數是 rust 的合法的語法代碼,然后通過宏的規則,和書寫好的模版,在編譯時轉換成代碼。
定義聲明式宏的語法是 macro_rule!。下面我來用 vec! 來介紹這一機制。第八章有關于 vec! 的內容。例如,創建一個新的 vector,包含 3 個 integer:
#![allow(unused)]
fn main() {
let v: Vec<u32>=vec![1, 2, 3];
}
我們可以通過 vec! 宏來創建任意類型的 vector,例如 2 個 integer,或者五個 string slice.
我們不能用函數去實現這個功能,因為我們不知道輸入的參數個數。(Hugo 注:這一點和 JS 非常不一樣,我們寫 JS,已經習慣了可以傳任意變量。當然如果你熟悉 TS,和 TS 有一些類似。Rust 雖然也有范型,但是和 TS 的范型非常不一樣。這里不一樣,我主要指關注點,因為 Rust 比較大一部分都是函數式的代碼,每個函數一般都承載非常細粒度的功能,一般每個函數都處理好了自己的輸入、輸出、報錯,所有可能性都寫好了,寫 rust 有一種在填狀態機的錯覺。。TS 有的代碼也有這種感覺。)
一個簡化的 vec! 宏:
#[macro_export]
macro_rules! vec {
( $( $x:expr ),* )=> {
{
let mut temp_vec=Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
注意:實際的 vec! 的聲明,還包括了提前分配合適的內存。這里簡化這個代碼,為了更好的講聲明式宏的概念。
#[macro_export] 標注指明了這個宏在 crate 的作用域里可用。沒有這個標注,宏不會被帶入到作用域里。
macro_rules! 后面就是宏的名字。這里只有一種模式匹配的邊(arm):( (( (x:expr ),* ) ,=> 后面是這個模式對應要生成的代碼。如果這個模式匹配成功,對應的代碼和輸入的參數組成的代碼就會生成在最終的代碼中。因為這里只有一種邊,所以只有這一種可以匹配的條件。不符合這個條件的輸入,都會報錯。一般復雜的宏,都會有多個邊。
這里匹配的規則和 match 是不一樣的,因為這里的語法匹配的是 rust 的語法,而不是 rust 的類型,或者值。更全的宏匹配語法,見文檔。
對于 宏的輸入條件 ( (( (x:expr ),* ),()內部是匹配的語法,expr表示所有Rust的表達式。() 內部是匹配的語法,expr 表示所有 Rust 的表達式。()內部是匹配的語法,expr表示所有Rust的表達式。() 后面的都喊表示這個變量后面有可能有逗號,* 表示前面的模式會出現一次或者多次。(Hugo 注:像不像正則?宏語法其實挺簡單的,不要被高級唬住了。當然,宏還是難的,宏要考慮的問題本身是一個復雜的問題。)
當我們調用:vec![1, 2, 3]; 時,$x 模式會匹配 3 個表達式 1 , 2 和 3。
現在我們看一下和這個邊匹配的生成代碼的部分:
{
{
let mut temp_vec=Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
在 ()里的tempvec.push(() 里的 temp_vec.push(()里的tempvec.push(x); 就是生成的代碼的部分。* 號仍然表示生成零個和多個,這個匹配的具體個數,要看匹配條件命中的個數。
當我們調用:vec![1, 2, 3]; 時,生成了這個代碼。(Hugo 注:Cargo 有 expand 插件,對于聲明宏,多看看展開基本就能學會了。)
{
let mut temp_vec=Vec::new();
temp_vec.push(1);
temp_vec.push(2);
temp_vec.push(3);
temp_vec
}
你傳任意參數,最后就生成符合上面條件的代碼。
有一些 macro_rules! 的奇怪的邊界例子。在未來,Rust 會有第二種聲明式宏,和現在的機制類似,但是會解決這些邊界問題。在那一次升級后,macro_rules! 會被棄用。(Hugo 注:Rust 仍然是非常年輕的語言,做好隨時接受改變的準備)記住這些,當然另一個事實是,大部分 Rust 程序員更多是宏的使用者,而不是開發者,我們不會在深入討論 macro_rules!。如果你對這塊特別感興趣。請閱讀《“The Little Book of Rust Macros”》。(Hugo 注:站在入門的角度,能知道機制去使用就 ok 了。在絕大部分入門的情況下,函數以及使用 crates.io 上的宏都能滿足你的需求。)
第二種宏是過程宏,表現形式更像函數(過程的一種類型)。過程宏的入參是一些代碼,你可以操作這些代碼,然后襯衫一些代碼。(Hugo:從結果看和聲明宏沒區別,其實站在 JS 的角度,更像是 babel,你可以根據輸入的 token 做變換)
雖然過程宏有三種:custom derive、attribute-like 和 function-like,但是原理都是一樣的。
如果要創建過程宏,定義的部分需要在自己的 crate 里,并且要定義特殊的 crate 類型。(Hugo 注:相當于定義了一個 babel 插件,只不過有一套 rust 自己的體系。這些宏會在編譯的時候,按照書寫的規則,轉成對應的代碼。所有的宏,都是代碼生成的手段,輸入是代碼,輸入是代碼。)這種設計,我們有可能會在未來消除。
下面是一個過程宏的例子:
use proc_macro;
#[some_attribute]
pub fn some_name(input: TokenStream) -> TokenStream {
}
過程宏接收一個 TokenStream,輸出一個 TokenStream。TokenStream 類型定義在 proc_macro 里,表示一系列的 tokens。這個就是這種宏的核心機制,輸入的代碼(會被 rust) 轉成 TokenStream,然后做一些按照業務邏輯的操作,最后生成 TokenStream。這個函數也可以疊加其他的屬性宏(#[some_attribute], 看起來像裝飾器的邏輯,也可以理解為一種鏈式調用),可以在一個 crate 里定義多個過程。(Hugo 注:搞過 babel 的同學肯定很熟悉,一樣的味道。沒搞過的同學,強烈建議先學學 babel。)
下面我們來看看不同類型的過程宏。首先從自定義 derive 宏開始,然后我們介紹這種宏和其他幾種的區別。
我們創建一個 crate 名字叫 hello_macro,定義一個 HelloMacro 的 trait,關聯的函數名字叫 hello_macro。通過使用這個宏,用戶的結構可以直接獲得默認定義的 hello_macro 函數,而不需要實現這個 trait。默認的 hello_macro 可以打印 Hello, Macro! My name is TypeName!,其中 TypeName 是實現這個 derive 宏的結構的類型名稱。
use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;
#[derive(HelloMacro)]
struct Pancakes;
fn main() {
Pancakes::hello_macro();
}
創建這個宏的過程如下,首先
$ cargo new hello_macro --lib
然后定義 HelloMacro trait
pub trait HelloMacro {
fn hello_macro();
}
這樣我們就有了一個 trait,和這個triat 的函數。用戶可以通過這個 trait 直接實現對應的函數。
use hello_macro::HelloMacro;
struct Pancakes;
impl HelloMacro for Pancakes {
fn hello_macro() {
println!("Hello, Macro! My name is Pancakes!");
}
}
fn main() {
Pancakes::hello_macro();
}
但是,用戶需要每次都實現一遍 hello_macro。如果 hello_macro 的實現都差不多,就可以通過 derive 宏來是實現。
因為 Rust 沒有反射機制,我們不可以在執行時知道對應類型的名字。我們需要在編譯時生成對應的代碼。
下一步,定義過程宏。在這個文章編寫時,過程宏需要在自己的 crates 里。最終,這個設計可能改變。關于 宏 crate 的約定是:對于一個名為 foo 的 crate,自定義 drive 宏的crate 名字為 foo_derive。我們在 hello_macro 項目中創建 hello_macro_derive crate。
$ cargo new hello_macro_derive --lib
我們的兩個的 crate 關聯緊密,所以我們在 hello_macro crate 里創建這個 crate。如果我們要改變 hello_macro 的定義,我們同樣也要更改 hello_macro_derive 的定義。這兩個 crates 要隔離發布。當用戶使用時,要同時添加這兩個依賴。為了簡化依賴,我們可以讓 hello_macro 使用 hello_macro_derive 作為依賴,然后導出這個依賴。但是,這樣,如果用戶不想使用 hello_macro_derive,也會自動添加上這個依賴。
下面開始創建 hello_macro_derive,作為一個過程宏 crate。需要添加依賴 syn 和 quote。下面是這個 crate 的 Cargo.toml。
[lib]
proc-macro=true
[dependencies]
syn="1.0"
quote="1.0"
在 lib.rs 里添加下述代碼。注意,這個代碼如果不增加 impl_hello_macro 的實現是通不過編譯的。
use proc_macro::TokenStream;
use quote::quote;
use syn;
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast=syn::parse(input).unwrap();
// Build the trait implementation
impl_hello_macro(&ast)
}
注意,這里把代碼分散成兩部分,一部分在 hello_macro_derive 函數里,這個函數主要負責處理 TokenStream,另一部分在 impl_hello_macro,這里負責轉換語法樹:這樣編寫過程宏可以簡單一些。在絕大部分過程宏立,對于前者的過程一般都是一樣的。一般來說,真正的區別在 impl_hello_macro,這里的邏輯一般是一個過程宏的業務決定的。
我們引入了三個 crates: proc_macro, syn 和 quote。proc_macro 內置在 rust 立,不需要在 Cargo.toml 中引入。proc_macro 實際是 rust 編譯器的一個接口,用來讀取和操作 Rust 代碼。
syn crate 把 Rust 代碼從字符串轉換為可以操作的結構體。quote crate 把 syn 數據在轉回 Rust 代碼。這些 Crate 可以極大簡化過程宏的編寫:寫一個 Rust 代碼的 full parser 可不是容易的事兒!
當在一個類型上標注 [derive(HelloMacro)] 時,會調用 hello_macro_derive 函數。之所以會有這樣的行為,是因為在定義 hello_macro_derive 時,標注了 #[proc_macro_derive(HelloMacro)] 在函數前面。
hello_macro_derive 會把輸入從 TokenStream 轉換為一個我們可以操作的數據結構。這就是為什么需要引入 syn 。sync 的 parse 函數會把 TokenStream 轉換為 DeriveInput。
DeriveInput {
// --snip--
ident: Ident {
ident: "Pancakes",
span: #0 bytes(95..103)
},
data: Struct(
DataStruct {
struct_token: Struct,
fields: Unit,
semi_token: Some(
Semi
)
}
)
}
上述這個結構的意思是:正在處理的是 ident(identifier, 意味著名字)為 Pancakes 的 unit struct。其他的字段表示其余的 Rust 代碼。如果想了解更詳細的內容,請參考。
接下來,我們就要開始定義 impl_hello_macro。這個函數實現了添加到 Rust 代碼上的函數。在我們做之前,注意 derive macro 的輸出也是 TokenStream。返回的 TokenStream 就是添加完代碼以后的代碼。當編譯 crate 時,最終的代碼,就是處理完成的代碼了。
你也許也會發現,這里調用 syn::parse 時使用了 unwrap,如果報錯就中斷。這里必須這么做,因為最終返回的是 TokenStream,而不是 Result。這里是為了簡化代碼說明這個問題。在生產代碼,你應該處理好報錯,提供更詳細的報錯信息,例如使用 panic! 或者 expect。
下面是代碼:
fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
// 通過&ast.ident 獲取類型的名字
let name=&ast.ident;
// 使用 quote 宏,可以使用 rust 語法來定義要實現的 trait(Hugo 注:JS 要有這個就好了)
let gen=quote! {
// #name 是 quote! 的模版語法,會自動替換為這個變量里的值
impl HelloMacro for #name {
fn hello_macro() {
// 這里把對應的 struct 名字轉換好了,stringfy!把值轉換為字符串
println!("Hello, Macro! My name is {}!", stringify!(#name));
}
}
};
// 轉換為最終的 TokenStream
gen.into()
}
通過上面的代碼,cargo build 就可以正常工作了。如果要使用這個代碼,需要把兩個依賴都加上。
hello_macro={ path="../hello_macro" }
hello_macro_derive={ path="../hello_macro/hello_macro_derive" }
現在執行下面的代碼,就可以看到 Hello, Macro! My name is Pancakes!
use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;
#[derive(HelloMacro)]
struct Pancakes;
fn main() {
Pancakes::hello_macro();
}
下一步,我們來探索其他類型的過程宏。
屬性宏和 derive 宏類似,但是可以創造除了 derive 意外的屬性。derive 只能作用于 structs 和 enums,屬性宏可以作用于其他的東西,比如函數。下面是一個屬性宏的例子:例如你制作了一個名為 route 的屬性宏來在一個web 框架中標注函數。
#[route(GET, "/")]
fn index() {
#[route] 是框架定義的過程宏。定義這個宏的函數類似:
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
這里,有兩個參數,類型都是 TokenStream。第一個是屬性的內容,GET, "/" 部分,第二個是標注屬性宏傳入的語法部分,在這個例子里,就是剩下的 fn index() {}。
工作原理和 derive 宏是一樣的。
函數宏的使用比較像調用一個 rust 函數。函數宏有點像 macro_rules! ,能提供比函數更高的靈活性。例如,可以接受未知個數的參數。但是,macro_rules! 只能使用在上述章節的匹配型的語法。而函數宏接受 TokenStream 參數作為入參,和其他過程宏一樣,可以做任何變換,然后返回 TokenStream。下面是一個函數宏 sql!
let sql=sql!(SELECT * FROM posts WHERE id=1);
這個宏接受 SQL 語句,可以檢查這個 SQL 的語法是否正確,這種功能比 macro_rules! 提供的要復雜的多。這個 sql! 的宏可以定義為:
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
這個定義和自定義 derive 宏類似:接受括號內的 tokens,返回生成的代碼。
好了,現在你有了一些可能不常用的 Rust 新工具,但是你要知道的是,在需要的場合,他們的運行原理是什么。我們介紹了一些復雜的話題,當你在錯誤處理或者別人的代碼里看到這些宏時,可以認出這些概念和語法。可以使用這一章的內容作為解決這些問題的索引。
概述
在工程規模較小,不是很復雜,與硬件結合緊密,要求移植性的時候,可采用宏定義簡化編程,增強程序可讀性。
當宏作為常量使用時,C程序員習慣在名字中只使用大寫字母。但是并沒有如何將用于其他目的的宏大寫的統一做法。由于宏(特別是帶參數的宏)可能是程序中錯誤的來源,所以一些程序員更喜歡使用大寫字母來引起注意。
無參宏的宏名后不帶參數,其定義的一般形式為:
#define 標識符 字符串
// 不帶參數的宏定義
#define MAX 10
注意:不要在宏定義中放置任何額外的符號,比如"="或者尾部加";"
使用#define來為常量命名一些優點:
帶參數的仍要遵循上述規則,區別只是宏名后面緊跟的圓括號中放置了參數,就像真正的函數那樣。
#define <宏名>(<參數列表>) <宏體>
注意參數列表中的參數必須是有效的c標識符,同時以,分隔
算符優先級問題:
#define COUNT(M) M*M
int x=5;
print(COUNT(x+1));
print(COUNT(++X));
//結果輸出:11 和42 而不是函數的輸出36
注意:
解決辦法:
分號吞噬問題:
#define foo(x) bar(x); baz(x)
假設這樣調用:
if (!feral)
foo(wolf);
將被宏擴展為:
if (!feral)
bar(wolf);
baz(wolf);
==baz(wolf);==,不在判斷條件中,顯而易見,這是錯誤。如果用大括號將其包起來依然會有問題,例如
#define foo(x) { bar(x); baz(x); }
if (!feral)
foo(wolf);
else
bin(wolf);
判斷語言被擴展成:
if (!feral) {
bar(wolf);
baz(wolf);
}>>++;++<<
else
bin(wolf);
==else==將不會被執行
解決方法:通過==do{…}while(0)
#define foo(x) do{ bar(x); baz(x); }while(0)
if (!feral)
foo(wolf);
else
bin(wolf);
被擴展成:
#define foo(x) do{ bar(x); baz(x); }while(0)
if (!feral)
do{ bar(x); baz(x); }while(0);
else
bin(wolf);
注意:使用do{…}while(0)構造后的宏定義不會受到大括號、分號等的影響,總是會按你期望的方式調用運行。
#的作用就是將#后邊的宏參數進行字符串的操作,也就是將#后邊的參數兩邊加上一對雙引號使其成為字符串。例如a是一個宏的形參,則替換文本中的#a被系統轉化為"a",這個轉換過程即為字符串化。
#define TEST(param) #param
char *pStr=TEST(123);
printf("pSrt=%s\n",pStr);
//輸出結果為字符 ”123“
##運算符也可以用在替換文本中,它的作用起到粘合的作用,即將兩個宏參數連接為一個數
#define TEST(param1,param2) (param1##param2)
int num=TEST(13,59);
printf("num=%d\n",num);
//輸出結果為:num=1359
作用主要是為了方便管理軟件中的打印信息。在寫代碼或DEBUG時通常需要將一些重要參數打印出來,但在軟件發行的時候不希望有這些打印,這時就用到可變參數宏了。
# define PR(...) printf(_VA_ARGS_)
2 PR("hello world\n");
3
4 輸出結果:hello world
#ifndef COMDEF_H
#define COMDEF_H
//頭文件內容
#endif
#define MEM_B(x) (*((byte *)(x)))
#define MEM_W(x) (*((word *)(x)))
#define MAX(x,y) (((x)>(y)) ? (x) : (y))
#define MIN(x,y) (((x) < (y)) ? (x) : (y))
#define FPOS(type,field) ((dword)&((type *)0)->field)
#define FSIZ(type,field) sizeof(((type *)0)->field)
#define FLIPW(ray) ((((word)(ray)[0]) * 256) + (ray)[1])
#define WORD_LO(xxx) ((byte) ((word)(xxx) & 255))
#define WORD_HI(xxx) ((byte) ((word)(xxx) >> 8))
#define UPCASE(c) (((c)>='a' && (c) <='z') ? ((c) – 0×20) : (c))
#define DECCHK(c) ((c)>='0' && (c)<='9')
#define HEXCHK(c) (((c) >='0' && (c)<='9') ((c)>='A' && (c)<='F') \
((c)>='a' && (c)<='f'))
#define INC_SAT(val) (val=((val)+1>(val)) ? (val)+1 : (val))
#define ARR_SIZE(a) (sizeof((a))/sizeof((a[0])))
*請認真填寫需求信息,我們會在24小時內與您取得聯系。