语句与表达式
If
a := 10
b := 20
if a < b {
println('${a} < ${b}')
} else if a > b {
println('${a} > ${b}')
} else {
println('${a} == ${b}')
}
if 语句使用上相当简单明了,与大多数编程语言类似。与 C 类语言不同,条件周围无需括号,并且必须使用花括号包围代码块。
If 表达式
不同于 C 语言,V 没有三元操作符(如 x = c ? 1 : 2
)。相反,它支持将 if 作为表达式使用,语法更清晰但稍显冗长。将三元操作转换为 V 语言时(假设 c
是布尔条件),可写为 x = if c { 1 } else { 2 }
。
示例:
num := 777
s := if num % 2 == 0 { 'even' } else { 'odd' }
println(s) // "odd"
if 表达式的每个分支可包含多条语句,最后一行值作为该分支的返回值:
n := arguments().len
x := if n > 2 {
dump(arguments())
42
} else {
println('something else')
100
}
dump(x)
If 解包
在任何可使用 or {}
的地方,都可以用 "if 解包"。当表达式非 none
且非错误时,将解包后的值绑定到变量。
m := {
'foo': 'bar'
}
// 处理缺失键
if v := m['foo'] {
println(v) // bar
} else {
println('not found')
}
返回结果类型的函数:
fn res() !int {
return 42
}
if v := res() {
println(v)
}
变量赋值解包:
struct User {
name string
}
arr := [User{'John'}]
// 解包并赋值变量
u_name := if v := arr[0] {
v.name
} else {
'Unnamed'
}
println(u_name) // John
类型检查与转换
通过 is
和 !is
可检查 sum type 的当前类型。
if 形式:
struct Abc {
val string
}
struct Xyz {
foo string
}
type Alphabet = Abc | Xyz
x := Alphabet(Abc{'test'}) // sum 类型
if x is Abc {
// x 自动转换为 Abc 类型
println(x)
}
if x !is Abc {
println('Not Abc')
}
match 形式:
match x {
Abc {
// x 自动转换为 Abc 类型
println(x)
}
Xyz {
// x 自动转换为 Xyz 类型
println(x)
}
}
适用于结构体字段:
struct MyStruct {
x int
}
struct MyStruct2 {
y string
}
type MySumType = MyStruct | MyStruct2
struct Abc {
bar MySumType
}
x := Abc{
bar: MyStruct{123} // MyStruct 自动转换为 MySumType
}
if x.bar is MyStruct {
// x.bar 自动转换
println(x.bar)
} else if x.bar is MyStruct2 {
new_var := x.bar as MyStruct2 // 使用 as 手动类型转换
println(new_var)
}
match x.bar {
MyStruct {
// x.bar 自动转换
println(x.bar)
}
else {}
}
可变变量可能改变,因此强制转换不安全。开发人员可用 mut
关键字标记表达式以显式操作:
mut x := MySumType(MyStruct{123})
if mut x is MyStruct {
println(x) // 强制转换可变变量
}
// match 形式相同
match mut x {
MyStruct {
println(x)
}
}
Match
os := 'windows'
print('V is running on ')
match os {
'darwin' { println('macOS.') }
'linux' { println('Linux.') }
else { println(os) }
}
match 语句是 if-else
序列的简写形式。当匹配到分支时,执行其代码块;else
分支在所有其他分支不匹配时执行。
match 语句可返回值:
number := 2
s := match number {
1 { 'one' }
2 { 'two' }
else { 'many' }
}
作为 if-else if-else
替代方案:
match true {
2 > 4 { println('if') }
3 == 4 { println('else if') }
2 == 2 { println('else if2') } // 输出 'else if2'
else { println('else') }
}
或作为 unless
替代方案:
match false {
2 > 4 { println('if') } // 输出 'if'
3 == 4 { println('else if') }
2 == 2 { println('else if2') }
else { println('else') }
}
match 返回匹配分支的最终表达式值:
enum Color {
red
blue
green
}
fn is_red_or_blue(c Color) bool {
return match c {
.red, .blue { true } // 逗号用于测试多个值
.green { false }
}
}
枚举分支:
c := `v`
typ := match c {
`0`...`9` { 'digit' }
`A`...`Z` { 'uppercase' }
`a`...`z` { 'lowercase' } // 输出 'lowercase'
else { 'other' }
}
println(typ)
匹配 sumtype 变体:
此时 match 是穷举式,无需 else 分支:
struct Dog {}
struct Cat {}
struct Veasel {}
type Animal = Dog | Cat | Veasel
a := Animal(Veasel{})
match a {
Dog { println('Bay') }
Cat { println('Meow') }
Veasel { println('Vrrrrr-eeee') } // 参考: https://www.youtube.com/watch?v=qTJEDyj2N0Q
}
范围匹配:
如果值在分支范围内,则执行该分支。范围使用 ...
(三圆点,包含末尾元素),而非 ..
(二圆点)。
const start = 1
const end = 10
c := 2
num := match c {
start...end { 1000 } // 输出 1000
else { 0 }
}
println(num)
常量可用于范围表达式。
match 表达式在 for 循环和 if 语句中不可用。
In 操作符
in
用于检查数组或 map 是否包含元素。反向操作用 !in
。
nums := [1, 2, 3]
println(1 in nums) // true
println(4 !in nums) // true
in
检查 map 是否包含键,而非值。
m := {
'one': 1
'two': 2
}
println('one' in m) // true
println('three' !in m) // true
用于编写更简洁的布尔表达式:
enum Token {
plus
minus
div
mult
}
struct Parser {
token Token
}
parser := Parser{}
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
一个循环关键字,支持多种形式。for/in用于数组、map 或数字范围。
数组遍历:
numbers := [1, 2, 3, 4, 5]
for num in numbers {
println(num)
}
names := ['Sam', 'Peter']
for i, name in names {
println('${i}) ${name}') // 输出: 0) Sam, 1) Peter
}
for index, value in arr
获取索引和值。
如需修改数组元素,声明变量为可变:
mut numbers := [0, 1, 2]
for mut num in numbers {
num++
}
println(numbers) // [1, 2, 3]
单下划线 _
忽略标识符。
自定义迭代器:
实现 next
方法返回 Option
的类型可用于 for 循环。
struct SquareIterator {
arr []int
mut:
idx int
}
fn (mut iter SquareIterator) next() ?int {
if iter.idx >= iter.arr.len {
return none
}
defer {
iter.idx++
}
return iter.arr[iter.idx] * iter.arr[iter.idx]
}
nums := [1, 2, 3, 4, 5]
iter := SquareIterator{ arr: nums }
for squared in iter {
println(squared) // 输出: 1, 4, 9, 16, 25
}
Map 遍历:
m := {
'one': 1
'two': 2
}
for key, value in m {
println('${key} -> ${value}') // 输出: one -> 1, two -> 2
}
单下划线忽略键或值:
// 仅遍历键
for key, _ in m {
println(key) // 输出: one, two
}
// 仅遍历值
for _, value in m {
println(value) // 输出: 1, 2
}
范围遍历:
for i in 0 .. 5 { // 输出: 01234
print(i)
}
low..high
表示独占范围(包含 low
但不包含 high
)。
此设计基于 Edsger W. Dijkstra 的 "Why Numbering Should Start at Zero" (EWD831),强调逻辑一致性和减少错误。
条件遍历:
mut sum := 0
mut i := 0
for i <= 100 { // 类似 while 循环
sum += i
i++
}
println(sum) // 5050
布尔条件为 false 时停止,无需括号但需花括号。
无限循环:
mut num := 0
for {
num += 2
if num >= 10 {
break
}
}
println(num) // 10
C 风格 for:
for i := 0; i < 10; i += 2 { // i 自动可变
if i == 6 {
continue // 跳过 6
}
println(i) // 输出: 0, 2, 4, 8
}
比 while 更安全,避免忘记更新计数器。
带标签的 break 和 continue
默认控制最内层循环,用标签指定外层循环:
outer: for i := 4; true; i++ {
println(i) // 输出: 4, 5, 6, 7
for {
if i < 7 {
continue outer
} else {
break outer
}
}
}
标签必须紧贴外层循环前。
Defer
defer
语句延迟执行代码块,直到外围函数返回。
import os
fn read_log() {
mut ok := false
mut f := os.open('log.txt') or { panic(err) }
defer {
f.close() // 文件将在函数结束时关闭
}
// ...
if !ok {
return // defer 在此执行
}
// ... // defer 在此执行
}
函数返回值时,defer 在表达式求值后执行:
import os
enum State {
normal
write_log
return_error
}
fn write_log(s State) !int {
mut f := os.create('log.txt')!
defer {
f.close()
}
if s == .write_log {
return f.writeln('This is a log file') // 先写入再关闭
} else if s == .return_error {
return error('file open: ${f.is_opened}') // 关闭前报告错误
}
return 0
}
访问函数结果:
单返回值用 $res()
,多返回值用 $res(idx)
。
// 单返回值
fn (mut app App) auth_middleware() bool {
defer {
if !$res() { // 访问返回值
app.response.status_code = 401
}
}
return true
}
// 多返回值
fn (mut app App) auth_with_user_middleware() (bool, string) {
defer {
if !$res(0) { // 访问第一个返回值
app.response.status_code = 401
} else {
app.user = $res(1) // 访问第二个返回值
}
}
return true, 'TestUser'
}
Goto
V 允许用 goto
无条件跳转到标签,但需在相同函数内,并标记为 unsafe
(可能跳过变量初始化或访问已释放内存)。
if x {
// ...
if y {
unsafe {
goto my_label // 跳转风险操作
}
}
// ...
}
my_label:
优先使用 for
循环和带标签的 break/continue,这些更安全。