仓颉编程语言:宏的使用案例

仓颉语言 -- 宏 阅读详情

快速幂的计算

我们通过一个简单的例子展示使用宏进行编译期求值,生成优化代码的应用。在计算幂 n ^ e 的时候,如果 e 是一个(比较大的)整数,可以通过重复取平方(而不是迭代相乘)的方式加速计算。这个算法可以直接使用 while 循环实现,例如:

func power(n: Int64, e: Int64) {
    var result = 1
    var vn = n
    var ve = e
    while (ve > 0) {
        if (ve % 2 == 1) {
            result *= vn
        }
        ve /= 2
        if (ve > 0) {
            vn *= vn
        }
    }
    result
}

然而,这个实现需要每次对 e 的值进行分析,在循环和条件判断中多次对 ve 进行判断和更新。此外,实现只支持 n 的类型为Int64的情况,如果要支持其他类型的 n,还要处理如何表达 result = 1 的问题。如果我们预先知道 e 的具体值,可以将这个代码写的更简单。例如,如果知道 e 的值为 10,我们可以展开整个循环如下:

func power_10(n: Int64) {
    var vn = n
    vn *= vn         // vn = n ^ 2
    var result = vn  // result = n ^ 2
    vn *= vn         // vn = n ^ 4
    vn *= vn         // vn = n ^ 8
    result *= vn     // result = n ^ 10
    result
}

当然,手动编写这些代码非常繁琐,我们希望在给定 e 的值之后,自动将这些代码生成出来。宏允许我们做到这一点。我们先看使用案例:

public func power_10(n: Int64) {
    @power[10](n)
}

这个宏展开的代码是(根据.macrocall文件):

public func power_10(n: Int64) {
    /* ===== Emitted by MacroCall @power in main.cj:20:5 ===== */
    /* 20.1 */var _power_vn = n
    /* 20.2 */_power_vn *= _power_vn
    /* 20.3 */var _power_result = _power_vn
    /* 20.4 */_power_vn *= _power_vn
    /* 20.5 */_power_vn *= _power_vn
    /* 20.6 */_power_result *= _power_vn
    /* 20.7 */_power_result
/* ===== End of the Emit ===== */
}

下面,我们看宏 @power 的实现。

macro package define

import std.ast.*
import std.convert.*

public macro power(attrib: Tokens, input: Tokens) {
    let attribExpr = parseExpr(attrib)
    if (let Some(litExpr) <- attribExpr as LitConstExpr) {
        let lit = litExpr.literal
        if (lit.kind != TokenKind.INTEGER_LITERAL) {
            diagReport(DiagReportLevel.ERROR, attrib,
                       "Attribute must be integer literal",
                       "Expected integer literal")
        }
        var n = Int64.parse(lit.value)
        var result = quote(var _power_vn = $(input)
        )
        var flag = false
        while (n > 0) {
            if (n % 2 == 1) {
                if (!flag) {
                    result += quote(var _power_result = _power_vn
                    )
                    flag = true
                } else {
                    result += quote(_power_result *= _power_vn
                    )
                }
            }
            n /= 2
            if (n > 0) {
                result += quote(_power_vn *= _power_vn
                )
            }
        }
        result += quote(_power_result)
        return result
    } else {
        diagReport(DiagReportLevel.ERROR, attrib,
                   "Attribute must be integer literal",
                   "Expected integer literal")
    }
    return input
}

这段代码的解释如下:

  • 首先,确认输入的属性 attrib 是一个整数字面量,否则通过 diagReport 报错。将这个字面量解析为整数 n。
  • 设 result 为当前积累的输出代码,首先添加 var _power_vn 的声明。这里为了避免变量名冲突,我们使用不易造成冲突的名字 _power_vn。
  • 下面进入 while 循环,布尔变量 flag 表示 var _power_result 是否已经被初始化。其余的代码结构和之前展示的 power 函数的实现类似,但区别是我们使用 while 循环和 if 判断在编译时决定生成的代码是什么,而不是在运行时做这些判断。最后生成由 _power_result *= _power_vn 和 _power_vn *= _power_vn 适当组合的代码。
  • 最后添加返回 _power_result 的代码。

将这段代码放到 macros/power.cj 文件中,并在 main.cj 添加如下测试:

public func power_10(n: Int64) {
    @power[10](n)
}

main() {
    let a = 3
    println(power_10(a))
}

输出结果为:

59049

Memoize 宏

Memoize(记忆化)是动态规划算法的常用手段。它将已经计算过的子问题的结果存储起来,当同一个子问题再次出现时,可以直接查询表来获取结果,从而避免重复的计算,提高算法的效率。

通常 Memoize 的使用需要开发者手动实现存储和提取的功能。通过宏,我们可以自动化这一过程。首先,让我们先看一下宏使用的效果:

@Memoize[true]
func fib(n: Int64): Int64 {
    if (n == 0 || n == 1) {
        return n
    }
    return fib(n - 1) + fib(n - 2)
}

main() {
    let start = DateTime.now()
    let f35 = fib(35)
    let end = DateTime.now()
    println("fib(35): ${f35}")
    println("execution time: ${(end - start).toMicroseconds()} us")
}

在以上代码中,fib 函数采用简单的递归方式实现。如果没有 @Memoize[true] 标注,这个函数的运行时间将随着 n 指数增长。例如,如果在前面的代码中去掉 @Memoize[true] 这一行,或者把 true 改为 false,则 main 函数的运行结果为:

fib(35): 9227465
execution time: 199500 us

恢复 @Memoize[true],运行结果为:

fib(35): 9227465
execution time: 78 us

相同的答案和大幅缩短的计算时间表明,@Memoize 的使用确实实现了记忆化。

现在让我们理解 @Memoize 的原理。首先,展示对以上 fib 函数进行宏展开的结果(来自 .macrocall 文件,但是为了提高可读性整理了格式)。

var _memoize_fib_map = HashMap<Int64, Int64>()

func fib(n: Int64): Int64 {
    if (_memoize_fib_map.contains(n)) {
        return _memoize_fib_map.get(n).getOrThrow()
    }

    let _memoize_eval_result = { =>
        if (n == 0 || n == 1) {
            return n
        }

        return fib(n - 1) + fib(n - 2)
    }()
    _memoize_fib_map.put(n, _memoize_eval_result)
    return _memoize_eval_result
}

上述代码的执行流程如下:

  • 首先,定义 _memoize_fib_map 为一个从 Int64 到 Int64 的哈希表,这里第一个 Int64 对应 fib 的唯一参数的类型,第二个 Int64 对应 fib 返回值的类型。
  • 其次,在函数体中,检查入参是否在 _memoize_fib_map 中,如果是则立即反馈哈希表中存储的值。否则,使用 fib 原来的函数体得到计算结果。这里使用了(不带参数的)匿名函数使 fib 的函数体不需要任何改变,并且能够处理任何从 fib 函数退出的方式(包括中间的 return,返回最后一个表达式等)。
  • 最后,把计算结果存储到 _memoize_fib_map 中,然后将计算结果返回。

有了这样一个“模版”之后,下面宏的实现就不难理解了。我们给出完整的代码如下。

public macro Memoize(attrib: Tokens, input: Tokens) {
    if (attrib.size != 1 || attrib[0].kind != TokenKind.BOOL_LITERAL) {
        diagReport(DiagReportLevel.ERROR, attrib,
                   "Attribute must be a boolean literal (true or false)",
                   "Expected boolean literal (true or false) here")
    }

    let memoized = (attrib[0].value == "true")
    if (!memoized) {
        return input
    }

    let fd = FuncDecl(input)
    if (fd.funcParams.size != 1) {
        diagReport(DiagReportLevel.ERROR, fd.lParen + fd.funcParams.toTokens() + fd.rParen,
                   "Input function to memoize should take exactly one argument",
                   "Expect only one argument here")
    }

    let memoMap = Token(TokenKind.IDENTIFIER, "_memoize_" + fd.identifier.value + "_map")
    let arg1 = fd.funcParams[0]

    return quote(
        var $(memoMap) = HashMap<$(arg1.paramType), $(fd.declType)>()

        func $(fd.identifier)($(arg1)): $(fd.declType) {
            if ($(memoMap).contains($(arg1.identifier))) {
                return $(memoMap).get($(arg1.identifier)).getOrThrow()
            }

            let _memoize_eval_result = { => $(fd.block.nodes) }()
            $(memoMap).put($(arg1.identifier), _memoize_eval_result)
            return _memoize_eval_result
        }
    )
}

首先,对属性和输入做合法性检查。属性必须是布尔字面量,如果为 false 则直接返回输入。否则,检查输入必须能够解析为函数声明(FuncDecl),并且必须包含正好一个参数。下面,产生哈希表的变量,取不容易造成冲突的变量名。最后,通过 quote 模版生成返回的代码,其中用到哈希表的变量名,以及唯一参数的名称、类型和输入函数的返回类型。

一个 dprint 宏的扩展

本节一开始使用了一个打印表达式的宏作为案例,但这个宏一次只能接受一个表达式。我们希望扩展这个宏,使其能够接受多个表达式,由逗号分开。我们展示如何使用 parseExprFragment 来实现这个功能。

宏的实现如下:

public macro dprint2(input: Tokens) {
    let exprs = ArrayList<Expr>()
    var index: Int64 = 0
    while (true) {
        let (expr, nextIndex) = parseExprFragment(input, startFrom: index)
        exprs.append(expr)
        if (nextIndex == input.size) {
            break
        }
        if (input[nextIndex].kind != TokenKind.COMMA) {
            diagReport(DiagReportLevel.ERROR, input[nextIndex..nextIndex+1],
                       "Input must be a comma-separated list of expressions",
                       "Expected comma")
        }
        index = nextIndex + 1  // 跳过逗号
    }
    let result = quote()
    for (expr in exprs) {
        result.append(quote(
            print($(expr.toTokens().toString()) + " = ")
            println($(expr))
        ))
    }
    return result
}

使用案例:

let x = 3
let y = 2
@dprint2(x, y, x + y)

输出结果为:

x = 3
y = 2
x + y = 5

在宏的实现中,使用 while 循环从索引 0 开始依次解析每个表达式。变量 index 保存当前解析的位置。每次调用 parseExprFragment 时,从当前位置开始,并返回解析后的位置(以及解析得到的表达式)。如果解析后的位置到达了输入的结尾,则退出循环。否则检查到达的位置是一个括号,如果不是括号,报错并退出,如果是括号,跳过这个括号并开始下一轮的解析。在得到表达式的列表后,依次输出每个表达式。

一个简单的 DSL

在这个案例中,我们展示如何使用宏实现一个简单的 DSL(Domain Specific Language,领域特定语言)。LINQ(Language Integrated Query,语言集成查询)是微软 .NET 框架的一个组成部分,它提供了一种统一的数据查询语法,允许开发者使用类似 SQL 的查询语句来操作各种数据源。在这里,我们仅展示一个最简单的 LINQ 语法的支持。

我们希望支持的语法为:

from <variable> in <list> where <condition> select <expression>

其中,variable 是一个标识符,list、condition 和 expression 都是表达式。因此,实现宏的策略是先后提取标识符和表达式,同时检查中间的关键字是正确的。最后,生成由提取部分组成的查询结果。

宏的实现如下:

public macro linq(input: Tokens) {
    let syntaxMsg = "Syntax is \"from <attrib> in <table> where <cond> select <expr>\""
    if (input.size == 0 || input[0].value != "from") {
        diagReport(DiagReportLevel.ERROR, input[0..1], syntaxMsg,
                   "Expected keyword \"from\" here.")
    }
    if (input.size <= 1 || input[1].kind != TokenKind.IDENTIFIER) {
        diagReport(DiagReportLevel.ERROR, input[1..2], syntaxMsg,
                   "Expected identifier here.")
    }
    let attribute = input[1]
    if (input.size <= 2 || input[2].value != "in") {
        diagReport(DiagReportLevel.ERROR, input[2..3], syntaxMsg,
                   "Expected keyword \"in\" here.")
    }
    var index: Int64 = 3
    let (table, nextIndex) = parseExprFragment(input, startFrom: index)
    if (nextIndex == input.size || input[nextIndex].value != "where") {
        diagReport(DiagReportLevel.ERROR, input[nextIndex..nextIndex+1], syntaxMsg,
                   "Expected keyword \"where\" here.")
    }
    index = nextIndex + 1  // 跳过where
    let (cond, nextIndex2) = parseExprFragment(input, startFrom: index)
    if (nextIndex2 == input.size || input[nextIndex2].value != "select") {
        diagReport(DiagReportLevel.ERROR, input[nextIndex2..nextIndex2+1], syntaxMsg,
                   "Expected keyword \"select\" here.")
    }
    index = nextIndex2 + 1  // 跳过select
    let (expr, nextIndex3) = parseExprFragment(input, startFrom: index)

    return quote(
        for ($(attribute) in $(table)) {
            if ($(cond)) {
                println($(expr))
            }
        }
    )
}

使用案例:

@linq(from x in 1..=10 where x % 2 == 1 select x * x)

这个例子从 1, 2, ... 10 列表中筛选出奇数,然后返回所有奇数的平方。输出结果为:

1
9
25
49
81

可以看到,宏的实现的很大部分用于解析并校验输入的 tokens,这对宏的可用性至关重要。实际的 LINQ 语言(以及大多数 DSL)的语法更加复杂,需要一整套解析的机制,通过识别不同的关键字或连接符来决定下一步解析的内容。

1.6 宏的使用实例 宏定义就是用较短的符号代替一些较长的代码语句或者代码段。合理的使用宏定义,不但可以减少编码时的工作量,还能提高代码的可维护性。1.6.1 技术要点: 在Visual C++ 6.0中使用语句“#define 标识符 宏内容”即可定义一个不带参数的宏。编码时,可以用宏的标识符代替具体内容,在程序编译时,编译器自动将这些标识符代替为真正的内容; 宏也可以带参数,其定义语法为“#define 标识符(参数 阅读详情

相关推荐

【华为仓颉编程语言】运行第一个仓颉程序

上节课我们成功安装了仓颉工具链,今天让我们一起编写并运行第一个仓颉程序。相信很多同学都还记得学习第一门编程语言时写的"Hello World"程序,那种看到程序成功运行的激动心情。今天,我们就要在仓颉语言中重现这个经典时刻!

九丘教育官方博客 1789

Excel——宏教程(精简版)

宏属于脚本语言,就是不需要编译为exe文件,可以由解释器直接解释运行。可以通过Sub关键字来定义一个过程格式为Sub宏名称()而过程相当于一个函数的概念,一个模块中也可以定义为多个过程,每个过程其实就是一个宏,都可以被单独使用,也可以在过程内通过call 过程名的方式调用另一个过程。宏名称可以是中文Sub 宏示例()Cells.Select '选中单元格Range("G22").Activate '将G22单元格设置为活动单元格Selection.Copy '复制G22其中的内容。

好好学习 天天向上 2万+

华为仓颉编程语言观感

华为仓颉编程语言概览

HelloMagina的博客 7541

仓颉编程入门(一)

摘要:仓颉(Cangjie)是华为为鸿蒙系统设计的静态类型编程语言,具有高效、安全和易用等特点。主要特性包括:静态类型检查、多范式支持(面向对象和函数式)、内存安全机制(所有权系统和空值处理)、优化的并发模型(Actor和异步/等待)、与鸿蒙深度集成(分布式能力和组件化开发)以及完善的工具链支持。该语言目前处于开发者预览阶段,专注物联网和分布式系统开发,未来有望成为鸿蒙生态的重要开发工具。

weixin_42314209的博客 766

国产自研编程语言“仓颉”来了!

在探索仓颉编程语言的旅程中,我们领略了其独特的设计理念、强大的功能和无尽的潜力。从基础的数据类型到复杂的算法结构,仓颉语言展现了一种既注重效率又追求简洁与优雅的编程范式。其枚举类型作为语言中的一个重要组成部分,不仅增强了代码的可读性和类型安全性,还为开发者提供了更多的灵活性和便利性。然而,仓颉语言仍然是一个不断发展的领域,它将继续吸引来自全球各地的开发者们共同探索和创新。我们期待着未来仓颉语言能够带来更多的惊喜和突破,为解决现实世界中的复杂问题提供更加高效和可靠的编程工具。

静Yu的博客 3135

掌握Excel 259个常用宏:自动化与提升效率

本文还有配套的精品资源,点击获取 简介:Excel中的宏利用VBA代码自动化重复性任务,提高工作效率。本资源详细介绍了宏的基本概念、安全性、录制与播放方法、VBA编程基础及259个实例分类,如数据处理、自动化工作表格式、图表操作等。学习宏的步骤和注意事项被强调,以帮助用户在各个领域实现Excel数据处理自动化。 1. 宏的定义与基本概念 宏是什么? 宏...

weixin_32306683的博客 9118

一文带你入门仓颉编程语言(下)

仓颉编程语言,专为 HarmonyOS 精心打造,它宛如一座桥梁,无缝打通了鸿蒙与欧拉在应用开发生态上的阻碍,为鸿蒙生态基础设施建设添上了画龙点睛的最后一笔。从移动应用、智能穿戴,到智能家居、工业控制,鸿蒙系统广泛的应用场景,对编程语言提出了严苛至极的要求。仓颉编程语言应运而生,承载着满足多样化需求、助力开发者高效创新的重任,已然成为鸿蒙生态蓬勃发展的核心驱动力。

倔强的石头的博客 6748

逆向工程师的Shopee爬虫避坑指南:af-ac-enc-dat参数生成原理与反反爬策略

本文深入剖析了Shopee电商平台的反爬虫加密机制,重点解析了核心请求头参数x-sap-ri、x-sap-sec及af-ac-enc-dat的生成原理与联动关系。文章从逆向工程视角,提供了定位加密代码、分析算法逻辑的实战方法,并分享了基于JS执行环境模拟与无头浏览器控制的可持续反反爬策略,为高效、稳定地采集数据提供专业指南。

weixin_28607671的博客 1497

仓颉编程语言入门

五年磨一剑,华为仓颉编程语言正式发布。

程小二的博客 4万+

1.28寸圆屏LCD驱动开发:GC9A01与RT-Thread嵌入式实现

圆形TFT-LCD是一种面向可穿戴、IoT终端的紧凑型人机交互显示技术,其核心在于SPI/QSPI接口时序控制与GRAM内存映射原理。通过驱动芯片(如GC9A01)完成电源管理、伽马校正和MADCTL方向配置,可实现低功耗、高刷新率的图形渲染。该技术显著提升嵌入式设备的UI表现力与空间利用率,在智能手环、传感器节点、微型HMI等资源受限场景中具备突出工程价值。本文围绕1.28寸圆屏LCD模块,详解硬件接口规范、RT-Thread下QSPI驱动架构及Bresenham算法优化实践。

weixin_42599908的博客 161

【Mode Management】AUTOSAR架构下RH850-F1KM芯片深度休眠配置实践

AUTOSAR架构下RH850-F1KM芯片深度休眠配置实践

qq_36056498的博客 1092

探秘仓颉编程语言:使用体验与功能剖析

华为,作为全球科技领域的领军者,在 2024 年华为开发者大会上,重磅推出了自研的编程语言 —— 仓颉。这一具有里程碑意义的事件,瞬间在业界引发了强烈的震动,为鸿蒙生态注入了全新的活力,也为国产编程领域开辟了一片广阔新天地

倔强的石头的博客 8284

【异常】解决 Claude Code 及第三方插件提示 “API Error: 404“ 的配置修复指南

在使用 Claude 相关的 CLI 工具(如)或 IDE 插件进行开发辅助时,配置错误的模型参数是导致连接失败的常见原因。本文将针对这一典型报错进行复盘与解决。

奶爸的编程之路,也就一周冷个三天 7750

仓颉编程语言开发指南(下)

文章目录仓颉编程语言开发指南(上)十五、宏1.宏的简介2.Tokens 相关类型和 quote 表达式Token 类型Tokens 类型quote 表达式和插值3.语法节点节点的解析使用解析表达式和声明的函数使用构造函数进行解析节点的组成部分BinaryExpr 案例FuncDecl 案例使用 quote 插值语法节点4.宏的实现非属性宏属性宏嵌套宏宏定义中嵌套宏调用宏调用中嵌套宏调用嵌套宏之间的消息传递5.编译、报错与调试宏的编译和使用并行宏展开diagReport 报错机制使用 --debug-macr

欢迎来到我的学习笔记薄 2994

R语言中的summarise函数和mutate函数用于计算分组占比。下面是关于这两个函数的详细介绍以及相应的源代码示例:

R语言中的summarise函数和mutate函数用于计算分组占比。

CodeByte的博客 1183

华为OD机试 - 单词倒序(Python/JS/C/C++ 2025 A卷 100分)

华为OD机试 2025A卷题库疯狂收录中,刷题。

学Java,找哪吒 1226

一文带你入门仓颉编程语言(上)

仓颉编程语言,专为 HarmonyOS 精心打造,它宛如一座桥梁,无缝打通了鸿蒙与欧拉在应用开发生态上的阻碍,为鸿蒙生态基础设施建设添上了画龙点睛的最后一笔。从移动应用、智能穿戴,到智能家居、工业控制,鸿蒙系统广泛的应用场景,对编程语言提出了严苛至极的要求。仓颉编程语言应运而生,承载着满足多样化需求、助力开发者高效创新的重任,已然成为鸿蒙生态蓬勃发展的核心驱动力。

倔强的石头的博客 2万+

【DRAM存储器二十六】DDR4介绍-DDR4 SDRAM的主要技术特性之CA Parity、CRC Write date

详述DDR4的CA奇偶检验和写数据CRC校验功能

highman110的博客 1298

仓颉编程语言:宏的实现

本章节还会介绍宏出现嵌套时的行为。

Androidbye的博客 936

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

仓颉编程入门 -- 宏的介绍

在编程中,宏可以被视为一种强大的工具,它允许开发者在编译时期对代码进行变换。与普通的函数不同,宏不仅操作数据值,还直接操作代码本身。通过宏,我们可以将复杂的、重复的代码模式抽象化,并在编译时自动生成这些模式的具体实现。假设我们有一个需求:在开发过程中频繁地需要打印变量或表达式的值及其表达式本身,以便于调试。传统的做法是通过编写大量的print语句来实现,但这既繁琐又容易出错。宏提供了一种优雅的解决方案,让我们能够定义一个dprint宏,自动完成这一任务。为了定义并实现dprint。

几许的博客 1万+

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

仓颉编程语言-02-VSCode插件使用

本文主要结束仓颉语言的VScode插件使用与开发

IT从业者的成长历程 1809

华为仓颉编程语言正式发布,仓颉编程教程

华为在 2024 年 6 月 21 日的华为开发者大会上,华为终端 BG 软件部总裁龚体正式官宣了华为自研仓颉编程语言,并发布了 HarmonyOS NEXT 仓颉语言开发者预览版。仓颉 | 华为开发者联盟仓颉编程语言文件后缀名为.cj你好,仓颉。lambda表达式可以让函数的创建和使用更加灵活,lambda表达式的值就是一个匿名函数仓颉提供了Exception和Error两个类型,用于描述程序运行时的异常行为开发者可以继承Exception或其子类来自定义异常类,但不能继承Error或其子类。

shixuyong的博客 4408

跟我一起学“仓颉”编程语言—仓颉语言介绍及Hello World

本章为大家详细的介绍了仓颉编程语言的特性、环境安装以及Hello World的内容。

Cool_foolisher的博客 9003

仓颉编程语言:编程世界的 “文化瑰宝”

在当今编程领域百花齐放的时代,各种编程语言争奇斗艳,服务于不同的应用场景和开发者群体。然而,有这样一种编程语言,它承载着独特的文化内涵,宛如编程世界里一颗熠熠生辉的“文化瑰宝”,那就是仓颉编程语言。

2301_80350265学无止尽5的博客 3770

使用仓颉编程语言是一种怎样的体验?

摘要:华为在2024开发者大会上正式发布自研仓颉编程语言及HarmonyOS NEXT预览版,这款历时5年研发的新语言融合现代特性与编译优化,提供高效开发体验。开发者评价其兼具函数式与OOP优势,在元编程、静态编译等方面表现突出,但生态工具仍需完善。与C#等成熟语言对比,仓颉在语法简洁性上具有优势,实际性能有待验证。早期使用者分享从质疑到认可的心路历程,认为伴随新语言成长具有独特价值。目前仓颉尚未开源,其跨平台能力与社区生态建设将是未来发展关键。(149字)

2532
上一篇: 仓颉编程语言:内置编译标记
下一篇: 仓颉编程语言:仓颉-C 互操作
懂你的大海
博客等级 码龄6年 9049粉丝 1381原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值