关于LPC语言你可能不知道的实用冷知识

LPC游戏开发,我们看看代码能学个大概,但到写代码时会感觉为什么别人写的代码那么简洁优秀,自己写的这么麻烦?除了天天复制粘贴的那些代码,我们有多少不知道的知识点?

这里整理一些冷门知识点,看你能GET多少。其中有部分在LPC基础教程中都有讲过或提过,但因为冷门可能没有被大家重视,这里整理到一起,做个冷知识小结。

冷门程度根据常规MUD中使用的多少判断,用的越少越冷门,最高五星(★★★★★),实用程度根据这个功能的实用性评价,最高五星(★★★★★)。都是本人个人评价,仅供参考。

关于数组的定界符

冷门程度:★★★★★

实用程度:★★★★

我们基本都知道对字符串赋值可以使用字符串定界符格式:

string text = @TEXT
    积土成山,风雨兴焉;积水成渊,蛟龙生焉;积善成德,而神明自得,圣心备焉。
故不积跬步,无以至千里;不积小流,无以成江海。骐骥一跃,不能十步;驽马十驾,
功在不舍。锲而舍之,朽木不折;锲而不舍,金石可镂。蚓无爪牙之利,筋骨之强,
上食埃土,下饮黄泉,用心一也。蟹六跪而二螯,非蛇鳝之穴无可寄托者,用心躁也。
TEXT;

但很多人不知道字符串数组也有定界符格式的赋值方式。如下代码:

void test()
{
    string *poem = @@TEXT
【摊破浣溪沙】

揉破黄金万点轻,
剪成碧玉叶层层。
风度精神如彦辅,
太鲜明。

梅蕊重重何俗甚,
丁香千结苦粗生。
熏透愁人千里梦,
却无情。

TEXT;

    foreach(string line in poem)
    {
        write(line + "\n");
    }
}

代码中 poem 是字符串数组,相对字符串的赋值使用一个 @ ,数组是使用二个 @@,在很多情况下使用这个会让你更轻松。

关于数组运算符

冷门程度:★★★

实用程度:★★★★★

在LPC中数组运算的操作特别骚,可以秒杀其它多数编程语言,多数开发者应该都知道数组的+[x..y]运算,但只有这些吗?看看以下示例:

({1,3,5,7}) + ({2,3,5,8}) = ({1,3,5,7,2,3,5,8})
({1,3,5,7}) - ({2,3,5,8}) = ({1,7})
({1,3,5,7}) & ({2,3,5,8}) = ({3,5})
({1,3,5,7}) | ({2,3,5,8}) = ({1,3,5,7,2,8})

关于数组展开运算符

冷门程度:★★★★★

实用程度:★★★★

在LPC中支持数组展开运算符...,这个语法在全世界最好的编程语言PHP中也支持,而且是在php7.4(2019年底发布)以后才增加的语法,突然感觉LPC好超前。什么是数组展开运算?简单的说就是把数组展开,如({1,2,3})...展开是1,2,3。用法示例:

    int *a = ({2, 3, 4});
    int *b = ({1, a..., 5});
    printf("%O\n",b); // b = ({1,2,3,4,5})

使用这个又有很多骚操作可以实现,如类似PHP中的参数解包(Argument Unpacking):

    mixed *arr = ({"mudren", 18, "武当派"});
    // 输出:name = mudren, age = 18, family = 武当派
    printf("name = %s, age = %d, family = %s\n", arr...);

关于对象(类)的继承

冷门程度:★★★

实用程度:★★★★

我们先上代码,分别新建 a.cb.cc.c三个文件,每个文件的代码都是:

void f()
{
    debug(__FILE__);
}

如果有文件d.c同时继承abc三个文件,代码如下:

inherit __DIR__ "a";
inherit __DIR__ "b";
inherit __DIR__ "c";

void f()
{
    ::f();
}

那么在d.c中调用 ::f() 是哪个继承对象中的方法?a?b?c?另外,如果在d.c中不重写f()的话,d.c 调用 f()::f() 一样吗?

对以上二个问题我不给答案,欢迎留言回答,如果不确定可以自己测试一下。这里主要是补充的冷知识是,在继承多个同名方法时,我们可以明确父对象,继承指定对象中的方法。如:

inherit __DIR__ "a";
inherit __DIR__ "b";
inherit __DIR__ "c";

void f()
{
    a::f();
    b::f();
    c::f();
}

使用类::方法()的方式调用继承对象(类)的方法,这是一个好习惯,推荐在调用继承对象的方法时使用这种格式,如果可能重复继承时,也养成重写方法并明确指定的习惯,否则不只是可能继承错方法,还会报重复继承的警告。

关于create()方法

冷门程度:★★★★

实用程度:★★★★★

create()这个apply方法的作用大家都知道,但是我们多数人不清楚这个方法是可以带参数的。如下示例:

varargs void create(string name, int age)
{
    // 你的代码
}

然后,我们可以使用 new(file, name, age) 的方式个性化的初始化对象,这在某些情况下非常有用,很多WIZ是重复建一堆类似的文件,而通过参数特别是可变参数我们其实可以实现一个文件生成无数个不同属性的对象,配合虚拟对象功能 compile_object() 你可以很容易的用一个文件实现无限的世界。

关于函数的不定参数

冷门程度:★★★★★

实用程度:★★★★

我们知道varargs这种可变参数的使用,这种方式参数的数量还是相对固定的,其实还有一种不定参数的用法,如下示例:

void test(mixed *x...)
{
    printf("x = %O\n", x);
}

运行后自己传参数调用试试?你传的所有参数都成为数组 x 的元素,而且没有限制参数数量。如:test()test(1,2,3)test("hello",1,2,({3,4,5}))test(1,3,5,(["name":"abc","age":12]),3.14)

这个用法和使用 varargs 相比还有一个很大的不同点,不传参数时函数参数在 varargs 定义的函数中是未定义状态(undefinedp/nullp),不管任何类型默认初始化为整型值为0,但在这里是初始化为空数组。

提示:配合数组延展操作符操作还可以直接把数组x做为参数以x...的方式传给函数。

关于函数参数传引用

冷门程度:★★★★★

实用程度:★★★

函数参数传的是值,但其它高级语言中有传引用(传地址)的方式,你知道在LPC中可以传引用吗?,看示例:

void test(int ref x)  // 声明x为引用参数,接收变量的引用
{
    x = 100;  // 直接修改引用指向的内存地址,即修改原始变量a
}

int main(object me, string arg)
{
    int a = 1;
    test(ref a);  // 传递a的引用给函数
    printf("a = %d\n", a); // 输出100(a的值已被函数修改)

    return 1;
}

使用ref可以传引用,函数中对值的修改会直接改变参数的值。

关于函数指针

冷门程度:★★

实用程度:★★★

我们知道对函数指针 f 可以使用 evaluate(f, xxx)来执行,能否像直接调用函数一样调用执行?也可以,具体格式是:(*f)(xxx)

另外,在LPC语言中也有匿名函数,如下代码:

    int x = evaluate(
        function(int x, int y) {
            return x * y;
        },
        123, 456);

    printf("x = %d\n", x);

关于switch

冷门程度:★★★★★

实用程度:★★★★★

我们对switch选择语句的使用都不陌生,相对C语言中case只能用整型类型(int、char等)的常量,而LPC语言中增加了字符串类型的支持。但是绝大多数人不知道下面这种用法:

int switchTest(int x)
{
  switch (x)
  {
  case 0..0:
    return 0;
  case 11..20:
    return 2;
  case 1..10:
    return 1;
  case 21..:
    return 999;
  case ..-20:
    return 3;
  default:
    return 9;
  }
}

关于结构体(class/struct)

冷门程度:★★★★★

实用程度:★★★

结构体目前在项目开发中用的少,但也是有用的,那关于结构体的以下语法你知道吗?

// 结构体class
class example {
    string name;
    int age;
}

#define Example class example

Example test(string name, int age)
{
    Example test = new (Example, name:name, age:age);

    return test;
}

亮点在哪里?哦,在这里:

    Example test = new (Example, name:name, age:age);

在变量声明时可以直接给元素赋值,具体格式为:new(class 结构体, 参数名:参数值),这里参数用法非常像其它编程语言中的高级特色命名参数

关于socket

冷门程度:★★★

实用程度:★★★★★

我们知道 socket 是对TCP/UDP协议的封装,通过相关函数我们可以实现任何基于TCP和UDP协议的功能,包括模拟 HTTP 协议实现网页请求,这样我们就可以读取网页或向网页发送数据。

因为这个三言两语说不清楚,具体可见:https://bbs.mud.ren/threads/64

关于预处理指令

冷门程度:★★★★★

实用程度:★★

我们知道可以用function_exists判断对象中指定函数是否存在,但怎么判断驱动中efun是否存在?大家能想到的应该是判断某个包的宏是否定义的方式判断当前驱动是否支持某个efun,如:

#ifdef __PACKAGE_DB__
    debug("支持数据库!");
#endif

能否直接判断某个efun是否定义了呢?这是很多个都不知道的,看示例:

#if efun_defined(parse_command)
    // todo
#else
    // todo
#endif

使用efun_defined可以判断驱动中某个efun是否存在,你学废了吗?

关于系统指令的调用

冷门程度:★★★★★

实用程度:★★★★

这是绝大多数人都不知道的知识,当驱动编译时启用了PACKAGE_EXTERNAL后,驱动就支持external_start函数,使用这个函数可以调用操作系统中的指令和脚本。具体介绍见:https://bbs.mud.ren/threads/244

关于time_expression

冷门程度:★★★

实用程度:★★★★

我们有时需要测试代码的时间复杂度,大家可能知道int time_expression( mixed expr );这个函数调用的用法,但是不知道可以直接使用语句块的方式int cost = time_expression{ /*测试代码*/};,如下示例:

void create()
{
    int cost;
    mixed arg;

    cost = time_expression
    {
        mapping m = (["a":1, "b":2, "c":3]);
        arg = m["a"];
    };
    printf("代码执行耗时:%d 微秒, arg = %O", cost, arg);

    cost = time_expression
    {
        int *list = ({1, 2, 3, 4, 5});
        arg = list[1];
        // arg = list[2..3];
    };
    printf("代码执行耗时:%d 微秒, arg = %O", cost, arg);

    cost = time_expression
    {
        string str = "abcde";
        arg = str[1];
        // arg = str[1..2];
    };
    printf("代码执行耗时:%d 微秒, arg = %O", cost, arg);
}

提示:time_expression严格说算是语言结构,类似的还有catch,如下用法:

void create()
{
    string err;

    err = catch
    {
        throw("出错啦!");
    };

    debug_message(err);
}

关于FluffOS的一些efun

冷门程度:★★★

实用程度:★★★

内容更新中~...不!没有回贴不更新

京ICP备13031296号-4