Lua学习笔记--模块与包

Lua 模块与包 模块就是一些代码(可以是 Lua 编写的,也可以是 C 语言编写的),这些代码可以通过函数require加载,然后创建和返回一个表,这个表就类似命名空间。所有的标准库都是模块,例如mathstring模块使用表来承载模块,有很显著的优点,可以像操作普通表一样操作模块,而且能利用 Lua 语言的所有功能实现额外的功能。例如引入math模块-- 两种书写方式都可以使用-- sin 使用的是弧度,不是角度也可以直接引入模块中的一个函数,例如以下代码。 阅读详情

学习完了函数,那么,一堆函数就成了一个模块,一堆模块就是一个包。今天来学习一下怎么写一个模块和怎么调用模块。


一.简介

Lua的感觉就是简洁,自由,一个万能的table可以搞定所有的事情。Lua从5.1开始提供了require(用于加载模块)和module(用于创建模块)的两个函数增加对模块的支持,当然,不使用这两个关键字也是可以用table自己实现模块加载的。
模块就是一个程序库,可以通过require加载,然后我们就得到了一个全局变量,是一个包含这个模块所有内容的table。其实感觉require就像#include一样,将我们需要的内容包含进来。先看一个简单的例子:
--加载另一个模块
require "mod"
--调用另一个模块中的func函数
mod.func()
当然,还可以把模块设置成一个较短的名称,供局部使用,或者设置模块中的某个函数为较短的名称:
--设置模块的局部名称
local m = require "mod"
--设置模块函数局部名称
local f = m.func
--使用
m.func()
--或者
f()

二.关于全局变量

我们之前用的变量,如果没有加local关键字,那就是全局变量。Lua将所有的全局变量都保存在了一个常规的table中,这个table称为“环境”,这个table被命名为_G。这种实现方式一方面不需要再额外为全局变量创建数据结构,另一方面,我们也可以像操作普通table那样操作全局的table。
一个例子,看一下_G中都有神马鬼,lua代码如下:
--定义一个全局变量
a = 100;
--自己定义全局函数
function testfunc()
	print("hehe")
end
--输出Lua中的全局变量
for n in pairs(_G) do
	print(n)
end
结果:
ipairs
load
package
require
coroutine
select
pairs
rawlen
tonumber
assert
type
os
io
math
debug
next
setmetatable
error
rawset
tostring
dofile
_VERSION
getmetatable
print
table
rawequal
string
rawget
_G
testfunc
pcall
utf8
xpcall
loadfile
a
collectgarbage
请按任意键继续. . .

我们自己定义了两个全局变量和函数,不写local就是全局的,然后打印所有的全局变量,发现这两个也被打出来了。其他的就是偶们熟悉的比如tostring等等方法。但是,这样的话,如果我们的程序比较大,那么就会特别凌乱。大家都是全局的,像找一个函数也比较费劲,还可能有命名冲突,这时候,模块就该登场了。


三.编写一个简单的模块

最简单的方式,就是建立一个全局的table,然后编写一堆函数,添加到这个table中去,然后在模块的最后把这个table返回,例如:
--定义一个模块

--模块名
func = {}

--模块内部的内容加上模块名
function func.test1()
	print("test1 func!")
end

function func.test2()
	print("test2 func!")
end

func.num = 10

--最后需要将模块名返回
return func 
这样,一个最简单的模块就编写好了,我们使用的时候,只需要加载这个模块就可以了。但是,这个模块的设计有很多很多缺陷,不过作为测试暂时够了,先看看怎么使用这个模块。

四.使用一个模块

加载一个模块还是相对比较简单的,通过require就可以搞定一切。就是require "模块名",其实require就是负责一个加载的过程,关于table等都是模块内部写好的,并且返回给require。

关于require的详细操作:

function require (name)
    if not package.loaded[name] then<span style="white-space:pre">	</span>--判断模块是否已经加载了,避免重复加载
        local loader = findload(name)
        if loader == nil then
            error("unable to load module " .. name)
        end
        package.loaded[name] = true --避免递归加载时死循环。
        local res = loader(name)<span style="white-space:pre">	</span>--初始化模块
        if res ~= nil then
            package.loaded[name] = res
        end
    end
    return package.loaded[name]
end
分析一下上面的操作:
1.当我们给出模块名后,Lua并不是无脑的去加载,而是先去package.loaded这个table中查看,如果这个表中已经有了这个模块,那么就不会重复加载这个模块。然后通过loader加载相关模块。如果我们想要强制重新加载一个模块,那么需要先将其删除,即package.loaded["mod"]=nil,然后再重新require即可。
2.关于findload,因为Lua支持Lua本身加载模块,也支持加载C程序库的模块,加载Lua模块使用loadfile,加载C程序库使用loadlib。注意,这一步lua并不会运行这些模块,仅仅是加载。
3.如果开始加载了,就将package.load[mod]置为true,防止有关联加载时,返回又加载本模块,导致死循环。
4.加载完之后,会将package.loaded[mod]置为加载的模块名,注意这个地方,如果加载器中没有返回模块名,即我们写模块的时候没有返回名字,那么就返回package.loaded中的值。

使用一下上面编写好的模块:

模块文件func.lua
--定义一个模块

--模块名
func = {}

--模块内部的内容加上模块名
function func.test1()
	print("test1 func!")
end

function func.test2()
	print("test2 func!")
end

func.num = 10

--最后需要将模块名返回
return func 
测试文件test.lua:
local m = require "func"

--使用模块中的内容
m.test1()
m.test2()
print(m.num)

print("\n")

--再看一下全局变量的情况
for n in pairs(_G) do
	print(n)
end
结果:
test1 func!
test2 func!
10

setmetatable
rawget
next
tostring
getmetatable
load
rawset
pairs
pcall
os
coroutine
xpcall
dofile
print
select
type
table
tonumber
func
error
debug
utf8
math
ipairs
loadfile
rawlen
string
collectgarbage
_VERSION
rawequal
io
package
require
_G
assert
请按任意键继续. . .

可见,我们使用了另一个模块中的内容,实现了功能的模块化。而且,这次的全局变量中只有func一个,即我们这个模块,不会再造成特别麻烦的命名冲突等的问题。

关于模块路径的问题:

还有一个很关键的问题,我们写好了一个模块,要放在哪里才能被Lua加载呢?按照国际惯例,放在当前路径是肯定可以的,刚才的例子就是将test.lua,func.lua都放在了编译出来的Lua解释器的路径下的。
搜索一个文件时,在windows上,很多都是根据windows的环境变量path来搜索,而require所使用的路径与传统的路径不同,require采用的路径是一连串的模式,其中每项都是一种将模块名转换为文件名的方式。require会用模块名来替换每个“?”,然后根据替换的结果来检查是否存在这样一个文件,如果不存在,就会尝试下一项。路径中的每一项都是以分号隔开,比如路径为以下字符串:
?;?.lua;c:\windows\?;/usr/local/lua/?/?.lua
那么,lua就会尝试加载下面的内容:
mod
mod.lua
c:\windows\mod
/usr/local/lua/mod/mod.lua

那么,这样,我们就知道Lua加载的默认规则了,但是,万一我们想把Lua的东东全都放在一个目录里面,想改一下这个默认的设置,要怎么办呢?
上面的那一串内容存储在package的一个字段中,而且,Lua对于Lua模块和C模块是分开的,Lua的路径在packet.path中,C的在packet.cpath中。我们打印一下瞧瞧:
--输出Lua默认的加载搜寻路径
print("Lua:", package.path)
--输出C默认的加载搜寻路径
print("C:",package.cpath)

结果:
Lua:    C:\Users\puppet_master\Desktop\LuaTest\Lua Scrip\lua\?.lua;C:\Users\puppet_master \Desktop\LuaTest\Lua Scrip\lua\?\init.lua;C:\Users\zhangjian \Deskto
p\LuaTest\Lua Scrip\?.lua;C:\Users\puppet_master\Desktop\LuaTest\Lua Scrip\?\ini
t.lua;C:\Users\puppet_master\Desktop\LuaTest\Lua Scrip\..\share\lua\5.3\?.lua;C:
\Users\puppet_masterDesktop\LuaTest\Lua Scrip\..\share\lua\5.3\?\init.lua;.\?.l
ua;.\?\init.lua
C:      C:\Users\puppet_master\Desktop\LuaTest\Lua Scrip\?.dll;C:\Users\puppet_master\Desktop\LuaTest\Lua Scrip\..\lib\lua\5.3\?.dll;C:\Users\puppet_master\Desk
top\LuaTest\Lua Scrip\loadall.dll;.\?.dll
请按任意键继续. . .

好大一串,那么,我们如果想改的话,要在哪里改呢?当然好改了,我们有Lua的源码啊,想改什么改什么。在luaconf.h中的LUA_PATH中定义了这个路径:
#define LUA_PATH_DEFAULT  \
		LUA_LDIR"?.lua;"  LUA_LDIR"?\\init.lua;" \
		LUA_CDIR"?.lua;"  LUA_CDIR"?\\init.lua;" \
		LUA_SHRDIR"?.lua;" LUA_SHRDIR"?\\init.lua;" \
		".\\?.lua;" ".\\?\\init.lua"
#define LUA_CPATH_DEFAULT \
		LUA_CDIR"?.dll;" \
		LUA_CDIR"..\\lib\\lua\\" LUA_VDIR "\\?.dll;" \
		LUA_CDIR"loadall.dll;" ".\\?.dll"
如果想要进行修改就可以改啦。


关于模块名字和模块文件名的问题:
这个我感觉还是一样的好。省去不少麻烦,还好记。

五.编写一个真正的模块

上面编写了一个测试是模块,但是这个模块漏洞百出,非常不严格。下面,逐步修改一下这个模块:

为了方便修改模块名称:

上面的那个模块,直接定义了一个全局的变量,为了防止冲突,这个全局变量必定比较长,模块中每个东东都要加上这么长的一个前缀显然不合适,更要名的是,入过我想改掉模块的名字,那么,所有的东东的名字就都得重新写,这简直是灾难。所以,一种偷懒的方法油然而生:
--定义一个模块

--本地模块名
local M = {}
--真正的模块名
func = M


--模块内部的内容加上模块名
function M.test1()
	print("test1 func!")
end

function M.test2()
	print("test2 func!")
end

M.num = 10

--最后需要将模块名返回
return func 
这种方法就是用了一个本地的,简单的变量,操作本地的内容,将内容都创建好了,把这个table的引用再给真正的全局引用,然后返回真正的模块名即可。

如果模块名和模块所在的文件不同名时,会发生什么?

我也不知道,事实胜于雄辩:

func.lua文件:
--定义一个模块

--本地模块名
local M = {}
--真正的模块名
mod = M


--模块内部的内容加上模块名
function M.test1()
	print("test1 func!")
end

function M.test2()
	print("test2 func!")
end

M.num = 10

--最后需要将模块名返回
return mod 
test.lua文件:
--require 必须跟模块文件名,直接给模块名找不到的
--这里的文件名不考虑后缀的问题,lua会自动补全后缀
require "func"
for n in pairs(_G) do
	print(n)
end
结果:
xpcall
collectgarbage
utf8
require
next
select
package
rawset
error
ipairs
tostring
getmetatable
rawequal
type
dofile
table
os
print
coroutine
string
setmetatable
load
pairs
rawlen
mod
_G
debug
loadfile
pcall
io
assert
tonumber
math
_VERSION
rawget
请按任意键继续. . .

例子中,模块名为mod,模块文件名为func,require时跟的是文件名,但是加载出来之后仍然是模块原来的名字。所以,这样搞乱七八糟,一般而言,一个文件作为一个模块再好不过,所以文件名和模块名还是一致为好,否则加载的名字和使用的名字不一样,蛋疼得要命。


通过改文件名改模块名:

既然一个模块就是一个文件了,就更想偷懒了,当我们想改名字的时候,改文件名就可以改掉模块名,那该多好啊。下面是一个例子:
--定义一个模块

--由于require函数会将需要的模块名传递给该函数,所以...就代表了模块名
local modname = ...
--本地模块名
local M = {}
--真正的模块名直接祖册到全局变量中
_G[modname] = M


--模块内部的内容加上模块名
function M.test1()
	print("test1 func!")
end

function M.test2()
	print("test2 func!")
end

M.num = 10

这样,require这个模块的时候,模块名会传递给...,这个就作为模块名,就是文件名。所以,我们就不需要自己定义文件名了。








详细Ubuntu 22.04 配置最新UR机械臂仿真环境(ros2-humble, ur-sim, ur-ros2-driver) 使用ubuntu22.04.01和ur-sim, ur-driver进行ur机械臂的仿真 阅读详情

相关推荐

使用moveit!控制真实机械臂(5)——编写真实机械臂节点中的action服务程序

上一篇文章中,我们简要介绍了ROS中action的基本知识,为了控制真实机械臂,我们已经修改了moveit配置文件,使得moveti启动后具备一个control_msgs::FollowJointTrajectoryAction类型的action客户端,接下来,我们完成该类型action的另一半,编写C++程序来实现action的服务端。由于底层机器人控制程序一般差别都很大,这里展示的只是编程要点...

爱学习的草莓熊 1万+

Lua 程序设计:模块与包

文章目录模块与包require 函数搜索路径编写模块的基本方法使用环境子模块与包 模块与包 从用户观点看,一个模块就是一些代码(Lua 或 C 代码),可以使用 require 函数加载这些代码,然后创建并返回一个 table 。这个 table 就是一个名称空间(namespace),其中包含模块导出的所有东西,比如函数和常量。 例如,所有的标准库都是模块。可以以如下的方式使用数学库: local m = require "math" print(m.sin(3.14)) 然而,Lua 会以如下的方式预

alenliu's blog 1870

告别交越失真:手把手教你设计高性能互补输出级电路(LTspice实测)

本文深入剖析音频放大器中常见的交越失真问题,并提供基于LTspice仿真的实战解决方案。通过对比二极管偏置、Vbe倍增器和集成运放驱动三种经典方案,手把手指导设计高性能互补输出级电路,涵盖原理分析、仿真验证及实物调试要点,助力消除音频失真,提升音质。

weixin_29190573的博客 551

014-Lua 模块与包详解

我们继续深入探讨 Lua 模块与包的更多高级应用,包括模块的性能优化、内存管理、依赖管理、动态模块生成,以及如何在复杂项目中实现模块化架构。,包括更复杂的场景、社区工具、跨平台模块开发,以及 Lua 模块在大型项目中的实际应用案例。的更高级概念,包括模块的使用技巧、包管理工具、模块依赖处理、以及如何在大型项目中组织模块与包。模块允许开发者将代码封装成独立的单元,方便复用和管理,而包是模块的集合,用于实现更复杂的功能。Lua 的垃圾回收机制会自动管理模块的内存,但在某些情况下,需要手动优化模块的内存占用。

小宝哥Code的专栏 1673

模电学习笔记(十二)——跨阻放大器

模电学习笔记(十二) 跨阻放大器 跨组运算放大器是将电流信号转换成为电压信号,电流到电压增益基于反馈电阻。 设计目标: 输入 输出 带宽 电源 0A 100uA 0V 7V 20kHz 15V -15V 1.计算R1的值(增益电阻器) 2.计算C1的值满足电路带宽 ...

tang11081023的博客 2万+

2025使用VM虚拟机安装配置Macos苹果系统下Flutter开发环境保姆级教程--下篇

我们可以看到,目前配置基本完成了,现在有两个错,一个是Chrome没安装,这个自己安装就可以了,如果不需要WEB测试也不影响开发,另一个是Simulator还没安装,这个主要是安装的时间太长了,也就是上面的这一步。这里要注意,这个下载安装会比较慢,有的载会特别卡造成安装失败,最好的办法是挂一个VPN,保证安装完成,不过这个可以在以后单独进行安装。至此,基本的安装完毕,好多人会以为Xcode就OK了,其实还没有,我们还缺少命令行工具包,这个在安装完Flutter以后进行设置。

weixin_48803304的博客 1411

lua 13 模块(类似class)与包

转自: http://www.runoob.com/lua/lua-modules-packages.html 模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。 Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块...

288

C# 与蓝牙 BLE 设备通信:基于 Windows.Devices.Bluetooth 实现手环/传感器数据读取

本文介绍了如何利用C#的Windows.Devices.Bluetooth命名空间实现与蓝牙BLE设备的通信。首先概述了BLE技术特性及其在物联网中的应用场景,包括智能穿戴设备和传感器。随后讲解了BLE的GATT协议架构,解析了服务(Service)和特性(Characteristic)的概念。文章重点展示了通过C#实现设备扫描、连接、数据读取的完整代码示例,并详细说明了如何使用通知(Notifications)功能实现实时数据更新。最后提供了订阅心率测量特性变化的实现代码,为开发者提供了完整的BLE设备通

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 1321

Lua 基础教程(十四)模块

模块就像一个库,可以使用require加载,并有一个包含表的全局名称。模块可以由许多函数和变量组成。所有这些函数和变量都包装在表中,表充当命名空间。此外,行为良好的模块有必要的规定,根据需要可返回此表。

m0_65012566的博客 294

PCL 使用RANSAC拟合平面【2026最新版】

RANSAC拟合平面算法原理和实现流程详解以及最大迭代次数和内点期望概率的设置方法。最后再对拟合后的平面法向量与向量(1,1,1)进行定向。博客长期更新,最近一次更新时间为:2026年4月24日。

点云侠的博客 42万+

IgH EtherCAT 从入门到精通:第 5 章 多主站与系统集成配置

本文介绍了 EtherCAT 多主站配置与系统集成方法。主要内容包括: 多主站实例配置原理:通过内核模块参数指定多个主站设备,每个主站独立管理一条 EtherCAT 总线,最多支持32个主站实例。 配置文件详解:在/etc/ethercat.conf中通过MASTER<X>_DEVICE变量定义各主站绑定的网卡或MAC地址,支持冗余备份配置。 系统服务集成:提供了systemd服务单元文件和传统init.d脚本两种方式实现开机自启动和管理,并建议通过override文件定制服务依赖关系。 架构示

从事嵌入式linux android驱动开发,拥有全栈能力 349

lua学习笔记之模块、包

模块需要通过函数require来加载,创建返回表。模块导出的所有如函数、常量作为一个工作空间。 1、require函数 require首先检查表package.loaded是否模块已经加载,如果已经加载,则返回对应的值。如果模块没有加载,require搜索模块名字对应的lua文件,搜索通过package.path来引导,如果找到文件,则使用loadfile加载,返回的结果是称为加载器的函数。如果没有找到模块名对应的lua文件,则搜索对应名字的c库文件,此时搜索是通过package.cpath变量来引导,

多看多听多总结 1185

电脑技巧:Hyper-V安装UOS操作系统图文教程

今天给大家分享微软官方Hyper-v虚拟机管理软件安装UOS操作系统图文教程,感兴趣的朋友也可以下载试一试,当然有啥问题也可以互相沟通交流!

IT技术分享社区 4705

13.lua学习笔记:模块与包

从Lua 5.1开始,我们可以使用require和module函数来获取和创建Lua中的模块。从使用者的角度来看,一个模块就是一个程序库,可以通过require来加载,之后便得到一个类型为table的全局变量。此时的table就像名字空间一样,可以访问其中的函数和常量,如: 1 require "mod" 2 mod.foo() 3 local m2 = require "mod2" 4 l

无香花自开 851

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Lua学习笔记 第十五章 模块与包

模块系统的一个主要目标是允许程序以不同的形式来共享代码。要实现共享就需要一些公共的规则。 Lua从5.1开始,为模块和包定义了一系列的规则。这些规则不需要语言引入额外的技能, 程序员可以使用table、函数、元表和环境来实现这些规则。有两个重要的函数可以很容易通过这些规则,它们是require(用于使用模块)和module(用于创建模块)。程序员可以使用不同的规则来重新实现这两个函数。 从用

wang1029的专栏 743

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

Lua学习笔记(5)----模块与包

学习传送门:https://www.runoob.com/lua/lua-modules-packages.html 1.模块 由变量、函数等已知元素组成的table,返回一个table。模块为xx.lua //文件名为classTest.lua //定义classTest模块 classTest={} //定义一个常量 classTest.constant="常量" //定义一个公有方法 function classTest.func1() print("公有函数") end //定义一个

南投北浪 300

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

Lua 学习笔记(4) -- 模块与包

从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。 创建模块 其实 Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。格式如下:

luyafei_89430的专栏 1015

Lua学习笔记-day6: 表、模块与包

一、Lua table(表) 特点: table是Lua的一种数据结构,可以用来创建不同的数据类型,如:数组(索引默认从1开始的简单的线性表),字典等。 table使用关联型数组,可以使用任何类型的值作为数组的索引,nil除外。 大小不固定。 table(表)的构造 构造器是创建和初始化表的表达式。最简单的构造函数是{},创...

HappyMrSpring 303

lua 学习笔记

lua学习笔记(入门篇·上) 最近总听人说lua 是一种小巧而且速度非常快的语言,挺有意思,不要怂就是干。 1.环境安装: 在下用的是win10,所以安装的是从Github上面下载的安装包,传送门:Lua for Windows 嗯,傻瓜式安装,安装好连环境变量都给我配好了,cmd打开,lua -i: 2.语法: 其实说白了就是两种,交互式编程和脚本式编程,交互式就不多说了,打开lu...

qq_43268015的博客 405

Lua(十三)——模块与包

Lua中的模块就是一个封装库,类似于C#的命名空间,可以把公有的变量和方法放在一个文件中,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度 一:模块的定义 两种定义模块中函数的形式,可以用匿名函数定义也可以用普通函数定义 二:模块的使用(require) 我个人理解引入模块就是引入一个类,引入这个模块(类)之后就可以使用这个模块(类)中的全局变量和全局函数例如...

L的博客 2995

Lua语言入门 - Lua 模块与包

模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。 Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。

qq_40788199的博客 962

Lua模块与包

一、模块与包的概念 模块系统的一个主要目标是允许以不同的形式来共享代码,但是如果没有一项公共的规则就无法实现共享; 两个重要的函数 module:用于创建模块,使用table来实现模块的优点在于:可以向操作普通的table那样来操作模块; require:用于加载模块,使用require加载后得到一个table,这个table就像是一个命名空间,table中的内容就是模块所导出的所有东西...

mozhi 869
上一篇: Lua学习笔记--Lua调用C初探
下一篇: 《Effective C++》读书笔记(二)
puppet_master
puppet_master 领域专家: C/C++技术领域 领域专家: C/C++技术领域
博客等级 码龄12年 2695粉丝 161原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值