V 语言中文文档

V 语言文档

简介

V是设计用于构建可维护软件的静态类型的编译程序语言。
它与Go相似,其设计也受到Oberon,Rust,Swift,Kotlin和Python的影响。
V是一种非常简单的语言。阅读本文档将花费您大约半小时的时间,到最后,您将学到全部语言。
该语言促进以最少的抽象编写简单清晰的代码。尽管很简单,但是V给开发人员很大的力量。
您可以使用其他语言进行的任何操作,都可以使用V语言进行。

目录

Hello World

fn main() {
    println('hello world')
}

将该代码段保存到文件 hello.v 中 . 然后执行: v run hello.v .

这是已经把V添加到了环境变量中
如果还没有, 看这里.
,则必须手动键入到V的路径。

祝贺您——您刚刚编写了您的第一个V程序,并执行了它!

您可以编译一个程序而不执行 v hello.v.
v help 查看所有支持的命令。

在上面的示例中,可以看到用。返回类型紧跟在函数名之后。在本例中不返回任何内容,因此可以省略返回类型。

在大多数语言一样(如C, Go和Rust),入口函数main

println是少数内置函数之一。它将传递给它的值打印到标准输出。

fn main() 在一个文件程序中可以跳过声明。这在编写小程序、“脚本”或学习语言时很有用。为简洁起见,本教程将跳过这些内容。

这意味着“hello world”程序可以简单为

println('hello world')

Comments

注释
// 这是一个单行注释。

/* 这是一个多行注释。
   /* 它可以嵌套。 */
*/

Functions

函数
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
}

同样,类型出现在参数名称之后。

就像在Go和C中一样,函数不能重载。
这简化了代码,提高了可维护性和可读性。

函数可以在声明之前使用:
addsub 是在 main 后声明的, 但是仍然可以在 main 中调用他们.
对于V中的所有声明都是这样,这样就不需要头文件,也不需要考虑文件和声明的顺序。

Returning multiple values

多值返回
fn foo() (int, int) {
    return 2, 3
}

a, b := foo()
println(a) // 2
println(b) // 3
c, _ := foo() // 忽略值使用 `_`

Variable number of arguments

可变数量的参数
fn sum(a ...int) int {
    mut total := 0
    for x in a {
        total += x
    }
    return total
}
println(sum())    // Output: 0
println(sum(1))   //         1
println(sum(2,3)) //         5

Symbol visibility

访问修饰符
pub fn public_function() {
}

fn private_function() {
}

函数默认是私有的(没有导出)。要允许其他模块使用它们,请提前声明pub
该方法也适用于常量和类型。

Variables

变量
name := 'Bob'
age := 20
large_number := i64(9999999999)
println(name)
println(age)
println(large_number)

变量被声明和初始化 使用 :=. 这是在v中声明变量的唯一方法,这意味着变量总是有一个初始值。

变量的类型是从右侧的值推断出来的。
要选择不同的类型,请使用类型转换:表达式T(v)将值v转换为类型T

与大多数其他语言不同,V只允许在函数中定义变量。不允许使用全局(模块级)变量。V中没有全局状态
(详情请看 Pure functions by default ).

Mutable variables

可变变量
mut age := 20
println(age)
age = 21
println(age)

要更改变量的值,请使用:=
在V中,变量默认是不可变的。为了能够改变变量的值,你必须用mut 声明它。
从第一行删除mut后,尝试编译上面的程序。

Initialization vs assignment

初始化和赋值

注意以下(重要的)区别 := and =
:= 用于声明和初始化, = 用于赋值.

fn main() {
    age = 21
}

这段代码无法编译,因为没有声明变量“age”。所有变量都需要在V中声明。

fn main() {
    age := 21
}

Declaration errors

申明错误

在开发模式下,编译器将警告您没有使用该变量(您将得到一个“未使用的变量”警告)。
在生产模式下(通过将-prod标志传递给v - v -prod foo.v来启用),它根本不会编译(就像在Go中一样)。

fn main() {
    a := 10
    if true {
        a := 20 // error: shadowed variable
    }
    // warning: unused variable `a`
}

不像大多数语言,变量隐藏是不允许的。使用已在父范围中使用的名称声明变量将导致编译错误。

Types

类型

Primitive types

原始类型(基础类型)
bool

string

i8    i16  int  i64      i128 (soon)
byte  u16  u32  u64      u128 (soon)

rune // 表示Unicode代码点

f32 f64

any_int, any_float // 数字文字的内部中间类型

byteptr, voidptr, charptr, size_t // 这些主要用于C语言的互操作性

any // 类似于C的void*和Go的接口{}

这里请注意,和 C and Go 不同, int 总是32位整数。

V中的所有运算符必须在两边都有相同类型的值,这是一个例外. 如果一端的小的数据类型完全符合另一端的类型的数据范围,
则可以自动提升它。这些是允许的可能性:

   i8 → i16 → int → i64
                  ↘     ↘
                    f32 → f64
                  ↗     ↗
 byte → u16 → u32 → u64 ⬎
      ↘     ↘     ↘      ptr
   i8 → i16 → int → i64 ⬏

例如,一个int值可以自动提升为f64i64但不能提升为f32u32.
(对于大值,f32意味着丢失精度,而对于负值,u32 意味着丢失符号).

Strings

字符串
name := 'Bob'
println('Hello, $name!')  //  `$` 用于字符串插值
println(name.len)

bobby := name + 'by' // +用于连接字符串
println(bobby) // "Bobby"

println(bobby[1..3]) // "ob"
mut s := 'hello '
s += 'world' // `+=` 用于附加到字符串
println(s) // "hello world"

在V中,字符串是只读字节数组。字符串数据使用UTF-8编码。字符串是不可变的。

单引号和双引号都可以用来表示字符串。为了保持一致性,vfmt将双引号转换为单引号,除非字符串包含单引号字符。

插值语法非常简单。它也适用于字段:
'age = $user.age'. 如果您需要更复杂的表达式,请使用 ${}: 'can register = ${user.age > 13}'.

也支持类似于C中的printf()的格式说明符。
fgx等是可选的,并指定输出格式。编译器负责存储大小,所以没有hdllu

println('x = ${x:12.3f}')
println('${item:-20} ${n:20d}')

V中的所有运算符必须在两边都有相同类型的值。如果“age”不是一个字符串(例如,如果“age”是一个“int”),这段代码将不会编译:

println('age = ' + age)

我们必须将age转换为string:

println('age = ' + age.str())

或使用串插补(首选):

println('age = $age')

要表示字符字面量,请使用 `

a := `a`
assert 'aloha!'[0] == `a`

对于原始字符串,在前加“r”。原始字符串不被转义:

s := r'hello\nworld'
println(s) // "hello\nworld"

Numbers

数字
a := 123

这将把123的值赋值给‘a’。默认情况下,“a”的类型是“int”。

你也可以使用十六进制,二进制或八进制符号的整型文字:

a := 0x7B
b := 0b01111011
c := 0o173

所有这些都被赋值相同,123。它们的类型都是’ int ',
不管你用什么表示法。V还支持以“_”作为分隔符写入数字:

num := 1_000_000 // 等同于 1000000
three := 0b0_11 // 等同于 0b11
float_num := 3_122.55  // 等同于 3122.55
hexa := 0xF_F // 等同于 255
oct := 0o17_3 // 等同于 0o173

如果需要不同类型的整数,可以使用类型转换:

a := i64(123)
b := byte(42)
c := i16(12345)

赋值浮点数的方法与此相同:

f := 1.0
f1 := f64(3.14)
f2 := f32(3.14)

如果没有显式指定类型,默认情况下float文字的类型为“f64”。

Arrays

数组
mut nums := [1, 2, 3]
println(nums) // "[1, 2, 3]"
println(nums[1]) // "2"
nums[1] = 5
println(nums) // "[1, 5, 3]"

println(nums.len) // "3"
nums = [] // 数组现在是空的
println(nums.len) // "0"

// 声明一个int类型的空数组:
users := []int{}

数组的类型由第一个元素决定:

  • [1, 2, 3] 是int数组 ([]int).
  • ['a', 'b'] 是字符串数组 ([]string).

如果V不能推断出数组的类型,用户可以为第一个元素显式地指定它:’ [byte(16), 32, 64, 128] ‘。
数组是同构的(所有元素必须具有相同的类型)。这意味着像’ [1,‘a’] '这样的代码将不能编译。

字段 .len 返回数组的长度。注意,它是一个只读字段,用户不能修改它。
在V.中导出的字段默认是只读的。参见访问修饰符

Array operations
数组操作
mut nums := [1, 2, 3]
nums << 4
println(nums) // "[1, 2, 3, 4]"

// append array
nums << [5, 6, 7]
println(nums) // "[1, 2, 3, 4, 5, 6, 7]"

mut names := ['John']
names << 'Peter'
names << 'Sam'
// names << 10  <-- 这将无法编译。`names '是一个字符串数组。
println(names.len) // "3"
println('Alex' in names) // "false"

<< 是将值追加到数组末尾的运算符。它还可以附加整个数组。

如果数组中包含val,则val in array返回true。看 in operator.

Initializing array properties
初始化数组属性

在初始化的时候,你可以指定数组的容量(cap),或者它的初始长度(len)和默认元素(init):

arr := []int{ len: 5, init: -1 } // `[-1, -1, -1, -1, -1]`

设置容量可以提高插入的性能,因为它减少了需要重新分配的次数:

mut numbers := []int{ cap: 1000 }
println(numbers.len) // 0
// 现在附加元素不会重新分配
for i in 0 .. 1000 {
    numbers << i
}

注意:上面的代码使用了range ’ for '语句。

Array methods
数组方法

使用’ println(arr) ‘可以很容易地打印所有数组,并使用’ s:= arr.str() '转换为字符串。

可以使用.filter().map()方法有效地筛选和映射数组:

nums := [1, 2, 3, 4, 5, 6]
even := nums.filter(it % 2 == 0)
println(even) // [2, 4, 6]

words := ['hello', 'world']
upper := words.map(it.to_upper())
println(upper) // ['HELLO', 'WORLD']

it是一个内置变量,它指的是当前在filter/map方法中正在处理的元素。

Multidimensional Arrays
多维数组

数组可以有多个维度。

2d array example:

mut a := [][]int{len:2, init: []int{len:3}}
a[0][1] = 2
println(a) // [[0, 2, 0], [0, 0, 0]]

3d array example:

mut a := [][][]int{len:2, init: [][]int{len:3, init: []int{len:2}}}
a[0][1][1] = 2
println(a) // [[[0, 0], [0, 2], [0, 0]], [[0, 0], [0, 0], [0, 0]]]
Sorting arrays
排序数组

对各种数组进行排序是非常简单和直观的。在提供自定义排序条件时使用特殊变量“a”和“b”。

mut numbers := [1, 3, 2]
numbers.sort()      // 1, 2, 3
numbers.sort(a > b) // 3, 2, 1
struct User { age int  name string }
mut users := [...]
users.sort(a.age < b.age)   // sort by User.age int field
users.sort(a.name > b.name) // reverse sort by User.name string field

Maps

mut m := map[string]int // 现在只允许带字符串键的映射
m['one'] = 1
m['two'] = 2
println(m['one']) // "1"
println(m['bad_key']) // "0"
println('bad_key' in m) // 使用 `in`来检测是否存在这样的键
m.delete('two')

// 短的语法
numbers := {
    'one': 1
    'two': 2
}

Module imports

模块引入

有关创建模块的信息,请参见Modules

Importing a module

导入一个模块

模块可以使用关键字 import 导入。

import os

fn main() {
    name := os.input('Enter your name:')
    println('Hello, $name!')
}

当使用来自其他模块的常量时,必须给模块名称加上前缀。不过,您也可以直接从其他模块导入函数和类型:

import os { input }
import crypto.sha256 { sum }
import time { Time }

Module import aliasing

模块导入别名

任何导入的模块名称都可以使用“as”关键字作为别名:

注意: 除非您创建了mymod/sha256.v,否则此示例将无法编译。

import crypto.sha256
import mymod.sha256 as mysha256

fn main() {
    v_hash := sha256.sum('hi'.bytes()).hex()
    my_hash := mysha256.sum('hi'.bytes()).hex()
    assert my_hash == v_hash
}

不能给导入的函数或类型起别名。 但是,您可以重新声明类型。

import time

type MyTime time.Time

fn main() {
    my_time := MyTime{
        year: 2020,
        month: 12,
        day: 25
    }
    println(my_time.unix_time())
}

Statements & expressions

逻辑控制与表达

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可以用作表达:

num := 777
s := if num % 2 == 0 {
    'even'
}
else {
    'odd'
}
println(s) // "odd"
Is check
检查

你可以用 ifmatch 来检查和类型。

struct Abc {
    val string
}
struct Xyz {
    foo string
}
type Alphabet = Abc | Xyz

x := Alphabet(Abc{'test'}) // sum type
if x is Abc {
    //x是自动转换为Abc,可以在这里使用
    println(x)
}

如果你有一个struct字段需要检查,也有一种方法来命名一个别名。

if x.bar is MyStruct as bar {
   `x.bar` 不能自动进行转换,而是使用 `as bar` 创建一个MyStruct类型的变量
    println(bar)
}

In operator

In 操作符

’ in '允许检查 array或map是否包含元素。

nums := [1, 2, 3]
println(1 in nums) // true

m := {'one': 1, 'two': 2}
println('one' in m) // 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 loop

for 循环

V只有一个循环关键字“for”,有几种形式。

Array 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

for value in arr形式用于遍历数组中的元素。
如果需要索引,可以使用另一种形式’ for index, value in arr '。
注意,该值是只读的。如果你需要修改数组,则循环时,你必须使用索引:

mut numbers := [0, 1, 2]
for i, _ in numbers {
    numbers[i]++
}
println(numbers) // [1, 2, 3]

当标识符只有一个下划线时,它会被忽略。

Map for
m := {'one':1, 'two':2}
for key, value in m {
    println("$key -> $value")  // Output: one -> 1
}                              //         two -> 2

键或值都可以通过使用一个下划线作为标识符来忽略。

m := {'one':1, 'two':2}

// iterate over keys
for key, _ in m {
    println(key)  // Output: one
}                 //         two

// iterate over values
for _, value in m {
    println(value)  // Output: 1
}                   //         2
Range for
// Prints '01234'
for i in 0..5 {
    print(i)
}

low..high指一个独占范围,它表示从low到但不包括high的所有值。

Condition for

条件循环 for

mut sum := 0
mut i := 0
for i <= 100 {
    sum += i
    i++
}
println(sum) // "5050"

这种形式的循环类似于其他语言中的“while”循环。
当布尔值为false时,循环将停止迭代。同样,该条件周围没有括号,括号始终是必需的。

Bare for
mut num := 0
for {
    num += 2
    if num >= 10 {
        break
    }
}
println(num) // "10"

可以省略该条件,从而导致无限循环。

C for

类似C的普通 for循环

for i := 0; i < 10; i += 2 {
    // Don't print 6
    if i == 6 {
        continue
    }
    println(i)
}

最后,还有传统的C风格的for循环。
它比while表单更安全,因为后者很容易忘记更新计数器,从而陷入无限循环。这里i不需要用mut声明因为根据定义它总是可变的。

Match

译者注释:类似 switch case

os := 'windows'
print('V is running on ')
match os {
    'darwin' { println('macOS.') }
    'linux'  { println('Linux.') }
    else     { println(os) }
}

match 语句是编写一系列if - else语句的一种更短的方法。
当找到匹配的分支时,将运行以下语句块。当没有其他分支匹配时,将运行else分支。

number := 2
s := match number {
    1    { 'one' }
    2    { 'two' }
    else { 'many'}
}

匹配表达式返回每个分支的最终表达式。

enum Color {
    red
    blue
    green
}

fn is_red_or_blue(c Color) bool {
    return match c {
        .red   { true  }
        .blue  { true  }
        .green { false }
    }
}

match语句还可以使用简写对枚举enum的分支语法。
当所有分支都是穷举的时,不允许使用else分支。

c := `v`
typ := match c {
    `0`...`9` { 'digit' }
    `A`...`Z` { 'uppercase' }
    `a`...`z` { 'lowercase' }
    else      { 'other' }
}
println(typ) // 'lowercase'

您还可以使用范围作为 match 模式。如果该值落在一个分支的范围内,则将执行该分支。

注意,范围使用’…(三个点)而不是“…””(两个点)。
这是因为范围包含最后一个元素,而不是排除(如’…的范围)。使用“…'将抛出一个错误。

Defer

defer语句是周围的函数代码块执行完并返回后延迟执行的语句块。

fn read_log() {
    f := os.open('log.txt')
    defer { f.close() }
    ...
    if !ok {
        // 在这里调用defer语句,文件将被关闭
        return
    }
    ...
    // 在这里调用defer语句,文件将被关闭
}

Structs

结构体
struct Point {
    x int
    y int
}

mut p := Point{
    x: 10
    y: 20
}

println(p.x) // 使用点访问结构字段

// 3个字段或更少的结构的可选文字语法
p = Point{10, 20}
assert p.x == 10

Heap structs

堆结构

在堆栈上分配结构。要在堆上分配结构并获得对它的引用,请使用&前缀:

p := &Point{10, 10}
// 引用具有相同的语法来访问字段
println(p.x)

p的类型是&Point。这是一个reference指向。引用类似于Go指针和c++引用。

Embedded structs

嵌入式结构

V不允许子类,但它支持嵌入式结构:

// TODO: 这将在稍后实现
struct Button {
    Widget
    title string
}

button := new_button('Click me')
button.set_pos(x, y)

// 如果没有嵌入,我们就不得不这样做
button.widget.set_pos(x,y)

Default field values

默认字段值
struct Foo {
    n   int      // n is 0 by default
    s   string   // s is '' by default
    a   []int    // a is `[]int{}` by default
    pos int = -1 // custom default value
}

在创建结构时,默认情况下所有结构字段都归零。
数组和映射字段被分配。还可以定义自定义默认值。

Short struct literal syntax

短的结构实例创建语法
mut p := Point{x: 10, y: 20}

// 如果结构名是已知的,可以省略它
p = {x: 30, y: 4}
assert p.y == 4

省略结构名称也可以用于返回结构实例或将结构实例作为函数参数传递。

Trailing struct literal arguments
尾随结构实例参数

V没有默认函数参数或命名参数,因为后面的结构文字语法可以代替:

struct ButtonConfig {
    text        string
    is_disabled bool
    width       int = 70
    height      int = 20
}

fn new_button(c ButtonConfig) &Button {
    return &Button{
        width: c.width
	height: c.height
	text: c.text
    }
}

button := new_button(text:'Click me', width:100)
// the height is unset, so it's the default value
assert button.height == 20

如你所见,结构名和大括号都可以省略,而不是:

new_button(ButtonConfig{text:'Click me', width:100})

这只适用于最后一个参数采用结构的函数。

Access modifiers

默认情况下,结构字段是私有的和不可变的(使得结构也是不可变的)。
它们的访问修饰符可以用“pub”和“mut”来更改。总共有5种可能的选择:

struct Foo {
    a int   // 私有不可变的 (default)
mut:
    b int   // 私有可变
    c int   // (你可以用相同的访问修饰符列出多个字段)
pub:
    d int   // 公开不可变的(readonly)
pub mut:
    e int   // public,但只在父模块中是可变的
__global:
    f int   // 父模块内部和外部都是公共的和可变的
}           // (不推荐使用,这就是为什么‘global’关键字以__开头)

例如,下面是在’ builtin ‘模块中定义的’ string '类型:

struct string {
    str byteptr
pub:
    len int
}

从这个定义很容易看出‘string’是一个不可变类型。
带有字符串数据的字节指针在’内置’外部根本不可访问。len字段是公共的,但不可变的:

fn main() {
    str := 'hello'
    len := str.len // OK
    str.len++      // Compilation error
}

这意味着在V中定义公共只读字段非常容易,不需要在getter /setter或属性中定义。

Methods

方法
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没有类,但是你可以在类型上定义方法。方法是带有特殊接收者参数的函数。
接收方出现在它自己的参数列表中,位于’ fn '关键字和方法名之间。在本例中,
’ can_register ‘方法有一个类型为’ User ‘的接收器,名为’ u '。
惯例是不要使用“self”或“this”这样的收信人名字,
而是使用一个简短的,最好是一个字母长的名字。

Functions 2

Pure functions by default

纯函数( 默认)

V函数在默认情况下是纯函数,这意味着它们的返回值只是它们的参数的函数,而且它们的计算没有副作用(除了I/O)。

这是通过缺少全局变量和所有函数参数默认不变来实现的,即使在传递references时也是如此。

然而,V不是一种纯粹的函数式语言。

有一个编译器标志来启用全局变量(’——enable-globals '),但是这是针对底层应用程序的,比如内核和驱动程序。

Mutable arguments

可变参数

可以使用关键字“mut”修改函数参数:

struct User {
mut:
    is_registered bool
}

fn (mut u User) register() {
    u.is_registered = true
}

mut user := User{}
println(user.is_registered) // "false"
user.register()
println(user.is_registered) // "true"

在这个例子中,接收者(仅仅是第一个参数)被标记为可变的,
因此’ register() '可以更改用户对象。对于非接收方参数也是这样:

fn multiply_by_2(mut arr []int) {
    for i in 0..arr.len {
        arr[i] *= 2
    }
}

mut nums := [1, 2, 3]
multiply_by_2(mut nums)
println(nums) // "[2, 4, 6]"

注意,在调用此函数时,必须在’ nums ‘之前添加’ mut '。这表明被调用的函数将修改该值。

最好使用返回值,而不是修改参数。修改参数应该只在应用程序的性能关键部分进行,以减少分配和复制。

由于这个原因,V不允许用基本类型(比如整数)修改参数。只有更复杂的类型(如数组和映射)可以修改。
Use user.register() or user = register(user)
instead of register(mut user).

V使它很容易返回一个修改版本的对象:

fn register(u User) User {
    return { u | is_registered: true }
}

user = register(user)

Anonymous & high order functions

匿名和高阶函数
fn sqr(n int) int {
    return n * n
}

fn run(value int, op fn(int) int) int {
    return op(value)
}

fn main()  {
    println(run(5, sqr)) // "25"

    // 匿名函数可以在其他函数内部声明:
    double_fn := fn(n int) int {
        return n + n
    }
    println(run(5, double_fn)) // "10"

    // 函数可以在不给变量赋值的情况下传递:
    res := run(5, fn(n int) int {
        return n + n
    })
}

References

参照
fn (foo Foo) bar_method() {
    ...
}

fn bar_function(foo Foo) {
    ...
}

如果一个函数参数是不可变的(像上面例子中的’ foo '),
V可以传递它的值或引用。编译器将自己确定这个值,开发人员不需要考虑它。

您不再需要记住应该按值还是按引用传递结构。

您可以通过添加’ & '来确保结构总是通过引用传递。:

fn (foo &Foo) bar() {
    println(foo.abc)
}

’ foo '仍然是不可变的,不能被改变。为此, (mut foo Foo) 必须被使用。

通常,V的引用类似于Go指针和c++引用。例如,树结构的定义是这样的:

struct Node<T> {
    val   T
    left  &Node
    right &Node
}

Constants

常量
const (
    pi    = 3.14
    world = '世界'
)

println(pi)
println(world)

常量用“const”声明。它们只能在模块级别(在函数之外)定义。
常量值永远不能改变。
常数比大多数语言更灵活。您可以分配更复杂的值:

struct Color {
        r int
        g int
        b int
}

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}
    // evaluate function call at compile-time
    blue = rgb(0, 0, 255)
)

println(numbers)
println(red)
println(blue)

不允许使用全局变量,因此这可能非常有用。

println('Top cities: $TOP_CITIES.filter(.usa)')
vs
println('Top cities: $top_cities.filter(.usa)')

println and other builtin functions

println和其他内置函数

println是一个简单而强大的内置函数。它可以打印任何东西:字符串、数字、数组、映射、结构体。

println(1) // "1"
println('hi') // "hi"
println([1,2,3]) // "[1, 2, 3]"
println(User{name:'Bob', age:20}) // "User{name:'Bob', age:20}"

如果你想为你的类型定义一个自定义的打印值,只需定义一个’ .str() string '方法:

struct Color {
    r int
    g int
    b int
}

pub fn (c Color) str() string { return '{$c.r, $c.g, $c.b}' }

red := Color{r: 255, g: 0, b: 0}
println(red)

如果不想打印换行符,可以使用print()代替。

内置函数的数量很低少。其他内建函数有:

fn exit(exit_code int)
fn panic(message string)
fn print_backtrace()

Modules

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!')
}

你可以在mymodule/中有任意多的 .v文件。就这样,你现在可以在你的代码中使用它:

module main

import mymodule

fn main() {
    mymodule.say_hi()
}

注意,每次调用外部函数时都必须指定模块。
乍一看,这可能有点冗长,但它使代码可读性强得多
更容易理解,因为它总是很清楚来自哪个函数
正在调用哪个模块。特别是在大型代码库中。

模块名称应该简短,不超过10个字符。不允许循环进口。

您可以在任何地方创建模块。

所有模块都被静态地编译为单个可执行文件。

如果你想写一个模块,会自动调用一些
导入时的设置/初始化代码(也许您想调用
一些C库函数),在模块内部写一个init函数:

fn init() {
    // your setup code here ...
}

init函数不能是公共的。它将被自动调用。

Types 2

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) string {
    if s is Dog { //使用' is '检查接口的底层类型
        println('perform(dog)')
	println(s.breed) // “s”自动转换为“Dog”(智能转换)
    } else if s is Cat {
        println('perform(cat)')
    }
    return s.speak()
}

dog := Dog{}
cat := Cat{}
println(perform(dog)) // "woof"
println(perform(cat)) // "meow"

类型通过实现其方法来实现接口。
没有显式声明的意图,没有“implements”关键字。

Enums

枚举
enum Color {
    red green blue
}

mut color := Color.red
// V知道 `color` 是一个 `Color` 。不需要使用`color = Color.green`。
color = .green
println(color) // "green"

match color {
    .red { ... }
    .green { ... }
    .blue { ... }
}

枚举匹配必须是穷举的,或者有一个“else”分支。这确保了如果添加了新的enum字段,它将在代码中的所有地方得到处理。

Sum types

组合类型

sum类型实例可以保存几种不同类型的值。使用“type”
关键字声明一个组合类型:

struct Moon {}
struct Mars {}
struct Venus {}

type World = Moon | Mars | Venus

sum := World(Moon{})

若要检查sum类型实例是否包含某个类型,请使用“sum is Type”。
要将sum类型转换为它的一个变体,可以使用“sum as Type”:

fn (m Mars) dust_storm() bool

fn main() {
    mut w := World(Moon{})
    assert w is Moon

    w = Mars{}
    //使用' as '访问Mars实例
    mars := w as Mars
    if mars.dust_storm() {
        println('bad weather!')
    }
}

Matching sum types

组合类型匹配

您还可以使用“match”来确定变体:

fn open_parachutes(n int)

fn land(w World) {
    match w {
        Moon {} // no atmosphere
        Mars {
            // light atmosphere
            open_parachutes(3)
        }
        Venus {
            // heavy atmosphere
            open_parachutes(1)
        }
    }
}

“match”必须为每个变体都有一个模式,或者有一个“else”分支。

有两种方法可以访问匹配分支中的cast变量:

  • match 传入的变量
  • 使用’ as '指定变量名
fn (m Moon) moon_walk()
fn (m Mars) shiver()
fn (v Venus) sweat()

fn pass_time(w World) {
    match w {
        // using the shadowed match variable, in this case `w` (smart cast)
        Moon { w.moon_walk() }
        Mars { w.shiver() }
        else {}
    }
    // using `as` to specify a name for each value
    match w as var {
        Mars  { var.shiver() }
        Venus { var.sweat() }
        else {
            // w is of type World
            assert w is Moon
        }
    }
}

注意:只有当匹配表达式是一个变量时,隐藏才有效。它不能用于结构字段、数组索引或映射键查找。

Option/Result types and error handling

可选类型

可选类型使用 ?Type申明:

struct User {
    id int
    name string
}

struct Repo {
    users []User
}

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 := Repo {
        users: [User{1, 'Andrew'}, User {2, 'Bob'}, User {10, 'Charles'}]
    }
    user := repo.find_user_by_id(10) or { // Option types must be handled by `or` blocks
        return
    }
    println(user.id) // "10"
    println(user.name) // "Charles"
}

V将“Option”和“Result”组合成一种类型,所以您不需要决定使用哪一种。

将一个功能“升级”为可选功能所需的工作量极小;
你必须加上一个’ ?返回类型,并在出错时返回一个错误。

如果您不需要返回错误消息,您可以简单地’ return none ‘(这是比’ return error("") '的一个更有效的等效物)。

这是v中错误处理的主要机制,它们仍然是值,就像在Go中,
但是这样做的好处是错误是不能被处理的,而且处理它们也不会那么冗长。
与其他语言不同,V不通过‘throw/try/catch’块处理异常。
err 定义在or块中,并设置为传递的字符串消息
error()函数。如果返回noneerr为空。

user := repo.find_user_by_id(7) or {
    println(err) // "User 7 not found"
    return
}

Handling optionals

可选类型(错误)处理

有四种方法处理可选的。第一种方法是
继续抛出错误:

import net.http

fn f(url string) ?string {
    resp := http.get(url)?
    return resp.text
}

http.get 返回 ?http.Response. 因为 ?
然后错误将被传播到f的调用者。当使用?后一个
函数调用产生一个可选的,封闭函数必须返回
也是可选的。如果在main()中使用了错误传播
函数它将“恐慌”,因为错误不能传播
任何进一步的。

f的主体本质上是以下内容的浓缩版:

    resp := http.get(url) or {
        return error(err)
    }
    return resp.text

第二种方法是提早结束执行:

user := repo.find_user_by_id(7) or {
    return
}

在这里,您可以调用’ panic() ‘或’ exit() ‘,这将停止整个程序的执行,
或者使用控制流语句(’ return ', ’ break ', ’ continue '等)从当前块中断开。
注意,“break”和“continue”只能在“for”循环中使用。

V没有办法强制“unwrap”一个可选选项(像其他语言一样,例如Rust的 unwrap()
或 Swift’s !)。为此,可以使用’ or {panic(err)} '。


第三种方法是在 or块的末尾提供一个默认值。万一出现错误,
该值将被赋值,因此它必须与被处理的“选项”的内容具有相同的类型。

fn do_something(s string) ?string {
    if s == 'foo' { return 'foo' }
    return error('invalid string') // Could be `return none` as well
}

a := do_something('foo') or { 'default' } // a will be 'foo'
b := do_something('bar') or { 'default' } // b will be 'default'

第四种方法是使用“if”展开:

if resp := http.get(url) {
    println(resp.text) // resp is a http.Response, not an optional
} else {
    println(err)
}

上图中, http.get 返回一个 ?http.Responseresp 只在第一个范围内
if 分支。err只在 else 分支 。

Generics

泛型
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)?

Another example:

fn compare<T>(a, b T) int {
    if a < b {
        return -1
    }
    if a > b {
        return 1
    }
    return 0
}

println(compare<int>(1,0)) // Outputs: 1
println(compare<int>(1,1)) //          0
println(compare<int>(1,2)) //         -1

println(compare<string>('1','0')) // Outputs: 1
println(compare<string>('1','1')) //          0
println(compare<string>('1','2')) //         -1

println(compare<float>(1.1, 1.0)) // Outputs: 1
println(compare<float>(1.1, 1.1)) //          0
println(compare<float>(1.1, 1.2)) //         -1

Concurrency

并发运行

V的并发模型与Go的非常相似。要并发运行’ foo() ‘,只需
用’ go foo() '调用它。现在,它在一个新系统上启动该功能
线程。很快将实现协同程序和调度程序。

import sync
import time

fn task(id, duration int, mut wg sync.WaitGroup) {
    println("task ${id} begin")
    time.sleep_ms(duration)
    println("task ${id} end")
    wg.done()
}

fn main() {
    mut wg := sync.new_waitgroup()
    wg.add(3)
    go task(1, 500, mut wg)
    go task(2, 900, mut wg)
    go task(3, 100, mut wg)
    wg.wait()
    println('done')
}

// Output: task 1 begin
//         task 2 begin
//         task 3 begin
//         task 3 end
//         task 1 end
//         task 2 end
//         done

与Go不同,V还没有通道。然而,数据可以在协同程序之间交换
以及通过共享变量调用的线程。这个变量应该作为引用创建并传递给
协同程序为 mut 。底层的 struct 也应该包含一个mutex 来锁定并发访问:

import sync

struct St {
mut:
	x int // 共享数据
	mtx &sync.Mutex
}

fn (mut b St) g() {
	...
	b.mtx.m_lock()
	// read/modify/write b.x
	...
	b.mtx.unlock()
	...
}

fn caller() {
	mut a := &St{ // create as reference so it's on the heap
		x: 10
		mtx: sync.new_mutex()
	}
	go a.g()
	...
	a.mtx.m_lock()
	// read/modify/write a.x
	...
	a.mtx.unlock()
	...
}

Decoding JSON

JSON解析
import json

struct User {
    name string
    age  int

    // 使用“skip”属性跳过某些字段
    foo Foo [skip]

    // 如果字段名在JSON中不同,可以指定它
    last_name string [json:lastName]
}

data := '{ "name": "Frodo", "lastName": "Baggins", "age": 25 }'
user := json.decode(User, data) or {
    eprintln('Failed to decode json')
    return
}
println(user.name)
println(user.last_name)
println(user.age)

由于JSON无处不在的特性,对它的支持被直接构建到V中。

decode函数有两个参数:’ JSON.decode '函数的第一个参数是JSON值应该被解码到的类型,第二个参数是包含JSON数据的字符串。

V生成JSON编码和解码的代码。不使用运行时反射。性能会好得多。

Testing

单元测试
// hello.v
fn hello() string {
    return 'Hello world'
}

// hello_test.v
fn test_hello() {
    assert hello() == 'Hello world'
}

assert 关键字也可以在测试之外使用。

所有测试函数都必须放在名为’ test的文件中。v ‘和测试函数名必须以’ test '开头。

您还可以定义一个特殊的测试函数:’ testsuite_begin ‘,它将是
在’ _test中的所有其他测试函数之前运行 '*.v '文件。

您还可以定义一个特殊的测试函数:’ testsuite_end ‘,它将是
在’ _test中运行所有其他测试函数。v '文件。

执行’ v hello_test.v '来运行测试。

要测试整个模块,做’ v test mymodule '。

你也可以做“v测试”来测试当前文件夹(和子目录)中的所有内容。

可以将’ -stats '传递给v test,以查看每个_test中各个测试的详细信息。v文件。

Memory management

内测管理

(Work in progress)

V不使用垃圾收集或引用计数。编译器会清除一切
在编译过程中。如果你的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)
    ...
}

字符串没有转义’ draw_text ',所以当
函数退出。

实际上,前两个调用根本不会导致任何分配。
这两根弦很小,
V将为它们使用一个预先分配的缓冲区。

fn test() []int {
    number := 7 // 栈变量
    user := User{} // 结构分配在栈上
    numbers := [1, 2, 3] // 数组结构分配在堆上,将在函数退出时释放
    println(number)
    println(user)
    println(numbers)
    numbers2 := [4, 5, 6] // 返回的数组在这里不会被释放
    return numbers2
}

ORM

对象关系映射

(这仍然处于alpha版本)

V有一个内置的ORM(对象关系映射),它支持SQLite,并将很快支持MySQL、Postgres、MS SQL和Oracle。

V的ORM提供了许多好处:

-一种语法为所有SQL方言。(数据库之间的迁移变得更容易了。)
-查询是用V的语法构造的。(没有必要学习另一种语法。)
——安全。(所有查询都会自动清理,以防止SQL注入。)
-编译时检查。(这样可以防止只有在运行时才能捕捉到的打字错误。)
-可读性和简单性。(您不需要手动解析查询的结果,然后从解析的结果手动构造对象。)

struct Customer { // 结构名必须与表名相同(目前)
    id int // 一个名为“id”的整数类型的字段必须是第一个字段
    name string
    nr_orders int
    country string
}

db := sqlite.connect('customers.db')

// select count(*) from Customer
nr_customers := sql db { select count from Customer }
println('number of all customers: $nr_customers')

// V syntax can be used to build queries
// db.select returns an array
uk_customers := sql db { select from Customer where country == 'uk' && nr_orders > 0 }
println(uk_customers.len)
for customer in uk_customers {
    println('$customer.id - $customer.name')
}

// by adding `limit 1` we tell V that there will be only one object
customer := sql db { select from Customer where id == 1 limit 1 }
println('$customer.id - $customer.name')

// insert a new customer
new_customer := Customer{name: 'Bob', nr_orders: 10}
sql db { insert new_customer into Customer }

For more examples, see vlib/orm/orm_test.v.

Writing Documentation

文档编写

它的工作方式和Go非常相似。这很简单:没有必要
为你的代码单独编写文档,vdoc会从源代码中的文档字符串生成它。

每个函数/类型/常量的文档必须放在声明之前:

// clearall clears all bits in the array
fn clearall() {

}

注释必须以定义的名称开始。

模块的概述必须放在模块名称后面的第一个注释中。

使用vdoc生成文档,例如“vdoc net.http”。

Tools

v fmt

您不需要担心格式化代码或设置样式指南。
“v fmt”就解决了这个问题:

v fmt file.v

建议设置编辑器,以便在每次保存时运行“v fmt -w”。
vfmt运行通常非常便宜(花费小于30ms)。

总是跑v fmt -w file.v 在输入代码之前。

Profiling

资料收集

V对分析你的程序有很好的支持:’ V -profile profile profile.txt run file.v ’
这将生成一个profile.txt文件,然后您可以对该文件进行分析。

生成的profile.txt文件将有4列的行:
a)一个函数被调用的次数
b)一个函数总共花费了多少时间(ms)
c)调用一个函数平均花费多少时间(在ns中)
d) v函数的名称

你可以排序列3(每个函数的平均时间)使用:
sort -n -k3 profile.txt|tail

你也可以使用秒表来测量你的代码的部分显式:

import time
fn main(){
    sw := time.new_stopwatch({})
    println('Hello world')
    println('Greeting the world took: ${sw.elapsed().nanoseconds()}ns')
}

Advanced Topics

高级主题

Memory-unsafe code

Memory-unsafe代码

有时,为了提高效率,您可能希望编写底层代码
损坏内存或容易受到安全攻击。V支持编写这样的代码,
但不是默认的。

V要求有意标记任何潜在的内存不安全操作。
对它们进行标记还可以向任何阅读代码的人表明可能存在
如果有错误,就会违反内存安全。

潜在的内存不安全操作的例子是:

  • 指针算术
  • 指针索引
  • 从不兼容类型转换为指针
  • 调用某些C函数,例如。" free ", " strlen “和” strncmp "

要标记潜在的内存不安全操作,请将它们封装在一个“不安全”块中:

// 分配2个未初始化的字节&返回一个对它们的引用
mut p := unsafe { &byte(malloc(2)) }
p[0] = `h` // Error: pointer indexing is only allowed in `unsafe` blocks
unsafe {
    p[0] = `h`
    p[1] = `i`
}
p++ // Error: pointer arithmetic is only allowed in `unsafe` blocks
unsafe {
    p++ // OK
}
assert *p == `i`

最佳实践是避免将内存安全的表达式放入一个 unsafe块中,
所以使用 unsafe的原因就越清楚越好。一般来说任何代码
你认为是内存安全不应该在一个 unsafe 块,所以编译器
可以验证它。

如果您怀疑您的程序确实违反了内存安全,那么您就有了一个良好的开端
找出原因:查看 unsafe块(以及它们是如何相互作用的)
周围的代码)。

  • 注意:这项工作正在进行中。

Calling C functions from V

从V中调用C函数
#flag -lsqlite3
#include "sqlite3.h"

// See also the example from https://www.sqlite.org/quickstart.html
struct C.sqlite3{}
struct C.sqlite3_stmt{}

type FnSqlite3Callback fn(voidptr, int, &charptr, &charptr) int

fn C.sqlite3_open(charptr, &&C.sqlite3) int
fn C.sqlite3_close(&C.sqlite3) int
fn C.sqlite3_column_int(stmt &C.sqlite3_stmt, n int) int
// ... you can also just define the type of parameter & leave out the C. prefix
fn C.sqlite3_prepare_v2(&sqlite3, charptr, int, &&sqlite3_stmt, &charptr) int
fn C.sqlite3_step(&sqlite3_stmt)
fn C.sqlite3_finalize(&sqlite3_stmt)
fn C.sqlite3_exec(db &sqlite3, sql charptr, FnSqlite3Callback, cb_arg voidptr, emsg &charptr) int
fn C.sqlite3_free(voidptr)

fn my_callback(arg voidptr, howmany int, cvalues &charptr, cnames &charptr) int {
    for i in 0..howmany {
	    print('| ${cstring_to_vstring(cnames[i])}: ${cstring_to_vstring(cvalues[i]):20} ')
	}
    println('|')
    return 0
}

fn main() {
    db := &C.sqlite3(0) // this means `sqlite3* db = 0`
    C.sqlite3_open('users.db', &db) // passing a string literal to a C function call results in a C string, not a V string
    // C.sqlite3_open(db_path.str, &db) // you can also use `.str byteptr` field to convert a V string to a C char pointer
    query := 'select count(*) from users'
    stmt := &C.sqlite3_stmt(0)
    C.sqlite3_prepare_v2(db, query.str, - 1, &stmt, 0)
    C.sqlite3_step(stmt)
    nr_users := C.sqlite3_column_int(stmt, 0)
    C.sqlite3_finalize(stmt)
    println('There are $nr_users users in the database.')
    //
    error_msg := charptr(0)
    query_all_users := 'select * from users'
    rc := C.sqlite3_exec(db, query_all_users.str, my_callback, 7, &error_msg)
    if rc != C.SQLITE_OK {
        eprintln( cstring_to_vstring(error_msg) )
        C.sqlite3_free(error_msg)
    }
    C.sqlite3_close(db)
}

#flag

在你的V文件顶部添加 #flag指令来提供C编译标志,比如:

  • -I 用于添加C包括文件搜索路径
  • -l 用于添加您想要链接的C库名称
  • -L 用于添加C库文件的搜索路径
  • -D 用于设置编译时变量

可以为不同的目标使用不同的标志。目前支持“linux”、“darwin”、“freebsd”和“windows”标志。
NB: 每一面flag必须在一行(目前)

#flag linux -lsdl2
#flag linux -Ivig
#flag linux -DCIMGUI_DEFINE_ENUMS_AND_STRUCTS=1
#flag linux -DIMGUI_DISABLE_OBSOLETE_FUNCTIONS=1
#flag linux -DIMGUI_IMPL_API=

Including C code

引入C代码

您还可以在V模块中直接包含C代码。例如,假设您的C代码位于模块文件夹中名为“C”的文件夹中。然后:

  • Put a v.mod file inside the toplevel folder of your module (if you
    created your module with v new you already have v.mod file). For
    example:
Module {
	name: 'mymodule',
	description: 'My nice module wraps a simple C library.',
	version: '0.0.1'
	dependencies: []
}
  • 将这些行添加到模块的顶部:
#flag -I @VROOT/c
#flag @VROOT/c/implementation.o
#include "header.h"

NB: @VROOT will be replaced by V with the nearest parent folder, where there is a v.mod file.
Any .v file beside or below the folder where the v.mod file is, can use #flag @VROOT/abc to refer to this folder.
The @VROOT folder is also prepended to the module lookup path, so you can import other
modules under your @VROOT, by just naming them.

The instructions above will make V look for an compiled .o file in your module folder/c/implementation.o.
If V finds it, the .o file will get linked to the main executable, that used the module.
If it does not find it, V assumes that there is a @VROOT/c/implementation.c file,
and tries to compile it to a .o file, then will use that.

This allows you to have C code, that is contained in a V module, so that its distribution is easier.
You can see a complete minimal example for using C code in a V wrapper module here:
project_with_c_code.

You can use -cflags to pass custom flags to the backend C compiler. You can also use -cc to change the default C backend compiler.
For example: -cc gcc-9 -cflags -fsanitize=thread.

C types

Ordinary zero terminated C strings can be converted to V strings with string(cstring) or string(cstring, len).

NB: Each string(...) function does NOT create a copy of the cstring, so you should NOT free it after calling string(). If you need to make a copy of the C string (some libc APIs like getenv pretty much require that, since they
return pointers to internal libc memory), you can use cstring_to_vstring(cstring).

On Windows, C APIs often return so called wide strings (utf16 encoding).
These can be converted to V strings with string_from_wide(&u16(cwidestring)) .

V has these types for easier interoperability with C:

  • voidptr for C’s void*,
  • byteptr for C’s byte* and
  • charptr for C’s char*.
  • &charptr for C’s char**

To cast a voidptr to a V reference, use user := &User(user_void_ptr).

voidptr can also be dereferenced into a V struct through casting: user := User(user_void_ptr).

socket.v has an example which calls C code from V .

Debugging generated C code

To debug issues in the generated C code, you can pass these flags:

  • -cg - produces a less optimized executable with more debug information in it.
  • -showcc - prints the C command that is used to build the program.

For the best debugging experience, you can pass all of them at the same time: v -cg -showcc yourprogram.v , then just run your debugger (gdb/lldb) or IDE on the produced executable yourprogram.

If you just want to inspect the generated C code, without further compilation, you can also use the -o flag (e.g. -o file.c). This will make V produce the file.c then stop.

If you want to see the generated C source code for just a single C function, for example main, you can use: -printfn main -o file.c .

To see a detailed list of all flags that V supports, use v help, v help build, v help build-c .

Conditional compilation

条件预编译
$if windows {
    println('Windows')
}
$if linux {
    println('Linux')
}
$if macos {
    println('macOS')
}
$else {
    println('different OS')
}

$if debug {
    println('debugging')
}

如果希望在编译时计算’ If ‘,则必须以’ $ ‘符号作为前缀。现在它只能用于检测
操作系统或’ -debug '编译选项。

Compile time pseudo variables

编译时伪变量

V还允许你的代码访问一组伪字符串变量,这些变量在编译时被替换:

  • @FN => replaced with the name of the current V function
  • @MOD => replaced with the name of the current V module
  • @STRUCT => replaced with the name of the current V struct
  • @FILE => replaced with the path of the V source file
  • @LINE => replaced with the V line number where it appears (as a string).
  • @COLUMN => replaced with the column where it appears (as a string).
  • @VEXE => replaced with the path to the V compiler
  • @VHASH => replaced with the shortened commit hash of the V compiler (as a string).
  • @VMOD_FILE => replaced with the contents of the nearest v.mod file (as a string).

That allows you to do the following example, useful while debugging/logging/tracing your code:

eprintln( 'file: ' + @FILE + ' | line: ' + @LINE + ' | fn: ' + @MOD + '.' + @FN)

另一个例子,如果你想嵌入的版本/名称从v.mod到你的可执行文件:

import v.vmod
vm := vmod.decode( @VMOD_FILE ) or { panic(err) }
eprintln('$vm.name $vm.version\n $vm.description')

Performance tuning

性能调优

在编译代码时,生成的C代码通常足够快
-prod . 但有些情况下,你可能会想要给予
编译器的附加提示,以便进一步优化一些
代码块。

注:这些是很少需要,不应该使用,除非你
分析你的代码,然后看到它们有显著的好处。
引用gcc的文档:“程序员在预测方面是出了名的糟糕
他们的程序是如何运行的。

[inline] - 你可以用’ [inline] '来标记函数,所以C编译器会这样做
尝试内联它们,在某些情况下,这可能有利于性能,
但是可能会影响可执行文件的大小。

[direct_array_access] -在标有’ [direct_array_access] '的函数中
编译器将数组操作直接转换为C数组操作
呕吐边界检查。这可以在迭代函数中节省大量时间
以使函数不安全为代价——除非
边界将由用户检查。

if _likely_(bool expression) { 这提示C编译器,即传递
布尔表达式很可能为真,因此可以生成程序集
代码,有更少的机会的分支错误预测。在JS后台,
什么也不做。

if _unlikely_(bool expression) {类似于’ likely(x) ',但它暗示
布尔表达式是极不可能的。在JS后端,它什么也不做。

Compile-time reflection

编译时反射

拥有内置的JSON支持很好,但是V还允许您高效地创建
序列化器适用于任何数据格式。V有编译时的“if”和“for”结构:

// TODO: not implemented yet

struct User {
    name string
    age  int
}

// Note: T should be passed a struct name only
fn decode<T>(data string) T {
    mut result := T{}
    // compile-time `for` loop
    // T.fields gives an array of a field metadata type
    $for field in T.fields {
        $if field.Type is string {
            // $(string_expr) produces an identifier
            result.$(field.name) = get_string(data, field.name)
        } else $if field.Type is int {
            result.$(field.name) = get_int(data, field.name)
        }
    }
    return result
}

// `decode<User>` generates:
fn decode_User(data string) User {
    mut result := User{}
    result.name = get_string(data, 'name')
    result.age = get_int(data, 'age')
    return result
}

Limited operator overloading

有限的操作符重载
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中的所有操作符一样).

Inline assembly

内联汇编

TODO: 没有实现的

fn main() {
    a := 10
    asm x64 {
        mov eax, [a]
        add eax, 10
        mov [a], eax
    }
}

Translating C/C++ to V

TODO: 在v0.3中可以将C翻译成V。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;
}

Run v translate test.cpp and V will generate test.v:

fn main {
    mut s := []string{}
    s << 'V is '
    s << 'awesome'
    println(s.len)
}

一个在线的C/ c++到V转换器即将面世。

什么时候应该翻译C代码,什么时候应该简单地从V调用C代码?

如果您有编写良好、测试良好的C代码,那么当然您总是可以简单地从V中调用此C代码。

翻译成V给你几个好处:

-如果你计划开发代码库,你现在所有的东西都用一种语言,这比用C更安全更容易开发。
交叉编译变得容易多了。你根本不用担心。
-没有更多的建立标志和包括文件。

Hot code reloading

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_reload) (https://github.com/vlang/v/tree/master/examples/hot_reload)。

Cross compilation

交叉编译

要交叉编译您的项目,只需运行

v -os windows .

or

v -os linux .

(macOS的交叉编译暂时不可能。)

如果您没有任何C依赖项,那么这就是您需要做的全部工作。这工作
当编译GUI应用程序使用’ ui ‘模块或图形应用程序使用’ gg '。

您将需要安装Clang, LLD链接器,并下载一个zip文件
库和包含Windows和Linux的文件。V将为您提供一个链接。

Cross-platform shell scripts in V

在V使用跨平台shell脚本

V可以作为Bash的替代品来编写部署脚本、构建脚本等。

使用V进行此操作的优点是该语言的简单性和可预测性
跨平台支持。“V脚本”可以在类unix系统和Windows上运行。

使用.vsh文件扩展名。 它会使 os 中的所有函数
模块全局化(例如,您可以使用 ls() 而不是 os.ls() )。

#!/usr/local/bin/v run
//在类unix系统上,上面的shebang将文件与V关联,
//这样就可以通过指定文件的路径来运行它
//一旦使用' chmod +x '使其可执行。

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.vsh && ./deploy

或者就像传统的Bash脚本一样运行它:
v run deploy.vsh

在类unix平台上,使用 chmod +x使文件可执行后可以直接运行:
./deploy.vsh

Attributes

属性

V有几个属性可以修改函数和结构体的行为。

属性是在函数/结构声明之前的 [] 中指定的,并且只应用于下面的声明。

// 调用此函数将导致一个弃用警告
[deprecated]
fn old_function() {}

// 这个函数的调用将内联。
[inline]
fn inlined_function() {}

// 下面的结构只能用作引用(' &Window ')并在堆上分配。
[ref_only]
struct Window {
}

// 如果提供的标志为false, // V将不会生成此函数及其所有调用。
//要指明flag,请使用' v -d flag '
[if debug]
fn foo() { }

fn bar() {
   foo() // will not be called if `-d debug` is not passed
}

// 只是用于C互操作,告诉V下面的结构是用C中的' typedef struct '定义的
[typedef]
struct C.Foo { }

// 在Win32 API代码中使用时需要传递回调函数
[windows_stdcall]
fn C.DefWindowProc(hwnd int, msg int, lparam int, wparam int)

Appendices

附件

Appendix I: Keywords

V有29个关键词(3 are literals):

as
assert
break
const
continue
defer
else
enum
false
fn
for
go
goto
if
import
in
interface
is
match
module
mut
none
or
pub
return
struct
true
type
unsafe

参见Types.

Appendix II: Operators

附录2: 运算符

这只列出了原语类型的操作符。

+    sum                    integers, floats, strings
-    difference             integers, floats
*    product                integers, floats
/    quotient               integers, floats
%    remainder              integers

&    bitwise AND            integers
|    bitwise OR             integers
^    bitwise XOR            integers

<<   left shift             integer << unsigned integer
>>   right shift            integer >> unsigned integer


Precedence    Operator
    5             *  /  %  <<  >>  &
    4             +  -  |  ^
    3             ==  !=  <  <=  >  >=
    2             &&
    1             ||


Assignment Operators
+=   -=   *=   /=   %=
&=   |=   ^=
>>=  <<=
  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值