Skip to content

Latest commit

 

History

History
742 lines (494 loc) · 26.8 KB

vbook_03.md

File metadata and controls

742 lines (494 loc) · 26.8 KB

变量,常量和注释

在上一章中,我们学习了如何在各种操作系统上安装V。 现在,是时候让我们了解V中的基本概念了。 本章将涵盖入门V所需的绝对基础知识,包括定义和使用变量,常量和代码注释。

变量是任何编程语言的基本概念,程序员在编写代码时几乎总是会遇到它们。 因此,了解变量的详细信息非常重要。

同样,我们还将学习有关常量的知识,它们在编程软件应用程序时提供可重用性和一致性。 常量保存一个在应用程序的整个生命周期内永远不会更改的值。

最后,我们将了解如何在代码中添加注释。 代码注释是软件开发的重要组成部分,它们帮助其他程序员阅读和理解注释所在的逻辑的功能。 在本章中,我们将学习以下主题:

  • 了解变量
  • 使用常量
  • 变量与常量
  • 在V中添加代码注释

通过本章的学习,您将具备定义和使用变量,常量和代码注释的工作知识。

技术要求

本章中的大部分代码可以通过访问V的REPL来运行,详见第2章"安装V编程"中的"使用REPL访问V编程"。 建议您为本章中的每个部分重新启动REPL,因为您可能会遇到使用相同变量名称的情况。

您将在 https://github.com/ToughStyle/V-programming-book-cn/tree/main/codes/Chapter03 中找到本章的所有代码片段。

此外,您可以将代码片段保存到您选择的带有.v扩展名的文件名中,然后访问命令行终端以运行代码,如下所示:

v run filename.v

了解V中的变量

变量按定义保持值或对另一个变量的引用。 通常,变量由名称标识。 实际上,当声明变量时,它被赋予一个有意义的名称,通常反映了它所持有的值。 变量声明是V语句的典型形式,其中您将变量分配给初始值。 这通常被称为定义变量或声明变量。 在V中,变量可以被分配任何值,包括以原始数据类型,复杂数据类型(例如结构)的形式或可以分配匿名函数的值。 我们将在第7章"V中的函数"中涵盖匿名函数,第8章"V中的结构"中涵盖结构。

默认情况下,声明变量时变量是不可变的。 不可变变量表示该值只能在声明期间分配一次,并且在声明后无法更新。 V使用mut关键字来方便声明可变变量。 可变变量允许您重新分配变量的值。 随着本章的阶段的推进,我们将更好地了解可变变量。 但是,现在,让我们看一下V中的简单变量声明,如下所示:

x := 100

尽管前面的语句看起来足够简短,但有各种事情需要了解。 在这里提到:

  • 该语句表示已声明变量。
  • 变量名为x
  • 使用:=符号将x变量声明为值为100。
  • 由于未指定mut关键字,因此x变量是不可变的。
  • 变量的数据类型由其所持有的值确定,在这种情况下,它是一个整数。

变量所持有的值在堆栈上分配。对于那些不了解堆栈的人,它是一个简单的结构,允许将元素按照后进先出(LIFO)的顺序插入和取出。

这是变量声明和V中变量的各种特性的基本示例。在深入了解V中变量的各种特性之前,让我们看一下如何在V中分配变量的不同方法。

变量命名约定

V允许使用字母数字字符来命名变量。除了字母数字字符外,下划线符号也允许在变量名称中使用。为确保变量名称在项目中具有一致的样式,V强制执行以下变量命名规则:

  • 变量名只能以小写字母开头。
  • 变量名不能包含大写字母。
  • 不允许使用特殊字符,除了下划线。
  • 变量名可以以数字和下划线结尾。

长度较长的变量名称可以使用_分隔单词以增强代码的可读性和一致性。这种类型的变量命名约定通常称为snake_case

以下表格将帮助您了解V中变量的命名约定:

示例代码 是否有效 原因
1x:='hi' 无效 变量名不能以数字开头,并显示以下错误消息:error: this number has unsuitable digit x
x:='hi' 有效 变量名可以以小写字母开头。
firstName:='Navule' 无效 firstName变量名不能包含大写字母;请改用snake_case
first_Name:='Navule' 无效 first_Name变量名不能包含大写字母;请改用snake_case
first_name:='Navule' 有效 使用snake_case的冗长变量名称提供更好的可读性。
counter_:=10 有效 变量名可以以下划线结尾,但是不太可能有一个以_结尾的变量名。通常,这种样式用于保留关键字,例如struct_
_counter:=10 无效 _counter变量名不能以_开头。
counter1:=10 有效 变量名可以以数字结尾。
lengthy_variable_name_1:='hi' 有效 变量名可以在中间使用多个下划线。
表3.1-V中的命名约定

变量分配

在处理V中的变量分配时,使用两个基本符号。它们如下:

:=是冒号后跟等号,用于在V中声明变量。

=是等号,用于重新分配值给已声明的可变变量。

借助这两个符号,V支持各种样式的变量分配,包括并行变量声明和分配以及增强分配。

并行变量声明和分配

V中的并行赋值允许在单个行中同时声明多个变量,如下所示:

a,b,c := 3, 4, 5

此语句表示不可变变量abc的同时声明分别赋值为3,45

同样,您可以同时声明所有可变变量,如下所示:

mut i,mut j := 'Hi''Hello'

前面的语句表示i和j可变变量同时声明并初始化为Hi和Hello字符串。

由于i和j变量是可变的,因此我们可以执行并行重新分配值,例如以下内容:

i,j = 'Hi there''Hello,Good Day!'

在这里,=符号用于重新分配值给ij可变变量。

变量的值使用并行变量赋值方法,将可变变量i和j分别更新为Hi thereHello, Good Day!

您可以以并行形式声明既可变又不可变的变量。考虑以下代码:

mut msg,i := 'Hello'32

println(msg)// Hello

msg ='Hi'

println(msg)// Hi

println(i)// 32

i = 2 //错误:`i`是不可变的,请使用`mut`声明它以使其可变

在上面的代码中,msg是一个可变变量,它保存一个字符串值,i是一个不可变变量,它保存一个整数值。

增强的变量赋值

增强的赋值是一种在原地更新可变变量而不显式提及它们作为更新语句的方法。 V仅允许可变变量进行增强变量赋值。假设已声明了一个可变变量,如下所示:

mut greet := 'Hi'

假设需要将greet变量与其他文本连接起来。然后,通常,语句如下所示:

greet = greet +' there, How are you?'

使用增强赋值,可以在原地连接greet变量,如下所示:

greet +=' Hope you have a great day!'

或者,考虑以下示例:

mut cnt:=10

假设您希望将cnt变量增加5;通常,您将编写添加cnt值的语句,然后再编写一个更新cnt变量的语句,如下所示:

cnt = cnt + 5

或者,您可以对cnt变量执行原地增强赋值,如下所示:

cnt += 5

在这种情况下,使用增强赋值方法原地增加了整数类型的cnt可变变量5

V中的变量特征

V中的变量遵循以下特征:

  • 变量可以声明为可变或不可变。
  • 变量一旦声明,必须分配一个值。
  • 所有变量必须在声明后使用。

让我们详细了解V中的每个属性,并使用代码示例更好地理解它们。

我们将详细探讨如何在V中使用可变和不可变变量。

可变变量

术语可变表示可以更改值。在V编程中,定义变量的此类行为非常重要,允许它们稍后进行修改。使用mut关键字声明可变变量,并使用:=符号进行初始化。可以使用=符号或使用增强变量赋值进行可变变量的后续更新,如前面所述。

以下是可变变量的语法:

mut <variable_name>:=<initializing_value>

更新可变变量的语法如下:

<variable_name> = <updated_value>

让我们考虑一个声明可变变量并将其更新为不同值的示例:

mut i:=10
i = 100

此示例演示了使用:=符号声明和初始化i可变变量的值为10。在下一条语句中,使用=使用新值100更新它。

注意

重新分配的可变变量要求新值与修改前变量所持有的值具有相同的数据类型。

让我们尝试使用字符串更新int类型的i可变变量,如下所示:

i = 'Apple'

尽管i变量是可变的,但此分配会导致错误。这是因为变量是int类型,但正在使用不同的数据类型字符串进行更新,因此会显示以下错误:

error: cannot assign to `i`: expected `int`, not `string`

不可变变量

术语不可变指的是一旦定义,值就保持不变。在V中,默认情况下,变量(除非使用mut关键字声明)都是不可变的。以下是声明一个不可变的变量的语法:

<variable_name> := <initializing_value>

让我们考虑以下示例:

msg := 'Hello'

该语句表示变量msg的声明,并使用:=符号将其初始化为Hello值。由于变量在其语句中未指定mut关键字,因此msg变量默认为不可变。因此,msg变量无法进一步更新。

让我们使用与更新可变变量相似的语法强制更新msg不可变变量:

msg = 'Good Day!'

更新不可变变量会导致错误,如下所示:

error: `msg` is immutable, declare it with `mut` to make it mutable

一旦声明变量,就必须为其分配一个值

V要求您使用初始值定义变量。与其他编程语言(如C#或Java)不同,它允许您声明变量而无需初始化值,您必须在声明期间将特定值分配给变量。这是因为V试图避免具有null值,因为它们经常导致应用程序崩溃。

因此,如果不确定要使用什么初始化值,则建议将变量声明为可变的。因此,您可以在计算期间根据需要更新可变变量。

让我们考虑以下示例:

mut i := 0

根据语法,必须使用一些值初始化变量。让我们看看如果您只声明变量并留下值未分配会发生什么,如下所示:

mut a

编译器将抛出错误,如下所示:

error: expecting `:=` (e.g. `mut x :=`)

所有变量必须在声明后使用

任何声明但未使用的变量都会导致警告或错误,具体取决于您运行程序的模式。 V不建议将声明的变量未使用,这种情况在开发模式下被视为警告,在生产模式下被视为错误。

为了演示,让我们看一下以下代码:

i := 'hello' 
// i is not used anywhere, so warns when run
// in dev mode and throws error when run in prod mode

x := 3
y := 2
println(x + y)

前面的代码声明了ixy变量,并最终将xy变量的总和打印到控制台。但是,在执行主函数的作用域期间,i变量仅仅被声明而没有在任何地方使用。

现在,将此代码保存到名为unused-variable.v的文件中。从命令提示符中,导航到保存文件的位置,并使用v运行命令来执行文件,如下所示:

v run unused-variable.v

控制台记录警告,然后打印xy变量的总和,如下所示:

unused-variable.v:1:2: warning: unused variable: `i`

1 | i := 'hello' // i is not used anywhere, so warns when run in dev mode and throws error where
  | ^
2 | x := 3
3 | y := 2

现在,我们将观察在使用-prod标志以以下命令在生产模式下运行此程序时的输出:

v -prod unused-variable.v

控制台将输出错误并停止进一步执行代码。这是因为在生产模式下,未使用的变量被视为错误:

unused-variable.v:4:9: error: unused variable: `i`
2 |
3 | fn main() {
4 |         i := 'hello'
  |         ^
5 |         x := 3
6 |         y := 2

V中变量的限制

在处理V中的变量时存在某些限制:

  • V中不允许全局变量。
  • 不允许重新声明或重新定义变量。
  • V中不允许变量遮蔽。

让我们看看这些限制并尝试更详细地了解它们。

V中不允许全局变量

全局变量允许您与定义它们的程序范围之外的代码进行交互和修改。它们促进了软件组件之间的耦合,可能会导致系统缺陷。

默认情况下,V不允许全局状态,因此不允许使用全局变量。当涉及到变量声明时,V将变量限制为在函数内部声明。因此,变量的作用域限于声明它的函数内。

V通过-enable-globals编译器标志来实现全局变量的启用,当您通常想要运行程序时,可以使用该标志。但是,此编译器标志的目的是使变量对低级应用程序(例如驱动程序和内核)可用。在第7章"V中的函数"中有一个专门的"函数不允许访问模块变量或全局变量"部分,在其中演示如何使用全局变量以及代码示例以及如何使用-enable-globals编译器标志运行代码。

让我们看一下以下代码,它演示了变量的作用域仅限于声明它的函数内部:

module main

fn method1() {
    msg := 'Hello from Method1'
    println(msg)
}

fn main() {
    method1()
    println(msg) // 会抛出错误,因为msg只在method1中声明并访问
}

在上面的代码片段中,method1main是函数。我们将在第7章"V中的函数"中更详细地学习函数。 msg变量被声明在method1中,但我们试图从main中访问它。这个代码将无法运行,显示错误消息:error: undefined ident: msg

在条件语句和迭代器中声明的变量仅限于相应的条件语句或迭代器。让我们看一下以下代码:

module main

fn method1() {
    if true {
        mut b := 10
        b++
    }
    println(b)
}

fn main() {
    method1()
}

在这里,method1if条件语句中定义了一个b变量,并将其赋值为10,然后将其增加了1。当我们尝试在条件语句之外访问b变量并将其打印时,它将无法执行并显示错误消息:undefined ident: b

V不允许重新声明或重新定义变量

变量一旦声明,在变量可访问的范围内就不能重新声明。例如,让我们看一下以下代码:

module main

fn main() {
    x := 3
    y := 2
    println(x + y)
    x := 5 // 不允许重新定义变量x
}

上面的代码将无法运行并显示一个错误消息,指出error: redefinition of x

V只允许在函数和脚本中定义变量;变量的名称仅保留作用域为函数。让我们看一下以下代码片段,在两个方法中声明了x变量:

module main

fn method1() {
    msg := 'Hello from Method1'
    println(msg)
}

fn method2() {
    msg := 'Hello from Method2'
    println(msg)
}

fn main() {
    method1()
    method2()
}

在上面的代码中,两个函数method1method2都声明了名为msg的变量。这个代码将编译正常,因为变量的作用域仅限于它所声明的方法内部。

在V中不允许变量遮蔽

在一些编程语言如Python和Java中,允许变量遮蔽,即在内部范围内以与外部范围中的变量相同的名称定义变量,并包含与在外部范围定义的变量不同的值。在其执行范围内,变量声明在外部范围将是相同的,除了在变量被遮蔽的内部范围中。V不允许您遮蔽变量。正如我们所学,使用相同的名称声明变量将导致错误。考虑以下代码:

module main

fn scope_demo() {
    x := 10
    println(x)
    if true {
        x := 20 // 会抛出错误,因为不允许变量遮蔽
    }
}

在V语言中使用常量

有时候程序需要特定的值在整个应用程序的生命周期内保持不变。在V语言中,使用 const 关键字可以方便地声明这样的常量。一旦向常量赋值,就不能再次更改它们的值。与变量只能在函数内部声明不同,V语言允许常量在函数外部声明,并且可以在模块级别进行作用域控制。

常量的命名规则

与变量类似,常量有以下命名规则:

  • 常量名称只能以小写字母开头。
  • 常量名称不能包含大写字母。
  • 不允许使用特殊字符,除了下划线。
  • 常量名称可以由数字和下划线结尾。
  • 对于较长的常量名称,可以使用下划线分隔单词以提高可读性和代码一致性。这种命名惯例通常称为蛇形命名法(snake_case)。

声明常量

以下是使用 const 关键字声明常量的语法:

const <constant_name> = <constant_value>

在此语法中,常量使用 const 关键字声明,该关键字在常量名称之前指定。常量的值赋值给等号 = 符号。

声明单个常量

以下示例演示了如何定义一个名为 app_name 的单个常量,其值为字符串类型:

const app_name = 'V on Wheels'

在上面的代码中,使用 const 关键字定义单个常量,后跟 app_name 的名称,然后是 =,后跟我们要分配给常量的值。

声明多个常量

有时候需要定义多个常量。在理解定义常量的语法后,您可能会尝试定义多个常量,如下所示:

const app_name = 'V on Wheels'
const max_connections = 1000
const decimal_places = 2
const pi = 3.14

定义多个常量的代码是有效的,但也可以定义不同数据类型的多个常量,如下所示:

const (
    app_name = 'V on Wheels'
    max_connections = 1000
    decimal_places = 2
    pi = 3.14
)

多个常量可以分组在圆括号内并在单独一行中定义每个常量。这种方法看起来干净,并防止在每行中为多个常量声明 const 关键字。

定义复杂的常量

V语言支持定义具有值设置为结构和函数的复杂常量。具有函数返回值的常量在编译时计算。因此,让我们查看如何在V中定义复杂常量。

定义结构类型的常量

V语言允许您创建具有结构数据类型设置为值的复杂常量。让我们考虑以下演示将常量声明为结构数据类型的代码:

module main

struct Space3D {
    mut:
        x int
        y int
        z int
}

const origin = Space3D{
    x: 0
    y: 0
    z: 0
}

fn main() {
    println(origin)
}

输出结果如下:

Space3D{
    x: 0
    y: 0
    z: 0
}

在上面的示例中,Space3D结构体有三个字段xyz,代表一个三维坐标系的坐标。将原点定义为常量非常有用,以便在多个地方重复使用。因此,我们定义了一个名为origin的常量,其值为类型为Space3D结构体,其xyz字段初始化为整数值0

定义函数类型的常量

V允许将函数的结果分配给常量。在将函数分配给常量的过程中,这些值在编译时计算。

为了演示如何将函数评估的结果定义为常量,请考虑以下代码:

module main

struct Space3D {
    mut:
        x int
        y int
        z int
}

fn get_point(x int, y int, z int) Space3D {
    return Space3D{
        x: x
        y: y
        z: z
    }
}

const origin = get_point(0, 0, 0)

fn main() {
    println(origin)
}

输出结果如下:

Space3D{
    x: 0
    y: 0
    z: 0
}

由于三维空间的原点所有坐标都设置为零,因此我们将其定义为名为origin的常量。

在上面的代码中,get_point函数返回Space3D结构体类型,并用作输入参数的x,y和z初始值进行初始化。

然后,我们定义一个名为origin的常量;但是,这次一个值是作为一个名为get_point的函数的评估结果返回的,该函数接受xyz的三个参数。由于这是从函数的返回值构建的常量,因此在这种情况下,常量的分配发生在程序的编译时间。

与常量一起工作时的最佳实践

在V中使用常量时有一些需要注意的事项。这些通常都是最佳实践,可以帮助你编写干净有效的代码。它们的说明如下:

常量必须在模块级别定义。 必须使用它们的模块前缀来访问常量。

关于模块的用法,您将在第9章"V中的模块"中学习。不过,现在,我们将详细讨论上述做法,并提供代码示例。

常量必须在模块级别定义

常量只能在模块级别声明。它们不能在函数内部声明。让我们考虑以下定义常量的示例:

module main

const app_name = 'V on Wheels'

fn main() {
    println(app_name)
}

输出结果如下:

V on Wheels

从上面的代码片段中,我们声明了一个名为app_name的字符串常量。在函数内部,我们打印它的值。此外,请注意,我们直接使用其变量名称访问常量,因为它在主要模块中定义。对于在其他模块中声明的常量,您需要指定模块前缀才能访问常量。我们将在下一节中详细介绍如何执行此操作。

现在,让我们考虑如何在函数内部定义常量,如以下代码片段所示:

module main

const app_name = 'V on Wheels'

fn main() {
    const greet = 'hi'
    println(app_name)
}

请注意,我们在函数内部定义了一个名为greet的常量,但该代码不会编译。因此,以上代码将失败,并显示以下错误消息:

error: const can only be defined at the top level 
(outside of functions).

常量必须使用它们的模块前缀标识

除非在主要模块中另有定义,否则常量必须使用其模块来标识,即使它们在定义它们的同一模块内被使用也是如此。我们将在即将到来的章节中更详细地介绍模块。但是,为了简洁起见,让我们假设我们正在一个名为mod1的模块中定义常量,如下所示:

module mod1

const greet_count = 5

pub fn do_work() {
    println(mod1.greet_count)
}

在上面的代码片段中,我们在属于mod1模块的文件中声明了一个名为greet_count的常量。尽管它被同一模块内的公共do_work函数使用,该函数只是打印greet_count常量,但我们需要指定它所属的模块,即在这种情况下是mod1

我们将在第9章"V中的模块"中进一步探讨如何处理模块中的常量,其中我们将学习访问模块的常量。但是,从上述伪代码中可以明显看出,为了消耗模块内部或外部定义的常量,必须指定模块名称,然后使用点号分隔常量名称。

由于常量(除了在主要模块中定义的常量)必须使用它们的模块前缀进行标识,因此这种方法为我们提供了一种方法,可以在不同模块之间定义具有相同名称的常量。接下来,我们将查看V中变量和常量之间的关键区别。

变量 常量
使用:=声明变量。 使用=声明常量。
在可变变量的情况下,值可以更改。 值保持不变。
变量不仅在函数级别声明。 常量只能在模块级别声明。

现在,让我们继续学习如何在V中添加代码注释。

在V中添加代码注释

想象一种情况,您已经开始查看一个包含大量代码的新仓库,并且希望快速了解任何特定功能或逻辑所做的事情。您可以花费一些时间查看逻辑,可能会得出代码片段实际执行的假设。通常,对于其他程序员来说,花费时间阅读代码以了解代码正在执行的操作是一项繁琐的任务。大多数时候,即使编写代码的程序员也会忘记逻辑实际上做了什么,并且只有在花费时间写作后才能回忆起来。

大多数编程语言允许您向代码添加注释。这些注释可以是单行注释或多行注释。

单行注释

单行注释用于编写有关通常适合单行的被注释代码的简短详细信息。单行注释以双斜杠 // 开头,跟随您要描述代码的注释:

// greet function prints greetings to the console
pub fn greet() {
    println('Hello, Welcome to the Jungle!')
}

// 后写的任何内容都成为注释的一部分。程序员偶尔注释掉代码,以在解决问题的各种方法中尝试。

多行注释

有时,描述过于详细,超出了多行,或者您需要在列表项之间包含注释,例如

food := [ 'apple', 'orange', 'lays' /* not fruit */, 'mango'] 

V允许多行注释,以 /* 标记开头,以 */ 标记结尾,这两个标记之间写入的任何内容都被视为注释。

确保当您使用 /* 开始多行注释时,它具有适当的并对应的关闭 */ 标记,如下所示:

module main

/*
multiply accepts two integer arguments
namely x and y.
It then performs multiplication of input arguments and returns the product which is again a type
x is an input argument accepts values of type of int
y is an input argument accepts values of type of int
multiply function returns the result of type int which is a multiplication of input arguments x a
*/

fn multiply(x int, y int) int {
    return x * y
}

fn main() {
    println(multiply(4, 5))
}

代码中的注释可以适应模块、函数或结构体中的任何位置。单行注释也可以从语句结束的代码处开始,如下所示:

// constant
fn main() {

        // origin := Space3D {x: 0, y: 0, z:0}

        println(origin)

}

上述代码片段展示了一个V程序,其中有大量注释,演示了如何在代码中使用单行和多行注释。

总结

本章中,我们学习了V中的各种基本概念,例如变量、在定义变量时使用的命名约定以及可变和不可变变量。我们还了解了变量的限制,如全局作用域、重声明和变量阴影。此外,我们学习了如何定义和处理常量,并理解了如何定义结构体和函数等复杂类型的常量。

在本章后面的部分,我们还探讨了各种添加描述性和简短代码注释的方法,例如多行和单行注释。

在V中定义和处理变量、常量和代码注释的知识后,我们将转到下一章,了解V中的原始数据类型。