V语言 基本使用

 

新手必看-如何安装配置vlang运行环境(linux,macOS篇)


前置条件

发稿截止前只有Linux 或者 macOS系统能编译通过。

你需要安装clanggcc

如果是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命令介绍

  V语言中文网  |   963 |    2019-06-26 23:35:14

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个基本类型,如下:

布尔类型(Boolean)
  • bool

 

字符串(String)
  • string

 

整数(Integer)
  • 有符号整数: i8i16i32 、i64
  • 无符号整数: u8u16u32u64
  • 别名: byte(u8)、 int(i32)、 rune(i32,表示Unicode代码点)

 

浮点数(Float)
  • f32f64

 

*请注意,与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)


 

结构体字段默认是私有的和不可变的(结构体也不可变)。 他们的访问修饰符可以用pubmut来改变。 总共有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没有办法强制打开一个可选项(比如Rustunwrap()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应用程序时也能正常工作。

 

 

转载自:https://www.v-lang.cn/



posted @ 2019-07-13 05:23  极光天际  阅读(3323)  评论(1编辑  收藏  举报