Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 天天干天天干天天干天天,日韩视频中文字幕视频一区,国产精品一区二区久久

          整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          vue3的宏到底是什么東西?

          vue3的宏到底是什么東西?

          從vue3開始vue引入了宏,比如defineProps、defineEmits等。我們每天寫vue代碼時(shí)都會(huì)使用到這些宏,但是你有沒有思考過vue中的宏到底是什么?為什么這些宏不需要手動(dòng)從vue中import?為什么只能在setup頂層中使用這些宏?

          vue 文件如何渲染到瀏覽器上

          要回答上面的問題,我們先來了解一下從一個(gè)vue文件到渲染到瀏覽器這一過程經(jīng)歷了什么?

          我們的vue代碼一般都是寫在后綴名為vue的文件上,顯然瀏覽器是不認(rèn)識(shí)vue文件的,瀏覽器只認(rèn)識(shí)html、css、jss等文件。所以第一步就是通過webpack或者vite將一個(gè)vue文件編譯為一個(gè)包含render函數(shù)的js文件。然后執(zhí)行render函數(shù)生成虛擬DOM,再調(diào)用瀏覽器的DOM API根據(jù)虛擬DOM生成真實(shí)DOM掛載到瀏覽器上。

          vue3的宏是什么?

          我們先來看看vue官方的解釋:

          宏是一種特殊的代碼,由編譯器處理并轉(zhuǎn)換為其他東西。它們實(shí)際上是一種更巧妙的字符串替換形式。

          宏是在哪個(gè)階段運(yùn)行?

          通過前面我們知道了vue 文件渲染到瀏覽器上主要經(jīng)歷了兩個(gè)階段。

          第一階段是編譯時(shí),也就是從一個(gè)vue文件經(jīng)過webpack或者vite編譯變成包含render函數(shù)的js文件。此時(shí)的運(yùn)行環(huán)境是nodejs環(huán)境,所以這個(gè)階段可以調(diào)用nodejs相關(guān)的api,但是沒有在瀏覽器環(huán)境內(nèi)執(zhí)行,所以不能調(diào)用瀏覽器的API。

          第二階段是運(yùn)行時(shí),此時(shí)瀏覽器會(huì)執(zhí)行js文件中的render函數(shù),然后依次生成虛擬DOM和真實(shí)DOM。此時(shí)的運(yùn)行環(huán)境是瀏覽器環(huán)境內(nèi),所以可以調(diào)用瀏覽器的API,但是在這一階段中是不能調(diào)用nodejs相關(guān)的api。

          而宏就是作用于編譯時(shí),也就是從vue文件編譯為js文件這一過程。

          舉個(gè)defineProps的例子:在編譯時(shí)defineProps宏就會(huì)被轉(zhuǎn)換為定義props相關(guān)的代碼,當(dāng)在瀏覽器運(yùn)行時(shí)自然也就沒有了defineProps宏相關(guān)的代碼了。所以才說宏是在編譯時(shí)執(zhí)行的代碼,而不是運(yùn)行時(shí)執(zhí)行的代碼。

          一個(gè)defineProps宏的例子

          我們來看一個(gè)實(shí)際的例子,下面這個(gè)是我們的源代碼:

          <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>
          


          在這個(gè)例子中我們使用defineProps宏定義了一個(gè)類型為String,屬性名為content的props,并且在template中渲染content的內(nèi)容。

          我們接下來再看看編譯成js文件后的代碼,代碼我已經(jīng)進(jìn)行過簡(jiǎn)化:

          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文件主要由兩部分組成,第一部分為執(zhí)行defineComponent函數(shù)生成一個(gè) __sfc__ 對(duì)象,第二部分為一個(gè)render函數(shù)。render函數(shù)不是我們這篇文章要講的,我們主要來看看這個(gè)__sfc__對(duì)象。

          看到defineComponent是不是覺得很眼熟,沒錯(cuò)這個(gè)就是vue提供的API中的 definecomponent函數(shù)。這個(gè)函數(shù)在運(yùn)行時(shí)沒有任何操作,僅用于提供類型推導(dǎo)。這個(gè)函數(shù)接收的第一個(gè)參數(shù)就是組件選項(xiàng)對(duì)象,返回值就是該組件本身。所以這個(gè)__sfc__對(duì)象就是我們的vue文件中的script代碼經(jīng)過編譯后生成的對(duì)象,后面再通過__sfc__.render=render將render函數(shù)賦值到組件對(duì)象的render方法上面。

          我們這里的組件選項(xiàng)對(duì)象經(jīng)過編譯后只有兩個(gè)了,分別是props屬性和setup方法。明顯可以發(fā)現(xiàn)我們?cè)驹趕etup里面使用的defineProps宏相關(guān)的代碼不在了,并且多了一個(gè)props屬性。沒錯(cuò)這個(gè)props屬性就是我們的defineProps宏生成的。

          我們?cè)賮砜匆粋€(gè)不在setup頂層調(diào)用defineProps的例子:

          <script setup lang="ts">
          import {ref} from "vue"
          const title=ref("title")
          
          if (title.value) {
            const props=defineProps({
              content: String,
            });
          }
          </script>
          


          運(yùn)行這個(gè)例子會(huì)報(bào)錯(cuò):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的頂層調(diào)用defineProps宏,在編譯時(shí)就不會(huì)將defineProps宏替換為定義props相關(guān)的代碼,而是原封不動(dòng)的輸出回來。在運(yùn)行時(shí)執(zhí)行到這行代碼后,由于我們沒有任何地方定義了defineProps函數(shù),所以就會(huì)報(bào)錯(cuò)defineProps is not defined。

          總結(jié)

          現(xiàn)在我們能夠回答前面提的三個(gè)問題了。

          • vue中的宏到底是什么?
          • vue3的宏是一種特殊的代碼,在編譯時(shí)會(huì)將這些特殊的代碼轉(zhuǎn)換為瀏覽器能夠直接運(yùn)行的指定代碼,根據(jù)宏的功能不同,轉(zhuǎn)換后的代碼也不同。
          • 為什么這些宏不需要手動(dòng)從vue中import?
          • 因?yàn)樵诰幾g時(shí)已經(jīng)將這些宏替換為指定的瀏覽器能夠直接運(yùn)行的代碼,在運(yùn)行時(shí)已經(jīng)不存在這些宏相關(guān)的代碼,自然不需要從vue中import。
          • 為什么只能在setup頂層中使用這些宏?
          • 因?yàn)樵诰幾g時(shí)只會(huì)去處理setup頂層的宏,其他地方的宏會(huì)原封不動(dòng)的輸出回來。在運(yùn)行時(shí)由于我們沒有在任何地方定義這些宏,當(dāng)代碼執(zhí)行到宏的時(shí)候當(dāng)然就會(huì)報(bào)錯(cuò)。


          作者:歐陽碼農(nóng)
          鏈接:https://juejin.cn/post/7335721246931189795

          文鏈接:The Rust Programming Language

          作者:rust 團(tuán)隊(duì)

          譯文首發(fā)鏈接:zhuanlan.zhihu.com/p/516660154

          著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

          前言

          中間加了一些對(duì)于 JavaScript 開發(fā)者有幫助的注解。在學(xué)習(xí) Rust 的代碼時(shí),尤其是有一些經(jīng)驗(yàn)的開發(fā)者,一般都會(huì)去看一些相對(duì)簡(jiǎn)單的庫,或者項(xiàng)目去學(xué)習(xí)。Rust 的原生語法本身并不復(fù)雜,有一定的 TypeScript 經(jīng)驗(yàn)的開發(fā)者,應(yīng)該通過看一些教程,都能開始熟悉基本的語法。反而是宏相關(guān)內(nèi)容,雖然就像本文寫的,大部分開發(fā)者不需要自己去開發(fā)宏,但是大家使用宏,和看懂別人代碼的主體邏輯,幾乎是繞不開宏的。雖然宏在 rust 里屬于“高級(jí)”內(nèi)容,但是因?yàn)槠浜?rust 本身語法的正交性,Hugo 認(rèn)為,反而應(yīng)該早一些學(xué)習(xí)。并且,如果一個(gè) JavaScript 開發(fā)者之前接觸過代碼生成器、babel,對(duì)這一章的內(nèi)容反而會(huì)比較親切。

          Derive 宏、attribute 宏特別像 rust 里的裝飾器。從作用上,和一般庫提供的接口來看,也特別像。所以如果之前有裝飾器的經(jīng)驗(yàn)的開發(fā)者,對(duì)這一章節(jié)應(yīng)該也會(huì)比較親切。

          正文

          整本書經(jīng)常使用 println! 宏,但是還沒介紹宏這個(gè)機(jī)制。宏實(shí)際上是 rust 的一系列特性的合集:聲明式宏(declarative macro):使用 marco_rules!聲明的代碼和三種過程式宏(procedural macros):

          • 自定義 #[derive] 宏,可以把制定的代碼作用在 struct 和 enum 里
          • 屬性類似的宏,可以把任何屬性定義在任何東西上
          • 函數(shù)類似的宏,看起來像是函數(shù)調(diào)用,但是是作用在它參數(shù)的 tokens 上

          我們一個(gè)個(gè)來討論這些內(nèi)容,但是首先,我們看既然我們已經(jīng)有了函數(shù),我們?yōu)槭裁葱枰@些特性。

          函數(shù)和宏的區(qū)別

          基本上,宏是指一些代碼可以生成另一些代碼,這一塊的技術(shù)一般稱為元編程(Hugo 注:代碼生成器也屬于這一類技術(shù))。在附錄 C,我們討論了 derive 屬性,可以幫助你生成一系列的 trait。整本書我們也在用 println! 和 vec! 宏。這些宏在編譯時(shí),都會(huì)展開成為代碼,這樣你就不需要手寫這些代碼。

          元編程可以幫助你減少手寫和維護(hù)的代碼量,當(dāng)然,函數(shù)也能幫助你實(shí)現(xiàn)類似的功能。但是,宏有函數(shù)沒有的威力。

          函數(shù)必須聲明如慘的數(shù)量和種類。而宏,在另一方面,可以接收任意數(shù)量的參數(shù):我們可以調(diào)用 println!("hello"),也可以調(diào)用 println!("hello {}", name)。并且,宏是在編譯階段展開了代碼,所以一個(gè)宏可以在編譯時(shí)為一個(gè)類型實(shí)現(xiàn)一個(gè) trait。一個(gè)函數(shù)就不可以。因?yàn)楹瘮?shù)是在運(yùn)行時(shí)調(diào)用,而 trait 實(shí)現(xiàn)只可以發(fā)生在編譯時(shí)。(Hugo 注:JS 可以實(shí)現(xiàn)運(yùn)行時(shí)生成 trait(這里只是套用 rust 的概念),當(dāng)然,如果你需要的話。動(dòng)態(tài)語言在某些場(chǎng)景能很簡(jiǎn)單實(shí)現(xiàn)非常強(qiáng)大的功能。)

          宏不好的地方在于,宏很復(fù)雜,因?yàn)槟阋?rust 代碼寫 rust 代碼(Hugo 注:任何元編程都不是簡(jiǎn)單的事兒,包括 JS 里的。)。因?yàn)檫@種間接性,宏的代碼要更難讀、難理解、難維護(hù)。(Hugo 注:個(gè)人學(xué) rust,感覺最難不是生命周期,因?yàn)樯芷诘膯栴},可以通過用一些庫繞過去,或者無腦 clone,如果是應(yīng)用程序,則可以通過使用 orm 和數(shù)據(jù)庫來繞過很多生命周期的問題。反而是宏,因?yàn)樯晕⒂悬c(diǎn)規(guī)模的代碼的,都有一大堆宏。宏最難的不是語法,而是作者的意圖,因?yàn)楸举|(zhì)是他造了一套 DSL)

          另一個(gè)和函數(shù)不一樣的地方是,宏需要先定義或者引入作用域,而函數(shù)可以在任何地方定義和使用。

          使用聲明式宏 macro_rules! 進(jìn)行通用元編程

          在 Rust 中使用最廣泛的宏是聲明式宏。它們有時(shí)也被稱為 “macros by example”、“macro_rules!宏” 或者就是 “macros”。聲明式宏寫起來和 Rust 的 match 語法比較像。在第六章里講到,match 語法是一種流程控制語法,接收一個(gè)表達(dá)式,然后和結(jié)果進(jìn)行模式匹配,然后執(zhí)行匹配到結(jié)果的代碼。宏也會(huì)做類似的比較:在這種情況下,傳入的參數(shù)是 rust 的合法的語法代碼,然后通過宏的規(guī)則,和書寫好的模版,在編譯時(shí)轉(zhuǎn)換成代碼。

          定義聲明式宏的語法是 macro_rule!。下面我來用 vec! 來介紹這一機(jī)制。第八章有關(guān)于 vec! 的內(nèi)容。例如,創(chuàng)建一個(gè)新的 vector,包含 3 個(gè) integer:

          #![allow(unused)]
          fn main() {
          let v: Vec<u32>=vec![1, 2, 3];
          }

          我們可以通過 vec! 宏來創(chuàng)建任意類型的 vector,例如 2 個(gè) integer,或者五個(gè) string slice.

          我們不能用函數(shù)去實(shí)現(xiàn)這個(gè)功能,因?yàn)槲覀儾恢垒斎氲膮?shù)個(gè)數(shù)。(Hugo 注:這一點(diǎn)和 JS 非常不一樣,我們寫 JS,已經(jīng)習(xí)慣了可以傳任意變量。當(dāng)然如果你熟悉 TS,和 TS 有一些類似。Rust 雖然也有范型,但是和 TS 的范型非常不一樣。這里不一樣,我主要指關(guān)注點(diǎn),因?yàn)?Rust 比較大一部分都是函數(shù)式的代碼,每個(gè)函數(shù)一般都承載非常細(xì)粒度的功能,一般每個(gè)函數(shù)都處理好了自己的輸入、輸出、報(bào)錯(cuò),所有可能性都寫好了,寫 rust 有一種在填狀態(tài)機(jī)的錯(cuò)覺。。TS 有的代碼也有這種感覺。)

          一個(gè)簡(jiǎn)化的 vec! 宏:

          #[macro_export]
          macro_rules! vec {
              ( $( $x:expr ),* )=> {
                  {
                      let mut temp_vec=Vec::new();
                      $(
                          temp_vec.push($x);
                      )*
                      temp_vec
                  }
              };
          }

          注意:實(shí)際的 vec! 的聲明,還包括了提前分配合適的內(nèi)存。這里簡(jiǎn)化這個(gè)代碼,為了更好的講聲明式宏的概念。

          #[macro_export] 標(biāo)注指明了這個(gè)宏在 crate 的作用域里可用。沒有這個(gè)標(biāo)注,宏不會(huì)被帶入到作用域里。

          macro_rules! 后面就是宏的名字。這里只有一種模式匹配的邊(arm):( (( (x:expr ),* ) ,=> 后面是這個(gè)模式對(duì)應(yīng)要生成的代碼。如果這個(gè)模式匹配成功,對(duì)應(yīng)的代碼和輸入的參數(shù)組成的代碼就會(huì)生成在最終的代碼中。因?yàn)檫@里只有一種邊,所以只有這一種可以匹配的條件。不符合這個(gè)條件的輸入,都會(huì)報(bào)錯(cuò)。一般復(fù)雜的宏,都會(huì)有多個(gè)邊。

          這里匹配的規(guī)則和 match 是不一樣的,因?yàn)檫@里的語法匹配的是 rust 的語法,而不是 rust 的類型,或者值。更全的宏匹配語法,見文檔。

          對(duì)于 宏的輸入條件 ( (( (x:expr ),* ),()內(nèi)部是匹配的語法,expr表示所有Rust的表達(dá)式。() 內(nèi)部是匹配的語法,expr 表示所有 Rust 的表達(dá)式。()內(nèi)部是匹配的語法,expr表示所有Rust的表達(dá)式。() 后面的都喊表示這個(gè)變量后面有可能有逗號(hào),* 表示前面的模式會(huì)出現(xiàn)一次或者多次。(Hugo 注:像不像正則?宏語法其實(shí)挺簡(jiǎn)單的,不要被高級(jí)唬住了。當(dāng)然,宏還是難的,宏要考慮的問題本身是一個(gè)復(fù)雜的問題。)

          當(dāng)我們調(diào)用:vec![1, 2, 3]; 時(shí),$x 模式會(huì)匹配 3 個(gè)表達(dá)式 1 , 2 和 3。

          現(xiàn)在我們看一下和這個(gè)邊匹配的生成代碼的部分:

           {
                  {
                      let mut temp_vec=Vec::new();
                      $(
                          temp_vec.push($x);
                      )*
                      temp_vec
                  }
              };

          在 ()里的tempvec.push(() 里的 temp_vec.push(()里的tempvec.push(x); 就是生成的代碼的部分。* 號(hào)仍然表示生成零個(gè)和多個(gè),這個(gè)匹配的具體個(gè)數(shù),要看匹配條件命中的個(gè)數(shù)。

          當(dāng)我們調(diào)用:vec![1, 2, 3]; 時(shí),生成了這個(gè)代碼。(Hugo 注:Cargo 有 expand 插件,對(duì)于聲明宏,多看看展開基本就能學(xué)會(huì)了。)

          {
              let mut temp_vec=Vec::new();
              temp_vec.push(1);
              temp_vec.push(2);
              temp_vec.push(3);
              temp_vec
          }

          你傳任意參數(shù),最后就生成符合上面條件的代碼。

          有一些 macro_rules! 的奇怪的邊界例子。在未來,Rust 會(huì)有第二種聲明式宏,和現(xiàn)在的機(jī)制類似,但是會(huì)解決這些邊界問題。在那一次升級(jí)后,macro_rules! 會(huì)被棄用。(Hugo 注:Rust 仍然是非常年輕的語言,做好隨時(shí)接受改變的準(zhǔn)備)記住這些,當(dāng)然另一個(gè)事實(shí)是,大部分 Rust 程序員更多是宏的使用者,而不是開發(fā)者,我們不會(huì)在深入討論 macro_rules!。如果你對(duì)這塊特別感興趣。請(qǐng)閱讀《“The Little Book of Rust Macros”》。(Hugo 注:站在入門的角度,能知道機(jī)制去使用就 ok 了。在絕大部分入門的情況下,函數(shù)以及使用 crates.io 上的宏都能滿足你的需求。)

          從屬性生成代碼的過程宏

          第二種宏是過程宏,表現(xiàn)形式更像函數(shù)(過程的一種類型)。過程宏的入?yún)⑹且恍┐a,你可以操作這些代碼,然后襯衫一些代碼。(Hugo:從結(jié)果看和聲明宏沒區(qū)別,其實(shí)站在 JS 的角度,更像是 babel,你可以根據(jù)輸入的 token 做變換)

          雖然過程宏有三種:custom derive、attribute-like 和 function-like,但是原理都是一樣的。

          如果要?jiǎng)?chuàng)建過程宏,定義的部分需要在自己的 crate 里,并且要定義特殊的 crate 類型。(Hugo 注:相當(dāng)于定義了一個(gè) babel 插件,只不過有一套 rust 自己的體系。這些宏會(huì)在編譯的時(shí)候,按照書寫的規(guī)則,轉(zhuǎn)成對(duì)應(yīng)的代碼。所有的宏,都是代碼生成的手段,輸入是代碼,輸入是代碼。)這種設(shè)計(jì),我們有可能會(huì)在未來消除。

          下面是一個(gè)過程宏的例子:

          use proc_macro;
          
          #[some_attribute]
          pub fn some_name(input: TokenStream) -> TokenStream {
          }

          過程宏接收一個(gè) TokenStream,輸出一個(gè) TokenStream。TokenStream 類型定義在 proc_macro 里,表示一系列的 tokens。這個(gè)就是這種宏的核心機(jī)制,輸入的代碼(會(huì)被 rust) 轉(zhuǎn)成 TokenStream,然后做一些按照業(yè)務(wù)邏輯的操作,最后生成 TokenStream。這個(gè)函數(shù)也可以疊加其他的屬性宏(#[some_attribute], 看起來像裝飾器的邏輯,也可以理解為一種鏈?zhǔn)秸{(diào)用),可以在一個(gè) crate 里定義多個(gè)過程。(Hugo 注:搞過 babel 的同學(xué)肯定很熟悉,一樣的味道。沒搞過的同學(xué),強(qiáng)烈建議先學(xué)學(xué) babel。)

          下面我們來看看不同類型的過程宏。首先從自定義 derive 宏開始,然后我們介紹這種宏和其他幾種的區(qū)別。

          如何編寫自定義 derive 宏

          我們創(chuàng)建一個(gè) crate 名字叫 hello_macro,定義一個(gè) HelloMacro 的 trait,關(guān)聯(lián)的函數(shù)名字叫 hello_macro。通過使用這個(gè)宏,用戶的結(jié)構(gòu)可以直接獲得默認(rèn)定義的 hello_macro 函數(shù),而不需要實(shí)現(xiàn)這個(gè) trait。默認(rèn)的 hello_macro 可以打印 Hello, Macro! My name is TypeName!,其中 TypeName 是實(shí)現(xiàn)這個(gè) derive 宏的結(jié)構(gòu)的類型名稱。

          use hello_macro::HelloMacro;
          use hello_macro_derive::HelloMacro;
          
          #[derive(HelloMacro)]
          struct Pancakes;
          
          fn main() {
              Pancakes::hello_macro();
          }

          創(chuàng)建這個(gè)宏的過程如下,首先

          $ cargo new hello_macro --lib

          然后定義 HelloMacro trait

          pub trait HelloMacro {
              fn hello_macro();
          }

          這樣我們就有了一個(gè) trait,和這個(gè)triat 的函數(shù)。用戶可以通過這個(gè) trait 直接實(shí)現(xiàn)對(duì)應(yīng)的函數(shù)。

          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();
          }

          但是,用戶需要每次都實(shí)現(xiàn)一遍 hello_macro。如果 hello_macro 的實(shí)現(xiàn)都差不多,就可以通過 derive 宏來是實(shí)現(xiàn)。

          因?yàn)?Rust 沒有反射機(jī)制,我們不可以在執(zhí)行時(shí)知道對(duì)應(yīng)類型的名字。我們需要在編譯時(shí)生成對(duì)應(yīng)的代碼。

          下一步,定義過程宏。在這個(gè)文章編寫時(shí),過程宏需要在自己的 crates 里。最終,這個(gè)設(shè)計(jì)可能改變。關(guān)于 宏 crate 的約定是:對(duì)于一個(gè)名為 foo 的 crate,自定義 drive 宏的crate 名字為 foo_derive。我們?cè)?hello_macro 項(xiàng)目中創(chuàng)建 hello_macro_derive crate。

          $ cargo new hello_macro_derive --lib

          我們的兩個(gè)的 crate 關(guān)聯(lián)緊密,所以我們?cè)?hello_macro crate 里創(chuàng)建這個(gè) crate。如果我們要改變 hello_macro 的定義,我們同樣也要更改 hello_macro_derive 的定義。這兩個(gè) crates 要隔離發(fā)布。當(dāng)用戶使用時(shí),要同時(shí)添加這兩個(gè)依賴。為了簡(jiǎn)化依賴,我們可以讓 hello_macro 使用 hello_macro_derive 作為依賴,然后導(dǎo)出這個(gè)依賴。但是,這樣,如果用戶不想使用 hello_macro_derive,也會(huì)自動(dòng)添加上這個(gè)依賴。

          下面開始創(chuàng)建 hello_macro_derive,作為一個(gè)過程宏 crate。需要添加依賴 syn 和 quote。下面是這個(gè) crate 的 Cargo.toml。

          [lib]
          proc-macro=true
          
          [dependencies]
          syn="1.0"
          quote="1.0"

          在 lib.rs 里添加下述代碼。注意,這個(gè)代碼如果不增加 impl_hello_macro 的實(shí)現(xiàn)是通不過編譯的。

          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 函數(shù)里,這個(gè)函數(shù)主要負(fù)責(zé)處理 TokenStream,另一部分在 impl_hello_macro,這里負(fù)責(zé)轉(zhuǎn)換語法樹:這樣編寫過程宏可以簡(jiǎn)單一些。在絕大部分過程宏立,對(duì)于前者的過程一般都是一樣的。一般來說,真正的區(qū)別在 impl_hello_macro,這里的邏輯一般是一個(gè)過程宏的業(yè)務(wù)決定的。

          我們引入了三個(gè) crates: proc_macro, syn 和 quote。proc_macro 內(nèi)置在 rust 立,不需要在 Cargo.toml 中引入。proc_macro 實(shí)際是 rust 編譯器的一個(gè)接口,用來讀取和操作 Rust 代碼。

          syn crate 把 Rust 代碼從字符串轉(zhuǎn)換為可以操作的結(jié)構(gòu)體。quote crate 把 syn 數(shù)據(jù)在轉(zhuǎn)回 Rust 代碼。這些 Crate 可以極大簡(jiǎn)化過程宏的編寫:寫一個(gè) Rust 代碼的 full parser 可不是容易的事兒!

          當(dāng)在一個(gè)類型上標(biāo)注 [derive(HelloMacro)] 時(shí),會(huì)調(diào)用 hello_macro_derive 函數(shù)。之所以會(huì)有這樣的行為,是因?yàn)樵诙x hello_macro_derive 時(shí),標(biāo)注了 #[proc_macro_derive(HelloMacro)] 在函數(shù)前面。

          hello_macro_derive 會(huì)把輸入從 TokenStream 轉(zhuǎn)換為一個(gè)我們可以操作的數(shù)據(jù)結(jié)構(gòu)。這就是為什么需要引入 syn 。sync 的 parse 函數(shù)會(huì)把 TokenStream 轉(zhuǎn)換為 DeriveInput。

          DeriveInput {
              // --snip--
          
              ident: Ident {
                  ident: "Pancakes",
                  span: #0 bytes(95..103)
              },
              data: Struct(
                  DataStruct {
                      struct_token: Struct,
                      fields: Unit,
                      semi_token: Some(
                          Semi
                      )
                  }
              )
          }
          

          上述這個(gè)結(jié)構(gòu)的意思是:正在處理的是 ident(identifier, 意味著名字)為 Pancakes 的 unit struct。其他的字段表示其余的 Rust 代碼。如果想了解更詳細(xì)的內(nèi)容,請(qǐng)參考。

          接下來,我們就要開始定義 impl_hello_macro。這個(gè)函數(shù)實(shí)現(xiàn)了添加到 Rust 代碼上的函數(shù)。在我們做之前,注意 derive macro 的輸出也是 TokenStream。返回的 TokenStream 就是添加完代碼以后的代碼。當(dāng)編譯 crate 時(shí),最終的代碼,就是處理完成的代碼了。

          你也許也會(huì)發(fā)現(xiàn),這里調(diào)用 syn::parse 時(shí)使用了 unwrap,如果報(bào)錯(cuò)就中斷。這里必須這么做,因?yàn)樽罱K返回的是 TokenStream,而不是 Result。這里是為了簡(jiǎn)化代碼說明這個(gè)問題。在生產(chǎn)代碼,你應(yīng)該處理好報(bào)錯(cuò),提供更詳細(xì)的報(bào)錯(cuò)信息,例如使用 panic! 或者 expect。

          下面是代碼:

          fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
              // 通過&ast.ident 獲取類型的名字
              let name=&ast.ident;
              // 使用 quote 宏,可以使用 rust 語法來定義要實(shí)現(xiàn)的 trait(Hugo 注:JS 要有這個(gè)就好了)
              let gen=quote! {
                  // #name 是 quote! 的模版語法,會(huì)自動(dòng)替換為這個(gè)變量里的值
                  impl HelloMacro for #name {
                      fn hello_macro() {
                          // 這里把對(duì)應(yīng)的 struct 名字轉(zhuǎn)換好了,stringfy!把值轉(zhuǎn)換為字符串
                          println!("Hello, Macro! My name is {}!", stringify!(#name));
                      }
                  }
              };
             // 轉(zhuǎn)換為最終的 TokenStream
              gen.into()
          }

          通過上面的代碼,cargo build 就可以正常工作了。如果要使用這個(gè)代碼,需要把兩個(gè)依賴都加上。

          hello_macro={ path="../hello_macro" }
          hello_macro_derive={ path="../hello_macro/hello_macro_derive" }

          現(xiàn)在執(zhí)行下面的代碼,就可以看到 Hello, Macro! My name is Pancakes!

          use hello_macro::HelloMacro;
          use hello_macro_derive::HelloMacro;
          
          #[derive(HelloMacro)]
          struct Pancakes;
          
          fn main() {
              Pancakes::hello_macro();
          }

          下一步,我們來探索其他類型的過程宏。

          屬性宏(Attribute-like)

          屬性宏和 derive 宏類似,但是可以創(chuàng)造除了 derive 意外的屬性。derive 只能作用于 structs 和 enums,屬性宏可以作用于其他的東西,比如函數(shù)。下面是一個(gè)屬性宏的例子:例如你制作了一個(gè)名為 route 的屬性宏來在一個(gè)web 框架中標(biāo)注函數(shù)。

          #[route(GET, "/")]
          fn index() {

          #[route] 是框架定義的過程宏。定義這個(gè)宏的函數(shù)類似:

          #[proc_macro_attribute]
          pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {

          這里,有兩個(gè)參數(shù),類型都是 TokenStream。第一個(gè)是屬性的內(nèi)容,GET, "/" 部分,第二個(gè)是標(biāo)注屬性宏傳入的語法部分,在這個(gè)例子里,就是剩下的 fn index() {}。

          工作原理和 derive 宏是一樣的。

          函數(shù)宏(Function-like)

          函數(shù)宏的使用比較像調(diào)用一個(gè) rust 函數(shù)。函數(shù)宏有點(diǎn)像 macro_rules! ,能提供比函數(shù)更高的靈活性。例如,可以接受未知個(gè)數(shù)的參數(shù)。但是,macro_rules! 只能使用在上述章節(jié)的匹配型的語法。而函數(shù)宏接受 TokenStream 參數(shù)作為入?yún)?,和其他過程宏一樣,可以做任何變換,然后返回 TokenStream。下面是一個(gè)函數(shù)宏 sql!

          let sql=sql!(SELECT * FROM posts WHERE id=1);

          這個(gè)宏接受 SQL 語句,可以檢查這個(gè) SQL 的語法是否正確,這種功能比 macro_rules! 提供的要復(fù)雜的多。這個(gè) sql! 的宏可以定義為:

          #[proc_macro]
          pub fn sql(input: TokenStream) -> TokenStream {

          這個(gè)定義和自定義 derive 宏類似:接受括號(hào)內(nèi)的 tokens,返回生成的代碼。

          總結(jié)

          好了,現(xiàn)在你有了一些可能不常用的 Rust 新工具,但是你要知道的是,在需要的場(chǎng)合,他們的運(yùn)行原理是什么。我們介紹了一些復(fù)雜的話題,當(dāng)你在錯(cuò)誤處理或者別人的代碼里看到這些宏時(shí),可以認(rèn)出這些概念和語法??梢允褂眠@一章的內(nèi)容作為解決這些問題的索引。

          概述

          在工程規(guī)模較小,不是很復(fù)雜,與硬件結(jié)合緊密,要求移植性的時(shí)候,可采用宏定義簡(jiǎn)化編程,增強(qiáng)程序可讀性。

          當(dāng)宏作為常量使用時(shí),C程序員習(xí)慣在名字中只使用大寫字母。但是并沒有如何將用于其他目的的宏大寫的統(tǒng)一做法。由于宏(特別是帶參數(shù)的宏)可能是程序中錯(cuò)誤的來源,所以一些程序員更喜歡使用大寫字母來引起注意。

          1. 簡(jiǎn)單宏定義

          無參宏的宏名后不帶參數(shù),其定義的一般形式為:

          #define 標(biāo)識(shí)符 字符串

          // 不帶參數(shù)的宏定義
          #define MAX 10

          注意:不要在宏定義中放置任何額外的符號(hào),比如"="或者尾部加";"

          使用#define來為常量命名一些優(yōu)點(diǎn):

          • 程序會(huì)更易讀。一個(gè)認(rèn)真選擇的名字可以幫助讀者理解常量的意義;
          • 程序會(huì)更易于修改。我們僅需要改變一個(gè)宏定義,就可以改變整個(gè)程序中出現(xiàn)的所有該常量的值;
          • 可以幫助避免前后不一致或鍵盤輸入錯(cuò)誤;
          • 控制條件編譯;
          • 可以對(duì)C語法做小的修改;
          1. 帶參數(shù)的宏

          帶參數(shù)的仍要遵循上述規(guī)則,區(qū)別只是宏名后面緊跟的圓括號(hào)中放置了參數(shù),就像真正的函數(shù)那樣。

          #define <宏名>(<參數(shù)列表>) <宏體>

          注意參數(shù)列表中的參數(shù)必須是有效的c標(biāo)識(shí)符,同時(shí)以,分隔

          算符優(yōu)先級(jí)問題:

          #define COUNT(M) M*M
          int x=5;
          print(COUNT(x+1));
          print(COUNT(++X));
          //結(jié)果輸出:11   和42 而不是函數(shù)的輸出36

          注意:

          • 預(yù)編譯器只是進(jìn)行簡(jiǎn)單的文本替換,COUNT(x+1)被替換成COUNT(x+1x+1),5+15+1=11,而不是36
          • CUNT(++x)被替換成++x*++x即為6*7=42,而不是想要的6*6=36,連續(xù)前置自加加兩次

          解決辦法:

          • 用括號(hào)將整個(gè)替換文本及每個(gè)參數(shù)用括號(hào)括起來print(COUNT((x+1));
          • 即便是加上括號(hào)也不能解決第二種情況,所以解決辦法是盡量不使用++,-等符號(hào);

          分號(hào)吞噬問題:

          #define foo(x) bar(x); baz(x)

          假設(shè)這樣調(diào)用:

          if (!feral)
              foo(wolf);

          將被宏擴(kuò)展為:

          if (!feral)
              bar(wolf);
          baz(wolf);

          ==baz(wolf);==,不在判斷條件中,顯而易見,這是錯(cuò)誤。如果用大括號(hào)將其包起來依然會(huì)有問題,例如

          #define foo(x)  { bar(x); baz(x); }
          if (!feral)
              foo(wolf);
          else
              bin(wolf);

          判斷語言被擴(kuò)展成:

          if (!feral) {
              bar(wolf);
              baz(wolf);
          }>>++;++<<
          else
              bin(wolf);

          ==else==將不會(huì)被執(zhí)行

          解決方法:通過==do{…}while(0)

          #define foo(x)  do{ bar(x); baz(x); }while(0)
          if (!feral)
              foo(wolf);
          else
              bin(wolf);

          被擴(kuò)展成:

          #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)構(gòu)造后的宏定義不會(huì)受到大括號(hào)、分號(hào)等的影響,總是會(huì)按你期望的方式調(diào)用運(yùn)行。

          1. #運(yùn)算符

          #的作用就是將#后邊的宏參數(shù)進(jìn)行字符串的操作,也就是將#后邊的參數(shù)兩邊加上一對(duì)雙引號(hào)使其成為字符串。例如a是一個(gè)宏的形參,則替換文本中的#a被系統(tǒng)轉(zhuǎn)化為"a",這個(gè)轉(zhuǎn)換過程即為字符串化。

          #define TEST(param) #param
          
          char *pStr=TEST(123);
          printf("pSrt=%s\n",pStr);
          //輸出結(jié)果為字符  ”123“
          1. ##運(yùn)算符

          ##運(yùn)算符也可以用在替換文本中,它的作用起到粘合的作用,即將兩個(gè)宏參數(shù)連接為一個(gè)數(shù)

          #define TEST(param1,param2) (param1##param2)
          
          int num=TEST(13,59);
          printf("num=%d\n",num);
          //輸出結(jié)果為:num=1359
          1. VA_ARGS

          作用主要是為了方便管理軟件中的打印信息。在寫代碼或DEBUG時(shí)通常需要將一些重要參數(shù)打印出來,但在軟件發(fā)行的時(shí)候不希望有這些打印,這時(shí)就用到可變參數(shù)宏了。

           # define PR(...) printf(_VA_ARGS_)
          2 PR("hello world\n");
          3
          4 輸出結(jié)果:hello world

          2 一些建議

          • 雖然宏定義很靈活,并且通過彼此結(jié)合可以產(chǎn)生許多變形用法,但是C++/C程序員不要定義很復(fù)雜的宏,宏定義應(yīng)該簡(jiǎn)單而清晰。
          • 宏名采用大寫字符組成的單詞或其縮寫序列,并在各單詞之間使用“_”分隔。
          • 如果需要公布某個(gè)宏,那么該宏定義應(yīng)當(dāng)放置在頭文件中,否則放置在實(shí)現(xiàn)文件(.cpp)的頂部。
          • 不要使用宏來定義新類型名,應(yīng)該使用typedef,否則容易造成錯(cuò)誤。
          • 給宏添加注釋時(shí)請(qǐng)使用塊注釋(/* */),而不要使用行注釋。因?yàn)橛行┚幾g器可能會(huì)把宏后面的行注釋理解為宏體的一部分。
          • 盡量使用const取代宏來定義符號(hào)常量。
          • 對(duì)于較長(zhǎng)的使用頻率較高的重復(fù)代碼片段,建議使用函數(shù)或模板而不要使用帶參數(shù)的宏定義;而對(duì)于較短的重復(fù)代碼片段,可以使用帶參數(shù)的宏定義,這不僅是出于類型安全的考慮,而且也是優(yōu)化與折衷的體現(xiàn)。
          • 盡量避免在局部范圍內(nèi)(如函數(shù)內(nèi)、類型定義內(nèi)等)定義宏,除非它只在該局部范圍內(nèi)使用,否則會(huì)損害程序的清晰性。

          3 宏的常見用法

          • 防止一個(gè)頭文件被重復(fù)包含
          #ifndef COMDEF_H
          #define COMDEF_H
          //頭文件內(nèi)容
          #endif
          • 得到指定地址上的一個(gè)字節(jié)或字
          #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))
          • 得到一個(gè)field在結(jié)構(gòu)體(struct)中的偏移量
          #define FPOS(type,field) ((dword)&((type *)0)->field)
          • 得到一個(gè)結(jié)構(gòu)體中field所占用的字節(jié)數(shù)
          #define FSIZ(type,field) sizeof(((type *)0)->field)
          • 按照LSB格式把兩個(gè)字節(jié)轉(zhuǎn)化為一個(gè)Word
          #define FLIPW(ray) ((((word)(ray)[0]) * 256) + (ray)[1])
          • 得到一個(gè)字的高位和低位字節(jié)
          #define WORD_LO(xxx)  ((byte) ((word)(xxx) & 255))
          #define WORD_HI(xxx)  ((byte) ((word)(xxx) >> 8))
          • 將一個(gè)字母轉(zhuǎn)換為大寫
          #define UPCASE(c) (((c)>='a' && (c) <='z') ? ((c) – 0×20) : (c))
          • 判斷字符是不是10進(jìn)制的數(shù)字
          #define  DECCHK(c) ((c)>='0' && (c)<='9')
          • 判斷字符是不是16進(jìn)制的數(shù)字
          #define HEXCHK(c) (((c) >='0' && (c)<='9') ((c)>='A' && (c)<='F') \
          ((c)>='a' && (c)<='f'))
          • 防止溢出的一個(gè)方法
          #define INC_SAT(val) (val=((val)+1>(val)) ? (val)+1 : (val))
          • 返回?cái)?shù)組元素的個(gè)數(shù)
          #define ARR_SIZE(a)  (sizeof((a))/sizeof((a[0])))

          參考資料

          1. http://www.360doc.com/content/13/0125/13/10906019_262310086.shtml
          2. 高質(zhì)量程序設(shè)計(jì)指南C++/C語言第3版
          3. https://www.cnblogs.com/southcyy/p/10155049.html

          主站蜘蛛池模板: 在线播放一区二区| 国产午夜三级一区二区三| 91秒拍国产福利一区| 91视频一区二区| 一区一区三区产品乱码| 亚洲av无码片区一区二区三区| 久久久久人妻一区二区三区vr| 激情亚洲一区国产精品| 国产一区二区不卡老阿姨| 美女视频黄a视频全免费网站一区| 精品国产一区二区三区在线观看| 另类免费视频一区二区在线观看| 精品一区高潮喷吹在线播放| 精品国产一区二区三区av片| 国产精品一区二区久久精品无码| 国产成人精品无码一区二区| 亚洲av无码一区二区三区天堂 | 国产精品熟女视频一区二区| 一区二区三区内射美女毛片| 日韩人妻无码一区二区三区久久| 国产精品日韩欧美一区二区三区| 成人在线视频一区| 蜜桃视频一区二区三区在线观看 | 午夜DV内射一区二区| 亚洲AV无码一区二区三区网址| 亚洲免费一区二区| 亚洲综合色一区二区三区| 中文字幕av无码一区二区三区电影| 亚洲AV日韩综合一区尤物| 一区在线观看视频| 国产成人精品第一区二区| 国产乱码一区二区三区爽爽爽| 日本精品一区二区三本中文| 无码人妻精品一区二区三区66 | 久久精品一区二区三区资源网| 国产中的精品一区的| 国产精品视频分类一区| 少妇人妻偷人精品一区二区| 国产伦一区二区三区免费| 亚洲av成人一区二区三区| 亚洲一区AV无码少妇电影☆|