Gnome Vala 語言基礎

去年的一篇文章,從自己的博客搬了過來。

介紹

Vala 是一門新興的編程語言,由 Gnome 主導開發,支持很多現代語言特性,借鑑了大量的 C# 語法,Python 的手感,C 的執行速度,Vala 最終會轉換爲 C 語言,然後把 C 代碼編譯爲二進制文件,使用 Vala 編寫應用程序和直接使用 C 語言編寫應用程序,運行效率是一樣的,但是 Vala 相比 C 語言更加容易,可以快速編寫和維護。

編輯器選擇

GNOME Builder 感覺不怎麼好用,但是裏面有項目模板,可以用它來新建項目,哈哈哈。

推薦用 VSCode 來寫 vala,配上插件手感爽爽的。

編譯

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 字符型
unichar 32 位 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

其他高級特性可以從這裏瞭解一下:https://valadoc.org/glib-2.0/string.html

數組

定義二維數組首先要指定一個數據類型,然後緊跟着 [] 符號,用 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 API Vala Example
g_topic_foobar() GLib.Topic.foodbar() GLib.Path.get_basename()

相關鏈接

官方文檔:https://wiki.gnome.org/Projects/Vala/Documentation

源碼地址:https://github.com/GNOME/vala

Vala 語言中一些好玩的:https://segmentfault.com/a/1190000004179876

文章來源:https://rekols.github.io/2018/10/28/vala-beginner/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章