golang简介_Golang简介

golang简介

This post intends to be an introduction to the Go programming language, also known as Golang.

本文旨在对Go编程语言(也称为Golang)进行介绍。

免责声明 (Disclaimer)

I’m not an expert in Go. In fact, I’ve started learning about Go very recently. Therefore, take everything in this post with a pinch of salt.

我不是Go方面的专家。 实际上,我最近才开始学习Go。 因此,在这篇文章中的所有内容都请加上少量盐。

Then… why am I writing a post about Go? It’s simple: I want to use this post as a tool to reinforce my learning process. I believe that working on a blog post and publishing it will force me to get every detail straight. Moreover, I’ll be crafting the guide that I would have liked to have found in the first place (and, therefore, it is likely that it will become the guide that you were looking for as well). This also means that this post will probably be evolving as I learn new aspects of Go.

那……为什么我要写一篇关于Go的文章? 很简单:我想将这篇文章用作加强学习过程的工具 。 我相信,处理博客文章并发布它会迫使我弄清每个细节。 此外,我将首先编写本来希望找到的指南(因此,它很可能也将成为您正在寻找的指南)。 这也意味着,随着我学习Go的新方面,这篇文章可能会不断发展。

Keep in mind, though, that despite of the fact that I’ll be working on every detail of the post, I may make mistakes. I encourage you to indicate in the comments section any errors that you may find, and I’ll gladly correct them.

不过请记住,尽管我会处理帖子的每个细节,但我可能会犯错误。 我鼓励您在评论部分中指出您可能会发现的任何错误,我们将很乐意纠正它们。

If this disclaimer has let you down and you are not interested in reading this post anymore, here are some fantastic resources to get started with Go:

如果此免责声明使您失望,并且您不再对阅读这篇文章感兴趣,则可以使用以下很棒的资源开始使用Go:

If you, however, decide to stay and keep reading… welcome!

但是,如果您决定留下来继续阅读……欢迎光临!

一点历史 (A bit of history)

Golang was born in Google, and it was created by Robert Griesemer, Rob Pike and Ken Thompson. It was discussed for the first time on September 21, 2007 and eventually became an open-source project on November 10, 2009.

Golang出生于Google,由Robert Griesemer, Rob PikeKen Thompson创立。 它在2007年9月21日进行了首次讨论,并最终在2009年11月10日成为一个开源项目。

As reported in Go’s site, this language was born due to the commonly necessary choice between an efficient compilation, efficient execution or ease of programming at the time of choosing a programming language to work with. Therefore, the main goal of Go was to create a language that would achieve the ease of an interpreted and dynamically typed language, while preserving the efficiency and safety of a statically typed, compiled language. Another one of the main goals was to take advantage of the growth of multicore CPUs, making concurrency one of the priorities of the language.

正如Go网站上所报道的那样 ,之所以诞生这种语言,是因为在选择要使用的编程语言时,通常需要在高效编译,高效执行或易于编程之间进行选择。 因此,Go的主要目标是创建一种语言,该语言可以轻松实现解释型和动态类型化的语言,同时保留静态类型的编译型语言的效率和安全性。 另一个主要目标是利用多核CPU的增长,使并发成为该语言的优先考虑之一

To get a deeper insight into the goals of Go, I’d recommend reading this article, written by Rob Pike. Some of the goals mentioned in this article include finding a solution for:

为了更深入地了解Go的目标,我建议阅读由Rob Pike撰写的本文 。 本文提到的一些目标包括找到以下解决方案:

  • slow builds

    缓慢的构建
  • uncontrolled dependencies

    不受控制的依赖
  • each programmer using a different subset of the language

    每个程序员使用语言的不同子集
  • poor program understanding (code hard to read, poorly documented, and so on)

    对程序的理解较差(代码难于阅读,文档记录不良等)
  • duplication of effort

    重复劳动
  • cost of updates

    更新费用
  • version skew

    版本偏斜
  • difficulty of writing automatic tools

    编写自动工具的难度
  • cross-language builds

    跨语言构建

Considering that Google works with large-scale systems that have huge codebases, Go was primarily developed as a language “in the service of software engineering”, attempting to solve the most common issues in large systems.

考虑到Google可以与具有大量代码库的大规模系统一起使用,Go最初是作为一种“为软件工程服务”的语言开发的,旨在解决大型系统中最常见的问题。

去的吉祥物 (Go’s mascot)

If you are going to start programming in Go, you need to know the language’s mascot! It’s called the gopher.

如果要开始使用Go编程,则需要了解该语言的吉祥物! 它被称为地鼠。

Image for post
Image source] 图片来源 ]

Learn about the gopher here!

这里了解地鼠!

Go入门 (Getting started with Go)

建立我们的开发环境 (Setting up our development environment)

In order to follow through this post, you have two options:

为了继续阅读本文,您有两种选择:

  1. Set up a local development environment

    建立本地开发环境
  2. Use Go’s official online playground

    使用Go的官方在线游乐场

I will not go into the installation details for each platform, but here are some guides:

我不会介绍每个平台的安装详细信息,但以下是一些指南:

If you, however, prefer not to commit for now and test out the language, option 2 is a perfectly valid option. In any case, I recommend you to go through this introduction executing the examples, instead of just reading the code: I’m sure you would get a better grasp of the language.

但是,如果您不想立即提交并测试语言,则选项2是一个非常有效的选项。 无论如何,我建议您通过执行本示例来完成本简介,而不仅仅是阅读代码:我相信您会更好地理解该语言。

你好,世界! (Hello world!)

Well, first things first! Let’s get started writing our first program in Go. In the following sections I’ll be explaining in more detail each part of our program. We may print our “Hello world!” message with the following few lines of code:

好吧,第一件事首先! 让我们开始用Go编写我们的第一个程序。 在以下各节中,我将更详细地说明程序的每个部分。 我们可以打印“ Hello world!” 带有以下几行代码的消息:

package mainimport "fmt"func main() {
fmt.Println("Hello world!")
}

We’ll save the above snippet in a file called Main.go and then run the following instruction in our CLI:

我们将上面的代码段保存在一个名为Main.go的文件中,然后在CLI中运行以下指令:

go run Main.go

Voilà! If everything worked correctly, you should see our message printed in the terminal. Congrats! You’ve written your first Go program!

瞧! 如果一切正常,您应该在终端上看到我们的消息。 恭喜! 您已经编写了第一个Go程序!

Let’s make an initial explanation of the above snippet, so that we start getting our first building blocks. Go programs are organized into packages, and one special package is “main”, which must be used in executable commands. We may now move towards the import statement, which allows us to include packages in our program. It may be worth mentioning that we may import multiple packages with the following syntax:

让我们对上面的代码片段做一个初步的解释,以便我们开始获得我们的第一个构建块。 Go程序被组织成软件包 ,其中一个特殊的软件包是“ main”,必须在可执行命令中使用。 现在,我们可以转向import语句,该语句允许我们在程序中包含程序包。 值得一提的是,我们可以使用以下语法导入多个软件包:

import (
"<package_1>"
"<package_2>"
)

In our example, we can see the “fmt” package, which is commonly used to read from stdin or write to stdout. We’ll be discovering this package in more detail as this article develops.

在我们的示例中,我们可以看到“ fmt”包 ,该通常用于从stdin读取或写入stdout。 随着本文的发展,我们将更详细地发现此软件包。

Finally, we may find the func main() declaration which, unsurprisingly, declares the main function of our Go program.

最后,我们可能会发现func main()声明,毫不奇怪,该声明声明了Go程序的main函数。

基础 (The basics)

声明和初始化变量 (Declaring and initializing variables)

Go is a statically typed language. This means that, unlike dynamically typed languages (such as JavaScript or Python), you initially declare the type of a variable and stick to it. For instance, if you declare an integer variable, it cannot become a string in a later stage of its lifecycle. Even though it becomes more verbose and strict, it gives the compiler more opportunities to optimize your code, given that the variable type is always the same. Check out this article for a more in-depth comparison of statically and dynamically typed languages.

Go是一种静态类型的语言。 这意味着,与动态类型的语言(例如JavaScript或Python)不同,您首先声明变量的类型并坚持使用。 例如,如果声明一个整数变量,则它在其生命周期的后期将无法成为字符串。 尽管它变得更加冗长和严格,但鉴于变量类型始终相同,它为编译器提供了更多优化代码的机会。 请查看本文 ,以更深入地比较静态和动态类型的语言。

Let’s declare our first variables. To do that, we’ll have to use the following syntax:

让我们声明我们的第一个变量。 为此,我们必须使用以下语法:

var <name> <type> [= <value>]

I’m using the values between < and > as placeholders, and the square brackets [ ] to indicate an optional part of the statement. Therefore, both of the following statements would be valid:

我将<和>之间的值用作占位符,并使用方括号[]表示该语句的可选部分。 因此,以下两个语句都是有效的:

var i int
var j string = "hello"

Note that, while we have explicitly initialized variable j with the string "hello", the variable i is actually initialized as well: declared-only variables are initialized with a "zero value", which translates into 0 for numbers, "" for strings and false for boolean variables.

请注意,尽管我们已经使用字符串“ hello”显式初始化了变量j ,但变量i实际上也被初始化了:仅声明的变量使用“零值”初始化,数字表示为0,字符串表示为“”。布尔变量为false。

You may, moreover, declare (and optionally initialize) multiple variables of the same type using a single line of code with the following syntax:

此外,您可以使用单行代码使用以下语法声明(并可选地初始化)相同类型的多个变量:

var "name_1", "name_2" "type" [= "val_1", "val_2"]

Let’s illustrate this feature with an example:

让我们用一个例子来说明这个功能:

var i, j int = 12, 27

Golang, however, not only had the goal of producing efficient code, but also to make the process of programming faster. This is why we may also initialize a variable using the walrus operator :=. Using this operator, the syntax becomes more succinct, and we do not need to explicitly define the variable type (Go decides it for us, aka type inference). It's important to note, though, that the walrus operator may only be used inside a function. The following example may illustrate how simple it becomes to declare and initialize a variable:

但是,Golang的目标不仅是生成高效的代码,而且还在于使编程过程更快。 这就是为什么我们也可以使用walrus运算符:=初始化变量的原因。 使用此运算符,语法变得更加简洁,我们不需要显式定义变量类型(Go会为我们确定变量类型,也就是类型推断)。 不过,请务必注意,海象运算符只能在函数内部使用。 下面的示例可以说明声明和初始化变量变得多么简单:

k := 42.0

Go, moreover, allows you to decide the visibility of a variable based on its name. If your variable starts with a lowercase letter, it will remain internal to the package. However, if the first letter is uppercase, it will become visible (or exported) to other packages.

此外,Go允许您根据变量的名称来决定其可见性。 如果您的变量以小写字母开头,它将保留在包的内部。 但是,如果第一个字母是大写字母,它将在其他包中可见(或导出)。

I’d like to conclude this section by adding the following caveats:

我想通过添加以下警告来结束本节:

  • all variables need to be used, otherwise Go will return a compile-time error.

    所有变量都需要使用,否则Go将返回编译时错误。
  • redeclaring a variable is not allowed (with the exception of shadowing).

    不允许重新声明变量( 阴影除外)。

  • the above walrus operator example will create a variable of type float64. If we want to create a float32, we need to use the more verbose declaration.

    上面的海象运算符示例将创建一个float64类型的变量。 如果要创建float32,则需要使用更详细的声明。

You may also be interested in checking out the list of allowed data types.

您可能还想查看允许的数据类型列表

在类型之间转换 (Converting between types)

We may convert (or cast) a value into a different type by applying the following syntax:

我们可以通过应用以下语法将值转换(或强制转换)为其他类型:

<type>(<var>)

Naturally, in-place casting is not allowed, as we are working in a statically typed language:

当然,由于我们使用的是静态类型的语言,因此不允许就地转换:

var i int = 12
var j float32 = float32(i)

Let’s now attempt to convert an integer into a string:

现在让我们尝试将整数转换为字符串:

a := 65
b := string(a)
fmt.Println(b)

If you run the above 3 lines of code, you’ll see that it prints out “A”, instead of “65” as we could have expected. This happens because the direct casting of an integer into a string returns the Unicode character of the given number (“A” is the character #65 in Unicode).

如果运行上面的三行代码,您会看到它打印出“ A”,而不是我们预期的“ 65”。 发生这种情况是因为将整数直接转换为字符串会返回给定数字的Unicode字符(“ A”是Unicode中的字符#65)。

So… how can we cast an integer into a string? We’ll need the strconv package for this purpose. Specifically, in this case we’ll need the Itoa (Integer to ASCII) function:

那么……我们如何将整数转换为字符串? 为此,我们需要strconv软件包 。 具体来说,在这种情况下,我们需要Itoa(整数到ASCII)函数

package mainimport (
"fmt"
"strconv"
)func main() {
a := 65
b := strconv.Itoa(a)
fmt.Printf("%v, %T\n", b, b)
}

If you run the above snippet, you’ll see that b is now "65".

如果运行上面的代码段,您将看到b现在为“ 65”。

常数 (Constants)

You may also define constant values using the const keyword:

您也可以使用const关键字定义常量值:

const z = 123

Now, variable z will have a fixed value of 123 and you will not be able to modify it. I'd also like to add that the walrus operator is not applicable to constants.

现在,变量z的固定值为123,您将无法对其进行修改。 我还想补充一下,海象运算符不适用于常量。

注释 (Comments)

Writing comments in your code can help you explain to your co-workers (and your future self) a fragment of code. You may write a single-line comment with the following syntax:

在代码中编写注释可以帮助您向同事(以及将来的自己)解释代码片段。 您可以使用以下语法编写单行注释:

// <comment>

… or multi-line comments just like this:

…或类似这样的多行注释:

/*
<comment>
*/

指针 (Pointers)

Go allows you to work with pointers, which hold the memory address of a value. We may define a pointer with the following syntax:

Go使您可以使用保存值的​​内存地址的指针。 我们可以使用以下语法定义一个指针:

var <pointer_name> *<type>

As an example:

举个例子:

var i *int

We may also use the & operator, which returns the pointer of the referenced variable:

我们还可以使用&运算符,该运算符返回引用变量的指针:

var <pointer_name> *<type> = &<variable>

Finally, we may access the content of a pointer with the * operator:

最后,我们可以使用*运算符访问指针的内容:

*<pointer_name>

To wrap up, let’s see how we may work with pointers:

总结一下,让我们看看如何使用指针:

package mainimport "fmt"func main() {
var i int = 27
var p *int = &i
fmt.Println(*p)
*p += 1
fmt.Println(i)
}

In this example, we’ve created an integer variable i set to 27 and then a pointer p referencing to it. Then, we've printed out the value of the variable through the pointer. Finally, we've incremented the value of the variable through the pointer and printed out the final value.

在此示例中,我们创建了一个整数变量i将其设置为27,然后创建了一个引用它的指针p 。 然后,我们通过指针打印出了变量的值。 最后,我们通过指针增加了变量的值并打印出最终值。

功能 (Functions)

We may create a function in Go using the following syntax:

我们可以使用以下语法在Go中创建一个函数:

func <name>([<var_1> <type_1>, <var_2> <type_2>]) [<return_type>] {}

In the above fragment of pseudo-code I’ve included two arguments as an example, but we may provide zero or more arguments to a function. Let’s see an example:

在上面的伪代码片段中,我以两个参数为例,但是我们可以为函数提供零个或多个参数。 让我们来看一个例子:

func multiply(i int, j int) int {
return i * j
}

Note that the returned value is of the same type as the declared returned value in the function’s signature. We may now call this function as follows:

请注意,返回值的类型与函数签名中声明的返回值的类型相同。 现在,我们可以按以下方式调用此函数:

multiply(3, 11)

Go offers a more compact syntax when two (or more) consecutive arguments share the same type. We may simply declare the type of these variables by declaring it in the last one:

当两个(或多个)连续参数共享同一类型时,Go提供了更紧凑的语法。 我们可以通过在最后一个变量中声明它们来简单地声明这些变量的类型:

func multiply(i, j int) int {
return i * j
}

In the above example, both arguments are of type int.

在上面的示例中,两个参数均为int类型。

We may also return multiple values in a function:

我们还可以在函数中返回多个值:

func personal_info() (string, int) {
name := "John"
age := 27
return name, age
}

… or return named values (aka naked return):

…或返回命名值(又名裸返回):

func ops(x, y float64) (mult, div float64) {
mult = x * y
div = x / y
return
}

In this case, we don’t even need to specify the variables in the return statement, since they are already specified in the function’s signature. Moreover, note that we do not need to declare these variables in the function’s body, as they are declared in the function’s signature!

在这种情况下,我们甚至不需要在return语句中指定变量,因为它们已经在函数的签名中指定了。 此外,请注意,我们不需要在函数的主体中声明这些变量,因为它们是在函数的签名中声明的!

控制流 (Control flow)

条件语句 (Conditional statements)

If(-else) statements may be implemented in Go with the following syntax:

If(-else)语句可以使用以下语法在Go中实现:

if [<statement>;] <condition> {
<if-block>
} [else if <condition> {
<else-if-block>
}] [else {
<else-block>
}]

Let’s get started with the simplest case: a simple conditional statement.

让我们从最简单的情况开始:简单的条件语句。

package mainimport "fmt"var x int = 8func main() {
if x >= 5 {
fmt.Println("passed")
}
}

Easy, right? Note that parentheses are not needed!

容易吧? 请注意,不需要括号!

Let’s spice it up a bit more:

让我们加一点香料:

package mainimport "fmt"var foo int = 4
var bar int = 3func main() {
if baz := foo + bar; baz < 3 {
fmt.Println("very low")
} else if baz < 5 {
fmt.Println("low")
} else if baz < 7 {
fmt.Println("medium")
} else {
fmt.Println("high")
}
}

Note the statement that defines the variable baz in the first if statement baz := foo + bar. The scope of this variable is limited to the if(-else) block.

注意在第一个if语句baz := foo + bar中定义变量baz的语句。 此变量的范围限于if(-else)块。

对于循环 (For loops)

A loop in Go may be created with the following syntax:

可以使用以下语法在Go中创建循环:

for [<initializer>;] [<condition>] [; <update>] {
<loop-block>
}

Note that parentheses are not needed in Go’s for loop. Also, you may notice that both the initializer and the update step are optional, allowing you to define a for loop with only a condition. This kind of loop is normally known as a while loop in many languages. However, Go allows you to do this with a for loop.

请注意,在Go's for循环中不需要括号。 另外,您可能会注意到初始化程序和更新步骤都是可选的,从而使您可以仅使用条件来定义for循环。 在许多语言中,这种循环通常称为while循环。 但是,Go允许您使用for循环执行此操作。

Let’s see an example of a normal for loop:

让我们看一个普通的for循环的例子:

package mainimport "fmt"func main() {
res := 1
base := 2
exp := 9
for i := 1; i <= exp; i++ {
res = res * base
}
fmt.Println(res)
}

In this example, we’ve implemented the exponentiation (just as an example, you may use math.Pow if you need to use it).

在此示例中,我们实现了幂运算(仅作为示例,您可以使用math.Pow,如果需要使用它)。

Let’s now look at a while example (no initializer and no update step):

现在让我们看while示例(没有初始化程序,没有更新步骤):

package mainimport "fmt"func main() {
i := 1
sum := 0
for i < 10 {
sum += i
i += 1
}
fmt.Println(sum)
}

This loop keeps track of an incrementing i variable, which is compared to 10 in the condition, and adds it to a sum variable. As a result, we obtain the sum of integers from 1 to 9.

此循环跟踪递增的i变量,该变量在条件下与10进行比较,并将其添加到sum变量中。 结果,我们获得了1到9之间的整数之和。

As a final note, you may ignore the condition as well, and create an infinite loop.

最后一点,您也可以忽略条件,并创建一个无限循环。

推迟,恐慌和恢复 (Defer, Panic and Recover)

延期 (Defer)

Defers are a very interesting instruction of Go: they allow you to execute a statement after the surrounding function has returned. They are, however, evaluated immediately.

延迟是Go的一个非常有趣的指令:延迟使您可以在周围的函数返回后执行一条语句。 但是,将立即对其进行评估。

Let’s look at an example:

让我们看一个例子:

package mainimport "fmt"func main() {
defer fmt.Println("3")
defer fmt.Println("2") fmt.Println("1")
}

Note that I’ve included 2 defer statements. I wanted you to note that defers are stacked in a LIFO (Last In First Out) basis. In fact, if you execute the above snippet, you’ll see that the numbers are printed out in ascending order.

请注意,我包含了2个defer语句。 我想请您注意,延迟是按照LIFO ( 后进先出)的基础堆叠的。 实际上,如果执行上面的代码片段,您会看到数字按升序打印。

恐慌 (Panic)

Panic is a built-in function that allows us to indicate that something has gone wrong. It will stop the execution of the program and start unrolling the call stack, propagating the error. Let’s see how we can panic in Go:

Panic是一个内置函数,可让我们指示出问题了。 它将停止程序的执行并开始展开调用堆栈,从而传播错误。 让我们看看如何在Go中惊慌:

package mainimport "fmt"func main() {
r := divide(1, 0)
fmt.Println(r)
}func divide(i int, j int) int {
if j == 0 {
panic("dividing by 0 is not allowed")
}
return i / j
}

As you can see, Go exits with an error status, displaying our message.

如您所见,Go退出并显示错误状态,显示我们的消息。

恢复 (Recover)

Sometimes we’ll want to handle these errors, and we can do that by using the recover built-in function. It should be noted that this function will only have an effect if it is inside a deferred function. Let’s modify our panic example, and recover from the situation:

有时我们想处理这些错误,我们可以通过使用内置的restore函数来实现。 应该注意的是,该函数只有在延迟函数内部时才起作用。 让我们修改我们的紧急示例,并从情况中恢复:

package mainimport "fmt"func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered. Let's not panic. Error received:", r)
}
}()
r := divide(1, 0)
fmt.Println(r)
}func divide(i int, j int) int {
if j == 0 {
panic("dividing by 0 is not allowed")
}
return i / j
}

In this example, the error has been handled and we are not exiting with an error status anymore. Moreover, we are printing the error message.

在此示例中,错误已得到处理,并且我们不再以错误状态退出。 此外,我们正在打印错误消息。

数据结构 (Data structures)

数组 (Arrays)

We may define an array in Go with the following syntax:

我们可以使用以下语法在Go中定义一个数组:

var <name> [<length>]<type>

NB! In this case, with the square brackets [] I don’t mean an optional part… I mean square brackets.

注意! 在这种情况下,方括号[]不是指可选部分…而是指方括号。

Let’s look at an example:

让我们看一个例子:

package mainimport "fmt"func main() {
var position [2]float64
position[0] = 40.4168
position[1] = 3.7038
fmt.Println(position)
position[0] = 51.5074
fmt.Println(position)
}

Note that we may access (and update) an array index using the square brackets. It’s also important to note that Go’s arrays cannot be resized, and hence will always keep the initially set length.

请注意,我们可以使用方括号访问(并更新)数组索引。 同样重要的是要注意,Go的数组无法调整大小,因此将始终保持初始设置的长度。

An alternative syntax using the walrus operator allows to declare and initialize the array in one single statement:

使用walrus运算符的另一种语法允许在一个语句中声明和初始化数组:

<name> := [<length>]<type>{<value_1>, <value_2>, ..., <value_n>}

For instance:

例如:

position := [2]float64{40.4168, 3.7038}

切片 (Slices)

Even though arrays have a fixed length, slices allow us to work with dynamically-sized data. More specifically, they allow us to work with a dynamic range of an array. A slice may be defined with the following syntax:

即使数组具有固定的长度,切片也允许我们处理动态大小的数据。 更具体地说,它们使我们能够处理数组的动态范围。 可以使用以下语法定义切片:

[]<type>

Then, we may obtain the values of the array in a range of indices by using the following syntax:

然后,我们可以使用以下语法获取索引范围内的数组值:

<array_name>[<min_idx] : <max_idx>]

In this case, we would obtain the values of the array <array_name>, starting in index <min_idx> (included) and ending in index <max_idx> (excluded).

在这种情况下,我们将获取数组<array_name>的值,该值从索引<min_idx> (包括)开始,到索引<max_idx> (排除)结束。

Note again that, in these two cases above, the square brackets actually refer to… square brackets.

再次注意,在上述两种情况下,方括号实际上是指…方括号。

Let’s look at an example to better understand this concept:

让我们看一个例子来更好地理解这个概念:

package mainimport "fmt"func main() {
vowels := [5]string{"A", "E", "I", "O", "U"} var some_vowels []string = vowels[1:3]
fmt.Println(some_vowels)
}

In this script we have an array containing the vowels and, using a slice, we print the second and third vowels.

在此脚本中,我们有一个包含元音的数组,并使用切片来打印第二个和第三个元音。

You may also omit <min_idx> and/or <max_idx> in your slices. If you don't specify them, <min_idx> will default to 0 (start of the array) and <max_idx> will default to the array length (end of the array). Let's look at this with an example:

您也可以在切片中省略<min_idx>和/或<max_idx> 。 如果未指定它们,则<min_idx>将默认为0(数组的开始),而<max_idx>将默认为数组长度(数组的结尾)。 让我们来看一个例子:

package mainimport "fmt"func main() {
vowels := [5]string{"A", "E", "I", "O", "U"} var some_vowels []string = vowels[:3]
fmt.Println(some_vowels) some_vowels = vowels[2:]
fmt.Println(some_vowels) some_vowels = vowels[:]
fmt.Println(some_vowels)
}

In the first example, we’ve omitted <min_idx> and displayed every vowel until index 3 (excluded). In the second one, we've omitted <max_idx> and displayed every vowel starting from index 2 (included). Finally, in the third example we've omitted both <min_idx> and <max_idx> and displayed the full array of vowels.

在第一个示例中,我们省略了<min_idx>并显示每个元音,直到索引3(不包括)。 在第二个中,我们省略了<max_idx>并显示从索引2(包括)开始的每个元音。 最后,在第三个示例中,我们省略了<min_idx><max_idx>并显示了完整的元音数组。

Slices can be seen as a reference to the array given that, if we modify some value in a slice, such change will reflect in the original array (and in any slices referring to the modified array position). Let’s look at this with an example:

可以将切片视为对数组的引用,因为如果我们修改切片中的某些值,则此类更改将反映在原始数组中(以及任何引用修改后的数组位置的切片中)。 让我们来看一个例子:

package mainimport "fmt"func main() {
numbers := [5]int{1, 2, 3, 4, 5} var some_numbers []int = numbers[:3]
fmt.Println(some_numbers) var other_numbers []int = numbers[2:]
fmt.Println(other_numbers) some_numbers[2] = 7 fmt.Println(some_numbers)
fmt.Println(other_numbers)
fmt.Println(numbers)
}

As you can see, number 3 has been replaced by a 7, no matter if you print the original array or a slice of it.

如您所见,无论您打印原始数组还是数组的一部分,数字3都已由7代替。

地图 (Maps)

A map is a key-value data structure, aka dictionary. We may create a map either using the make function or initializing it with some initial values.

映射是键值数据结构,也称为字典。 我们可以使用make函数创建地图,也可以使用一些初始值对其进行初始化。

Let’s see how we may create it with the make function, which receives the type of the keys and the values, and creates an empty map:

让我们看看如何使用make函数创建它,该函数接收键的类型和值,并创建一个空的映射:

package mainimport "fmt"var colors map[string]stringfunc main() {
colors = make(map[string]string)
colors["red"] = "#FF0000"
colors["green"] = "#00FF00"
colors["blue"] = "#0000FF" fmt.Println(colors["red"])
}

You may also create a map without the make function, if you can provide some initial values:

如果可以提供一些初始值,也可以创建不带make函数的映射:

package mainimport "fmt"var colors = map[string]string {
"red": "#FF0000",
"green": "#00FF00",
"blue": "#0000FF",
}func main() {
fmt.Println(colors["red"])
}

Let’s now manage the map and see how we may add an element, delete it or check whether it is present:

现在让我们管理地图,看看如何添加元素,删除元素或检查元素是否存在:

package mainimport "fmt"var colors = map[string]string {
"red": "#FF0000",
"green": "#00FF00",
"blue": "#0000FF",
}func main() {
// add a new entry to the map
colors["black"] = "#000001" // edit an existing entry of the map
colors["black"] = "#000000" // delete an entry from the map
delete(colors, "red") fmt.Println(colors) rgb_black, present_black := colors["black"]
fmt.Printf("%s, %t\n", rgb_black, present_black) // remember that we've deleted "red"!
rgb_red, present_red := colors["red"]
fmt.Printf("%s, %t", rgb_red, present_red)
}

As you may have noticed, you can check whether a key is present in a map by unpacking the map[key] syntax to two values: the first one holds the value of the key (if present, otherwise it holds the type's zero-value), while the second value is a boolean that indicates whether it is present or not.

您可能已经注意到,可以通过将map[key]语法解压缩为两个值来检查映射中是否存在键:第一个保存键的值(如果存在,否则保存类型的零值) ),而第二个值是一个布尔值,指示它是否存在。

结构 (Structs)

Go allows us to create structs, which are collections of fields. We may define a struct with the following syntax:

Go允许我们创建结构,这些结构是字段的集合。 我们可以使用以下语法定义结构:

type <name> struct {
<name_1> <type_1>
<name_2> <type_2>
...
<name_n> <type_n>
}

As an example:

举个例子:

type Position struct {
Latitude float64
Longitude float64
}

Let’s now see how we may work with this new struct:

现在让我们看看如何使用这个新结构:

package mainimport "fmt"type Position struct {
Latitude float64
Longitude float64
}func main() {
pos := Position{40.4168, 3.7038}
fmt.Println(pos)
pos.Latitude = 51.5074
fmt.Println(pos)
pos2 := Position{Longitude: 41.9028, Latitude: 12.4964}
fmt.Println(pos2)
}

Note how we’ve created a new instance of our struct:

注意我们如何创建结构的新实例:

<name> := <struct>{<value_1>, <value_2>, ..., <value_n>}

Moreover, we may access (and update) the value of a field using the dot operator:

此外,我们可以使用点运算符访问(和更新)字段的值:

<struct>.<field> [= <value>]

Finally, note as well that we may define the value of our fields by using their name in the instantiation statement, even with a different order.

最后,还要注意,我们可以通过在实例化语句中使用它们的名称来定义字段的值,即使顺序不同。

方法 (Methods)

Even though Go does not have the concept of classes, it does allow us to define methods. What is a method? It’s a function that is applied to a type. To better understand how to create a method in Go, we need to introduce the concept of “receiver”. Such “receiver” is the type to which this function will be applied. Let’s take a look at the syntax:

即使Go没有类的概念,它也允许我们定义方法。 什么是方法? 这是应用于类型的函数。 为了更好地理解如何在Go中创建方法,我们需要引入“接收器”的概念。 这种“接收器”就是将要应用此功能的类型。 让我们看一下语法:

func (<receiver_instance> <receiver_type>) <method_name>([<arg_1> <type_1>, <arg_2> <type_2>, ..., <arg_n> <type_n>]) [<return_type>] {
<method_body>
}

Let’s look at an example:

让我们看一个例子:

package mainimport (
"fmt"
"math"
)type Point struct {
X float64
Y float64
}func (p Point) EuclideanDistanceFromOrigin() float64 {
return math.Sqrt(math.Pow(p.X, 2) + math.Pow(p.Y, 2))
}func main () {
point := Point{4.2, 5.7}
fmt.Println(point.EuclideanDistanceFromOrigin())
point = Point{7.4, 8.1}
fmt.Println(point.EuclideanDistanceFromOrigin())
}

高阶函数 (Higher-order functions)

Go embraces higher-order functions, i.e. a function that takes one (or more) function as an argument and/or returns a function. Let’s see an example of both cases:

Go包含高阶函数 ,即以一个(或多个)函数作为参数和/或返回一个函数的函数。 让我们来看两种情况的示例:

函数作为参数 (A function as an argument)

We may pass a function as an argument, such as in the following example:

我们可以将函数作为参数传递,例如以下示例:

package mainimport "fmt"func sum(a, b int) int {
return a + b
}func multiply(a, b int) int {
return a * b
}func printResult(fn func(int, int) int, a, b int) {
val := fn(a, b)
fmt.Println(val)
}func main() {
printResult(sum, 4, 5)
printResult(multiply, 4, 5)
}

Note that we need to pass both the type of function arguments and the type of the function output.

请注意,我们需要同时传递函数参数的类型和函数输出的类型。

返回函数 (Returning a function)

Let’s now look at an example of a higher-order function that returns another function:

现在让我们看一个返回另一个函数的高阶函数的示例:

package mainimport "fmt"func sum(a, b int) int {
return a + b
}func multiply(a, b int) int {
return a * b
}func getOperation(name string) func (int, int) int {
if name == "sum" {
return sum
} else if name == "multiply" {
return multiply
}
panic("Operation not supported")
}func main() {
op := getOperation("sum")
fmt.Println(op(4,5))
op = getOperation("multiply")
fmt.Println(op(4,5))
}

Note that, in this case, we are declaring the function type as the return type of the higher-order function.

请注意,在这种情况下,我们将函数类型声明为高阶函数的返回类型。

并发 (Concurrency)

Concurrency is one of the main reasons why many people decide to switch to Go, given its efficiency and simplicity. One of the main concepts to know is the “goroutine”, which is a lightweight thread managed by the Go runtime. A goroutine may be created as simply as follows:

考虑到它的效率和简单性,并发是许多人决定切换到Go的主要原因之一。 要了解的主要概念之一是“ goroutine”,它是Go运行时管理的轻量级线程。 可以如下简单地创建goroutine:

go f(<arg_1>, <arg_2>, ..., <arg_n>)

It should be noted that, even though the function f will be executed in the new goroutine, such function (and its arguments) are evaluated in the current goroutine (in fact, the main function runs in a goroutine as well!).

应该注意的是,即使函数f将在新的goroutine中执行,该函数(及其参数)也将在当前goroutine中进行评估(实际上,main函数也在goroutine中运行!)。

In order to allow two or more goroutines to communicate, we’ll have to make use of Go’s channels, which allow us to send and receive information from it. A channel may be created with the following syntax:

为了允许两个或更多goroutine进行通信,我们必须使用Go的通道,该通道允许我们从中发送和接收信息。 可以使用以下语法创建频道:

<variable> := make(chan <type>[, <buffer_size>])

Note that you may, optionally, define a buffer size. Send operations will be blocked if the buffer is full.

请注意,您可以选择定义缓冲区大小。 如果缓冲区已满,则发送操作将被阻止。

Values may be received/sent from/to a channel using the arrow operator <-. To send a value to a channel we would use the following syntax:

可以使用箭头运算符<-从/从通道接收值/向通道发送值。 要将值发送到通道,我们将使用以下语法:

<channel> <- <value>

Instead, receiving a value from a channel and assigning it to a variable would look as follows:

相反,从通道接收值并将其分配给变量将如下所示:

<variable> := <- <channel>

Note that the information always flows in the direction of the arrow.

请注意,信息始终沿箭头方向流动。

The use of channels also helps us in managing synchronization, without using any explicit locks or condition variables, given that send and receive operations block until the other side is ready.

使用通道还可以帮助我们管理同步,而无需使用任何显式的锁或条件变量,因为发送和接收操作会阻塞到另一端准备好为止。

Let’s wrap up everything we’ve seen so far with an example:

让我们用示例总结到目前为止所看到的一切:

package mainimport (
"fmt"
"math/rand"
)func randint(max int, c chan int) {
r := rand.Intn(max)
fmt.Println(r)
c <- r
}func main() {
c := make(chan int)
go randint(1000, c)
go randint(200, c) r1 := <- c
r2 := <- c
fmt.Println(r1 + r2)
}

In this example we’ve created a function that gets a random number and sends it to our channel. Then, we’ve called such function twice, each time running in its own goroutine. Finally, we’ve received the last two values of the channel, kept them in r1 and r2, and printed out the sum.

在此示例中,我们创建了一个获取随机数并将其发送到我们的频道的函数。 然后,我们两次调用了此类函数,每次都在其自己的goroutine中运行。 最后,我们收到了通道的最后两个值,将它们保留在r1r2 ,并打印出总和。

Also note that, for instance, the second printed number may be greater than 200. This would happen because each function call runs in a separate thread (or goroutine), and therefore they may not finish in the same order in which we have called them.

还要注意,例如,第二个打印的数字可能大于200。之所以会发生这种情况,是因为每个函数调用都在一个单独的线程(或goroutine)中运行,因此它们的执行顺序可能与我们调用它们的顺序不同。

Let’s now see what happens if we attempt to exceed the buffer size:

现在让我们看看如果尝试超过缓冲区大小会发生什么:

package mainimport (
"fmt"
)func main() {
c := make(chan int, 2)
c <- 1
c <- 5
c <- 4 r1 := <- c
r2 := <- c
r3 := <- c
fmt.Println(r1 + r2 + r3)
}

In this example we’ve defined a buffer size of 2, but then we’ve attempted to send 3 values to the channel. As expected, this makes our script to crash. Given that the buffer is full, the instruction c <- 4 is blocked until some value is received from the channel. Therefore, we may easily fix our script as follows:

在此示例中,我们将缓冲区大小定义为2,但随后尝试将3个值发送到通道。 不出所料,这使我们的脚本崩溃了。 给定缓冲区已满,指令c <- 4会被阻塞,直到从通道接收到某个值为止。 因此,我们可以按以下步骤轻松修复脚本:

package mainimport (
"fmt"
)func main() {
c := make(chan int, 2)
c <- 1
c <- 5
r1 := <- c c <- 4
r2 := <- c
r3 := <- c
fmt.Println(r1 + r2 + r3)
}

As you can see, we’ve just had to receive a value from the channel when the buffer was full, and then we’ve been able to send a new one to it without any issues.

如您所见,当缓冲区已满时,我们只需要从通道接收一个值,然后就可以向它发送一个新值而没有任何问题。

A channel may be closed (always by the sender) to indicate that no more values will be sent to it. This may be done with the following syntax:

通道可能会关闭(发送者始终会关闭)以指示不会再发送任何值。 可以使用以下语法完成此操作:

close(<channel>)

The receiver may then check whether the channel is closed or not. This may be achieved by defining a second variable in the receiving operation, which will be set to false if there are no more values to receive and the channel is closed. Let’s look at this with an example:

接收器然后可以检查通道是否关闭。 这可以通过在接收操作中定义第二个变量来实现,如果没有更多要接收的值并且通道已关闭,则将其设置为false。 让我们来看一个例子:

package mainimport (
"fmt"
"math/rand"
)func randints(max int, c chan int) {
c <- rand.Intn(max)
c <- rand.Intn(max)
c <- rand.Intn(max)
close(c)
}func main() {
c := make(chan int)
go randints(1000, c) r1, ok1 := <- c
fmt.Println(r1, ok1)
r2, ok2 := <- c
fmt.Println(r2, ok2)
r3, ok3 := <- c
fmt.Println(r3, ok3)
r4, ok4 := <- c
fmt.Println(r4, ok4)
}

In this example we’ve sent 3 values to the channel and then read them, always checking whether the channel is closed. In fact, the fourth time we’ve received from the channel, we get that ok4 is equal to false, meaning that the channel is closed. All in all, this means that we could continue reading values from the channel as long as our "ok" variable is true. This is where range comes in, allowing us to iterate over the values of a channel until it is closed. Let's see how we may simplify the above script with an example:

在此示例中,我们向通道发送了3个值,然后读取它们,并始终检查通道是否关闭。 实际上,第四次从通道接收到信号时,我们确定ok4等于false,这意味着通道已关闭。 总而言之,这意味着只要我们的“ ok”变量为true,就可以继续从通道读取值。 这就是range所在,允许我们遍历通道的值,直到通道关闭为止。 让我们来看一个示例如何简化上面的脚本:

package mainimport (
"fmt"
"math/rand"
)func randints(max int, c chan int) {
c <- rand.Intn(max)
c <- rand.Intn(max)
c <- rand.Intn(max)
close(c)
}func main() {
c := make(chan int)
go randints(1000, c) for i := range c {
fmt.Println(i)
}
}

In a much cleaner way, we’ve iterated through the channel until it was closed.

以一种更加干净的方式,我们遍历了通道,直到通道被关闭。

怎么办? (Now, what?)

First of all, congratulations if you’ve made it this far! You may now be wondering, how can I continue learning about Go? Well, I’m one of those who think that getting your hands dirty is one of the best ways to learn. Nothing replaces working towards something, finding issues in your path and solving them yourself.

首先,恭喜您已经做到了! 您现在可能想知道,我如何才能继续学习Go? 好吧,我是那些认为弄脏自己的手是最好的学习方法之一的人之一。 没有什么能代替在某件事上进行工作,在您的道路上发现问题并自己解决问题。

If you, however, want to continue reading about Go, you may check out these resources:

但是,如果您想继续阅读Go,可以查看以下资源:

As long as I discover more interesting learning resources, I will keep adding them to this list.

只要发现更多有趣的学习资源,我就会一直将它们添加到此列表中。

Now it’s your turn to Go have fun with Go!

现在轮到您去玩吧!

翻译自: https://towardsdatascience.com/an-introduction-to-golang-7369339dba3

golang简介

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值