#
原文出自程序人生 >> C++中的返回值优化(return value optimization) 返回值优化(Return Value Optimization,简称RVO),是这么一种优化机制:当函数需要返回一个对象的时候,如果自己创建一个临时对象用户返回,那么这个临时对象会消耗一个构造函数(Constructor)的调用、一个复制构造函数的调用(Copy Constructor)以及一个析构函数(Destructor)的调用的代价。而如果稍微做一点优化,就可以将成本降低到一个构造函数的代价,下面是在Visual Studio 2008的Debug模式下做的一个测试:(在GCC下测试的时候可能编译器自己进行了RVO优化,看不到两种代码的区别)
1 // C++ Return Value Optimization 2 // 作者:代码疯子 3 // 博客:http://www.programlife.net/ 4 #include <iostream> 5 using namespace std; 6 7 class Rational 8 { 9 public: 10 Rational( int numerator = 0, int denominator = 1) : 11 n(numerator), d(denominator) 12 { 13 cout << "Constructor Called " << endl; 14 } 15 ~Rational() 16 { 17 cout << "Destructor Called " << endl; 18 } 19 Rational( const Rational& rhs) 20 { 21 this->d = rhs.d; 22 this->n = rhs.n; 23 cout << "Copy Constructor Called " << endl; 24 } 25 int numerator() const { return n; } 26 int denominator() const { return d; } 27 private: 28 int n, d; 29 }; 30 31 //const Rational operator*(const Rational& lhs, 32 // const Rational& rhs) 33 //{ 34 // return Rational(lhs.numerator() * rhs.numerator(), 35 // lhs.denominator() * rhs.denominator()); 36 //} 37 38 const Rational operator*( const Rational& lhs, 39 const Rational& rhs) 40 { 41 cout << "----------- Enter operator* -----------" << endl; 42 Rational tmp(lhs.numerator() * rhs.numerator(), 43 lhs.denominator() * rhs.denominator()); 44 cout << "----------- Leave operator* -----------" << endl; 45 return tmp; 46 } 47 48 int main( int argc, char **argv) 49 { 50 Rational x(1, 5), y(2, 9); 51 Rational z = x * y; 52 cout << "calc result: " << z.numerator() 53 << "/" << z.denominator() << endl; 54 55 return 0; 56 }
函数输出截图如下:
可以看到消耗一个构造函数(Constructor)的调用、一个复制构造函数的调用(Copy Constructor)以及一个析构函数(Destructor)的调用的代价。 而如果把operator*换成另一种形式:
1 const Rational operator*( const Rational& lhs, 2 const Rational& rhs) 3 { 4 return Rational(lhs.numerator() * rhs.numerator(), 5 lhs.denominator() * rhs.denominator()); 6 } 就只会消耗一个构造函数的成本了:
本文最初发表于程序人生 >> Copy On Write(写时复制) 作者:代码疯子Copy On Write(写时复制)是在编程中比较常见的一个技术,面试中也会偶尔出现(好像Java中就经常有字符串写时复制的笔试题),今天在看《More Effective C++》的引用计数时就讲到了Copy On Write——写时复制。下面简单介绍下Copy On Write(写时复制),我们假设STL中的string支持写时复制(只是假设,具体未经考证,这里以Mircosoft Visual Studio 6.0为例,如果有兴趣,可以自己翻阅源码) Copy On Write(写时复制)的原理是什么? 有一定经验的程序员应该都知道Copy On Write(写时复制)使用了“引用计数”,会有一个变量用于保存引用的数量。当第一个类构造时,string的构造函数会根据传入的参数从堆上分配内存,当有其它类需要这块内存时,这个计数为自动累加,当有类析构时,这个计数会减一,直到最后一个类析构时,此时的引用计数为1或是0,此时,程序才会真正的Free这块从堆上分配的内存。 引用计数就是string类中写时才拷贝的原理! 什么情况下触发Copy On Write(写时复制) 很显然,当然是在共享同一块内存的类发生内容改变时,才会发生Copy On Write(写时复制)。比如string类的[]、=、+=、+等,还有一些string类中诸如insert、replace、append等成员函数等,包括类的析构时。 示例代码:
// 作者:代码疯子 // 博客:http://www.programlife.net/ // 引用计数 & 写时复制 #include <iostream> #include <string> using namespace std; int main(int argc, char **argv) { string sa = "Copy on write"; string sb = sa; string sc = sb; printf("sa char buffer address: 0x%08X\n", sa.c_str()); printf("sb char buffer address: 0x%08X\n", sb.c_str()); printf("sc char buffer address: 0x%08X\n", sc.c_str()); sc = "Now writing..."; printf("After writing sc:\n"); printf("sa char buffer address: 0x%08X\n", sa.c_str()); printf("sb char buffer address: 0x%08X\n", sb.c_str()); printf("sc char buffer address: 0x%08X\n", sc.c_str()); return 0; } 输出结果如下(VC 6.0): 可以看到,VC6里面的string是支持写时复制的,但是我的Visual Studio 2008就不支持这个特性(Debug和Release都是):
拓展阅读:(摘自《Windows Via C/C++》5th Edition,不想看英文可以看中文的PDF,中文版第442页) Static Data Is Not Shared by Multiple Instances of an Executable or a DLL
http://apt.jenslody.de/# 打开软件源配置文件添加下面5行sudo gedit /etc/apt/sources.listdeb http://apt.jenslody.de/ any maindeb-src http://apt.jenslody.de/ any maindeb http://apt.jenslody.de/ any releasedeb-src http://apt.jenslody.de/ any releasedeb http://apt.wxwidgets.org/ lenny-wx main# 更新软件源配置文件 和 安装Keysudo apt-get updatesudo apt-get install jens-lody-debian-keyringwget -q http://apt.wxwidgets.org/key.asc -O- | sudo apt-key add -这样只是把软件源家进去了,并没有安装好,所以还要输入安装命令# 然后输入下面这个命令开始安装 codeblockssudo apt-get install codeblocks现在安装好,从编程软件里开启CodeBlocks了,是英文的,并且是最近几日的最版本# 你也可以直接下载 CodeBlocks 的二进制发行包,从这个URL进入http://apt.jenslody.de/pool/# 中文化CodeBlocks 下载这个包,语言文件 Linux下通用的http://srgb.googlecode.com/files/CodeBlocks_Linux_Install.zip进入压缩包把语言文件放到桌面 '/locale/zh_CN/CodeBlocks.mo' 中文化 codeblocks locale/zh_CN/codeblocks.mo 里的 中文化文件放这里 '/usr/share/codeblocks/locale/zh_CN/codeblocks.mo' 设置权限为所有人可以访问# 使用管理员权限把 语言包 locale 目录 拉到 /usr/share/codeblocks里sudo nautilus /usr/share/codeblocks/注意 locale 的权限可能不完整,所以 选住目录 所有者-群组-其他设定都能访问文件;对包含的文件应用权限;进入 /usr/share/codeblocks/locale/zh_CN/ 目录选两个文件右键修改权限 所有者 和 群组 都可以读写现在安装的 CodeBlocks 打开是中文里,但是只有基本IDE环境,很多插件和开发包没安装可以输入 sudo apt-get install codeblocks <按两下TAB>列出没有安装的其他包, 你可以选择安装,我偷懒了sudo apt-get install codeblocks* <回车>sudo apt-get install libwxsmith* <回车>sudo apt-get install libwxgtk2.8-dev <回车>现在开启CB,建立一个wx项目,编译,可以编译成功了
在Lua中可以通过自定义类型的方式与C语言代码更高效、更灵活的交互。这里我们通过一个简单完整的示例来学习一下Lua中userdata的使用方式。需要说明的是,该示例完全来自于Programming in Lua。其功能是用C程序实现一个Lua的布尔数组,以提供程序的执行效率。见下面的代码和关键性注释。 1 #include <lua.hpp> 2 #include <lauxlib.h> 3 #include <lualib.h> 4 #include <limits.h> 5 6 #define BITS_PER_WORD (CHAR_BIT * sizeof(int)) 7 #define I_WORD(i) ((unsigned int)(i))/BITS_PER_WORD 8 #define I_BIT(i) (1 << ((unsigned int)(i)%BITS_PER_WORD)) 9 10 typedef struct NumArray { 11 int size; 12 unsigned int values[1]; 13 } NumArray; 14 15 extern "C" int newArray(lua_State* L) 16 { 17 //1. 检查第一个参数是否为整型。以及该参数的值是否大于等于1. 18 int n = luaL_checkint(L,1); 19 luaL_argcheck(L, n >= 1, 1, "invalid size."); 20 size_t nbytes = sizeof(NumArray) + I_WORD(n - 1) * sizeof(int); 21 //2. 参数表示Lua为userdata分配的字节数。同时将分配后的userdata对象压入栈中。 22 NumArray* a = (NumArray*)lua_newuserdata(L,nbytes); 23 a->size = n; 24 for (int i = 0; i < I_WORD(n - 1); ++i) 25 a->values[i] = 0; 26 //获取注册表变量myarray,该key的值为metatable。 27 luaL_getmetatable(L,"myarray"); 28 //将userdata的元表设置为和myarray关联的table。同时将栈顶元素弹出。 29 lua_setmetatable(L,-2); 30 return 1; 31 } 32 33 extern "C" int setArray(lua_State* L) 34 { 35 //1. Lua传给该函数的第一个参数必须是userdata,该对象的元表也必须是注册表中和myarray关联的table。 36 //否则该函数报错并终止程序。 37 NumArray* a = (NumArray*)luaL_checkudata(L,1,"myarray"); 38 int index = luaL_checkint(L,2) - 1; 39 //2. 由于任何类型的数据都可以成为布尔值,因此这里使用any只是为了确保有3个参数。 40 luaL_checkany(L,3); 41 luaL_argcheck(L,a != NULL,1,"'array' expected."); 42 luaL_argcheck(L,0 <= index && index < a->size,2,"index out of range."); 43 if (lua_toboolean(L,3)) 44 a->values[I_WORD(index)] |= I_BIT(index); 45 else 46 a->values[I_WORD(index)] &= ~I_BIT(index); 47 return 0; 48 } 49 50 extern "C" int getArray(lua_State* L) 51 { 52 NumArray* a = (NumArray*)luaL_checkudata(L,1,"myarray"); 53 int index = luaL_checkint(L,2) - 1; 54 luaL_argcheck(L, a != NULL, 1, "'array' expected."); 55 luaL_argcheck(L, 0 <= index && index < a->size,2,"index out of range"); 56 lua_pushboolean(L,a->values[I_WORD(index)] & I_BIT(index)); 57 return 1; 58 } 59 60 extern "C" int getSize(lua_State* L) 61 { 62 NumArray* a = (NumArray*)luaL_checkudata(L,1,"myarray"); 63 luaL_argcheck(L,a != NULL,1,"'array' expected."); 64 lua_pushinteger(L,a->size); 65 return 1; 66 } 67 68 extern "C" int array2string(lua_State* L) 69 { 70 NumArray* a = (NumArray*)luaL_checkudata(L,1,"myarray"); 71 lua_pushfstring(L,"array(%d)",a->size); 72 return 1; 73 } 74 75 static luaL_Reg arraylib_f [] = { 76 {"new", newArray}, 77 {NULL, NULL} 78 }; 79 80 static luaL_Reg arraylib_m [] = { 81 {"set", setArray}, 82 {"get", getArray}, 83 {"size", getSize}, 84 {"__tostring", array2string}, //print(a)时Lua会调用该元方法。 85 {NULL, NULL} 86 }; 87 88 extern "C" __declspec(dllexport) 89 int luaopen_testuserdata(lua_State* L) 90 { 91 //1. 创建元表,并将该元表指定给newArray函数新创建的userdata。在Lua中userdata也是以table的身份表现的。 92 //这样在调用对象函数时,可以通过验证其metatable的名称来确定参数userdata是否合法。 93 luaL_newmetatable(L,"myarray"); 94 lua_pushvalue(L,-1); 95 //2. 为了实现面对对象的调用方式,需要将元表的__index字段指向自身,同时再将arraylib_m数组中的函数注册到 96 //元表中,之后基于这些注册函数的调用就可以以面向对象的形式调用了。 97 //lua_setfield在执行后会将栈顶的table弹出。 98 lua_setfield(L,-2,"__index"); 99 //将这些成员函数注册给元表,以保证Lua在寻找方法时可以定位。NULL参数表示将用栈顶的table代替第二个参数。 100 luaL_register(L,NULL,arraylib_m); 101 //这里只注册的工厂方法。 102 luaL_register(L,"testuserdata",arraylib_f); 103 return 1; 104 } 轻量级userdata: 之前介绍的是full userdata,Lua还提供了另一种轻量级userdata(light userdata)。事实上,轻量级userdata仅仅表示的是C指针的值,即(void*)。由于它只是一个值,所以不用创建。如果需要将一个轻量级userdata放入栈中,调用lua_pushlightuserdata即可。full userdata和light userdata之间最大的区别来自于相等性判断,对于一个full userdata,它只是与自身相等,而light userdata则表示为一个C指针,因此,它与所有表示同一指针的light userdata相等。再有就是light userdata不会受到垃圾收集器的管理,使用时就像一个普通的整型数字一样。
摘要: 1. 数组操作: 在Lua中,“数组”只是table的一个别名,是指以一种特殊的方法来使用table。出于性能原因,Lua的C API为数组操作提供了专门的函数,如: void lua_rawgeti(lua_State* L, int index, int key);  ... 阅读全文
Lua可以调用C函数的能力将极大的提高Lua的可扩展性和可用性。对于有些和操作系统相关的功能,或者是对效率要求较高的模块,我们完全可以通过C函数来实现,之后再通过Lua调用指定的C函数。对于那些可被Lua调用的C函数而言,其接口必须遵循Lua要求的形式,即typedef int (*lua_CFunction)(lua_State* L)。简单说明一下,该函数类型仅仅包含一个表示Lua环境的指针作为其唯一的参数,实现者可以通过该指针进一步获取Lua代码中实际传入的参数。返回值是整型,表示该C函数将返回给Lua代码的返回值数量,如果没有返回值,则return 0即可。需要说明的是,C函数无法直接将真正的返回值返回给Lua代码,而是通过虚拟栈来传递Lua代码和C函数之间的调用参数和返回值的。这里我们将介绍两种Lua调用C函数的规则。 1. C函数作为应用程序的一部分。 1 #include <stdio.h> 2 #include <string.h> 3 #include <lua.hpp> 4 #include <lauxlib.h> 5 #include <lualib.h> 6 7 //待Lua调用的C注册函数。 8 static int add2(lua_State* L) 9 { 10 //检查栈中的参数是否合法,1表示Lua调用时的第一个参数(从左到右),依此类推。 11 //如果Lua代码在调用时传递的参数不为number,该函数将报错并终止程序的执行。 12 double op1 = luaL_checknumber(L,1); 13 double op2 = luaL_checknumber(L,2); 14 //将函数的结果压入栈中。如果有多个返回值,可以在这里多次压入栈中。 15 lua_pushnumber(L,op1 + op2); 16 //返回值用于提示该C函数的返回值数量,即压入栈中的返回值数量。 17 return 1; 18 } 19 20 //另一个待Lua调用的C注册函数。 21 static int sub2(lua_State* L) 22 { 23 double op1 = luaL_checknumber(L,1); 24 double op2 = luaL_checknumber(L,2); 25 lua_pushnumber(L,op1 - op2); 26 return 1; 27 } 28 29 const char* testfunc = "print(add2(1.0,2.0)) print(sub2(20.1,19))"; 30 31 int main() 32 { 33 lua_State* L = luaL_newstate(); 34 luaL_openlibs(L); 35 //将指定的函数注册为Lua的全局函数变量,其中第一个字符串参数为Lua代码 36 //在调用C函数时使用的全局函数名,第二个参数为实际C函数的指针。 37 lua_register(L, "add2", add2); 38 lua_register(L, "sub2", sub2); 39 //在注册完所有的C函数之后,即可在Lua的代码块中使用这些已经注册的C函数了。 40 if (luaL_dostring(L,testfunc)) 41 printf("Failed to invoke.\n"); 42 lua_close(L); 43 return 0; 44 } 2. C函数库成为Lua的模块。 将包含C函数的代码生成库文件,如Linux的so,或Windows的DLL,同时拷贝到Lua代码所在的当前目录,或者是LUA_CPATH环境变量所指向的目录,以便于Lua解析器可以正确定位到他们。在我当前的Windows系统中,我将其copy到"C:\Program Files\Lua\5.1\clibs\",这里包含了所有Lua可调用的C库。见如下C语言代码和关键性注释: 1 #include <stdio.h> 2 #include <string.h> 3 #include <lua.hpp> 4 #include <lauxlib.h> 5 #include <lualib.h> 6 7 //待注册的C函数,该函数的声明形式在上面的例子中已经给出。 8 //需要说明的是,该函数必须以C的形式被导出,因此extern "C"是必须的。 9 //函数代码和上例相同,这里不再赘述。 10 extern "C" int add(lua_State* L) 11 { 12 double op1 = luaL_checknumber(L,1); 13 double op2 = luaL_checknumber(L,2); 14 lua_pushnumber(L,op1 + op2); 15 return 1; 16 } 17 18 extern "C" int sub(lua_State* L) 19 { 20 double op1 = luaL_checknumber(L,1); 21 double op2 = luaL_checknumber(L,2); 22 lua_pushnumber(L,op1 - op2); 23 return 1; 24 } 25 26 //luaL_Reg结构体的第一个字段为字符串,在注册时用于通知Lua该函数的名字。 27 //第一个字段为C函数指针。 28 //结构体数组中的最后一个元素的两个字段均为NULL,用于提示Lua注册函数已经到达数组的末尾。 29 static luaL_Reg mylibs[] = { 30 {"add", add}, 31 {"sub", sub}, 32 {NULL, NULL} 33 }; 34 35 //该C库的唯一入口函数。其函数签名等同于上面的注册函数。见如下几点说明: 36 //1. 我们可以将该函数简单的理解为模块的工厂函数。 37 //2. 其函数名必须为luaopen_xxx,其中xxx表示library名称。Lua代码require "xxx"需要与之对应。 38 //3. 在luaL_register的调用中,其第一个字符串参数为模块名"xxx",第二个参数为待注册函数的数组。 39 //4. 需要强调的是,所有需要用到"xxx"的代码,不论C还是Lua,都必须保持一致,这是Lua的约定, 40 // 否则将无法调用。 41 extern "C" __declspec(dllexport) 42 int luaopen_mytestlib(lua_State* L) 43 { 44 const char* libName = "mytestlib"; 45 luaL_register(L,libName,mylibs); 46 return 1; 47 } 见如下Lua代码: 1 require "mytestlib" --指定包名称 2 3 --在调用时,必须是package.function 4 print(mytestlib.add(1.0,2.0)) 5 print(mytestlib.sub(20.1,19))
摘要: 1. 基础: Lua的一项重要用途就是作为一种配置语言。现在从一个简单的示例开始吧。 --这里是用Lua代码定义的窗口大小的配置信息 width = 200 height = 300 下面是读取配置信息的C/... 阅读全文
Lua是一种嵌入式脚本语言,即Lua不是可以单独运行的程序,在实际应用中,主要存在两种应用形式。第一种形式是,C/C++作为主程序,调用Lua代码,此时可以将Lua看做“可扩展的语言”,我们将这种应用称为“应用程序代码”。第二种形式是Lua具有控制权,而C/C++代码则作为Lua的“库代码”。在这两种形式中,都是通过Lua提供的C API完成两种语言之间的通信的。
1. 基础知识: C API是一组能使C/C++代码与Lua交互的函数。其中包括读写Lua全局变量、调用Lua函数、运行一段Lua代码,以及注册C函数以供Lua代码调用等。这里先给出一个简单的示例代码: 1 #include <stdio.h> 2 #include <string.h> 3 #include <lua.hpp> 4 #include <lauxlib.h> 5 #include <lualib.h> 6 7 int main(void) 8 { 9 const char* buff = "print(\"hello\")"; 10 int error; 11 lua_State* L = luaL_newstate(); 12 luaL_openlibs(L); 13 14 error = luaL_loadbuffer(L,buff,strlen(buff),"line") || lua_pcall(L,0,0,0); 15 int s = lua_gettop(L); 16 if (error) { 17 fprintf(stderr,"%s",lua_tostring(L,-1)); 18 lua_pop(L,1); 19 } 20 lua_close(L); 21 return 0; 22 } 下面是针对以上代码给出的具体解释: 1). 上面的代码是基于我的C++工程,而非C工程,因此包含的头文件是lua.hpp,如果是C工程,可以直接包含lua.h。 2). Lua库中没有定义任何全局变量,而是将所有的状态都保存在动态结构lua_State中,后面所有的C API都需要该指针作为第一个参数。 3). luaL_openlibs函数是用于打开Lua中的所有标准库,如io库、string库等。 4). luaL_loadbuffer编译了buff中的Lua代码,如果没有错误,则返回0,同时将编译后的程序块压入虚拟栈中。 5). lua_pcall函数会将程序块从栈中弹出,并在保护模式下运行该程序块。执行成功返回0,否则将错误信息压入栈中。 6). lua_tostring函数中的-1,表示栈顶的索引值,栈底的索引值为1,以此类推。该函数将返回栈顶的错误信息,但是不会将其从栈中弹出。 7). lua_pop是一个宏,用于从虚拟栈中弹出指定数量的元素,这里的1表示仅弹出栈顶的元素。 8). lua_close用于释放状态指针所引用的资源。
2. 栈: 在Lua和C语言之间进行数据交换时,由于两种语言之间有着较大的差异,比如Lua是动态类型,C语言是静态类型,Lua是自动内存管理,而C语言则是手动内存管理。为了解决这些问题,Lua的设计者使用了虚拟栈作为二者之间数据交互的介质。在C/C++程序中,如果要获取Lua的值,只需调用Lua的C API函数,Lua就会将指定的值压入栈中。要将一个值传给Lua时,需要先将该值压入栈,然后调用Lua的C API,Lua就会获取该值并将其从栈中弹出。为了可以将不同类型的值压入栈,以及从栈中取出不同类型的值,Lua为每种类型均设定了一个特定函数。 1). 压入元素: Lua针对每种C类型,都有一个C API函数与之对应,如: void lua_pushnil(lua_State* L); --nil值 void lua_pushboolean(lua_State* L, int b); --布尔值 void lua_pushnumber(lua_State* L, lua_Number n); --浮点数 void lua_pushinteger(lua_State* L, lua_Integer n); --整型 void lua_pushlstring(lua_State* L, const char* s, size_t len); --指定长度的内存数据 void lua_pushstring(lua_State* L, const char* s); --以零结尾的字符串,其长度可由strlen得出。 对于字符串数据,Lua不会持有他们的指针,而是调用在API时生成一个内部副本,因此,即使在这些函数返回后立刻释放或修改这些字符串指针,也不会有任何问题。 在向栈中压入数据时,可以通过调用下面的函数判断是否有足够的栈空间可用,一般而言,Lua会预留20个槽位,对于普通应用来说已经足够了,除非是遇到有很多参数的函数。 int lua_checkstack(lua_State* L, int extra) --期望得到extra数量的空闲槽位,如果不能扩展并获得,返回false。 2). 查询元素: API使用“索引”来引用栈中的元素,第一个压入栈的为1,第二个为2,依此类推。我们也可以使用负数作为索引值,其中-1表示为栈顶元素,-2为栈顶下面的元素,同样依此类推。 Lua提供了一组特定的函数用于检查返回元素的类型,如: int lua_isboolean (lua_State *L, int index); int lua_iscfunction (lua_State *L, int index); int lua_isfunction (lua_State *L, int index); int lua_isnil (lua_State *L, int index); int lua_islightuserdata (lua_State *L, int index); int lua_isnumber (lua_State *L, int index); int lua_isstring (lua_State *L, int index); int lua_istable (lua_State *L, int index); int lua_isuserdata (lua_State *L, int index); 以上函数,成功返回1,否则返回0。需要特别指出的是,对于lua_isnumber而言,不会检查值是否为数字类型,而是检查值是否能转换为数字类型。 Lua还提供了一个函数lua_type,用于获取元素的类型,函数原型如下: int lua_type (lua_State *L, int index); 该函数的返回值为一组常量值,分别是:LUA_TNIL、LUA_TNUMBER、LUA_TBOOLEAN、LUA_TSTRING、LUA_TTABLE、LUA_TFUNCTION、LUA_TUSERDATA、LUA_TTHREAD和LUA_TLIGHTUSERDATA。这些常量通常用于switch语句中。 除了上述函数之外,Lua还提供了一组转换函数,如: int lua_toboolean (lua_State *L, int index); lua_CFunction lua_tocfunction (lua_State *L, int index); lua_Integer lua_tointeger (lua_State *L, int index); const char *lua_tolstring (lua_State *L, int index, size_t *len); lua_Number lua_tonumber (lua_State *L, int index); const void *lua_topointer (lua_State *L, int index); const char *lua_tostring (lua_State *L, int index); void *lua_touserdata (lua_State *L, int index); --string类型返回字符串长度,table类型返回操作符'#'等同的结果,userdata类型返回分配的内存块长度。 size_t lua_objlen (lua_State *L, int index); 对于上述函数,如果调用失败,lua_toboolean、lua_tonumber、lua_tointeger和lua_objlen均返回0,而其他函数则返回NULL。在很多时候0不是一个很有效的用于判断错误的值,但是ANSI C没有提供其他可以表示错误的值。因此对于这些函数,在有些情况下需要先使用lua_is*系列函数判断是否类型正确,而对于剩下的函数,则可以直接通过判断返回值是否为NULL即可。 对于lua_tolstring函数返回的指向内部字符串的指针,在该索引指向的元素被弹出之后,将无法保证仍然有效。该函数返回的字符串末尾均会有一个尾部0。 下面将给出一个工具函数,可用于演示上面提到的部分函数,如: 1 static void stackDump(lua_State* L) 2 { 3 int top = lua_gettop(L); 4 for (int i = 1; i <= top; ++i) { 5 int t = lua_type(L,i); 6 switch(t) { 7 case LUA_TSTRING: 8 printf("'%s'",lua_tostring(L,i)); 9 break; 10 case LUA_TBOOLEAN: 11 printf(lua_toboolean(L,i) ? "true" : "false"); 12 break; 13 case LUA_TNUMBER: 14 printf("%g",lua_tonumber(L,i)); 15 break; 16 default: 17 printf("%s",lua_typename(L,t)); 18 break; 19 } 20 printf(""); 21 } 22 printf("\n"); 23 } 3). 其它栈操作函数: 除了上面给出的数据交换函数之外,Lua的C API还提供了一组用于操作虚拟栈的普通函数,如: int lua_gettop(lua_State* L); --返回栈中元素的个数。 void lua_settop(lua_State* L, int index); --将栈顶设置为指定的索引值。 void lua_pushvalue(lua_State* L, int index); --将指定索引的元素副本压入栈。 void lua_remove(lua_State* L, int index); --删除指定索引上的元素,其上面的元素自动下移。 void lua_insert(lua_State* L, int index); --将栈顶元素插入到该索引值指向的位置。 void lua_replace(lua_State* L, int index); --弹出栈顶元素,并将该值设置到指定索引上。 Lua还提供了一个宏用于弹出指定数量的元素:#define lua_pop(L,n) lua_settop(L, -(n) - 1) 见如下示例代码: 1 int main() 2 { 3 lua_State* L = luaL_newstate(); 4 lua_pushboolean(L,1); 5 lua_pushnumber(L,10); 6 lua_pushnil(L); 7 lua_pushstring(L,"hello"); 8 stackDump(L); //true 10 nil 'hello' 9 10 lua_pushvalue(L,-4); 11 stackDump(L); //true 10 nil 'hello' true 12 13 lua_replace(L,3); 14 stackDump(L); //true 10 true 'hello' 15 16 lua_settop(L,6); 17 stackDump(L); //true 10 true 'hello' nil nil 18 19 lua_remove(L,-3); 20 stackDump(L); //true 10 true nil nil 21 22 lua_settop(L,-5); 23 stackDump(L); //true 24 25 lua_close(L); 26 return 0; 27 } 3. C API中的错误处理: 1). C程序调用Lua代码的错误处理: 通常情况下,应用程序代码是以“无保护”模式运行的。因此,当Lua发现“内存不足”这类错误时,只能通过调用“紧急”函数来通知C语言程序,之后在结束应用程序。用户可通过lua_atpanic来设置自己的“紧急”函数。如果希望应用程序代码在发生Lua错误时不会退出,可通过调用lua_pcall函数以保护模式运行Lua代码。这样再发生内存错误时,lua_pcall会返回一个错误代码,并将解释器重置为一致的状态。如果要保护与Lua的C代码,可以使用lua_cpall函数,它将接受一个C函数作为参数,然后调用这个C函数。 2). Lua调用C程序: 通常而言,当一个被Lua调用的C函数检测到错误时,它就应该调用lua_error,该函数会清理Lua中所有需要清理的资源,然后跳转回发起执行的那个lua_pcall,并附上一条错误信息。
Lua为了保证高度的可移植性,因此,它的标准库仅仅提供了非常少的功能,特别是和OS相关的库。但是Lua还提供了一些扩展库,比如Posix库等。对于文件操作而言,该库仅提供了os.rename函数和os.remove函数。 1. 日期和时间: 在Lua中,函数time和date提供了所有的日期和时间功能。 如果不带任何参数调用time函数,它将以数字形式返回当前的日期和时间。如果以一个table作为参数,它将返回一个数字,表示该table中所描述的日期和时间。该table的有效字段如下: 字段名 | 描述 | year | 一个完整的年份 | month | 01-12 | day | 01-31 | hour | 00-23 | min | 00-59 | sec | 00-59 | isdst | 布尔值,true表示夏令时 |
print(os.time{year = 1970, month = 1, day = 1, hour = 8, min = 0}) --北京是东八区,所以hour等于时表示UTC的0。 print(os.time()) --输出当前时间距离1970-1-1 00:00:00所经过的秒数。输出值为 1333594721 函数date是time的反函数,即可以将time返回的数字值转换为更高级的可读格式,其第一个参数是格式化字符串,表示期望的日期返回格式,第二个参数是日期和时间的数字,缺省为当前日期和时间。如: 1 dd = os.date("*t",os.time()) --如果格式化字符串为"*t",函数将返回table形式的日期对象。如果为"!*t",则表示为UTC时间格式。 2 print("year = " .. dd.year) 3 print("month = " .. dd.month) 4 print("day = " .. dd.day) 5 print("weekday = " .. dd.wday) --一个星期中的第几天,周日是第一天 6 print("yearday = " .. dd.yday) --一年中的第几天,1月1日是第一天 7 print("hour = " .. dd.hour) 8 print("min = " .. dd.min) 9 print("sec = " .. dd.sec) 10 11 --[[ 12 year = 2012 13 month = 4 14 day = 5 15 weekday = 5 16 yearday = 96 17 hour = 11 18 min = 13 19 sec = 44 20 --]] date函数的格式化标识和C运行时库中的strftime函数的标识完全相同,见下表: 关键字 | 描述 | %a | 一星期中天数的缩写,如Wed | %A | 一星期中天数的全称,如Friday | %b | 月份的缩写,如Sep | %B | 月份的全称,如September | %c | 日期和时间 | %d | 一个月中的第几天(01-31) | %H | 24小时制中的小时数(00-23) | %I | 12小时制中的小时数(01-12) | %j | 一年中的第几天(001-366) | %M | 分钟(00-59) | %m | 月份(01-12) | %p | "上午(am)"或"下午(pm)" | %S | 秒数(00-59) | %w | 一星期中的第几天(0--6等价于星期日--星期六) | %x | 日期,如09/16/2010 | %X | 时间,如23:48:20 | %y | 两位数的年份(00-99) | %Y | 完整的年份(2012) | %% | 字符'%' |
print(os.date("%Y-%m-%d")) --输出2012-04-05 函数os.clock()返回CPU时间的描述,通常用于计算一段代码的执行效率。如: 1 local x = os.clock() 2 local s = 0 3 for i = 1, 10000000 do 4 s = s + i 5 end 6 print(string.format("elapsed time: %.2f\n", os.clock() - x)) 7 8 --输出结果为: 9 --elapsed time: 0.21 2. 其他系统调用: 函数os.exit()可中止当前程序的执行。函数os.getenv()可获取一个环境变量的值。如: print(os.getenv("PATH")) --如果环境变量不存在,返回nil。 os.execute函数用于执行和操作系统相关的命令,如: os.execute("mkdir " .. "hello")
I/O库为文件操作提供了两种不同的模型,简单模型和完整模型。简单模型假设一个当前输入文件和一个当前输出文件,他的I/O操作均作用于这些文件。完整模型则使用显式的文件句柄,并将所有的操作定义为文件句柄上的方法。 1. 简单模型: I/O库会将进程标准输入输出作为其缺省的输入文件和输出文件。我们可以通过io.input(filename)和io.output(filename)这两个函数来改变当前的输入输出文件。 1). io.write函数: 函数原型为io.write(...)。该函数将所有参数顺序的写入到当前输出文件中。如: io.write("hello","world") --写出的内容为helloworld
2). io.read函数: 下表给出了该函数参数的定义和功能描述: 参数字符串 | 含义 | "*all" | 读取整个文件 | "*line" | 读取下一行 | "*number" | 读取一个数字 | <num> | 读取一个不超过<num>个字符的字符串 |
调用io.read("*all")会读取当前输入文件的所有内容,以当前位置作为开始。如果当前位置处于文件的末尾,或者文件为空,那个该调用返回一个空字符串。由于Lua可以高效的处理长字符串,因此在Lua中可以先将数据从文件中完整读出,之后在通过Lua字符串库提供的函数进行各种处理。 调用io.read("*line")会返回当前文件的下一行,但不包含换行符。当到达文件末尾时,该调用返回nil。如: 1 for count = 1,math.huge do 2 local line = io.read("*line") --如果不传参数,缺省值也是"*line" 3 if line == nil then 4 break 5 end 6 io.write(string.format("%6d ",count),line,"\n") 7 end 如果只是为了迭代文件中的所有行,可以使用io.lines函数,以迭代器的形式访问文件中的每一行数据,如: 1 local lines = {} 2 for line in io.lines() do --通过迭代器访问每一个数据 3 lines[#lines + 1] = line 4 end 5 table.sort(lines) --排序,Lua标准库的table库提供的函数。 6 for _,l in ipairs(lines) do 7 io.write(l,"\n") 8 end 调用io.read("*number")会从当前输入文件中读取一个数字。此时read将直接返回一个数字,而不是字符串。"*number"选项会忽略数字前面所有的空格,并且能处理像-3、+5.2这样的数字格式。如果当前读取的数据不是合法的数字,read返回nil。在调用read是可以指定多个选项,函数会根据每个选项参数返回相应的内容。如: 1 --[[ 2 6.0 -3.23 1000 3 ... ... 4 下面的代码读取注释中的数字 5 --]] 6 while true do 7 local n1,n2,n3 = io.read("*number","*number","*number") 8 if not n1 then 9 break 10 end 11 print(math.max(n1,n2,n3)) 12 end 调用io.read(<num>)会从输入文件中最多读取n个字符,如果读不到任何字符,返回nil。否则返回读取到的字符串。如: 1 while true do 2 local block = io.read(2^13) 3 if not block then 4 break 5 end 6 io.write(block) 7 end io.read(0)是一种特殊的情况,用于检查是否到达了文件的末尾。如果没有到达,返回空字符串,否则nil。
2. 完整I/O模型: Lua中完整I/O模型的使用方式非常类似于C运行时库的文件操作函数,它们都是基于文件句柄的。 1). 通过io.open函数打开指定的文件,并且在参数中给出对该文件的打开模式,其中"r"表示读取,"w"表示覆盖写入,即先删除文件原有的内容,"a"表示追加式写入,"b"表示以二进制的方式打开文件。在成功打开文件后,该函数将返回表示该文件的句柄,后面所有基于该文件的操作,都需要将该句柄作为参数传入。如果打开失败,返回nil。其中错误信息由该函数的第二个参数返回,如: assert(io.open(filename,mode)) --如果打开失败,assert将打印第二个参数给出的错误信息。 2). 文件读写函数read/write。这里需要用到冒号语法,如: 1 local f = assert(io.open(filename,"r")) 2 local t = f:read("*all") --对于read而言,其参数完全等同于简单模型下read的参数。 3 f:close() 此外,I/O库还提供了3个预定义的文件句柄,即io.stdin(标准输入)、io.stdout(标准输出)、io.stderr(标准错误输出)。如: io.stderr:write("This is an error message.") 事实上,我们也可以混合使用简单模式和完整模式,如: 1 local temp = io.input() --将当前文件句柄保存 2 io.input("newInputfile") --打开新的输入文件 3 io.input():close() --关闭当前文件 4 io.input(temp) --恢复原来的输入文件 3). 性能小技巧: 由于一次性读取整个文件比逐行读取要快一些,但对于较大的文件,这样并不可行,因此Lua提供了一种折中的方式,即一次读取指定字节数量的数据,如果当前读取中的最后一行不是完整的一行,可通过该方式将该行的剩余部分也一并读入,从而保证本次读取的数据均为整行数据,以便于上层逻辑的处理。如: local lines,rest = f:read(BUFSIZE,"*line") --rest变量包含最后一行中没有读取的部分。 下面是Shell中wc命令的一个简单实现。
1 local BUFSIZE = 8192 2 local f = io.input(arg[1]) --打开输入文件 3 local cc, lc, wc, = 0, 0, 0 --分别计数字符、行和单词 4 while true do 5 local lines,rest = f:read(BUFSIZE,"*line") 6 if not lines then 7 break 8 end 9 if rest then 10 lines = lines .. rest .. "\n" 11 end 12 cc = cc + #lines 13 --计算单词数量 14 local _, t = string.gsub(lines."%S+","") 15 wc = wc + t 16 --计算行数 17 _,t = string.gsub(line,"\n","\n") 18 lc = lc + t 19 end 20 print(lc,wc,cc) 4). 其它文件操作: 如io.flush函数会将io缓存中的数据刷新到磁盘文件上。io.close函数将关闭当前打开的文件。io.seek函数用于设置或获取当前文件的读写位置,其函数原型为f:seek(whence,offset),如果whence的值为"set",offset的值则表示为相对于文件起始位置的偏移量。如为"cur"(默认值),offset则为相对于当前位置的偏移量,如为"end",则为相对于文件末尾的偏移量。函数的返回值与whence参数无关,总是返回文件的当前位置,即相对于文件起始处的偏移字节数。offset的默认值为0。如:
1 function fsize(file) 2 local current = file:seek() --获取当前位置 3 local size = file:seek("end") --获取文件大小 4 file:seek("set",current) --恢复原有的当前位置 5 return size 6 end 最后需要指出的是,如果发生错误,所有这些函数均返回nil和一条错误信息。
|