Chapter 14_2 全局变量声明

  Lua中的全局变量不需要声明就可以使用。对于小程序十分方便,但是大型程序中 一处简单的笔误就可能造成难以发现的bug。

不过,这种性能可以改变。由于Lua将全局变量放在一个普通的table中,可以通过元表来改变其访问全局变量时的行为。

一种方法是简单地检测所有对全局table中不存在key的访问:

setmetatable(_G,{
        __newindex = function(_, n)
                        error("attempt to write to undeclared variable " .. n ,2)
                    end ,    --这里的逗号不能省,因为是在table构造式里哦
        __index = function(_, n)
                        error("attempt to read undeclared variable " .. n, 2)
                    end ,   --这里的逗号不能省,因为是在table构造式里哦
            }
)

执行这段代码后,所有对全局table中不存在的key访问都将引发一个错误:

print(a)        --> stdin:1: attempt to read undeclared variable a

但是该如何声明一个新的变量呢?

一种方法是使用rawset,可以绕过元表:

function declare(name ,initval)
    rawset(_G,name ,initval or false)    --这里的false确保 新变量不为 nil
end

另外一中更简单的方法就是只允许在主程序块中对全局变量进行赋值,那么当声明以下变量时:

a  =  1

就只需检查此赋值是否在主程序块中。这可以使用debug库,调用debug.getinfo(2,"S")将返回一个table,

其中的字段what表示了调用元方法的函数是主程序块还是普通的Lua函数,或是C函数。

可以通过该函数将__newindex元方法重写:

__newindex = function(t,n,v)
    local w = debug.getinfo(2,"S").what
    if w ~= "main" and w ~= "C" then  --接收main和C代码的修改赋值
        error("attempt to write to undeclared variable ".. n,2)
    end
    rawset(t,n,v)
end

这个版本可以接收C代码的赋值,因为一般C代码都知道自己是做什么的。

为了测试一个变量是否存在,就不能简单地与nil 比较。因为如果它为nil ,访问就会抛出一个错误,这时同样可以使用rawget来绕过元方法:

if rawget(_G , var ) ==nil then
    -- ‘var‘ 没有声明
    ...
end

正如前面提到的,不允许全局变量具有nil 值,因为具有nil 的全局变量都会被自动认为是未声明的。

但要纠正这个问题并不难,只需引入一个辅助table用于保存已经声明变量的名称。

一旦调用了元方法,就检查该table,以确定变量是否已经声明过:

local declaredNames = {}
setmetatable( _G , {
        __newindex = function( t , n, v)
                        if not declaredNames[n] then
                            local w = debug.getinfo(2,"S").what
                            if w ~= "main" and w ~= "C" then
                                error("attempt to write to undeclared variable " .. n, 2 )
                            end
                            declaredNames[n] = true
                        end
                            rawset(t , n ,v )    --完成实际的设置
                    end ,
        __index = function (_, n)
                    if not declaredNames[n] then
                        error("attempt to read undeclared variable " .. n, 2 )
                    else
                        return nil
                    end
                end ,
            }
)

此时,即使是下面的赋值也可以起到声明全局变量的作用:

x = nil

上述两中方法所导致的开销基本可以忽略不计;

第一种方法中,完全没有涉及到元方法的调用。

第二种方法,只有当程序访问一个为nil的变量时才会去调用元方法。

以上内容来自:《Lua程序设计第二版》和《Programming in Lua  third edition》

时间: 2024-08-07 08:39:29

Chapter 14_2 全局变量声明的相关文章

全局变量声明的规范化

Lua将环境本身存储在一个全局变量_G中,(_G._G等于_G).为了对全局命名空间更好的管理,最好是显示的声明每一个变量,可以通过使用metamethod来改变_G的行为来进行规范: -- 声明新的变量,使用rawset绕过metamethod的限制 -- 保证声明的变量不为nil,如果声明一个nil变量,等于该变量还是不存在,访问该变量的时候还是会触发__index declare = function(name, initval) rawset(_G, name, initval or f

函数(二)_全局变量声明、可变参数、关键字参数

1. global声明全局变量 #声明name这个变量为全局变量,只是写在函数里面 #写代码时,尽量不要用全局变量,会一直占用内存.       ------->{'name':'abc','sex':'29'} #字典和list,这种可变变量,不需要用global来声明,就可以直接改了 2.可变参数,参数组 #对于可变参数,非必填,没有限制参数个 name为位置参数,age为默认参数,*args为可变参数 #位置参数(必填参数),默认值参数(不必填),可变参数(不必填的,不限制参数个数) 3.

全局变量声明的规范化(转)

Lua将环境本身存储在一个全局变量_G中,(_G._G等于_G).为了对全局命名空间更好的管理,最好是显示的声明每一个变量,可以通过使用metamethod来改变_G的行为来进行规范: -- 声明新的变量,使用rawset绕过metamethod的限制 -- 保证声明的变量不为nil,如果声明一个nil变量,等于该变量还是不存在,访问该变量的时候还是会触发__index declare = function(name, initval) rawset(_G, name, initval or f

Java 全局变量 声明与定义

JAVA全局变量(或称成员变量)可分两种,一种是静态变量,另一种是实例变量,即在类体中定义的变量,有三点得注意: 一.成员变量不能在类体中先声明(定义)后赋值,但静态变量可以先在类体中声明,然后在方法中赋值(当然实例变量是不行的): 1)如以下程序会出问题:1public class Test { static int a; //在类体中声明整型静态变量a. int b; //在类体中声明整型实体变量b. a=3; //在类体中对静态变量a赋初值. b=5; //在类体中对实体变量b赋初值. p

c语言全局变量和局部变量问题汇总

1.局部变量是否能和全局变量重名? 答:能,局部会屏蔽全局.要用全局变量,须要使用"::" 局部变量能够与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量.对于有些编译器而言,在同一个函数内能够定义多个同名的局部变量,比方在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内. 2.怎样引用一个已经定义过的全局变量? 答:extern 能够用引用头文件的方式,也能够用externkeyword,假设用引用头文件方式来引用某个在头文件

局部变量,全局变量,extend,static

main.c #include <stdio.h> #include "zs.h" /* 局部变量是定义在函数.代码块.函数形参列表.存储在栈中,从定义的那一行开始作用域结束释放. 全局变量:写在函数,代码块,形参列表外的变量,从定义的那一行开始一直直到文件末尾(暂时这样认为),全局变量如果没有进行初始化是0,存储在静态区中, 随着程序的结束而结束. */ /* 全局变量分为两种: 1.外部全局变量: 可以被其它文件访问的全局变量我们称之为外部全局变量(默认情况下所有的全局

在C++工程中main函数之前跑代码的廉价方法(使用全局变量和全局函数)

[cpp] view plain copy // test.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <windows.h> #include <crtdbg.h> /// 在C++工程中main函数之前跑代码的廉价方法 /// 利用全局变量可以赋可变初值的事实 /// mainCRTStartup() => _cin

C++定义全局变量/常量几种方法的区别

在讨论全局变量之前我们先要明白几个基本的概念: 1. 编译单元(模块):    在IDE开发工具大行其道的今天,对于编译的一些概念很多人已经不再清楚了,很多程序员最怕的就是处理连接错误(LINK ERROR), 因为它不像编译错误那样可以给出你程序错误的具体位置,你常常对这种错误感到懊恼,但是如果你经常使用gcc,makefile等工具在linux或者嵌入式下做开发工作的话,那么你可能非常的理解编译与连接的区别!当在VC这样的开发工具上编写完代码,点击编译按钮准备生成exe文件时,VC其实做了两

C语言的傻瓜式随笔(二):全局变量、预编译、goto

函数的作用:可以实现代码的重用. 函数只需要定义1次,那么函数中的代码就可以随意的调用.       -某不知出处的基本概念 学而时习之,如有误笔,请指正 一.goto跳转语句 goto在C语言的作用: 可以将CPU的执行跳转到当前函数的别的地方继续执行.因为会产生代码回朔,所以极易产生死循环. 注意: 1). 标签名随意,但是要符合标识符的命名规则及规范. 标识符: 那就是程序员可以自己给名字的东西 统称为标识符. 变量名.标签名.函数名. 2). goto可以往前跳 也可以往后跳. 3).