Vala编程文档参考
2019-02-17 22:04:19 阿炯

Vala 是 GObject 类型系统编译器,官方的介绍是“Vala is a new programming language that aims to bring modern programming language features to GNOME developers without imposing any additional runtime requirements and without using a different ABI compared to applications and libraries written in C. “

Vala 使用类似 C# 的语法,属高级语言,比C语言易用不错。它的编译流程是先将 vala 源代码编译成同等的 C 源代码,再使用本地C编译器将C源代码编译中本地机器码。Vala 和 C 的程序执行效率相当,但是 Vala 的开发效率要高的多。Vala就拥有了C,GLib,GObject的全部兼容性优点,和C#的美观。

Vala 是面向对象的,默认基于 GObject 对象系统,但是不强制基于它,还可以基于其它的类型系统。很多程序库都有它的绑定且在增加。Vala 的编译器系统也是使用 Vala 语言开发的,又基于 GLib 移植性是没有问题的。valac是将vala代码翻译为C代码,然后调用GCC编译的,所以我们来看翻译为C的这段代码是什么样子的:使用命令
valac -C freeoa.vala

没有错误的话会在当前目录生成freeoa.c文件。

Glib&GObject:

GLib是GTK+和GNOME工程的基础底层核心程序库,是一个综合用途的实用的轻量级的C程序库,它提供C语言的常用的数据结构的定义、相关的处理函数,有趣而实用的宏,可移植的封装和一些运行时机能,如事件循环、线程、动态调用、对象系统等的API。它能够在类UNIX的操作系统平台(如LINUX),WINDOWS,OS2和BeOS等操作系统台上运行。Glib提供一系列常用的、标准的C语言结构以及功能函数,例如链表、字符串、树等等。

简单的说,GObject对象系统是一个建立在GLIB基础上的,用C语言完成的,具有跨平台特色的、灵活的、可扩展的、非常容易映射到其它语言的面向对象的框架。利用Gobject,你可以使用面向对象的思想进行程序开发,而不必专门去学习一门面向对象的语言。然而GObject的代码不太直观,类似链表而不是树形结构,而且代码较冗长,通常一个很小的功能却要上百行的代码。


Vala is a new programming language that aims to bring modern programming language features to GNOME developers without imposing any additional runtime requirements and without using a different ABI compared to applications and libraries written in C.

Vala目标是把现代语言的特性带到GNOME开发上,不需要附加的运行时。

valac, the Vala compiler, is a self-hosting compiler that translates Vala source code into C source and header files. It uses the GObject type system to create classes and interfaces declared in the Vala source code.

valac 是vala的编译器,它是一个"自编译"的编译器,可以把vala源代码翻译成c源代码和头文件,使用GObject类型系统。

The syntax of Vala is similar to C#, modified to better fit the GObject type system. Vala supports modern language features as the following:
    Interfaces
    Properties
    Signals
    Foreach
    Lambda expressions
    Type inference for local variables
    Generics
    Non-null types
    Assisted memory management
    Exception handling
    Type modules (Plugins)

Vala的语法与C#类似,Vala语言的诸多特性:

1.语法类似C#

语法是语言的外观和形式。怎么样都行,不过当然还是越简洁明了越好。
比如引入库文件,C语言使用类似:
#include 'stdio.h'

C++语言除了上面的方式,还可以使用类似:
#include <stdio.h>

Java语言使用:
import javax.swing.*

而Vala采用类似C#的语法,所以Vala语言是使用类似:
using Gtk;

2.基本库是GLib

C语言的基本库是标准C语言库,其定义了基本数据类型等等信息。类似的,Vala的基本库是GLib。GLib是使用C语言编写的,但我们完全可以不去考虑GLib和标准C语言库的关系。Vala的基本库就是GLib,GLib定义了一系列的基本数据类型等等信息。这些基本类型包含C语言的基本数据类型,又有扩充,包括int,int8,int16,int32,int64,bool,unichar等等;不用去考虑它扩充了C语言的哪些部分,就当作是Vala本有的,这样便于理解。

3.基本类型有方法

如:
int a = -4.abs();
string s = a.to_string();
int b = int.max(1, 2);

4.支持逐字字符串

语法:"""..."""

5.支持字符串模板

如:
string name = "ming";
stdout.printf(@"Welcome, $name");
stdout.printf(@"3 + 2 = $(3 + 2)");

6.支持类型推导(可以使用var定义变量)!

如:
var obj = new object();
var map = new HashMap<string, int>;
var str = "hello, world";
var arr = new int[10];

7.没有隐式继承

定义类需要写明继承自Object(GLib.Object),如:
public class Foo : Object {

}

8.没有方法重载

为了保持与C兼容,没有方法重载功能,需要使用不同的方法名词来定义不同的方法(不论参数的个数、类型、顺序是否相同)。


Gnome Vala 语言基础

介绍
Vala 是一门新兴的编程语言,由 Gnome 主导开发,支持很多现代语言特性,借鉴了大量的 C# 语法,Python 的手感,C 的执行速度,Vala 最终会转换为 C 语言,然后把 C 代码编译为二进制文件,使用 Vala 编写应用程序和直接使用 C 语言编写应用程序,运行效率是一样的,但是 Vala 相比 C 语言更加容易,可以快速编写和维护。

编辑器选择
GNOME Builder 不怎么好用,但是里面有项目模板,可以用它来新建项目。

编译
debian 系发行版可通过以下命令来安装 vala 编译器:
sudo apt install valac

Vala 的源代码是以 .vala 扩展名保存的,valac 是 Vala 语言的编译器,它负责将代码编译为二进制文件,编译 Vala 代码很简单,把所有源码文件作为命令行参数提供给 valac,以及编译器标志,类似 javac 编译方式,可以在 pkg 参数后加上需要链接的包,比如:
$ valac hello.vala --pkg gtk+-3.0

它会生成一个二进制文件。

第一个 Vala 程序
class Application : GLib.Object {
    public static int main(string[] args) {
        stdout.printf("hello world\n");
        return 0;
    }
}

可以看到 Vala 的类定义和其他语言非常相似,一个类代表了一个对象的基本类型,基于该类创建的对象拥有相同属性,vala 程序入口点在 main() 函数,Vala 的 main 函数是程序的入口点,和 C/C++ 是一样,main 方法不需要在类中定义,如果要在一个类中定义的话必须加上 static 修饰符;其中 stdout 是 GLib 命名空间中的对象,stdout.printf(...) 这行告诉 Vala 调用 stdout 对象中的 printf 方法,这是很常见的语法。

注释
// Comment continues until end of line
/* Comment lasts between delimiters */

/**
* Document comment
*/

这个跟 C/C++ Java 等这些流行编程语言风格一样,支持单行注释和多行注释,所以不需要过多的解释。

数据类型

基本数据类型
基本数据类型概述
char字符型
unichar32 位 Unicode 字符
int, uint整型
long, ulong长整型
short, ushort短整型
int8, int16, int32, int64, uint8...固定长度的整型,其中的数字分别代表各类型所占的位的长度
float, double浮点数
bool布尔型
struct结构体
enum枚举型,内部表述为整型


使用例子:
char c = 'u';
float percent_tile = 0.75f;
const double PI = 3.141592654;
bool is_exists = false;

struct Vector {
    public double x;
    public double y;
    public double z;
}

enum WindowType {
    TOPLEVEL,
    POPUP
}

用法和 C 没有多大的区别,在不同平台上这些类型长度都是不一样的,可以使用 sizeof 操作符获取当前平台下相应类型的长度(以字节位单位):
ulong nbytes = sizeof(int32);

另外还可以对数据类型使用 .MIN 和 .MAX 的方法来获取相应数据类型的最小值和最大值,它会返回对应数据类型的值,比如使用 int.MAX 获取当前 int 整型所能表示数值范围,例子:
int32 max_value = int32.MAX;
int64 max_value2 = int64.MAX;
float max_value3 = float.MAX;
double max_value4 = double.MAX;

字符串类型
在 Vala 中使用 string 关键字来定义字符串类型变量,都是以 UTF-8 编码,而且还不可更改。除了常规用法,Vala 还支持使用 @ 符号表示字符串模板,可以将内部以 $ 开头的变量或者表达式展开,得到最终的字符串:
// 常规用法
string text = "Hello World";

// 字符串模板用法
int a = 10;
int b = 20;
string s = @"$a * $b = $(a * b)"; // => "10 * 20 = 200"

// 支持 ==、!= 运算符
string a1 = "hello";
string a2 = "world";
bool is_right = (a1 == a2);

string 可以使用 [start : end] 运算符来分割字符串,start 是起始位置,end 是末尾位置,如果使用负值则是从右方向到左方向的偏移。
string greeting = "hello, world";
string s1 = greeting[0 : 5];    // => "hello"
string s2 = greeting[-5 : -1];  // => "worl"

当然使用 [index] 访问字符串的索引,访问字符串的某个字节,但是不能给字符串中的某个字节赋新值,因为 Vala 字符串不能改动。许多基本类型都可以转换为字符串类型,或者字符串类型转换为其类型:
// 字符串型转换为基本数据类型
bool b = bool.parse("false");
int i = int.parse("21323");
double d = double.parse("3.14");
string s1 = i.to_string();
string s2 = 22222.to_string();

真是神奇,还可以直接从常量使用 to_string() 转换为字符串类,还可以使用 in 操作符来判断一个字符串是否被另一个字符串所包含:
string s1 = "keep on";
string s2 = "keep";
bool contains = s2 in s1;  // true

其他高级特性可以从这里了解一下。

数组
定义二维数组首先要指定一个数据类型,然后紧跟着 [] 符号,用 new 操作符来创建,比如 int[] arr = new int[10],创建了一个长度为 10 的整型数组。数组的长度包含在数组中的 length 数据成员中,比如 int count = arr.length,所以定义数组的方法就是:
数据类型[] 数组名 = new 数据类型[数组长度]
int[] a = new int[5];
int[] b = { 22, 222, 2222, 333, 444 };

它跟字符串类似,可以使用 [start : end] 来分割数组,而且不影响源数组:
int[] c = b[1 : 3];

类型推断
Vala 拥有类型推断的机制,定义一个变量时只需用 var 关键字声明一个模糊不清的类型,而不需要指定一个具体类型,具体类型取决于赋值表达式右边,这种机制有助于在不牺牲静态类型功能的前提下减少不必要的沉余代码;在 C++11 新标准后也有对应的 auto 关键字,让编译器帮助我们分析表达式的类型。
var a = new Application();  // 等价于 Application a = new Application();
var s = "hello";            // 等价于 string s = "hello";
var list = new List<int>();

缺点就是只能用于局部变量,优点就是让风格更简洁,对于那些拥有多参数类型的方法、长名称的类...

操作符

=
赋值操作符,操作符左边必须是标识符,右边必须是相应类型的值或者引用。

+, -, *, /, %
基本算术运算符,需要提供左右操作数,符号 + 可用于串联字符串。

+=, -=, /=, *=, %=
算术运算符,用在两个操作数中间。

++, --
自增、自减操作符,可以放在操作数的前面或后面,分别代表先运算赋值再返回值,和先返回值再运算赋值。

|, ^, &, ~, |=, &=, ^=
位操作符,分别位按位或、按位异获、按位与、按位取反,第二组带等号的操作符和前面的算术运算符类似。

<<, >>
位移操作符,对操作数左边的数字按位移动操作符右边数值大小的位数。

<<=, >>=
位移操作符,对该标志符的值按位移动操作符右边数值大小的位数,并将结果的值赋予操作符左边的标志符。

==
相等操作符

<, >, <=, >=, !=
不等操作符,根据符号代表的意思来对左右两边不等的形式,返回结果为 bool 型的值。

!, &&, ||
逻辑操作符,分别为逻辑非、逻辑与、逻辑或,第一个操作数为单个,其他两个则是两个操作数。

?:
三元条件操作符,比如 condition ? value if true : value if false

??
空否操作符,表达式 a ?? b 等价于 a != null ? a : b,这个操作符在引用一个空值的时候非常有用:
stdout.printf("Hello, %s!\n", name ??? "unknown person");

in
此操作符判断右操作数是否包含左操作数,适用于 arrays, strings, collections 等其他拥有 contains() 方法的类型,用于 string 类型的时候代表字符串匹配。

控制结构

Vala 基本控制结构包括:顺序结构、选择结构、循环结构;顺序结构就是按照顺序执行每一条语句;选择结构就是给定的条件进行判断,然后根据判断结果决定执行哪一段代码;循环结构就是在条件成立下反复执行某一段代码。
while (a > b) { a--; }

do { a--; } while (a > b);

for (int i = 0; i < 100; ++i) {
    // 跳过本次循环
    if (i == 60) {
        continue;
    }
    // 终止循环
    if (i == 70) {
        break;
    }
    stdout.printf("%d\n", i);
}

switch (a) {
  case 1:
     stdout.printf("one\n");
     break;
  case 2:
     stdout.printf("two\n");
     break;
  default:
     stdout.printf("unknown\n");
     break;
}

可以看到这跟 C 语言语法是一样的,包括 if else... 有一点 C 程序员需要注意的:条件判断必须是以布尔型为准,比如判断一个非0或者非 null 条件的时候,必须显式调用,比如:if (object != null) 或 if (number != 0),而不是 C 程序员惯用的 if (!object) 和 if (!number) 形式。

方法
在 Vala 中函数被称为方法(method),不论是定义在类中还是类外,Vala 官方文档坚持使用“方法”这个术语。
int method_name(int arg1, Object arg2) {
    return 1;
}

所有 Vala 中的方法都有对应 C 的函数,所以可以随意附带任意数量的参数;Vala 不支持方法重载,就是不允许多个方法拥有相同的方法名,但是可以通过提供的“默认参数”特性来完成,只需定义一个方法并定义参数的默认值,之后不需要显式的指明参数值就可以调用方法。
void f(int x, string s = "hello", double z = 0.5) { }

调用此方法的可能情况有:
f(2);
f(2, "world");
f(2, "world", 0.15);

匿名方法
匿名方法(Anonymous Methods)也被称为 lambda 表达式,或者闭包,在 Vala 中可以使用 => 符号来定义。

命名空间
命名空间的用法和 C++ 没什么两样:
namespace NamespaceName {
    // ...
}

以上两个中括号中所有元素都属于命名空间 NamespaceName 的,在外部代码中引用其命名空间中的内容必须使用完整的命名,或者在文件中导入命名空间后直接使用,比如:"using Gtk",导入了一个名为 Gtk 的命名空间,那么就可以简单使用:Window 来替代 Gtk.Window。但在某些情况下还是推荐用完整名,避免歧义,比如 GLib.Object 和 Gtk.Object,在 Vala 编程中,命名空间 GLib 是被默认导入的。


Vala 定义类的方法和 C# 是类似的,与结构体不同,基于类的的内存是在堆上分配的,一个简单类可以这么定义:
public class MyClass : GLib.Object {
    public int first_data = 0;
    private int second_data;

    // 构造函数
    public MyClass() {
        this.second_data = 0;
    }

    // 方法
    public int method_1() {
        return this.second_data;
    }
}

该类是 GLib.Object 的子类,拥有 GLib.Object 所有成员,由于继承了 GLib.Object,那么 Vala 中某些特性可以直接访问 Object 的特性来获取。此类被声明为 public 型,此声明表示该类可以被外部代码文件直接引用,等同于将该类声明在一个 C 代码的头文件中。

Vala 提供了四个不同的访问权限修饰符:
关键字描述
public没有访问限制
private仅限于类内部,没有指明访问修饰符情况下默认是 private
protected访问仅限该类,或者继承自该类的内部
internal访问仅限于从属同个包的类


使用类的方法:
MyClass t = new MyClass();
t.first_data = 10;
t.method_1();

由于 Vala 不支持重载方法,因此重载构造方法也是不支持,Vala 支持命名构造方法,如果需要提供多个构造方法只需要增加不同的命名即可:
public class Button : Object {
    public Button() {
    }

    public Button.width_label(string name) {
    }

    public Button.from_stock(string stock_id) {
    }
}

// 实例化方法
new Button();
new Button.width_label("click me");
new Button.from_stock(Gtk.STOCK_OK);

虽然 Vala 负责管理内存,但是还是提供析构方法,定义析构的方法和 C#、C++ 类似:
class Demo : Object {
    ~Demo() {
        stdout.printf("in destructor");
    }
}

信号
信号这个特性是 GLib 库中的 Object 类提供的,可以继承该类来使用这个特性,Vala 的信号相当于 C# 的事件。信号是作为一个成员在类中定义的,信号处理方法可以使用 connect() 方法来进行动态添加:
public Test : GLib.Object {
    public signal void sig_1(int);

    public static int main(string[] args) {
        Test t1 = new Test();

        t1.sig_1.connect((t, a) => {
            stdout.printf("%d\n", a);
        });

        t1.sig_1(100);
        return 0;
    }
}

以上代码新建了一个 Test 类,然后将一个信号的处理方法赋给 sig_1 成员,在这使用了 lambda 表达式,从这个 lambda 表达式来看,此方法接受了两个参数,分别是 t 和 a,并没有指明参数类型。目前只能使用 public 修饰符,发出信号直接调用 signal 类型。

泛型
Vala 有一套运行时泛型系统,某个类的实例指定一个或一组数据,比如可以实现一个针对特定类型对象的链表:
public class Wrapper<G> : GLib.Object {
    private G data;
    public void set_data(G data) {
        this.data = data;
    }
    public G get_data() {
        return this.data;
    }
}

为了实例化该类,必须指定一个数据类型,比如 Vala 内置的 string 类型,可以简单如下创建实例:
var wrapper = new Wrapper<string>();
wrapper.set_data("test");
var data = wrapper.get_data();

容器
Gee 是由 Gnome 使用 Vala 开发的一套容器类库,如果要使用 Gee 需要在系统中安装该库,然后链接 Gee 库,需要在编译的时候加上 --pkg gee-0.8 参数。基本容器类型有:
关键字描述
Lists一组相同类型有序元素,使用索引来访问
Sets一组无序任意类型元素
Maps一组无序任意类型元素,使用索引来访问


对于容器来说普遍接口是可送代接口(Iterable),也就是可以使用一组标准的方法来遍历容器类中的各个存储对象,或者使用 Vala 提供的 foreach 语法。

多线程
Vala 程序可以拥有多个线程,允许在同一时间内做多个事情,可以通过使用 GLib 中的 Thread 类静态方法来完成。
void *thread_func() {
    stdout.printf("Thread running!\n");
    return null;
}

int main(string[] args) {
    if (!Thread.supported()) {
        stderr.printf("Cannot run without threads.\n");
        return 1;
    }

    try {
        Thread.create(thread_func, false);
    } catch (ThreadError e) {
        return 1;
    }
    return 0;
}

这是一个简单的程序,尝试创建运行一个新线程,代码在 thread_func() 方法中执行,在运行时检查线程是否支持,为了支持多线程,可以在编译时加上下面的参数:
valac --thread simple.vala

为了等待一个线程完成,可以使用 join() 方法来完成。

使用 GLib 库
GLib 包含了大量的工具,包含绝大多数标准 C 函数实现的封装,这些工具可以用于 Vala 平台上,其中参考是基于 GLib 的 C API 形式:
C APIValaExample
g_topic_foobar()GLib.Topic.foodbar()GLib.Path.get_basename()


相关链接

官方文档
valadoc for glib-2.0
Vala编程手册.pdf
Vala内存管理.pdf
Vala语言中一些好玩的
Ubuntu论坛上的Vala编程手册
看完此文你的Vala基础就差不多了