V是一种静态类型的编译编程语言,用于构建可维护的软件。
它与Go类似,也受到Oberon,Rust,Swift的影响。
V是一种非常简单的语言。通过这个文档将花费你大约半个小时,到最后你将学习几乎整个语言。
尽管很简单,但它为开发人员提供了很多动力。你可以用其他语言做任何事情,你可以用V做。(官方文档入口:https://vlang.io/docs#option)
//hello.v
fn main() {
println('hello world')
}
//hello.v
println('hello world')
// 单行注释
/*
这是多行注释
/* 它可以嵌套 */
*/
fn main() {
println(add(77, 33))
println(sub(100, 50))
}
fn add(x int, y int) int {
return x + y
}
fn sub(x, y int) int {
return x - y
}
name := 'Bob'
age := 20
large_number := i64(9999999999)
println(name)
println(age)
println(large_number)
mut age := 20
println(age)
age = 21
println(age)
fn main() {
age = 21 //这段代码在V语言中是编译不通过的,因为age未声明变量。
}
fn main() {
age := 21 //此段代码也不会被编译通过,因为未使用的变量会导致编译错误
}
fn main() {
a := 10
if true {
a := 20 //与大多数语言不同,不允许使用已被声明的变量在局部变量。
//声明已在父作用域中使用的变量名,将导致编译错误。
}
}
name := 'Bob'
println('Hello, $name!') // `$` 用与字符串内引入变量
println(name.len) //字符串长度
bobby := name + 'by' // 通过 + 拼接字符串
println(bobby) // ==> "Bobby"
println(bobby.substr(1, 3)) // 字符串截取 ==> "ob"
// println(bobby[1:3]) // 这种写法很可能会替换 substr 方法
println('age = ' + age) //此段代码将无法编译通过,需改为如下
println('age = ' + age.str()) //将 age 转换为 string ,或者使用
println('age = $age') //字符串引入变量的方式
nums := [1, 2, 3]
println(nums)
println(nums[1]) // ==> "2"
mut names := ['John']
names << 'Peter' //数组中添加元素
names << 'Sam'
// names << 10 //这里将编译不通过,因为 names 是一个字符串数组
println(names.len) // ==> "3"
println('Alex' in names) // ==> "false" 判断变量是否在数组中
// 我们也可以预先分配一定数量的元素
nr_ids := 50
mut ids := [0 ; nr_ids] // 这里将创建 包含50个0的数组
mut m := map[string]int{} // 现在 map的键值只能为 string 类型,
m['one'] = 1 //设置值
println(m['one']) // ==> "1" //获取值
println(m['bad_key']) // ==> "0" //不包含返回 0
println('bad_key' in m) // 使用in 关键字来判断是否存在 key
numbers := {
'one': 1,
'two': 2,
}
if 语句非常简单,与大多数其他语言类似。
与其他类C语言不同,条件周围没有括号,并且始终需要大括号。
num := 777
s := if num % 2 == 0 {
'even'
}
else {
'odd'
}
println(s) // ==> "odd"
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 语言在这样的表达式中有做优化,上面两个语句会生成相同的机器码,不会创建任何数组
V 语言只有一种循环方法:for
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
mut numbers := [1, 2, 3, 4, 5]
for i, num in numbers {
println(num)
numbers[i] = 0
}
mut sum := 0
mut i := 0
for i <= 100 {
sum += i
i++
}
println(sum) // ==> "5050"
mut num := 0
for {
num++
if num >= 10 {
break
}
}
println(num) // ==> "10"
for i := 0; i < 10; i++ {
println(i)
}
//老版本V 使用
os := 'windows'
print('V is running on ')
switch os {
case 'darwin':
println('macOS.')
case 'linux':
println('Linux.')
default:
println(os)
}
//官方文档已经改为
os := 'windows'
print('V is running on ')
match os {
'darwin' => println('macOS.')
'linux' => println('Linux.')
else => println(os)
}
匹配语句是编写if - else语句序列的较短方式。找到匹配的分支时,将分配=>后面的表达式。当没有其他分支匹配时,将分配else =>分支。
注意:与C不同,每个代码块的末尾都不需要 break。
struct Point {
x int
y int
}
p := Point{
x: 10
y: 20
}
println(p.x) // 访问结构体对象的某字段
pointer := &Point{10, 10} // 字段少于3的结构体的简化初始化方式
println(pointer.x) // 指针访问结构体属性方式与默认的方式相同
// TODO: 这个功能将与7月末实施
struct Button {
Widget
title string
}
button := new_button('Click me')
button.set_pos(x, y)
// 没有潜入式,我们需要这么做
button.widget.set_pos(x,y)
结构字段默认是私有的和不可变的(使结构也是不可变的)。
他们可以被 pub(可被访问的)和mut(可被修改的) 修饰。总共有5种可能的选择:
struct Foo {
a int // 私有的 不可变的 (default)
mut:
b int // 私有的 可变的
c int // (你可以一次列出,相同访问修饰符的字段)
pub:
d int // 公开的,不可变的
pub mut:
e int // 公开的,但仅在父模块中可变
pub mut mut:
f int // 公开的,父模块内部和外部都可变
} // (不建议使用,这就是为什么它如此冗长的原因)
struct string {
str byteptr
pub:
len int
}
// 很容易看出 string 内容是不可变的
fn main() {
str := 'hello'
len := str.len // OK
str.len++ // 编译出错
}
//具有字符串数据的字节指针根本无法在外部builtin访问。 len字段是公开的,但不是可变的。
V 没有 class概念,但是我们可以在结构体上定义方法。
方法是具有特殊行参的函数。特殊行参 放在 fn关键字 和方法名之间的参数列表中。
struct User {
age int
}
fn (u User) can_register() bool {
return u.age > 16
}
user := User{age: 10}
println(user.can_register()) // ==> "false"
user2 := User{age: 20}
println(user2.can_register()) // ==> "true"
V语言的函数默认是纯函数,也就是函数的输出结果只依赖输入的参数,并且没有其它的影响。
因为V语言没有全局变量,且所有的参数默认都是只读的,即使传入的引用也是默认只读的。
然后V语言并不纯的函数式语言。我们可以通过mut来修饰行参数,使得可以被修改:
struct User {
mut:
is_registered bool
}
fn (u mut User) register() {
u.is_registered = true
}
mut user := User{}
println(user.is_registered) // ==> "false"
user.register()
println(user.is_registered) // ==> "true"
fn multiply_by_2(arr mut []int) {
for i := 0; i < arr.len; i++ {
arr[i] *= 2
}
}
mut nums := [1, 2, 3]
multiply_by_2(mut nums)
println(nums) // ==> "[2, 4, 6]"
注意1:您必须在执行函数之前 声明一个 mut 的nums 变量。这清楚的表明被调用的函数将要修改该值!
fn register(u User) User {
return { u | is_registered: true }
}
user = register(user)
常量声明为const。它们只能在模块级别上(函数body外)定义。
常量名称必须大写。这有助于将它们与变量区分开来
永远不能改变常量值。
const (
PI = 3.14
World = '世界'
)
println(PI)
println(World)
struct Color {
r int
g int
b int
}
fn (c Color) str() string { return '{$c.r, $c.g, $c.b}' } //重写 str方法
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)//结构体常量
)
println(Numbers)
println(Red)
println(Blue)
不支持使用全局变量,因此这种方式显得非常有必要
V 语言是一个模块化的语言。鼓励创建可重用的模块,而且创建模块也非常简单。
cd ~/code/modules
mkdir mymodule
vim mymodule/mymodule.v
// mymodule.v
module mymodule
// 要用 pub修饰符修饰,外部才能访问该 模块函数
pub fn say_hi() {
println('hello from mymodule!')
}
模块的编译:
模块的使用:
module main
import mymodule
fn main() {
mymodule.say_hi()
}
请注意,每次调用外部函数时都必须指定模块。这看起来似乎很冗长,但它使代码更易读,更容易理解,因为它始终清楚的表达出从哪个模块调用哪个函数。特别是在大型代码库中。
模块名称应短,不超过10个字符。模块不允许循环依赖。
您可以在任何地方创建模块 ,所有的模块都将静态编译到单一的可执行程序中。
类型通过实现接口同名的方法(注意返回值也相同类型)。和Go语言一样,V语言也是隐式接口,类型不需要显式实现接口,没有“implements”关键字。
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())
}
dog := Dog{}
cat := Cat{}
perform(dog) // ==> "woof"
perform(cat) // ==> "meow"
enum Color {
red green blue
}
mut color := Color.red
// 下面的写法要注意了,V语言知道 color的类型为Color,所以这个地方不需要使用 Color.green
color = .green
println(color) // ==> "1" TODO: print "green"?
V语言针对函数返回值增加了一个可选的属性,这样可以用于处理失败的情况
这是处理V中错误的主要手段。函数的返回值依然是值,但是错误处理要简洁很多。
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 自动将其包装为 Option 类型
return user
}
}
return error('User $id not found')
}
fn main() {
repo := new_repo()
user := repo.find_user_by_id(10) or { // Option类型必须使用 `or` 的代码块hold住;
return // `or` 代码块必须以, return ,break,或 continue 关键字结束
}
println(user.id) // ==> "10"
println(user.name) // ==> 'Charles'
}
resp := http.get(url)?
println(resp.body)
上面例子中,http.get return ?http.Response 的可选类型,如果错误发生,将传播到调用函数,这里是导致main函数抛出异常。
上面代码是下面代码的简写:
resp := http.get(url) or {
panic(err)
}
println(resp.body)
为了方便阅读, 允许使用 ⟨⟩ 来代替 <>. vfmt 自动替换 ⟨⟩ 为<> .
struct Repo⟨T⟩ {
db DB
}
fn new_repo⟨T⟩(db DB) Repo⟨T⟩ {
return Repo⟨T⟩{db: db}
}
// 这是一个范型函数. V 语言可以使用任意类型的范型
fn (r Repo⟨T⟩) find_by_id(id int) ?T {
table_name := T.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)?
并发模型与Go非常相似。要foo()同时运行,只需调用它go foo(),便会在新的系统线程执行该函数 .
很快 将实现 goroutines 和 scheduler 。
JSON 在今天已经非常流行,这也是为什么要内置json支持的原因。
struct User {
name string
age int
foo Foo [skip] //使用 skip 来跳过某些字段
}
data := '{ "name": "Frodo", "age": 25 }'
user := json.decode(User, data) or {
eprintln('Failed to decode json')
return
}
println(user.name)
println(user.age)
所有的单元测试都必须放在 *test.v 文件中,并且测试函数必须使用 test 开头方式命名。
// hello.v
fn hello() string {
return 'Hello world'
}
// hello_test.v
fn test_hello() {
assert hello() == 'Hello world'
}
V语言没有自动内存回收(GC)和引用计数,V语言会在编译阶段完成必要的清理工作。例如:
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)
...
}
字符串 string 生命周期不会超出 draw_text 函数,因此当函数执行完时,它们将会被清除。实际上,前面两次调用根本不会导致任何分配,因为这两个字符串很小,V语言会使用提前准备好的缓冲区构造字符串。
对于更复杂的情况,需要手动内存管理,我们将很快实现解决。
V 将在运行时检测并报告内存泄露,例如:要清除数组,请使用 free() 方法:
numbers := [0; 1000000]
...
numbers.free()
延迟代码块将推迟执行,直到所属的函数执行结束或return。
fn read_log() {
f := os.open('log.txt')
defer { f.close() }
...
if !ok {
// 将在此处调用defer语句,文件将被关闭
return
}
...
//将在此处调用defer语句,文件将被关闭
}
您不需要担心V语言的代码格式化问题, vfmt 帮你解决:
v fmt file.v
建议设置编辑器,以便vfmt在每次保存时运行,始终允许vfmt.
在推送代码之前始终运行vfmt。
#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)
}
预编译 使用 $if 。现在它只能用于检测操作系统。
$if windows {
println('Windows')
}
$if linux {
println('Linux')
}
$if mac {
println('macOS')
}
有内置的 json支持已经是很不错的选择,但是V还允许使用者为任意添加高效的序列化程序。
// TODO: 计划7月上线
fn decode(data string) T {
mut result := T{}
for field in T.fields {
if field.typ == 'string' {
result.$field = get_string(data, field.name)
} else if field.typ == 'int' {
result.$field = get_int(data, field.name)
}
}
return result
}
// 生成:
fn decode_User(data string) User {
mut result := User{}
result.name = get_string(data, 'name')
result.age = get_int(data, 'age')
return result
}
运算符重载违背了V的简单性和可预测性的理念。但由于科学和图形应用程序属于V域,因此为了提高可读性,运算符重载非常重要:
a.add(b).add(c.mul(d))比 a + b + c * d 可读性差得多了。
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}"
}
TODO 尚未实现
fn main() {
a := 10
asm x64 {
mov eax, [a]
add eax, 10
mov [a], eax
}
}
TODO: 将C翻译为V将于7月上市。C ++到V将于今年晚些时候推出
V可以将您的C / C ++代码转换为可读的V代码。让我们先创建一个简单的程序test.cpp
#include
#include
#include
int main() {
std::vector s;
s.push_back("V is ");
s.push_back("awesome");
std::cout << s.size() << std::endl;
return 0;
}
允许命令 v translate test.cpp 将会得到 test.v:
fn main {
mut s := []
s << 'V is '
s << 'awesome'
println(s.len)
}
一个在线C / C ++到V的翻译即将推出.
module main
import time
import os
[live]
fn print_message() {
println('Hello! Modify this message while the program is running.')
}
fn main() {
for {
print_message()
time.sleep_ms(500)
}
}
构建此示例:v -live message.v
要重新加载的函数必须 将 [live] 在定义之前添加。
现在,在程序运行时无法修改类型。
更多示例,包括图形应用程序: github.com/vlang/v/tree/master/examples/hot_code_reloading。
要交叉编译项目,只需简单执行命令:
(暂时无法对macOS进行交叉编译。)
如果您没有任何C依赖项,那就是您需要做的。即使在使用ui模块或图形应用程序编译GUI应用程序时也可以使用 gg。
您需要安装Clang,LLD链接器,并下载包含库和包含Windows和Linux文件的zip文件。V将为您提供链接。
V可以用作Bash的替代方案来编写部署脚本,构建脚本等。
使用V的优势在于语言的简单性和可预测性以及跨平台支持。“V脚本”在类Unix系统和Windows上运行。
使用#v指令启动程序。它将使os 模块中的所有函数全局化(您可以使用ls() 来代替os.ls() )例如:
#v
rm('build/*')
// 效果同:
for file in ls('build/') {
rm(file)
}
mv('*.v', 'build/')
// 效果同:
for file in ls('.') {
if file.ends_with('.v') {
mv(file, 'build/')
}
}
现在您可以像普通的V程序一样编译它,并获得可以在任何地方部署和运行的可执行文件: v deploy.v && ./deploy
(当然也可以使用传统脚本运行方式来允许它: v run deploy.v)
V 拥有22个关键字
break
const
continue
defer
else
enum
fn
for
go
goto
if
import
in
interface
match
module
mut
or
pub
return
struct
type
运算符
运算符 | 含义 | 支持类型 |
---|---|---|
+ | sum | integers, floats, strings |
- | difference | integers, floats |
* | product | integers, floats |
/ | quotient | integers, floats |
% | remainder | integers |
& | bitwise AND | integers |
| | bitwise OR | integers, floats |
^ | bitwise XOR | integers |
<< | left shift | integer << unsigned integer |
>> | right shift | integer >> unsigned integer |
运算符优先级
优先级 | 运算符 |
---|---|
5 | * / % << >> & |
4 | + - | ^ |
3 | == != < <= > >= |
2 | && |
1 | || |
+= -= *= /= %= &= |= ^= >>= <<=