惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

宝玉的分享
宝玉的分享
I
InfoQ
博客园 - 三生石上(FineUI控件)
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 【当耐特】
T
The Blog of Author Tim Ferriss
N
Netflix TechBlog - Medium
GbyAI
GbyAI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
B
Blog RSS Feed
Blog — PlanetScale
Blog — PlanetScale
酷 壳 – CoolShell
酷 壳 – CoolShell
WordPress大学
WordPress大学
L
LINUX DO - 热门话题
Security Latest
Security Latest
月光博客
月光博客
U
Unit 42
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Threat Research - Cisco Blogs
The GitHub Blog
The GitHub Blog
Simon Willison's Weblog
Simon Willison's Weblog
Help Net Security
Help Net Security
人人都是产品经理
人人都是产品经理
Engineering at Meta
Engineering at Meta
罗磊的独立博客
Attack and Defense Labs
Attack and Defense Labs
MongoDB | Blog
MongoDB | Blog
Microsoft Azure Blog
Microsoft Azure Blog
S
Securelist
P
Proofpoint News Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
A
About on SuperTechFans
PCI Perspectives
PCI Perspectives
S
Security Affairs
Schneier on Security
Schneier on Security
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
L
LangChain Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
H
Hacker News: Front Page
C
Cyber Attacks, Cyber Crime and Cyber Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Security Archives - TechRepublic
Security Archives - TechRepublic
P
Privacy & Cybersecurity Law Blog
W
WeLiveSecurity
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
K
Kaspersky official blog
Google Online Security Blog
Google Online Security Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
V
Vulnerabilities – Threatpost
Recorded Future
Recorded Future

机核

游戏性能旗舰最强之选,一加 Ace 6 至尊版国补到手价2999元起 6元钱自己更换电动车刹车线 《生化危机9:安魂曲》编剧Haris Orkin专访 摸金游戏?音乐游戏!暗区新赛季这把能弹的琴有何来历? 好评国产武侠SRPG《息风谷战略》免费DLC现已推出 | 机核 GCORES 碎片 《生化危机:安魂曲》将于5月实装首个免费更新内容 | 机核 GCORES 新锐东方游戏,谱写世界新章! 沉浸式恋爱视觉小说游戏《心象演算》免费试玩版现已正式上线 | 机核 GCORES 互动影游《代号三国:龙起》上线!穿越三国与曹操并肩、与佳人同行、与权谋博弈! 《老头收集梦想生活》,游戏酒桌会6,录音笔VOL.689 | 机核 GCORES 破界·共生——《白日梦:无限世界》五大核心游戏特点解析 价格已到史低,锐龙5 9600X/锐龙7 9700X正适合抄底 时间循环之旅即刻启程!《归环》一周目测试今日开启 热门在线韩游变魂游,洛奇衍生作能否打破“花瓶”魔咒? LG UltraGear evo 全新高端显示器系列:当“5K”遇见“AI”,不止强大,更懂热爱 反套路三国互动影游《代号三国:龙起》今日上线! 愿望单登记人数突破10万!备受瞩目的“女儿养成游戏” 《梦幻魔法公主》今日于Steam平台上线!限时八折优惠中 《黑神话:悟空》全球音乐会2026巡演将于4月29日12时开票 | 机核 GCORES “Snowguelike”生存肉鸽挖矿新作《蛙穿雪境》公布发售日期,5月7日正式上线 | 机核 GCORES 重塑移动办公、AI创作新境!全新华硕灵耀Air系列、ProArt 骁龙版震撼首发,创芯未来 均分88:《Saros》媒体评分汇总 | 机核 GCORES 上海烛龙公布合作遗迹探险游戏《吉时已到》首支预告片 | 机核 GCORES 首个独立游戏《萝薇日记》已上线Steam! 烛龙新IP《吉时已到》首曝,打造国内首款中式合作遗迹探险游戏 喜加一:《暗黑破坏神Ⅳ》国服现已开启限时免费领取本体活动 | 机核 GCORES 新版《生化危机》电影定于9月18日上映,官方网站现已上线 | 机核 GCORES 《冲就完事模拟器2》“星球大战”联动DLC正式宣布 | 机核 GCORES SteamController将于5月4日发售,售价99美元 | 机核 GCORES 事已至此,内存这么用也算省钱了,“2+1”非对称双通道应用实测 505游戏母公司现已收购《明末:渊虚之羽》IP | 机核 GCORES 基石 手动杂谈12|格斗游戏也能讲好故事 | 机核 GCORES I Love You Mr Snowball 我的向日葵小姐 会比GTA6先发售吗?最硬核的生存游戏《DAYZ》要推新DLC了 周记02:在2026遇到新怪谈 什么硬件,能让游戏Loading界面快速消失? | 机核 GCORES GadioSpec《百年风云世界杯》免费试听集 | 机核 GCORES 百年风云世界杯Vol.1丨足球崛起 | 机核 GCORES 100年前,一群人提前替我们经历了AI恐惧 | 机核 GCORES 《呼啸山庄》2026 ——一辆当代艺术的大卡车冲撞了我的大脑 手游《天穗之咲稻姬:日之香巡灵传》宣布将于7月27日停服 | 机核 GCORES 集结梦之队,征战世界杯,《最佳球会ONLINE》上线Steam 山水绝景随心拼 休闲建造游戏《千里山河录》Steam商店页公开 巫师帽、法袍、魔杖,为什么它们是影视、游戏里的法师必备三件套? 《无鞘信使》-第一章 复古风自动战斗肉鸽《终结之终结》Steam商店页面现已上线 电脑里有一款不破不立的MMO,录音笔VOL.688 | 机核 GCORES 《生化危机》30周年纪念周边发售,这次是真的“保护伞” 可靠耐用+AI全能,惠普战66 2025锐龙版深度体验 《时之书:无尽终章》关卡“大航海时代”全球首次公开 经典名作《乌龙派出所》改编经营模拟游戏《乌龙派出所~阿两的商店街物语~》正式宣布支持简体中文 明日开冲,解锁反套路三国互动影游《代号三国:龙起》即将上线 独立游戏《这是我的宝藏!》已发售~ 降低难度不是唯一解,无压力死亡也是好体验 Netflix官宣新片《普通人》:讲述韩国现代史上的权力风暴 《生化危机:安魂曲》全球销量现已突破700万份 《绝地鸭卫》PC版5月15日正式发售 亡妻回忆录?女性向情感叙事游戏《S-mail》现已正式发售 全新酷黑风格,酷睿Ultra 200S PLUS的高性价比搭档!七彩虹BATTLE-AX B860M-PLUS S WIFI7 V20 超级黑刃主板测评 胖狗 索尼发布了港台地区PS5产品价格调整公告,将于5月1日起实施 二次元怪猎+性感美女!《碧蓝幻想:无尽黄昏》开启Beta公测 这款怪谈类型中式恐怖游戏居然更新了?! 《百日战纪 -最终防卫学园-》改编漫画将于今年冬季开启连载 《如被附身,请致电我们》:匈牙利黑色幽默恐怖小说 《竹屿山房杂部卷五》(译文) 大树 超越引擎 摄影分享丨四月 碎片杂记vol.72 当老式FPS与老式动画技术碰撞出新时代的火花—《神探杰克鼠》 想做独游,如何避免首个项目就褒姒? 在线多人动作游戏《OCTOPinbs》将于5月12日上线Steam! 《酒鬼女神的酒诡》确定将于2026年登陆Steam 《CRYMELIGHT》将于11月5日(周四)正式发售!4月25日开始预购! 钢铁国度MKI部落Evolution,蛮兵部分 【钢铁国度】部落-Primal MK I,蛮兵部分 这是一个高中生用ai跑出来的作品,我自称他为物理神话 钢铁国度MKI部落Metamorphosis,蛮兵部分上 钢铁国度MKI部落Metamorphosis,蛮兵部分下 INDIE Live Expo于4月25日举办:首发9款新作,超200款独立游戏亮相 暗黑卡牌策略新作《魔忌:穷鼠啮狸》发布全新中文试玩版和发售预告片,4月30日正式上线在即 战锤40K长篇小说:变节者・苦难主宰(三)(全书完) 战锤40K长篇小说:变节者・苦难主宰(二) 战锤40K长篇小说:变节者・苦难主宰(一) 英语语言学习丨短语专题1:短语的特点 唯一的EVE战争 【少前同人】【M200】战术人形会梦见音乐会吗 【昏迷3】即将发售,前作主角悉数到场!“恶灵”宋老师化身可操控角色 业内人士:游戏公司“十有八九”使用生成式AI,包括卡普空 欢庆一周年:《光与影:33号远征队》全新纪念艺术图、超值折扣与免费更新同步上线 才刚刚开始呢 【抽奖】《星际卡车司机》推出免费大型更新,四折平史低折扣进行中 四人合作FPS游戏《佣兵猎手》抢先体验重大更新 1 现已上线 Raw Fury新作《深馅地牢 Deep Dish Dungeon》将于今秋加入 XGP 塔防幸存者游戏《魔怪来袭》推出首个 DLC 《饿狼传说:群狼之城》1周年纪念!新DLC“沃尔夫冈·克劳萨”今日参战 猫狗相伴 欢乐闯关 双人合作平台跳跃游戏《猫狗同行》上线Steam商店页 《同行:月球逃脱》(Together: Moon Escape)上线将于明日上线Steam
如何快速学会一门编程语言之Lua
韩大 · 2024-11-19 · via 机核

前言

如果是第一次学习一门编程语言,那么过程往往是比较困难的。有的语言比较容易上手,有的则比较费劲。有的语言包含的功能比较多,而有的语言又比较少。如果是第一次学习编程,当然最好是选择那些容易上手,功能比较没那么复杂的语言为好。一旦你学会了第一门编程语言,再学习第二门,难度就会下降非常多。

当你学会第二门语言,你才真正懂得一门语言

有人把各种语言分为三类:编译型、脚本型、函数型。相对来说,脚本型的语言是比较容易上手的,这类语言中比较著名的包括:PHP, Python, JavaScript, 以及本文想作为例子和大家一起学习的 Lua。

除了语言本身是否难学以外,我们还需要知道这门语言的用途。虽然现在大多数流行语言,用途都很多样,大部分应用领域在主流语言上都有使用。但是每一门语言能流行起来,肯定是历史上在某个应用领域被广泛使用过,了解这方面,对于提供学习这门语言的动力,以及学习过程中做一些例子程序,都是挺重要的。以 Lua 语言为例,它常常被用在游戏领域,作为游戏玩法逻辑的开发语言所使用,譬如一些游戏的 Mod 就可以用 Lua 来写:《饥荒》的插件、《魔兽世界》的插件。国内很多游戏公司也搭配 Lua 和 Unity 引擎来做手机游戏等等。

虽然本文用 Lua 语言作为例子来试图说明,如何学习一门编程语言的思路,但本文并不是一篇 Lua 语言的教程,因为这类的书和网站已经很多了。本文不会去完整介绍这门语言的细节,而是会去试图把一门语言要学习的内容,整理成几个“模块”。每个“模块”除了会介绍 Lua 在这个部分的一些定义以外,更重要的是介绍为什么会有这些“模块”,以便读者可以有目的性的学习。一门语言的细节非常多,一下子全部记住是不容易的,但是你如果知道了一门语言有哪些功能,一般存在于哪些“模块”中去定义,那么只需要在用的时候,去查查手册或者网上搜索一下就好了。

为什么我要选择 Lua 语言为例子来写这篇文章呢?原因是作为一门脚本语言,Lua 包含的功能和内容相对比较简洁,学习难度比较少。在游戏领域应用较多,也是一个原因。

Lua 的其他优点,包含:运行速度快,脚本解析库小,很适合用来嵌入在其他一些系统中。它的解析器本身是 C 语言写的,所以可以很好的和 C 语言代码交互,而大多数其他语言,都可以运行 C 编写的库,所以 Lua 也能比较容易的被运行起来。

基本语法

任何一门语言,都会有一些最基本的语法单位组成。大部分的语言,在基本语法方面,都是极为相似的。

HelloWorld 学什么

每一门语言学习的第一课,往往都是写一个 HelloWorld 程序。那么,我们看到一个 HelloWorld 程序的时候,除了对这么语言有个大概的印象以外,我们还需要注意什么呢?以下四个方面是我们需要注意的:

  1. 代码单元。这门语言是否对于“一段”代码有什么规定?在很多语言中,都需要把代码放在一个 main() 函数中才能运行。

  2. print()。HelloWorld 要能打印出来,必须要有一个打印的函数,这个函数是否需要一些“引用”到本程序里的代码,表明了这门语言在“模块管理”方面的一些定义。

  3. 注释。很多程序还会带有一行或者多行的注释,可以了解注释的写法,会便于读懂很多教程上的代码例子。

  4. 运行方法。我们需要了解需要经过怎样的步骤,才能将一段程序源码在计算机中运行起来。这将是之后学习编程最常进行的操作。

我们可以看一个 Lua 的 HelloWorld 程序:

-- 本段代码存入文件 helloworld.lua --[[ 本代码用于《如何快速学会一门计算机语言》的例子 ]] print("HelloWorld")

虽然这个代码很简单,但是也可以了解很多东西:

  1. Lua 语言没有 main() 函数。Lua 代码可以简单的一条条直接运行,而不需要用个什么东西包起来。这方面 PHP/Python 以及大部分脚本语言都是这样的。原因是脚本语言一般不需要先从一个源码文件进行编译,而是可以直接用这门语言的解析器去“运行”这个源码文件就可以了。还有一个值得注意的是,Lua 的每行程序不需要用什么符号进行结尾,每个自然的换行就是一行代码。C 语言必须要用 ; 分号来结束一行代码,Go 语言是加分号也可以,不加也可以。

  2. print() 函数不需要先“引用”或者“导入”什么库,说明 Lua 是有一个默认的标准库,函数直接使用就可以了。但是也要注意,自己定义函数的时候,要防止不要和这些默认的标准函数重名了。另外用双引号表示一段字符串内容,也是很多其他语言的惯例,说明 Lua 语言也可以使用这种字符串数据。

  3. 符号 -- 用来作为一行注释的开头,和很多语言用 // 或者 # 不一样,这个需要注意。而且 Lua 的多行注释,是用 --[[]] 来包括起来的(类似 C/JAVA 的 /**/),这个是比较特别的。原因是在 Lua 中 [[]] 本身是用来定义一个多行的字符串常量的,所以前面加一个 -- 刚好也就是注释掉的意思。所以你不能在多行注释里面,再写一个 --[[ ... ]],这样两个 ]] 的范围就冲突了。

  4. 运行方法很简单:lua helloworld.lua。在你的计算机上,你要先安装好 lua 这个解析器程序,其实就是一个命令行程序。而很多程序,都可以通过调用 lua 的 C 库,来执行一个写了 lua 源码的字符串——这些 lua 源码字符串,完全可以从文件或者网络读进去。和 Python 类似,lua 也可以用“交互式”的方法运行:不带参数的执行 lua 解析器程序,你就可以一行一行的输入 lua 代码来运行,非常适合学习。

其实通过一个 HelloWorld 的学习,已经可以掌握了这门语言相当多的内容。Lua 具备一个典型脚本语言的特征:没有 main() 函数、代码不用结束符、有默认标准库、直接解析运行。

但是如果没能理解上面这些说法也不要紧,只要把这个程序运行起来,就能开展后续的学习了。毕竟学习编程最重要的是动手操作。

变量、常量、运算符

编程语言最基本的能力,就是处理数据。因此数据如何在这门语言中表达,是最基本的定义。一般来说,编程语言至少要提供一些基本种类的数据的表达方法,他们包括数字、字符串、布尔值(表示 Yes 和 No)等等,有些语言对于数字还分为整数和浮点数(带小数的数字)。每一个种类的数据,一般会通过定义一种“变量类型”来代表这类数据的操作方法,一般包括以下一些定义:

  1. 定义有些什么样的值,譬如最大值最小值;以及可以用什么样的代码写法(常量)来表示这些值

  2. 定义了支持什么样的运算符,譬如加减乘除、与或非、连接和数数量、左移右移取反等等;

  3. 定义了用多大的内存以及何种二进制编码格式来存储。但这方面内容一般不需要开发者关心,除了……C语言。

这些数据,计算机语言都需要用一些变量来装载它们,才能真正的用于编程,因为变量往往代表了内存,数据在内存中才能被计算机处理。不同的编程语言,会把变量设计成“强类型”或者“弱类型”:

  • “强类型”的语言,定义变量的时候必须要设置好它是数字类型,还是字符串类型或者其他什么类型的,不同类型的数据是不能放入此变量的,这样的好处是编写程序不太容易出错,计算机的运行速度也比较快。

  • “弱类型”的语言,变量不需要定义数据类型,程序员可以随便把任何数据赋值给一个变量。显然这样代码写起来更轻松,但是可能会拖慢程序的运行速度。

Lua 就是“弱类型”的语言,因此我们不需要定义变量的类型。但是数据(值)还是有类型的,或者你可以理解变量的类型,就是它的值的类型。以下是 Lua 中基本的值的类型。

  1. 数字。Lua 中不分整数和浮点数,数字常量的写法支持常见的十进制数、十六进制数、小数、科学计数法。如:num = 3.1415926 或者 num = 0xFE。Lua 中的数字的写法非常的自由,基本上没有太多需要记忆的东西。

  2. 字符串。用双引号或者单引号都可以,如果字符串本身里面有双引号,那么就用单引号来定义,非常方便。还可以用 [[]] 来定义多行字符串(类似的 Python 也有),比较方便直接拿 Lua 源码来记录数据。有些程序就直接用 Lua 源文件作为配置文件用。和其他很多语言类似,你可以用 \n \x2E 这些写法来输入一些键盘没法输入的字符,每个 \ 输入一个字节的内容。

  3. 布尔值。很简单,就是 truefalse。Lua 大小写区分,所以记得全小写。

  4. 空。这个类型很特别,只有一个值:nil。所有 Lua 变量在赋值之前,都是 nil 值,判断一个变量有没有被用过,就是是判断是否 nil。在 if 等条件判断语句里,nil 和 false 是等价的。

有了各种类型的值和变量后,这些变量和值就可以和运算符组合在一起进行计算。不同类型的值使用的运算符也有所不同:

  1. 数字运算符:基本上就是常见的加减乘除 + - * /,取余 %,幂运算 ^,这几个和大部分其他语言都类似,还有一个整除//,因为 Lua 没有分整数和小数,所以需要这个专门的运算符。另外条件运算符等于、大于、小于、大于等于、小于等于 == > < >= <= 这些也很常见,要注意的是“不等于”符是 ~=,这个和其他语言不太一样。

  2. 字符串运算符:就三个,一个是连接 ..,别以为是 + 了,专门为了和数字区分开;另外一个是 #,用来返回字符串长度,这个长度是按字节长度算的,如果存的是 GBK 编码的中文,一个汉字的长度是 2,如果是 UTF-8 编码中文,一个汉字的长度是 3。所以这个东西对于中英文混排的字符串,不是很适合计算多少个“字”,只能算个内存占用长度。第三个 == 也是可以用于判断字符串是否相等的。

  3. 布尔运算符:and or not 三个用文字来写的布尔运算符,含义也很清晰。有些语言会用一些符号而不是文字,我觉得还是用文字来的比较清晰一点。

  4. 空类型值 nil 的运算符,常见的就是比较的 ==~= 了,其实可以完全不用,因为它就是 false 的意思,用 and or not 直接判断即可。

以上就是 Lua 语言最基本的数据类型和变量的定义了,这些和 PHP/Python/JavaScript 以及其他语言都是类似的。只需要注意一些不一样的地方,譬如:

  • “不等于” ~=

  • 空值写成 nil

  • 有个专门的整除运算 //

  • 字符串连接用 ..

  • 获得字符串的长度用 #

流程控制

所有的计算机语言几乎都支持顺序、分支、循环这三种运行代码的流程控制。从代码的直观角度来看,这部分的关键字更容易被看见。

顺序

基本上就是从上往下一行行的运行,还没见过从下往上运行的,虽然理论上也是可以的。Lua 每个回车就算一行代码,不需要用分号结尾。

分支

所谓分支流程,就是按照一个表达式,决定运行两段代码的其中一个。这就需要一个“判断表达式”和两端代码的写法。Lua 使用 if ... then 中的代码作为表达式,计算出 true 或者 false,用于决定后面两个代码段。这两个代码段用 then else end 区分开。如果你想要在表达式上加上括号 (...) 其实也可以,只不过这对括号其实是数学的优先级运算符号而已。至于代码段的划分,C/JAVA 等语言普遍用大括号{...},Lua 就没有遵循这种传统,而是用的 then...else...end 的文字标识符,似乎这样代码可读性好一点。Pascal 语言中的代码段是用 begin...end 代替大括号来标注的,Lua 则只有 end 没有 begin,是比较特别的。

if num > 2 then print("num>2") else print("num<=2") end

循环

和分支类似,循环也是需要一个表达式来判断循环是否继续,然后标注一段代码重复运行。Lua 的循环有三种写法,这和很多其他语言类似。虽然这三种写法,实际上是可以互相“转换”的。不过不同的写法都适应了一些对应的循环场景。由于循环的代码比较容易让人迷糊,所以根据不同场景选择不同的写法,能够减轻一些对代码理解的负担。

第一种,最万能的循环写法:

a=10 while( a < 20 ) do print("a 的值为:", a) a = a+1 end -- 运行 10 次的循环

while(...) do ... end 的循环非常简单,就是根据括号里表达式的计算,决定是否运行 do...end 里面的代码。和 if 有点像,只不过 then 换成了 do 作为代码段的开始标志。从这里看 Lua 还真的很希望自己的代码能接近人类语言呢。

当然在循环体代码内部,也可以加入单独的 break 语句直接结束结束循环,或者使用 continue 语句跳过本次循环,继续下次循环。一般来说这两个语句都要用 if 进行控制。下面这个里就只运行了 5 次循环就退出了

a = 10 while( a < 20 ) do print("a 的值为:", a) a=a+1 if( a > 15) then break end end

continue 的例子如下,循环会运行 10 次,但是前 5 次不会打印输出。如果你不用喜欢 continue,也可以放到 if...then...end 里面去实现,只不过用 continue 的话,读起代码来,意思就是:符合这个条件的话,本次循环就结束了,后面的代码被跳过。——可能更好理解一点。

a = 10 while( a < 20 ) do a=a+1 if( a > 15) then continue end print("a 的值为:", a) end

第二种常见的循环是著名的 for 循环。

这种写法一般用于对数组等一批数据进行遍历处理。for 后面的表达式,一般都是直接指定了循环的次数,并且在每次循环中可以获得一个“循环变量”,这个变量的内容随着循环的运行呈规律性变化。Lua 同样有这样的语法:

for i=0,9,1 do print(i) end

上面的例子会运行 10 次循环,输出从 0 到 9 的九行数字。这里用逗号隔开了三个部分,第一个部分 i=0 表示在循环体中,可以使用的循环变量为 i,初始值为 0;第二部分 ,9 表示如果 i 的值到达 9 以外的话,循环结束;第三部分 ,1 是可选的,表示每次循环结束,给 i 加上 1,当然你也可以用 -1 表示减少到某个值。虽然很多语言都 for 循环,但是 Lua 的 for 循环还是比较特别的,它执行的一个“某变量变化到某个值”的逻辑。所以这个循环变量必须是数字,而很多其他语言可以随便选,因为那些语言判断循环结束是根据一个逻辑表达式,如大于、小于这种。而 Lua 的 for 循环,并不会每次循环都计算一下 for 后面的某个部分,而是一开始就一次性定死了运行的次数,以及每次运行循环的循环变量的值。从某种意义上来说更死板,但是也更不容易出错,这可以让 for 循环更接近其设计初衷:遍历某个固定长度的数据集合。Lua 的 for 为了遍历数据集合,还有另外一种写法:

a = {"one", "two", "three"} for i, v in ipairs(a) do print(i, v) end

上面这个例子会打印出

1 one 2 two 3 three

这个例子中我们看见是对一个“数组”的遍历,通过 for 。。。 in ipairs() 的写法,每次遍历提取这个数组的索引i和内容v,是很方便的写法。

Lua 的索引是从 1 开始的,不再是从 0 开始数数了!

第三种就是 repeat ... until 循环

这种写法其实和 while ... do 循环几乎一样,只不过循环条件判断,是在循环体代码段运行之后,也就是说,这个循环体代码,至少会运行一次。需要注意的是,unitl() 里面的表达式是退出条件,而非继续循环的条件,until(false) 会一致循环下去。下面的例子循环体会运行 6 次:

--[ 变量定义 --] a = 10 --[ 执行循环 --] repeat print("a的值为:", a) a = a + 1 until( a > 15 )

除了用来写“至少运行一次”的循环,这个写法我们还可以用来做一些简单的“短路”逻辑。譬如我们有一个功能,需要先判断多个条件,要通过这些所有的判断后,才执行最后的操作。如果我们用 if...then...else 来写,会嵌套很多层。但是用 repeat...until 来写就会简单很多:

-- 假设我们需要判断这个变量 count count = 10 result = 0 repeat -- 判断是否为 0,因为后面要除这个数 if count == 0 then break end -- 判断是否为负数,我们不想处理负数 if count < 0 then break end -- 计算除了多少份 result = 1000/count until(true) -- 立刻退出循环 -- 打印结果 print(result)

上面这个代码,写了两个 if 判断,用简单的 break 就可以“跳过”最后的处理。由于 until(false) 导致只能运行一次,所以也不会真正的成为“循环”。

流程控制是每一门语言都必须有的部分,这三种流程控制的语义,几乎能完成所有的功能。分支流程控制对于绝大多数语言来说,都是类似的。循环的三种写法:标准 while、遍历 for、至少循环一次 repeat,也是大多数语言都有对应的写法。所以这个部分基本上主要是注意一下关键字有没有什么特别的就好。Lua 在这里还有一个特别之处,就是关于“代码段”的标记,不是好像 C 语言那些使用 {...} 大括号,也不是 Python 这种用缩进,而是根据流程控制的语句,使用不同的“开始标志”:then/do/repeat,以及使用“结束标志”:end/until()

函数

函数一般来说是代码的可复用的容器,我们可以把一块代码“放在”函数里面,方便反复运行。很多语言函数都是最重要甚至是唯一的代码“容器”。对于 Lua 来说,虽然我们不需要把所有代码都放到某个函数里面,但是我们依然可以定义函数。函数一般包含两方面的内容:

  • 定义:代码段、参数、返回值

  • 调用方法

我们来看看一个 Lua 函数的例子

--[[ 函数返回两个值的最大值 --]] function max(num1, num2) if (num1 > num2) then result = num1; else result = num2; end return result; end -- 调用函数 print("两值比较最大值为 ",max(10,4)) print("两值比较最大值为 ",max(5,6))

对于 Lua 来说,定义一个函数,需要用关键字 function 作为开头,然后跟着名字、参数列表 name(param),然后紧跟着下一行就是函数代码段了。这里不像其他的语言有一个代码段的“开始标志”,而是直接就开始了。最后函数代码段以关键字 end 结尾。由于 Lua 是一门“弱类型”语言,所以函数的参数也不需要写类型,只要把参数名字写上就好了。同样的,Lua 的返回值也不需要把类型声明到 function 那一行,直接在函数体里面用 return 去返回就好了。

对于函数来说,Lua 还支持两个特别的写法:可变参数、多返回值。这两个写法在某些情况下,能很方便的编写出容易使用的函数。

可变参数

所谓“可变”参数,意思是这个函数的参数个数是不确定的。这种设计很方便开发者用来输入多个数据,然后集中处理。C语言里面著名的 printf() 函数就是一个可变参数函数。同样 Lua 也可以这么做。说起来,好像大多数的语言都有这个能力呢。

function average(...) result = 0 local arg={...} --> arg 为一个 table 类型变量,把所有参数都装进去 for i,v in ipairs(arg) do --> 这里进行所有参数的遍历 result = result + v end print("总共传入 " .. #arg .. " 个数") return result/#arg --> #arg 返回这个表的成员个数 end print("平均值为",average(10,5,3,4,5,6))

上面的例子中,我们用了 Lua 的遍历一个 Table 这种数据集合(后续会专门介绍)的写法:for...in ipairs(),而被遍历的对象,就是可变参数,虽然它看起来只有三个点 ...。虽然大部分情况下,我们通过 {...} 都可以把可变参数作为一个 Table 来使用,但是 Lua 也提供了几种特别的使用 ... 的写法:

  1. 可以用来把全部的可变参数,以 ... 的写法直接调用另外一个可变参数函数,如 string.format(fmt, ...)

  2. select('#', …) 返回可变参数的长度

  3. select(n, …) 用于返回从起点 n 开始到结束位置的所有参数列表。

多返回值

大部分语言的函数返回值,都只能有一个。但是一个函数的功能,可能会不止返回一个数据结果,譬如最常见的是,需要返回一个结果和一个错误码——很多函数运行过程中都需要把出现的各种错误(异常)情况返回给调用者。所以每一种不支持多个返回值的语言,几乎都有一套自己的玩法来做这个事情。譬如 C 语言,就常常把返回值设计成 int,如果返回 0 表示成功,其他值表示失败,这样用 if(some_fun()){...} 的写法就可以直接写错误异常处理了,但是这种情况下就需要设计所谓“输出参数”,让使用者直接先准备一个存放结果的变量,通过参数传入这个函数。而 Java 语言则旗帜鲜明的反对“输出参数”,一来是 Java 不需要程序员自行管理内存,二来是 Java 有“异常”机制:函数可以返回一个对象作为返回值,并且可以抛出一个异常,来表示函数运行错误。Go 语言是支持多返回值的,这个事情就变得简单了,很多函数都会返回两个返回值,一个是正常的结果,另外一个是 error 类型的错误变量,通过判断这个错误返回值是否 nil,就可以进行错误处理。

对于 Lua 来说,由于支持多返回值,你可以学习 Go 的传统,让每个函数都有最少两个返回值,也可以用其他的方法来编写函数。但是至少你有多返回值这个能力,很多情况下都是很方便编程的。

-- 一个会返回“除0错”的除法函数 function devide(a, b) if b == 0 then return 0, "Division by zero"

end return a/b, "OK" end -- 调用函数 result,err = devide(10, 0) if err ~= "OK" then print("Error:", err) else print("Result:", result) end

这个例子我用了一个字符串来代表错误,其实是不够严谨的,但这只是一个例子。你可以用一个数字来表示错误码,然后建立一个错误码的 Table 来组织所有的错误码提示文字。

总结

看到这里,我们可以发现 Lua 真的是很不喜欢括号呢——不但不用大括号作为代码段的标记,也很少使用小括号用来做分支、循环判断表达式的标记,只在函数的参数列表上用了括号,估计这也是为了遵循“函数”这个数学传统。这一点和 Python 真的有点像,但又没有 Python 那样“强迫症”一样要求开发者进行代码缩进对齐。

如果了解了以上的内容,其实已经基本可以用 Lua 来做一些简单的事情,譬如编写配置文件。这一部分实际上和很多其他语言,都非常的相似。但是我们还有一个很重要的内容,也是 Lua 的其中一个特色内容没有涉及,就是 Lua 自带的一种通用数据结构:Table。下一篇我们会从 Table 入手,逐步探索编程语言的一些高级语法。