新手必看-如何安裝配置vlang運行環境(linux,macOS篇)
前置條件
發稿截止前只有Linux 或者 macOS系統能編譯通過。
你需要安裝clang
或gcc
如果是macOS上需運行xcode-select --install,如果沒有安裝XCode或XCode工具,請安裝一下。
如果是centos,需要檢查是否安裝有clang
# which clang
如果沒有,請安裝
# yum install -y clang
開始安裝
筆者本人使用的是macOS,以下是我的環境:
1.從github克隆vlang項目代碼
# git clone https://github.com/vlang/v
# cd v/compiler
# make
這里提示wget
命令沒有,使用brew
安裝
# brew install wget
沒有brew
的同學請先安裝brew
# /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
然后再執行make
命令即可。
到此,macOS會正常編譯通過,而linux可能會報以下錯誤:
解決辦法:刪除cc
,創建軟連接cc->clang
,使用clang代替cc來編譯,命令如下:
# cd /usr/bin
# sudo rm cc
# sudo ln -s clang cc
然后再回到compiler
文件夾,執行命令:
# make clean && make
循序漸進:V語言0.1.3版本更新,vlang命令介紹
6月26日,vlang官網(https://vlang.io)已更新版本到0.1.3
,同時發布了linux平台及macOS平台編譯好的二進制文件供下載,windows平台暫無,不久會發布。
該版本修復了vlang之前編譯會報錯的+=
、*=
等運算符,同時也更新了v命令,現整理v命令如下:
1.直接運行v,將進入交互式編程環境(REPL
)
2.編譯.v
文件
v file.v
這里將file.v
文件編譯為二進制可執行文件file
,如需運行,請在執行./file
。
默認情況下,生成的可執行文件的名稱和.v
文件名相同,如果在編譯時修改,可執行v -o <程序名> file.v
3.編譯並運行.v
文件
v run file.v
4.細心的同學會發現,每次編譯完.v
文件后,不僅會生成二進制可執行文件,同時還會生成一個file.dSYM
的文件夾,對於有潔癖的同學來說時難以忍受的,其實只要運行以下命令就會只生成純粹的可執行文件,不會生成“煩人”的file.dSYM
文件夾
v -prod file.v
P.S:以上-o
、-prod
參數可以混合使用😊
vpm
vpm是一個v語言包管理工具,它用v編寫而成。
github地址:https://github.com/yue-best-practices/vpm
前置條件(Precondition)
V
語言版本(v0.1.13
)。
- 安裝
git
,因為目前包是通過git clone
的方式下載的。 - 配置
VROOT
環境變量,內容指向v語言源碼路徑。
安裝(Install)
- 在任意目錄下,
git clone https://github.com/yue-best-practices/vpm
- 執行命令
v -prod .
即可編譯出vpm
可執行程序
命令(Commands)
命令/Command | 參數/Params | 釋義/Description |
---|---|---|
-v /version |
版本信息,目前版本是0.0.1 Show version,the current version is 0.0.1 |
|
init |
<project-name> |
創建.vpm.json 文件Create the .vpm.json file |
get |
<git-url> <pkg-name> |
從<git-url> 中獲取包。Fetch package from the git repo. |
install |
安裝.vpm.json 文件中的包。Install the package from the .vpm.json file. |
|
-h /help |
顯示幫助信息。 Show help information. |
|
clean |
刪除.vpm.json 文件。Delete the .vpm.json file. |
V語言簡介
V語言是一種靜態類型的編譯型編程語言,它與Go類似,也受到 Oberon、Rust、Swift語言的影響。
V語言是一種非常簡單的語言,閱讀此文檔將花費你大約半小時的時間來學習完幾乎整個V語言。
盡管很簡單,但它為開發人員提供了很多動力。 任何你可以用其他語言完成的事情,你都可以使用V語言來做。
V語言將在2019年6月開源發布。
V語言官網地址:https://vlang.io
V語言Github地址:https://github.com/vlang/v
Hello World
作為學習編程語言的傳統,Hello World環節必不可少!
fn main() { println('hello world') }
函數用fn
聲明。 返回類型在函數名稱后面。 在這種情況下,main
不返回任何內容,因此省略了類型。
就像在C和所有相關語言中一樣,main
方法是程序入口。
println
是V語言為數不多的內置函數之一,它將值打印到標准輸出。
注釋
V語言的注釋中規中矩,和眾多語言一樣。
// 單行注釋 /* 多行注釋 /* 嵌套注釋 */ */
函數
fn add(x int, y int) int { return x + y } fn sub(x, y int) int { return x - y } fn main() { println(add(77, 33)) println(sub(100, 50)) }
同樣,類型出現在參數的名稱之后。
就像在Go和C中一樣,函數不能重載。 這簡化了代碼並提高了可維護性和可讀性。
變量
fn main() { name := 'Bob' age := 20 large_number := i64(9999999999) println(name) println(age) println(large_number) }
使用:=
聲明和初始化變量。 這是在V語言中聲明變量的 唯一方法。這意味着變量始終具有初始值。
變量的類型是從右側的值推斷出來的。要強制使用其他類型,請使用類型轉換:表達式 T(v)
將值 v
轉換為類型 T
。
與大多數其他語言不同,V語言中只允許在函數中定義變量。 不允許使用全局(模塊級別)變量。
V語言中沒有全局變量。
基本類型
V語言中共有20個基本類型,如下:
bool
string
- 有符號整數:
i8
、i16
、i32
、i64
- 無符號整數:
u8
、u16
、u32
、u64
- 別名:
byte
(u8
)、int
(i32
)、rune
(i32
,表示Unicode
代碼點)
f32
、f64
*請注意,與C和Go不同, V語言中int
始終是32位整數。
字符串(Strings)
fn main() { name := 'Bob' println('Hello, $name!') println(name.len) bobby := name + 'by' // + is used to concatenate strings println(bobby) // ==> "Bobby" println(bobby.substr(1, 3)) // ==> "ob" // println(bobby[1:3]) // This syntax will most likely replace the substr() method }
在V語言中,字符串是只讀的字節數組。 字符串數據使用UTF-8編碼。
字符串是不可變的。 這意味着 substr
函數非常有效:不執行復制,不需要額外的分配。
連接運算符+
需要兩邊都有字符串。 如果age是int,則不編譯此代碼,如下所示:
println('age = ' + age)
必須先將age
轉為字符串:
println('age = ' + age.str())
或使用$
符號進行字符串插入:
println('age = $age')
或者將 age
作為第二個參數傳給println
(*此方法尚未實現):
println('age = ', age) // TODO: not implemented yet
數組(Arrays)
fn main() { nums := [1, 2, 3] println(nums) println(nums[1]) // ==> "2" mut names := ['John'] names << 'Peter' names << 'Sam' // names << 10 <-- This will not compile. `names` is an array of strings. println(names.len) // ==> "3" println(names.contains('Alex')) // ==> "false" // We can also preallocate a certain amount of elements. nr_ids := 50 mut ids := [0 ; nr_ids] // This creates an array with 50 zeroes }
數組類型由第一個元素決定:[1,2,3]
是一個int
數組([]int
)。
['a','b']
是字符串數組([]string
)。
數組中所有元素的類型必須統一。[1,'a']
將無法通過編譯。
<<
運算符可以將元素追加到數組末尾。
.len
屬性可以獲取數組的長度。請注意,它是一個只讀屬性,用戶無法修改。默認情況下,V語言中所有導出的屬性都是只讀的。
.contains(val)
方法可以檢測數組中是否包含某元素,返回值bool
類型。
數據截取可以使用.slice(start,end)
、.left(pos)
、.right(pos)
方法。
Maps
mut m := map[string]int{} // Only maps with string keys are allowed for now m['one'] = 1 println(m['one']) // ==> "1" println(m['bad_key']) // ==> "0" // TODO: implement a way to check if the key exists numbers := { // TODO: this syntax is not implemented yet 'one': 1, 'two': 2, }
If
fn main() { a := 10 b := 20 if a < b { println('$a < $b') } else if a > b { println('$a > $b') } else { println('$a == $b') } }
if
語句非常簡單,和Go等眾多語言類似。
但與類C語言不同的是,條件不需要()
,但{}
必須。
if
也可以被用作表達式:
num := 777 s := if num % 2 == 0 { 'even' } else { 'odd' } println(s) // ==> "odd"
In operator
in
可以檢查數組是否包含元素。
nums := [1, 2, 3] println(1 in nums) // ==> true
它對於編寫更清晰,更緊湊的布爾表達式也很有用:
if parser.token == .plus || parser.token == .minus || parser.token == .div || parser.token == .mult { ... } if parser.token in [.plus, .minus, .div, .mult] { ... }
V
語言優化了這樣的表達式,因此如果上面的if語句產生相同的機器代碼,則不會創建任何數組。
For循環
V語言中只有一個循環結構for
。
fn main() { numbers := [1, 2, 3, 4, 5] for num in numbers { println(num) } names := ['Sam', 'Peter'] for i, name in names { println('$i) $name') // Output: 0) Sam } // 1) Peter }
for...in
循環用於遍歷數組的元素。如果需要索引,則可以使用 for index,value in
的形式。
fn main() { mut sum := 0 mut i := 0 for i <= 100 { sum += i i++ } println(sum) // ==> "5050" }
這種形式的循環類似於其他語言中的 while
循環。一旦布爾條件求值為false,循環將停止迭代。
同樣,和if
類似,循環的條件沒有括號,而循環體需要。
fn main() { mut num := 0 for { num++ if num >= 10 { break } } println(num) // ==> "10" }
循環條件可以省略,這會導致無限循環。
fn main() { for i := 0; i < 10; i++ { println(i) } }
最后,還有傳統的C風格循環。 它比while
形式更安全,因為后者很容易忘記因更新計數器而陷入死循環。
在這里i
不需要用 mut
聲明,因為它在每次循環中都被重新定義並賦值。
Switch
fn main() { os := 'windows' print('V is running on ') switch os { case 'darwin': println('macOS.') case 'linux': println('Linux.') default: println(os) } // TODO: replace with match expressions }
switch
語句是編寫 if-else
語句的較短方法。 它運行第一種情況,其值等於條件表達式。
與C語言不同的是,V語言中不需要為每個匹配的代碼塊都添加break
語句。
結構體(Structs)
struct Point { x int y int } fn main() { p := Point{ x: 10 y: 20 } println(p.x) // Struct fields are accessed using a dot }
structs
在堆棧上分配。 要在堆上分配結構並獲取指向它的指針,請使用&
前綴:
pointer := &Point{10, 10} // Alternative initialization syntax for structs with 3 fields or fewer println(pointer.x) // Pointers have the same syntax for accessing fields
V
沒有子類,但它支持嵌入式結構:
// TODO: this will be implemented later in July struct Button { Widget title string } button := new_button('Click me') button.set_pos(x, y) // Without embedding we'd have to do button.widget.set_pos(x,y)
資源修飾符(Access modifiers)
結構體字段默認是私有的和不可變的(結構體也不可變)。 他們的訪問修飾符可以用pub
和mut
來改變。 總共有5種可能的選擇:
struct Foo { a int // private immutable (default) mut: b int // private mutable c int // (you can list multiple fields with the same access modifier) pub: d int // public immmutable (readonly) pub mut: e int // public, but mutable only in parent module pub mut mut: f int // public and mutable both inside and outside parent module } // (not recommended to use, that's why it's so verbose)
例如,這是內置模塊中定義的字符串類型:
struct string { str byteptr pub: len int }
從這個定義中很容易看出字符串是一個不可變類型。
內置字符串數據的字節指針根本不可訪問。 len字段是公開的,但不是可變的。
fn main() { str := 'hello' len := str.len // OK str.len++ // Compilation error }
方法(Methods)
struct User { age int } fn (u User) can_register() bool { return u.age > 16 } fn main() { user := User{age: 10} println(user.can_register()) // ==> "false" user2 := User{age: 20} println(user2.can_register()) // ==> "true" }
V語言中沒有類的概念,但是你可以在結構體 struct
上定義方法(Methods)。
方法(Methods)是具有特殊接收器參數的函數。接收器出現在 fn
關鍵字和方法名稱之間的參數列表中(和Go語言類似)。
在上面的示例中,can_register
方法具有名為 u
的 User
結構體的接收器。不是使用像 self
或 this
這樣的名稱,而是使用短名稱,最好是所屬結構體名稱的首字母小寫。
可變接收器及純函數(Mutable receivers & pure functions)
struct User { is_registered bool } fn (u mut User) register() { u.is_registered = true } fn main() { mut user := User{} println(user.is_registered) // ==> "false" user.register() // TODO: Maybe force marking methods that modify the receiver with `!` // user.register()! println(user.is_registered) // ==> "true" }
請注意,功能只能修改接收器。fn register(u mut User)
這樣的做法無法通過編譯。
這點非常重要,所以再次申明:V語言中函數是部分純的,它們的參數永遠不會被函數修改。
修改對象的另一種方法是返回修改后的版本(*待實現):
// TODO: this syntax is not implemented yet fn register(u User) User { return { u | is_registered: true } } user = register(user)
常量(Constants)
const ( PI = 3.14 WORLD = '世界' ) fn main() { println(PI) println(WORLD) }
使用 const
關鍵字來申明常量,常量定義的位置只能在模塊級別(函數外)。
常量名稱必須大寫, 這有助於將它們與變量區分開來。
常量的值一經定義,永遠不能修改。
V語言中的常量比大多數語言更靈活,你可以指定更復雜的值:
struct Color { r int g int b int } fn (c Color) str() string { return '{$c.r, $c.g, $c.b}' } fn rgb(r, g, b int) Color { return Color{r: r, g: g, b: b} } const ( NUMBERS = [1, 2, 3] RED = Color{r: 255, g: 0, b: 0} BLUE = rgb(0, 0, 255) ) fn main() { println(NUMBERS) println(RED) println(BLUE) }
V語言中沒有全局變量,常量就更加有用了。
模塊(Modules)
Vlang是一種模塊化的語言,創建可重用模塊是V語言中備受推薦的做法且很簡單,只要需要創建一個以模塊為名的文件夾,然后在該文件夾下編寫.v
文件即可。
cd ~/code/modules mkdir mymodule vim mymodule/mymodule.v
// mymodule.v module mymodule // To export a function we have to use `pub` pub fn say_hi() { println('hello from mymodule!') }
在Vlang中,導出模塊中的函數,需要使用pub
關鍵字。
你可以編寫多個.v
文件在mymodule/
中,編譯模塊也很簡單,只要執行命令即可:
v -lib ~/code/modules/mymodule
使用模塊中導出的函數也非常簡單:
module main import mymodule fn main() { mymodule.say_hi() }
請注意,每次調用外部函數時都必須指定模塊。 這看起來似乎很冗長,但它使代碼更易讀,更容易理解,因為它始終清楚從哪個模塊調用哪個函數,特別是在大型項目的代碼庫中。
模塊名稱應簡短,不超過10個字符。 循環導入是不允許的。
現在你可以在任何地方創建模塊,也許這應該是標准化的(比如Go的GOPATH)。
所有模塊都靜態編譯為單個可執行文件。
接口(Interfaces)
struct Dog {} struct Cat {} fn (d Dog) speak() string { return 'woof' } fn (c Cat) speak() string { return 'meow' } interface Speaker { speak() string } fn perform(s Speaker) { println(s.speak()) } fn main() { dog := Dog{} cat := Cat{} perform(dog) // ==> "woof" perform(cat) // ==> "meow" }
和Go語言類似,結構體通過實現某接口中的方法來"實現"該接口,沒有明確的聲明,也沒有像Java中的 implements
關鍵字。
枚舉(Enums)
enum Color { red, green, blue } fn main() { mut color := red // TODO: color := Color.green color = green // TODO: color = .green println(color) // ==> "1" TODO: print "green"? }
Option/Result types & error handling
struct User { id int name string } struct Repo { users []User } fn new_repo() Repo { return Repo { users: [User{1, 'Andrew'}, User {2, 'Bob'}, User {10, 'Charles'}] } } fn (r Repo) find_user_by_id(id int) ?User { for user in r.users { if user.id == id { // V automatically wraps this into an option type return user } } return error('User $id not found') } fn main() { repo := new_repo() user := repo.find_user_by_id(10) or { // Option types must be handled by `or` blocks return // `or` block must end with `return`, `break`, or `continue` } println(user.id) // ==> "10" println(user.name) // ==> 'Charles' }
V將Option和Result組合成一種類型,因此您無需決定使用哪種類型。
將函數“升級”為可選函數所需的工作量很小:您必須添加一個?
返回類型並在出現錯誤時返回錯誤。
如果您不需要返回錯誤,則只能return none
。 (TODO:none
還沒有實現)。
這是V
中處理錯誤的主要方法。它們仍然是值,但是錯誤處理要簡潔很多。
當然,錯誤是可以被傳遞的:
resp := http.get(url)? println(resp.body)
http.get
返回的是?http.Response
可選類型。如果錯誤發生,將傳播到調用函數,這里是導致main函數拋出異常。
上面代碼是下面代碼的簡寫:
resp := http.get(url) or { panic(err) } println(resp.body)
V
沒有辦法強制打開一個可選項(比如Rust
的unwrap()
或Swift
中的!
)。 你必須使用或{panic(err)}
代替。
泛型(Generics)
*七月份恢復
struct Repo⟨T⟩ { db DB } fn new_repo⟨T⟩(db DB) Repo⟨T⟩ { return Repo⟨T⟩{db: db} } // This is a generic function. V will generate it for every type it's used with. fn (r Repo⟨T⟩) find_by_id(id int) ?T { table_name := T.name // in this example getting the name of the type gives us the table name return r.db.query_one⟨T⟩('select * from $table_name where id = ?', id) } db := new_db() users_repo := new_repo⟨User⟩(db) posts_repo := new_repo⟨Post⟩(db) user := users_repo.find_by_id(1)? post := posts_repo.find_by_id(1)?
為了方便閱讀,允許使用⟨⟩
代替<>
。vfmt最終會將⟨⟩
替換為<>
。
The initial version of V had generics because arrays and maps were built with them. Later I figured out a way to implement these data structures without generics. This simplified code greatly.
Since I no longer needed them, they were moved to a branch, because maintaining generics is tough. Generics will not be available when the language is released in June, but they should be back by July.
摘錄自V語言作者 Alex
並發(Concurrency)
Vlang的並發模型與Go非常相似。 要同時運行foo()
,只需使用go foo()
來調用它。 目前它在一個新的系統線程中運行該函數,很快就會實現goroutines
和調度程序。
JSON解析(Decoding JSON)
import json struct User { name string age int } fn main() { data := '{ "name": "Frodo", "age": 25 }' user := json.decode(User, data) or { eprintln('Failed to decode json') return } println(user.name) println(user.age) }
JSON現在非常流行,這就是內置JSON支持的原因。
json.decode
函數的第一個參數是要解碼的類型。 第二個參數是要解析的json字符串。
V語言生成用於json編碼和解碼的代碼,沒有使用反射,這樣性能更好。
測試(Testing)
// hello.v fn hello() string { return 'Hello world' } // hello_test.v fn test_hello() { assert hello() == 'Hello world' }
所有測試函數都必須放在 *_test.v
文件中,並以 test_
開頭。 要運行測試,請執行 v hello_test.v。 要測試整個模塊,請運行 v test mymodule。
內存管理(Memory management)
Vlang中沒有垃圾收集或引用計數,在編譯期間清理它能做的事情,比如:
fn draw_text(s string, x, y int) { ... } fn draw_scene() { ... draw_text('hello $name1', 10, 10) draw_text('hello $name2', 100, 10) draw_text(strings.repeat('X', 10000), 10, 50) ... }
字符串不會轉義draw_text
,因此在函數退出時會清除它們。
事實上,前兩個調用根本不會產生任何分配。 這兩個字符串很小,V將為它們使用預分配的緩沖區。
對於更復雜的情況,需要手動內存管理。 這將很快修復。
V將在運行時檢測內存泄漏並報告它們。 要清理(例如)數組,請使用free()
方法:
numbers := [0; 1000000] ... numbers.free()
調用C函數示例
#flag -lsqlite3 #include "sqlite3.h" struct C.sqlite3 struct C.sqlite3_stmt fn C.sqlite3_column_int(C.sqlite_stmt, int) int fn main() { path := 'sqlite3_users.db' db := &C.sqlite3{} C.sqlite3_open(path.cstr(), &db) query := 'select count(*) from users' stmt := &C.sqlite3_stmt{} C.sqlite3_prepare_v2(db, query.cstr(), - 1, &stmt, 0) C.sqlite3_step(stmt) nr_users := C.sqlite3_column_int(res, 0) C.sqlite3_finalize(res) println(nr_users) }
關鍵詞
V語言目前共有以下21個關鍵詞:
break
const
continue
defer
else
enum
fn
for
go
goto
if
import
in
interface
match
module
mut
or
return
struct
type
有限的運算符重載
struct Vec { x int y int } fn (a Vec) str() string { return '{$a.x, $a.y}' } fn (a Vec) + (b Vec) Vec { return Vec { a.x + b.x, a.y + b.y } } fn (a Vec) - (b Vec) Vec { return Vec { a.x - b.x, a.y - b.y } } fn main() { a := Vec{2, 3} b := Vec{4, 5} println(a + b) // ==> "{6, 8}" println(a - b) // ==> "{-2, -2}" }
運算符重載違背了V的簡單性和可預測性的理念。 但由於科學和圖形應用程序也屬於V的應用范圍,因此為了提高可讀性,運算符重載非常重要:
a.add(b).add(c.mul(d))
的可讀性遠遠不及 a + b + c * d
。
為了提高安全性和可維護性,運算符重載有幾個局限性:
- 只能重載
+
、-
、*
、/
運算符。 - 不允許在重載函數內調用其他函數。
- 運算符函數無法修改其參數。
- 兩個參數必須具有相同的類型(就像V中的所有運算符一樣)。
轉換C/C++代碼到V語言
V可以將您的C/C++代碼轉換為人類可讀的V語言代碼。讓我們先創建一個簡單的程序 test.cpp
:
#include <vector> #include <string> #include <iostream> int main() { std::vector<std::string> s; s.push_back("V is "); s.push_back("awesome"); std::cout << s.size() << std::endl; return 0; }
運行命令 v translate test.cpp,然后V將生成文件test.v
:
fn main { mut s := []string s << 'V is ' s << 'awesome' println(s.len) }
An online C/C++ to V translator is coming soon.
摘錄自V語言作者 Alex
交叉編譯(Cross compilation)
V語言中跨平台編譯(交叉編譯)只需要簡單執行以下命令:
v -os windows .
或者:
v -os linux .
(目前不支持macOS交叉編譯)
如果項目中沒有用到C的依賴庫,上面的命令就是你需要做的,甚至在使用ui模塊或使用gg的圖形應用程序編譯GUI應用程序時也能正常工作。