当前位置: 首页 > news >正文

网站建设费怎样摊销最牛的网站建

网站建设费怎样摊销,最牛的网站建,合肥高新区建设发展局网站,建站服务器宏 在 rust 中,我们一开始就在使用宏,例如 println!, vec!, assert_eq! 等。看起来宏和函数在使用时只是多了一个 !。实际上这些宏都是声明式宏(也叫示例宏或macro_rules!),rust 还支持过程宏,过程宏为我们…

在 rust 中,我们一开始就在使用宏,例如 println!, vec!, assert_eq! 等。看起来宏和函数在使用时只是多了一个 !。实际上这些宏都是声明式宏(也叫示例宏或macro_rules!),rust 还支持过程宏,过程宏为我们提供了强大的元编程工具。

声明式宏

声明式宏类似于 match 匹配。它可以将表达式的结果与多个模式进行匹配。一旦匹配成功,那么该模式相关联的代码将被展开。和 match 不同的是,宏里的值是一段 rust 源代码。所有这些都发生在编译期,并没有运行期的性能损耗。下面是一个例子:

// 声明一个add宏
macro_rules! add {($a: expr, $b: expr) => {$a + $b};
}fn main() {let a = 10;let b = 22;let _res = add!(a, b);let _res = add!(a+1, b);let _res = add!(a*2, b+3);
}

我们需要一个类似于 GCC -E 的方式来查看一下预处理阶段之后的代码。cargo-expand 正好提供了相应的功能。使用 cargo 安装 cargo-expand 即可。

cargo install cargo-expand

安装 cargo-expand 之后,可以使用 cargo expand 命令来查看声明式宏是如何被展开的。上面的代码在执行cargo expand之后输出如下所示:

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
fn main() {let a = 10;let b = 22;let _res = a + b;let _res = a + 1 + b;let _res = a * 2 + (b + 3);
}

可以看到,每一个 _res 的右边都被展开了,并且如果传入的参数是一个表达式,则会将整个表达式作为一个整体传递给宏。这就是某些地方提到的“Hygienic Macros”(有些地方也翻译为卫生宏,翻译的很抽象)。最后一行代码中传入的b+3被当做了一个整体。如果是在C/C++中,不会自动将表达式作为整体,而是直接进行字符串替换。而 Rust 编译器会自动处理变量名和作用域,确保宏展开后的代码不会引入未预料的变量冲突。下面是一个C/C++中使用宏的例子。

#include<stdio.h>
#define ADD(a, b) a + b;int main() {int a = 10;int b = 22;int _res = ADD(a, b)_res = ADD(a+1, b)_res = ADD(a*2, b+3)
} 

同样,我们使用 gcc -E main.c 来获取预处理之后的代码。由于展开之后的代码非常得多,我们只放上 main 函数中展开的部分。

int main() {int a = 10;int b = 22;int _res = a + b;_res = a+1 + b;_res = a*2 + b+3;
}

可以看到,调用的代码展开之后,并没有将 b+3 作为一个整体来处理,而是简单的进行替换。因此,我们在 C/C++ 中编写宏要特别注意,宏参数在使用的时候必须加上括号。现在我们来修复上面 C/C++ 代码中的宏。

#include<stdio.h>
#define ADD(a, b) (a) + (b);int main() {int a = 10;int b = 22;int _res = ADD(a, b)_res = ADD(a+1, b)_res = ADD(a*2, b+3)
} 

这样,我们在使用宏的时候,就避免了意外结果的发生。这样展开之后的代码如下所示:

int main() {int a = 10;int b = 22;int _res = (a) + (b);_res = (a+1) + (b);_res = (a*2) + (b+3);
}

我们接着来定义我们自己的 my_vec! 宏, 来对声明式宏的相关语法做一个解释。

macro_rules! my_vec {// 匹配 my_vec![]() => {std::vec::Vec::new()};// 匹配 my_vec![1,2,3]($($el:expr), *) => {// 这段代码需要用{}包裹起来,因为宏需要展开,这样能保证作用域正常,不影响外部。这也是rust的宏是 Hygienic Macros 的体现。 // 而 C/C++ 的宏不强制要求,但是如果遇到代码片段,在 C/C++ 中也应该使用{}包裹起来。{let mut v = std::vec::Vec::new();$(v.push($el);)*v}};// 匹配 my_vec![1; 3]($el:expr; $n:expr) => {std::vec::from_elem($el, $n)};
}
  1. 由于宏要在调用的地方展开,我们无法预测调用者的环境是否已经做了相关的 use,所以我们使用的代码最好带着完整的命名空间。

  2. 在声明宏中,条件捕获的参数使用 $ 开头的标识符来声明。每个参数都需要提供类型,这里 expr 代表表达式,所以 $el:expr 是说把匹配到的表达式命名为 $el$(...),* 告诉编译器可以匹配任意多个以逗号分隔的表达式,然后捕获到的每一个表达式可以用 $el 来访问。由于匹配的时候匹配到一个 $(...)* (我们可以不管分隔符),在执行的代码块中,我们也要相应地使用 $(...)* 展开。所以这句 $(v.push($el);)* 相当于匹配出多少个 $el 就展开多少句 push 语句。
    反复捕获
    反复捕获的一般形式是$ ( ... ) sep rep

     $ 是字面上的美元符号标记( ... ) 是被反复匹配的模式,由小括号包围。sep 是可选的分隔标记。它不能是括号或者反复操作符 rep。常用例子有 , 和 ; 。rep 是必须的重复操作符。当前可以是:1. ?:表示最多一次重复,所以此时不能前跟分隔标记。2. *:表示零次或多次重复。3. +:表示一次或多次重复。
    
  3. 如果传入用冒号分隔的两个表达式,那么会用 from_element 构建 Vec。

我们来使用一下自定义的 my_vec! 宏

let mut v = my_vec!();
v.push(1);
println!("{:?}", v);
let v = my_vec![1, 2, 3, 4, 5];
println!("{:?}", v);
let v = my_vec!{1; 3};
println!("{:?}", v);

我们在使用宏的时候,可以使用(), [], {},都是可以的。但是一般都是按照约定成俗的方式来使用。例如:vec![1,2,3],而不是使用 vec!{1,2,3}

这段宏调用,展开以后,如下所示:

let mut v = std::vec::Vec::new();
v.push(1);
{::std::io::_print(format_args!("{0:?}\n", v));
};
let v = {let mut v = std::vec::Vec::new();v.push(1);v.push(2);v.push(3);v.push(4);v.push(5);v
};
{::std::io::_print(format_args!("{0:?}\n", v));
};
let v = std::vec::from_elem(1, 3);
{::std::io::_print(format_args!("{0:?}\n", v));
};

可以看到,let v = my_vec![1, 2, 3, 4, 5]; 被展开为

let v = {let mut v = std::vec::Vec::new();v.push(1);v.push(2);v.push(3);v.push(4);v.push(5);v
};

它带上了我们在宏定义中的{},另外我们注意到println! 宏也被展开了, 但是并没有完全展开,其中还包含了一个format_args! 宏,我们来看一下,是否和println宏的定义一样。

// println宏的定义
macro_rules! println {() => {$crate::print!("\n")};($($arg:tt)*) => {{$crate::io::_print($crate::format_args_nl!($($arg)*));}};
}

可以看到,println带有参数将会使用 format_args_nl! 宏,但是expand确是 format_args 宏。大概可能是因为文档中说format_args_nl宏是nightly模式下的吧!并没有完全展开是因为该宏是内置宏(rustc_builtin_macro)。

在使用声明宏时,我们需要为参数明确类型,刚才的例子都是使用的expr,其实还可以使用下面这些:

  • item,比如一个函数、结构体、模块等。
  • block,代码块。比如一系列由花括号包裹的表达式和语句。
  • stmt,语句。比如一个赋值语句。
  • pat,模式。
  • expr,表达式。刚才的例子使用过了。
  • ty,类型。比如 Vec。
  • ident,标识符。比如一个变量名。
  • path,路径。比如:foo、::std::mem::replace、transmute::<_, int>。 meta,元数据。一般是在 #[...]`` 和 #![…]`` 属性内部的数据。
  • tt,单个的 token 树。
  • vis,可能为空的一个 Visibility 修饰符。比如 pub、pub(crate)

声明式宏还算比较简单。它可以帮助我们解决一些问题。

  1. 代码重复:声明式宏可以帮助消除代码中的冗余,通过将重复的代码逻辑抽象成宏,从而减少代码量并提高代码的可读性和维护性。
  2. 代码模板化:宏可以用于定义代码模板,允许在编译时根据不同的参数生成特定的代码片段,从而实现代码的泛化和重用。
  3. 实现函数重载,宏可以匹配多种模式的参数来实现函数重载。

宏的缺点

宏目前的编写无法得到IDE很好的支持,另外一点就是如无必要,就不要编写宏。如果要编写,那么尽量编写声明式宏,而不是过程宏。

  1. 宏编写复杂:过程宏的编写可能相对复杂,特别是对于复杂的语法分析和代码生成任务,编写和调试过程宏可能需要更多的时间和精力。
  2. 可读性下降:宏可能会导致代码的可读性下降,特别是在宏的展开代码复杂或嵌套层级较多时,代码可读性可能变差。
  3. 不利于错误检查:宏展开发生在编译期间,因此错误信息可能不够明确和直观,难以定位宏展开后的具体错误位置。
  4. 难以调试:宏展开过程对于开发者不是透明的,因此在调试过程中可能会遇到难以解决的问题。

参考资料

  1. https://github.com/rust-lang/rust/issues/93904
  2. https://blog.logrocket.com/macros-in-rust-a-tutorial-with-examples/#:~:text=Declarative%20macros%20enable%20you%20to,Rust%20code%20it%20is%20given.
  3. rust编程第一课-陈天
  4. The Little Book of Rust Macros
http://www.yayakq.cn/news/218217/

相关文章:

  • 福田网站建设方案网站访问量排行榜
  • 网站建设明细费用黑龙江省建设集团有限公司网站
  • 做网站一次付费专门做饮食加盟的网站
  • 如何做交互式网站用织梦做的网站好还是cms
  • 中国建设银行官方网站 认证贝壳房源网
  • 网站的对比厦门seo外包服务
  • 英文公司网站建设帝国cms下载站模板
  • 个人网站建设案例教程企业宣传片报价明细
  • 网站开发承诺函上海网站开发团队
  • iis网站发布教程腾讯企点收费标准
  • 网站开发技术的现状制作静态网页
  • 郑州哪里教做网站网站建设平台设备
  • 临时网站搭建做教育导航的网站
  • 南通网站建设 南大街万网解压缩wordpress
  • 肇庆 网站建设公司有哪些弹幕网站如何做
  • 上海旅游网站建设情况义乌外贸建网站
  • 相亲网站建设策划方案wordpress首页名称
  • wordpress 网站搬迁网站建设友链交换
  • 网站空间哪个比较好登录网站模板
  • 公司支付网站建设费进什么费用什么平台可以发广告引流
  • 机场建设集团网站做汉字的教育网站
  • 做优化网站注意什么网站架构设计文档
  • 做网站优化有必要网站建设与运营的预算方案模板
  • 企业网站源码带后台管理海丰建设局网站
  • 北京网站建设建设公司站长做2个网站
  • 嘉定网站设计制作公司网站开发项目业务要求
  • 网站开发自学难吗互联网运营管理
  • 外贸专业网站好的网站具备的条件
  • 乐清网站推广网络技术开发有限公司
  • 网站seo优化徐州百度网络做网站公司好