华为仓颉语言的函数初步

函数是一段完成特定任务的独立代码片段,可以通过函数名字来标识,这个名字可以被用来调用函数。

要特别注意,与C/C++、Python等语言不同,仓颉禁止参数重新赋值——函数参数均为不可变(immutable)变量,在函数定义内不能对其赋值。也就是说,仓颉函数参数与 let 声明的变量一样,只能读取,不能再次赋值。例如:
func demo(x: Int64): Int64 {
x = x + 1   // ❌ 编译错误
return x
}

如果需要修改,只能引入新的局部变量:
func demo(x: Int64): Int64 {
let y = x + 1   // ✅ 正确
return y
}

这说明,仓颉语言语言设计决策强化对不可变性和函数式编程范式的偏好。

函数定义

基本语法:仓颉语言使用func关键字来定义函数,后跟函数名、参数列表、可选的返回值类型以及函数体。其基本语法为:

func 函数名(参数列表): 返回值类型 {

    // 函数体

    return 返回值

}

参数列表:函数可以有 0 个或多个参数,参数分为非命名参数命名参数。非命名参数定义为p: T,如a: Int64;命名参数定义为p!: T,如a!: Int64,且只能为命名参数设置默认值,非命名参数不能设置默认值,参数列表中非命名参数只能定义在命名参数之前。

返回值类型:必须显式写出返回类型,除非返回Unit(可以省略)。

函数体定义在一对花括号内。函数体中定义了函数被调用时执行的操作,通常包含一系列的变量定义和表达式,也可以包含新的函数定义(即嵌套函数)。

函数定义示例

func add(a: Int64, b: Int64): Int64 {
return a + b
}

如果函数体是单个表达式,仓颉也支持隐式返回(省略 return 关键字):

func add(a: Int64, b: Int64): Int64 {
a + b  // 最后一行表达式的值即为返回值
}

函数参数示例

非命名参数 (Non-named Parameters): 即普通参数,调用时按顺序传递。非命名参数必须定义在命名参数之前。例如:

// 定义一个包含两个非命名参数的函数
func multiply(a: Int64, b: Int64): Int64 {return a * b
}main() {// 调用函数(必须按顺序传递参数,无需指定参数名)let result = multiply(3, 4)  // 正确调用,返回12println(result) 
}

命名参数 (Named Parameters): 在参数名后加 !,调用时需指定参数名,并可提供默认值,提高了代码可读性和灵活性。例如:

// 定义包含命名参数的函数(带默认值)
func calculateTotal(price: Int64, quantity!: Int64 = 1, discount!: Float64 = 0.0): Int64 {
let total = Float64(price * quantity)      // 先转成 Float64
let discounted = total * (1.0 - discount)    // 浮点运算
return Int64(discounted)                     // 再转回整数
}

注意:

只能为命名参数设置默认值,不能为非命名参数设置默认值。

参数列表中可以同时定义非命名参数和命名参数,但是需要注意的是,非命名参数只能定义在命名参数之前,也就意味着命名参数之后不能再出现非命名参数。例如,下例中 add 函数的参数列表定义是不合法的:

func add(a!: Int64, b: Int64): Int64 { // 错误!
return a + b
}

调用函数规则

a.非命名参数调用

调用规则:必须按定义顺序传递,不能指定参数名。

b.命名参数调用

必须用 参数名: 值 形式传递,顺序可任意调整,且支持使用默认值。

c.混合参数调用(非命名 + 命名)

非命名参数必须先定义、先传递,命名参数在后,且命名参数必须指定参数名。

示例:

// 定义包含命名参数的函数(带默认值)
func calculateTotal(price: Int64, quantity!: Int64 = 1, discount!: Float64 = 0.0): Int64 {let total = Float64(price * quantity)      // 先转成 Float64let discounted = total * (1.0 - discount)    // 浮点运算return Int64(discounted)                     // 再转回整数
}main() {// 调用方式1:只传非命名参数(price),命名参数用默认值let total1 = calculateTotal(100)  // quantity默认1,discount默认0 → 结果100println(total1)// 调用方式2:指定部分命名参数(可调整顺序)let total2 = calculateTotal(100, discount: 0.2, quantity: 2)  // 计算:100 * 2 * (1-0.2) = 160 → 结果160println(total2)// 调用方式3:显式指定所有参数名let total3 = calculateTotal(200, quantity: 3, discount: 0.1)  // 计算:200 * 3 * 0.9 = 540 → 结果540println(total3)
}

返回值类型的说明

在 仓颉语言(Cangjie) 的正式规范中:

必须显式写出返回类型,当返回类型为 Unit 时可省略 -> Unit。

// ✅ 合法

func add(a: Int64, b: Int64): Int64 { a + b }

// ❌ 不合法- 函数体中有返回值的表达式时必须声明返回类型

func add(a: Int64, b: Int64) { a + b }  

如果函数 没有有意义的返回值(即返回 Unit),可以写成 : Unit,也可以 直接省略 返回类型部分。

// 等价写法

func log(msg: String) { println(msg) }        // 省略 : Unit

func log(msg: String): Unit { println(msg) }  // 显式 : Unit

log("你好") //调用

单值返回和多值返回(Tuple)

单值返回示例:

// 返回单个 Int64
func square(n: Int64): Int64 {return n * n
}main(): Unit {let s = square(7)   println("square = ${s}")  //square = 49
}

多值返回(Tuple)示例:

// 返回一个二元组 (Int64, Int64)
func divmod(a: Int64, b: Int64): (Int64, Int64) {return (a / b, a % b)
}main(): Unit {let (q, r) = divmod(10, 3)   // q = 3, r = 1println("quotient = ${q}, remainder = ${r}")  //quotient = 3, remainder = 1
}

函数类型(Function Type)

仓颉编程语言中,函数是一等公民(first-class citizens),可以作为函数的参数或返回值,也可以赋值给变量。因此函数本身也有类型,称之为函数类型。

函数类型由函数的参数类型和返回类型组成,参数类型和返回类型之间使用 -> 连接。参数类型使用圆括号 () 括起来,可以有 0 个或多个参数,如果参数超过一个,参数类型之间使用逗号(,)分隔。

函数类型是编程中一个比较抽象但极其强大的概念,是函数式编程范式(FP)的核心基石之一。

函数类型的语法非常直观,遵循以下格式:

(参数1类型, 参数2类型, ...) -> 返回值类型

解释一下:

  •  括号 ():里面放置函数的参数类型列表。如果没有参数,就空着 ()。

  •  箭头 ->:连接参数和返回值,读作“返回”。

  •  返回值类型:在箭头后面,指定函数返回的数据类型。

例如:

没参数也要空括号:​() -> Unit

多个参数逗号隔:​(Int, String) -> Bool

返回元组括号包:​(Int, Int) -> (Int, Int)

函数类型既可以根据函数定义隐式存在,也可以由程序员在代码中显式地书写出来。

1. 隐式的函数类型 (由函数定义产生)

例子:

// 【函数定义】

// 程序员写的是具体的实现

func add(a: Int64, b: Int64): Int64 {

    return a + b

}

// 【隐式的函数类型】

// 编译器会自动识别出这个函数有一个类型:(Int64, Int64) -> Int64

// 这个类型是“依据函数定义存在的”,程序员没有显式写出 `(Int64, Int64) -> Int64` 这几个字。

2. 显式的函数类型 (由程序员主动书写)

你完全可以先写出类型,再去找或定义一个符合该类型的函数(甚至用变量、lambda、函数值等)。

例子

// 1. 【显式地用于变量声明】

// 程序员主动写下了类型注解 `: (Int64, Int64) -> Int64`

let myMathOperator: (Int64, Int64) -> Int64 = add // 将函数`add`赋值给变量

// 2. 【显式地用于函数参数】

// 程序员定义了一个高阶函数,它接受一个函数作为参数

// 参数 `operation` 的类型被显式地定义为 `(Int64, Int64) -> Int64`

func calculate(operation: (Int64, Int64) -> Int64, x: Int64, y: Int64) -> Int64 {

    return operation(x, y)

}

// 3. 【显式地用于解决重载歧义】

func add(i: Int64, j: Int64) -> Int64 { i + j }

func add(i: Float64, j: Float64) -> Float64 { i + j }

// 这里直接写 `add` 编译器不知道选哪个,产生歧义

// let f = add // Error!

// 程序员通过【显式地书写类型】来告诉编译器需要哪个函数

let f: (Int64, Int64) -> Int64 = add // OK

函数类型的核心用途

1. 声明函数类型的变量

在仓颉中,声明变量必须显式或通过初始化值来表明类型。

// 定义一个函数
func add(a: Int64, b: Int64): Int64 {return a + b
}main() {// 正确声明1: 显式指定变量类型,再赋值函数let operation: (Int64, Int64) -> Int64 // 声明一个函数类型的变量operation = add // 将函数赋值给变量// 正确声明2: 声明的同时初始化(类型由编译器推断)let anotherOperation = add // 编译器能推断出anotherOperation的类型是 (Int64, Int64) -> Int64//现在,operation或anotherOperation就代表了 add 函数let result = operation(5, 3)println(result) // 输出 8let result2 = anotherOperation(5, 3)println(result2) // 输出 8
}

2. 作为函数的参数(高阶函数)

// 导入标准库中的集合包
import std.collection.ArrayList // 使用ArrayList// 高阶函数的参数类型使用函数类型 (Int64) -> String
// 输入和输出使用 ArrayList 类型
func processNumbers(numbers: ArrayList<Int64>, transform: (Int64) -> String): ArrayList<String> {// 创建一个新的 ArrayList 来存放结果let results = ArrayList<String>()// 遍历输入的 ArrayList - 使用 for-in 循环for(num in numbers) {// 调用传入的 transform 函数处理每个元素let transformedValue = transform(num)// 将结果添加到新的集合中results.add(transformedValue)}return results
}// 处理行为的函数定义不变
func intToString(num: Int64): String {return "Number: ${num}"
}main() {// 使用 ArrayList 而不是原生数组let myNumbers = ArrayList<Int64>([1, 2, 3, 4, 5])// 调用方式完全一样,传递函数名let resultList = processNumbers(myNumbers, intToString)// 遍历结果 ArrayList - 使用 for-in 循环for (str in resultList) {println(str)}// 同样可以使用 Lambda 表达式let squaredList = processNumbers(myNumbers, { n => "Squared: ${n * n}" })// 遍历 squaredListfor (str in squaredList) {println(str)}
}

3. 作为函数的返回值

// 这个函数返回一个 () -> String 类型的函数
func getGreeter(prefix: String): () -> String {// 在内部定义一个函数,它捕获了参数 `prefix`func greeter(): String {return "${prefix}, Hello!" }return greeter // 返回这个内部函数
}main() {// getGreeter 返回的是一个函数let casualGreet = getGreeter("Hi")let formalGreet = getGreeter("Good morning")// 调用返回的函数println(casualGreet()) // 输出: Hi, Hello!println(formalGreet()) // 输出: Good morning, Hello!
}

顺便提示,先把函数定义好,再传递函数名。也可可以用Lambda 表达式(匿名函数),下面示例对比:

import std.collection.ArrayList // 使用ArrayList// 高阶函数的参数类型使用函数类型 (Int64) -> String
// 输入和输出使用 ArrayList 类型
func processNumbers(numbers: ArrayList<Int64>, transform: (Int64) -> String): ArrayList<String> {// 创建一个新的 ArrayList 来存放结果let results = ArrayList<String>()// 遍历输入的 ArrayList - 使用 for-in 循环for(num in numbers) {// 调用传入的 transform 函数处理每个元素let transformedValue = transform(num)// 将结果添加到新的集合中results.add(transformedValue)}return results
}main() {// 使用 ArrayList 而不是原生数组let myNumbers = ArrayList<Int64>([1, 2, 3, 4, 5])// ---------1.具名函数(普通写法)------------    // 先写一个具名函数func multiply10(num: Int64): String {return "Value is: ${num * 10}"}// 把函数名当参数传进去let result = processNumbers(myNumbers, multiply10)    // 遍历结果for(str in result) {println(str)}println("-----------")// ---------2.Lambda 表达式(匿名写法)------------ // Lambda 表达式写法let result2 = processNumbers(myNumbers, { num => "Value is: ${num * 10}" })    // 遍历结果for(str in result2) {println(str)}
}

特别提示,可给函数类型的参数标记显式名称(仅用于标识,不影响类型匹配),且需统一写或统一不写,不能混合。

示例:

// 首先定义 showFruitPrice 函数
func showFruitPrice(name: String, price: Int64): Unit {println("Fruit: ${name}, Price: ${price}")
}main() {// 1. 全部写名字 —— 合法let handler1: (name: String, price: Int64) -> Unit = showFruitPrice// 2. 全部不写名字 —— 合法  let handler2: (String, Int64) -> Unit = showFruitPrice// 3. 混写 —— 非法,编译时报错// let handler3: (name: String, Int64) -> Unit   // Error: 必须统一写或统一不写参数名// 调用函数handler1("apple", 5)   // 正确handler2("apple", 5)   // 同样正确
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/920170.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/920170.shtml
英文地址,请注明出处:http://en.pswp.cn/news/920170.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

服务初始化

目录 1.配置yum源 2. 更新系统与安装必备工具 3. 网络连接验证 4. 配置主机名 5. 同步时间 6. 配置防火墙 (两种方式) 6.1 iptables 6.2firewalld 1.配置yum源 1. 备份原有的源文件&#xff0c;以防万一 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.…

ICBC_TDR_UShield2_Install.exe [ICBC UKEY]

流程&#xff1a;1&#xff09;插入U盾&#xff0c;记住检测到U盾类型&#xff0c;需要根据这个下载驱动

在线提取维基百科Wikipedia文章页面及离线批处理Wikipedia XML Dump文件

1. 在线提取维基百科Wikipedia文章 本项目提供一个增强型 Wikipedia 概念条目抓取与摘要清洗脚本&#xff1a;支持多级回退策略 (wikipedia 库 →wikipediaapi → 直接网页 / REST 搜索)、智能标题匹配(精确/模糊判定)、摘要质量校验、内容结构化抽取、断点续跑(结果缓存)、统…

安全合规:AC(上网行为安全)--下

五、SSL移动接入方案概述1、SSL VPN概述SSL VPN是一种远程安全接入技术&#xff0c;因为采用SSL协议而得名。因为Web浏览器都内嵌支持SSL协议&#xff0c;使得SSL VPN可以做到“无客户端”部署。SSL VPN一般采用插件系统来支持各种TCP和UDP的非Web应用&#xff0c;使得SSL VPN真…

【86页PPT】特种车行业SAP解决方案(附下载方式)

篇幅所限&#xff0c;本文只提供部分资料内容&#xff0c;完整资料请看下面链接 https://download.csdn.net/download/2501_92808859/91716699 资料解读&#xff1a;《【86页PPT】特种车行业SAP解决方案》 ​​详细资料请看本解读文章的最后内容​​ 作为特种车行业信息化建…

【Kubernetes k8s】(两万字超详细)Ubuntu-22.04搭建 k8s-1.30.1集群,开启Dashboard-2.7.0、部署ingress-nginx-1.10.1

Ubuntu-22.04搭建 k8s-1.30.1集群&#xff0c;开启Dashboard-v2.7.0&#xff08;以及Token不生成的问题&#xff09;、部署ingress-nginx-1.10.1 引言 最近在研究分布式计算&#xff0c;想将分布式计算都容器化&#xff0c;使用 k8s 来调度&#xff0c;所以从0开始学 k8s &…

podman启动mongdb的container因为权限问题导致changing ownership和读取storage.bson失败的解决方法

用FROM mongo:8.0.8 为基础镜像&#xff0c;加了些初始化数据做的mongodb镜像。用podman管理和backend&#xff0c;frontend组成一个简单的BS架构。利用podman创建pod&#xff0c;3个镜像同用一个空间&#xff0c;项目内部连接就可以统一用127.0.0.1加上端口进行通信了。 要使…

UE5基本打光(新手向)

在UE5中场景照明往往是构建沉浸式视觉体验的关键环节与常见挑战。学会如何打光可以为项目创建出更具表现力和艺术感的灯光效果。 1.以UE5建筑展示demo为例&#xff0c;首先删除旧的光照&#xff0c;将光照相关配置放置新的场景Light中。这样更适合多人分工。 光照子场景Light&…

PiscCode使用OpenCV和Python实现运动检测与可视化

光流分析是计算机视觉中的重要技术&#xff0c;用于检测视频序列中物体的运动模式。本文将介绍如何使用OpenCV和Python实现一个实时的光流分析系统&#xff0c;该系统能够检测运动、生成热力图并提供详细的统计分析。 技术概述 本系统基于Farneback稠密光流算法&#xff0c;能…

Day 36 复习日

浙大疏锦行 今天是复习日&#xff0c;神经网络与机器学习最大的不同就是不止固定的三行代码&#xff0c;需要自己定义一个模型&#xff0c;先继承类的属性&#xff0c;然后去写自己的属性&#xff0c;以及前向传播方法&#xff0c;可以手动构建&#xff1a;中间层的数量、每一…

ES6/ES2015 - ES16/ES2025

ES6/ES2015 - ES16/ES2025 ECMAScript&#xff08;简称ES&#xff09;是JavaScript的官方标准&#xff0c;从2015年开始每年发布一个新版本。 版本一览表年份版本主要新特性2015ES6/ES2015let/const、箭头函数、Class、模板字符串、解构赋值、模块、Promise2016ES7/ES2016指数运…

BIM 地铁站可视化:智慧运维 “透视镜”

图扑 BIM 地铁站可视化系统&#xff0c;以三维建模完整复刻车站空间&#xff0c;从出入口、站厅到设备层&#xff0c;管线走向、设施分布精准呈现。实时汇聚客流数据、空调等设备运行状态&#xff0c;动态标记设备告警、空间占用情况。通过透明化模型&#xff0c;运维人员可直观…

淘宝商品详情页数据接口设计与实现:从合规采集到高效解析

在电商数据分析、比价系统开发等场景中&#xff0c;商品详情页数据是核心基础。本文将围绕淘宝商品详情页数据接口的合规设计、高效采集与智能解析展开&#xff0c;提供一套可落地的技术方案&#xff0c;重点解决动态渲染、参数加密与数据结构化等关键问题。 一、接口设计原则…

HTML应用指南:利用GET请求获取中国银行人民币存款利率数据

人民币存款利率是影响居民储蓄行为和企业资金配置的关键因素&#xff0c;也是宏观经济调控的重要工具。中国银行根据中国人民银行的指导政策&#xff0c;结合市场情况与自身经营策略&#xff0c;定期调整并公布人民币存款利率标准。这些利率信息主要涵盖活期存款、定期存款&…

RPS和QPS

简介 这是系统设计中两个最核心且容易混淆的性能指标。简单来说&#xff1a; • RPS 是 “每秒请求数”&#xff0c;是从客户端或负载均衡器的视角看&#xff0c;服务器每秒接收到的请求数量。 • QPS 是 “每秒查询数”&#xff0c;通常是从数据库或特定服务的视角看&…

如何将用户反馈转化为可执行需求

用户反馈是企业优化产品、改进服务的重要依据。将用户反馈转化为可执行需求的核心在于通过系统化的流程对反馈进行收集、分析和分类&#xff0c;并结合企业的战略目标与技术能力&#xff0c;制定出具体的执行方案。这一过程不仅要求企业深入理解用户需求&#xff0c;还需要跨部…

ry-vue docker部署

目录 整体架构概览 创建 Docker 自定义网络 Redis 部署&#xff08;缓存服务&#xff09; redis.conf修改 启动 Redis 容器 测试 启动 MySQL 容器 允许 root 用户远程访问&#xff08;%&#xff09; 初始化数据库&#xff08;可选&#xff09; RuoYi-Admin 后端服务部…

Redis之Keys命令和Scan命令

序言 网上看到的面试题&#xff1a;Redis有1亿个key&#xff0c;其中10w个key是以某个固定的前缀开头&#xff0c;如何将它们全部找出来&#xff1f;一般有两种命令可以实现&#xff1a; Keys命令Scan命令 下面具体分析一下两种命令 Keys命令 Keys pattern如下图所示&…

【小沐学GIS】基于Godot绘制三维数字地球Earth(Godot)

&#x1f37a;三维数字地球GIS系列相关文章&#xff08;C&#xff09;如下&#x1f37a;&#xff1a;1【小沐学GIS】基于C绘制三维数字地球Earth&#xff08;OpenGL、glfw、glut&#xff09;第一期2【小沐学GIS】基于C绘制三维数字地球Earth&#xff08;OpenGL、glfw、glut&…

day62 Floyd 算法 A * 算法

Floyd 算法本题是经典的多源最短路问题.Floyd 算法对边的权值正负没有要求&#xff0c;都可以处理。Floyd算法核心思想是动态规划。例如我们再求节点1 到 节点9 的最短距离&#xff0c;用二维数组来表示即&#xff1a;grid[1][9]&#xff0c;如果最短距离是10 &#xff0c;那就…