Vala编程语言教程-语言元素
语言元素
方法
在Vala中,函数无论是否定义在类内部均称为方法。下文将统一使用“方法”这一术语。
int method_name(int arg1, Object arg2) {
return 1;
}
此代码定义了一个名为 method_name
的方法,接受两个参数(一个整数值,一个对象引用,传递方式如前述),并返回整数值1。
所有Vala方法均为C函数,因此可接受任意数量的参数并返回一个值(若方法声明为 void
则不返回值)。通过将数据存放在调用方已知的位置,可近似实现多个返回值。
Vala的方法命名约定为全小写字母加下划线分隔单词。这对习惯驼峰命名法的C#或Java程序员可能稍显陌生,但此风格能使Vala代码与C/GObject库保持统一。
同一作用域内不允许存在同名但签名不同的方法(“方法重载”):
void draw(string text) {
}
void draw(Shape shape) { } // 不可能
因为Vala生成的库需兼容C语言使用。在Vala中应改为:
void draw_text(string text) {
}
void draw_shape(Shape shape) {
}
通过略微不同的命名避免冲突。其他语言中常通过重载实现便捷方法链式调用,但是在Vala中不允许, 例如:
void f(int x, string s, double z) {
}
void f(int x, string s) {
f(x, s, 0.5);
} // 不可能
void f(int x) { f(x, "hello"); } // 不可能
此时可使用Vala的默认参数功能,仅需定义单个方法即可实现类似行为。可为方法末尾参数设置默认值,调用时无需显式传递:
void f(int x, string s = "hello", double z = 0.5) { }
合法的调用方式包括:
f(2); f(2, "hi"); f(2, "hi", 0.75);
Vala也支持真正的可变参数列表(如 stdout.printf()
),但不建议过度使用,具体方法后续说明。
Vala对方法参数和返回值进行基础可空性检查。若允许参数或返回值为 null
,类型符号需以 ?
修饰。此信息帮助编译器执行静态检查及运行时断言,避免空引用错误。
string? method_name(string? text, Foo? foo, Bar bar) { // ... }
此例中,text
、foo
和返回值可为 null
,但 bar
不可为空。
委托
delegate void DelegateType(int a);
委托表示方法,允许像对象一样传递代码块。上例定义了一个名为 DelegateType
的新类型,表示接受整型参数且无返回值的方法。任何签名匹配的方法均可赋值给此类型的变量,或作为此类型的参数传递。
delegate void DelegateType(int a);
void f1(int a) { stdout.printf("%d\n", a); }
void f2(DelegateType d, int a) { d(a); // 调用委托 }
void main() {
f2(f1, 5); // 将方法作为委托参数传递给另一方法
}
此代码执行 f2
,传递 f1
的引用和数值5。f2
随后调用 f1
并传递该数值。
委托也可局部创建。成员方法亦可赋值给委托,例如:
class Foo {
public void f1(int a) {
stdout.printf("a = %d\n", a);
}
delegate void DelegateType(int a);
public static int main(string[] args) {
Foo foo = new Foo(); DelegateType d1 = foo.f1; d1(10); return 0;
}
}
匿名方法 / 闭包
(a) => { stdout.printf("%d\n", a); }
匿名方法(亦称lambda表达式、函数字面量或闭包)可通过 =>
操作符定义。参数列表在操作符左侧,方法体在右侧。
单独存在的匿名方法(如上例)无实际意义,需直接赋值给委托类型的变量或作为参数传递给其他方法。
注意参数和返回类型均未显式声明,而是通过委托签名自动推断。
将匿名方法赋值给委托变量:
delegate void PrintIntFunc(int a);
void main() {
PrintIntFunc p1 = (a) => { stdout.printf("%d\n", a); };
p1(10);
// 若方法体仅含单个语句,花括号可省略:
PrintIntFunc p2 = (a) => stdout.printf("%d\n", a); p2(20);
}
将匿名方法传递给其他方法:
delegate int Comparator(int a, int b);
void my_sorting_algorithm(int[] data, Comparator compare) {
// ... 在此某处调用 ``compare`` ...
}
void main() {
int[] data = { 3, 9, 2, 7, 5 };
// 匿名方法作为第二个参数传递:
my_sorting_algorithm(data, (a, b) => {
if (a < b) return -1; if (a > b) return 1; return 0;
});
}
匿名方法实现闭包:
可访问外部方法的局部变量:
delegate int IntOperation(int i);
IntOperation curried_add(int a) {
return (b) => a + b; // ``a`` 是外部变量
}
void main() { stdout.printf("2 + 4 = %d\n", curried_add(2)(4)); }
此例中,curried_add
(见柯里化)返回一个保存了a
值的新方法,随后立即以4为参数调用该方法,得到两数之和。
命名空间
namespace NameSpaceName { // ... }
大括号内的所有内容均属于NameSpaceName
命名空间。外部代码需使用完全限定名,或通过using
声明导入:
using NameSpaceName; // ...
例如导入Gtk
命名空间后,可直接写Window
代替Gtk.Window
。当存在歧义时(如GLib.Object
与Gtk.Object
),需使用完全限定名。
GLib
命名空间默认已导入(相当于每个Vala文件首行隐式包含using GLib;
)。
未放入命名空间的代码属于匿名全局命名空间。存在歧义时可用global::
前缀显式引用全局命名空间。
命名空间可嵌套声明(如NameSpace1.NameSpace2
),或通过类型定义的命名约定实现(如class NameSpace1.Test { … }
)。
结构体
struct StructName { public int a; }
定义值类型的复合结构体。Vala结构体可有限度地包含方法,且支持私有成员(需显式使用public
修饰符)。
结构体初始化方式:
// 无类型推断
Color c1 = Color();
// 或
Color c1 = {}
Color c2 = { 0.5, 0.5, 1.0 };
Color c3 = Color() { r
ed = 0.5, green = 0.5, blue = 1.0
};
// 含类型推断
var c4 = Color();
var c5 = Color() { red = 0.5, green = 0.5, blue = 1.0 };
结构体通过栈/内联分配,赋值时执行值拷贝。
类
class ClassName : SuperClassName, InterfaceName { }
定义引用类型的类。与结构体不同,类实例在堆上分配。更多面向对象语法详见后续章节。
接口
interface InterfaceName : SuperInterfaceName { }
定义不可实例化的接口类型。需在非抽象类中实现其抽象方法才能创建实例。Vala接口比Java/C#更强大,可用作混入(mixin),后续面向对象编成会详细讲解。