Vlang官网文档(中文翻译)-vlang中文文档--v0.1.0_20180625

本小妞迷上赌 提交于 2020-04-30 04:10:31

PS:主要为自己学习,,,,看的时候顺便翻译的,,,渣翻(非全人工)勿喷.

介绍

V是一种用于构建可维护软件的静态类型编译编程语言。

它与Go相似,也受到Oberon、Rust、Swift的影响。

V是一种非常简单的语言,阅读这份文档大概只需要半小时的时间,读完之后,您将学习到V的全部内容.

尽管很简单,但是它为开发人员提供了很多功能,你能用其它编程语言做的任何事情,都可以用V做到.


Hello World

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

函数用 fn 声明, 该函数返回值的类型在函数名后定义,在这个例子中,main函数不返回任何内容,所以函数返回值的类型被省略了.

就像在C语言和所有类C语言中一样,main函数是程序的一个入口点。

println 是为数不多的内置函数之一。它用于打印值到标准输出。

fn main() 也可以在程序中被忽略, 这在编写小程序 脚本 或学习语言时非常有用. 为简洁起见, 在这个例子中,将忽略fn main()

这意味着"hello world"程序可以像下面一样简单

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
}

同样,类型在参数名之后.

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

函数可以在声明之前被使用,addsub 两个函数的声明在 main之后, 但仍然可以在 main函数中调用. 这在V中的所有声明中都是对的.并且不需要考虑文件和声明的顺序.


变量

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

使用:=将变量声明并初始化赋值,这是在V中声明变量的唯一方法.这意味着被声明的变量总会有一个初始值.

变量的类型是由给它赋的值判断出来的.要强制使用不同类型的值,请使用类型转换:表达式T(v)将值v转换为类型T。

与大多数语言不同,V只允许在函数中定义变量。不允许全局(模块级)变量.在V中没有全局状态。

mut age := 20
println(age)
age = 21
println(age)

要改变变量的值,请使用=. 在V中,变量在默认情况下是不可变的. 要更改变量的值,必须使用mut声明它。

做个尝试 删除第一行中的mut,然后再编译程序..

请注意:=和=之间的区别

:=用于声明和初始化,=用于赋值。

fn main() {
	age = 21
} 

这段代码不会编译成功,因为没有声明变量age.在V中,所有变量都需要声明.

fn main() {
	age := 21
}

这段代码也不会编译成功,因为声明的变量未使用会导致编译错误。(PS:和Go一样,)

fn main() {
	a := 10 
	if true {
		a := 20
	} 
}

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


基本数据类型

bool 布尔

string  字符串

i8  i16  i32  i64      i128 (soon)
u8  u16  u32  u64      u128 (soon) 

byte // u8的别名  
int  // i32的别名  
rune // i32的别名, 表示Unicode

f32 f64

byteptr
voidptr


请注意,与C和Go不同,int 始终是32位整数.


字符串

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() 函数 

在V中, 字符串是一个只读的字节数组.字符串数据使用UTF-8编码,

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

字符串是不可变的,这意味着子字符串函数非常高效. 被执行时不需要复制,不需要额外的资源.

在V中,运算符的两边都必须是相同类型的数据. 在这个代码中,如果age是一个int类型,将不会编译成功.

println('age = ' + age)

我们可以把age替换成string类型

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

或者使用字符串插入 (首选):

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 的数组

数组的类型由它的第一个元素决定: [1, 2, 3] 是一个整型数组([]int). ['a', 'b'] 是一个字符串数组 ([]string).

所有的元素必须是相同的类型, [1, 'a'] 是不行滴.

<< 是一个将值追加到数组末尾的操作符.

.len 字段(field) 返回数组的长度. 注意, 这是一个只读的字段(field), 用户不能修改它. V中所有导出的字段(field)都是只读的。.

val in array 如果数组中包含val的时候,返回true.


Maps

mut m := map[string]int{} // 目前只允许使用字符串类型的key
m['one'] = 1
println(m['one']) // ==> "1"  
println(m['bad_key']) // ==> "0"  
// TODO: 实现检查key是否存在的方法 

numbers := { // TODO:此语法尚未实现  
	'one': 1,
	'two': 2,
} 


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"


in 运算符

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

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 .. in 循环可以用来遍历数组中的元素. 如果需要一个索引,可以使用 for index, value in 来实现

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

这种形式的循环类似于其它语言中的while循环

如果条件判断的结果为false,将停止循环

同样,判断的条件不需要括号,执行代码始终需要大括号。

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

判断条件可以省略,结果会导致无限循环

for i := 0; i < 10; i++ {
	println(i)
}

最后,这是传统C风格 的for循环 . 它比 while 方式更安全, 因为使用后者很容易忘记更新计数器,陷入死循环.

这的 i 这里不需要用 mut 声明,因为根据定义它总是可变的。


switch语句

os := 'windows' 
print('V is running on ')
switch os {
case 'darwin':
	println('macOS.')
case 'linux':
	println('Linux.')
default:
	println(os) 
}
// TODO: 用匹配表达式代替 

switch语句用于在条件多的时候替代if - else 语句. 当遇到第一个case满足时,即运行,运行完后不再向下匹配.

与C不同, 每个语句块后不需要break 语句.


struct 结构体

struct Point {
	x int
	y int 
} 

p := Point{
	x: 10 
	y: 20 
} 
println(p.x) // 结构体的字段使用点 . 访问 

结构体是在堆栈上分配的。要在堆上分配一个结构并获得指向它的指针,使用&前缀:

pointer := &Point{10, 10}  // 具有3个或更少字段的结构,可选初始化语法
println(pointer.x) // 指针有相同的语法访问字段

V 没有子类, 但它支持嵌入式结构

// TODO: 这将在6月晚些时候实现
struct Button {
	Widget
	title string
}

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

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


访问修饰符

默认情况下,struct的字段是私有不可变的(同时也使Struct不可变),可以用它们的pub和mut访问修饰符更改,总共有五种选项:

struct Foo {
	a int     // 私有不可变的 (默认) 
mut: 
	b int     // 私有可变的
	c int     // (您可以列出具有相同访问修饰符的多个字段)   
pub: 
	d int     // 公共不可变 (只读) 
pub mut: 
	e int     // 公共, 但仅在父模块中可变  
pub mut mut: 
	f int 	  // public 父模块的内部和外部都是可变的 
}                 // (不推荐使用,这就是它如此冗长的原因) 

例如,这是在内置模块中定义的字符串类型:

struct string {
	str byteptr
pub:
	len int
}

从这个定义很容易看出string是一个不可变类型。

指向字符串数据的字节指针在内置之外根本无法访问,len字段是公共的,但不是可变的(只读的).

fn main() {
	str := 'hello' 
	len := str.len // OK,一切正常  
	str.len++      // 编译错误(因为该字段是只读的)  
} 


方法

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 方法有一个被叫做 uUser类型接收器 . 惯例是不要使用self或this这样的接收方名称,而是使用一个简短的, 最好是一个字母的名称.


默认纯函数Pure functions by default

默认情况下,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"  

在这个例子中,接收器(第一个参数)被标记为可变的,所以register()可以改变user对象,对于非接收器参数也是如此:

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]"

注意,在你调用这个函数的时候,必须在nums前面加上mnt,这表明被调用的函数将修改值。

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

使用 user.register()user = register(user) 代替 register(mut user).

V使返回对象的修改版本变得很容易:

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

user = register(user) 


常量

const (
	PI    = 3.14
	World = '世界'
) 

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)
)

println(Numbers)
println(Red)
println(Blue)

V不允许全局变量,所以这非常有用


模块Modules

V是一种非常模块化的语言,鼓励创建可重用模块,而且非常简单.要创建一个新模块,请创建一个模块名称的目录,在目录中创建包含代码的.v文件:

cd ~/code/modules
mkdir mymodule
vim mymodule/mymodule.v

// mymodule.v
module mymodule

// 要导出的函数必须使用`pub`修饰符
pub fn say_hi() {
	println('hello from mymodule!')
}

您可以在mymodule/中创建任意数量的.v文件。

使用 v -lib ~/code/modules/mymodule生成它

就这样,你现在可以在你的代码中使用它:

module main

import mymodule

fn main() {
	mymodule.say_hi()
}

注意,每次调用外部函数时必须指定模块.乍一看,这似乎有些冗长,但它使代码更易于阅读和理解.因为总是很清楚从哪个模块调用哪个函数,特别是在大型代码库中。

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

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

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


接口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())
} 

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

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


枚举Enums

enum Color {
	red green blue 
} 

mut color := Color.red
// V knows that `color` is a `Color`. No need to use `Color.green` here.
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将选项和结果组合成一种类型,所以您不需要决定使用哪种类型。

将一个函数“升级”为一个可选函数所需的工作量很小:您必须添加一个?返回类型,当出现错误时返回一个错误。

如果不需要返回错误,可以简单地返回None(TODO:然而None还没实现)

这是在v中处理错误的主要方法。它们仍然是值,就像在Go中一样,但是优点是错误不能被取消处理,而且处理它们要少很多麻烦。

你也可以这样传错误信息

resp := http.get(url)?
println(resp.body)

http.get 返回 ?http.Response,返回?,所以错误会被传递到调用函数的函数

上面的代码基本是:

resp := http.get(url) or {
	panic(err)
}
println(resp.body)


泛型(7月)Generics(July)

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

为了可读性,⟨⟩允许而不是< >。vfmt自动替换< >⟨⟩。


并发性Concurrency

并发模型与Go非常相似。要同时运行foo(),只需用go foo()调用它。现在,它在一个新的系统线程中启动该函数。很快goroutines和调度程序将被实现。


Decoding JSON

struct User {
	name string
	age  int 
	foo  Foo    [skip]  // Use `skip` attribute to skip certain fields 
} 

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支持的原因。

decode函数的第一个参数是要解码的类型。第二个参数是JSON字符串。

V生成用于JSON编码和解码的代码。不使用运行时反射。这导致了更好的性能。


测试Testing

// hello.v 
fn hello() string {
	return 'Hello world'
} 

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

所有的测试函数都以test_开头必须放在*_test.v文件中。要运行测试,请执行 v hello_test.v. 要测试整个模块, 运行 v test mymodule.


内存管理Memory management

没有垃圾收集或引用计数。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将为它们使用预先分配的缓冲区。

对于更复杂的情况,需要手动内存管理。这个问题很快就会解决。

V将在运行时检测内存泄漏并报告它们。例如,要清理数组,可以使用free()方法:

numbers := [0; 1000000] 
...
numbers.free()


Defer

TODO

vfmt

TODO

高级主题


V中调用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) 
} 


Compile time if

$if windows {
	println('Windows')  
}  
$if linux {
	println('Linux') 
} 
$if mac {
	println('macOS') 
} 

编译时if 使用 $. 现在它只能用来检测操作系统.


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!