# Golang Bases

\[TOC]

## 1. Why Golang

golang 是一个开源，编译的，静态类型的编程语言．其主要目的是更加简单的开发高可用性，可扩展的web应用．虽然有很多可以完成相同工作的语言，但是与之相比，golang有以下优势:

* 并发性是该语言固有的部分,更加简易的进行多进程编写,通过`Goroutines` `channels` 实现
* Golang是编译性的语言,可以产生二进制文件,对于js,python等动态语言不具备该特征
* 支持静态链接,所有的go代码可以静态连接到一个巨大的二进制库中,可以快速部署到云服务器而不需担心依赖问题&#x20;

## ２. Workspace and directory structure

首先需要建立一个go的工作目录,可以直接在当前终端`export GOPATH=/path/` 作为工作目录,对于golang编译产生的可执行文件,位于当前工作目录下的`bin`文件夹,可以在path中引入该环境变量

`export PATH=${PATH}:${GOPATH}/bin`

go项目的基本目录结构如下:

```
├── bin
│   └── hello
└── src
    └── github.com
        └── nf
            ├── hello
            │   └── echo.go
            └── string
                ├── reverse.go
                └── reverse_test.go
```

* `src` :

  源代码存放的目录,可以包含多个子文件夹,每个文件夹包含多个包,对于`hello` `string` 是直接可以被引用的包名
* `bin`:

  可执行文件的位置

对于程序的入口,需要以`package main` 作为包名,进行`go build` `go install` 后生成相应的二进制文件,也可以直接使用`go run xxxx.go` 以类似脚本的形式进行运行,go run 命令只能接受一个命令源码文件以及若干个库源码文件（必须同属于 main 包）作为文件参数，且**不能接受测试源码文件**。它在执行时会检查源码文件的类型。如果参数中有多个或者没有命令源码文件，那么 go run 命令就只会打印错误提示信息并退出，而不会继续执行.

## 3. Syntax

### 1. 代码结构

golang 为静态类型语言,对于每一个变量必须赋予其相应类型,golang相邻的语句间不需要添加`;` 作为间隔,使用{}表示语句块

```go
package main //1

import "fmt" //2

func main() { //3  
    fmt.Println("Hello World") //4
}
```

* `package main` 每个源代码文件必须以一个包名为开始,标识其所属的包,`main` 包名是程序入口包名
* `import` 引入所需要的包名,多个包名可以采用()引入,并且可以更名

  ```go
  import (
      fmt "fmt"
      Str "string"
      "time"
      "math/rand"
  )  // 可以在每个包名后添加;如果不加编译器会自动添加,也可以写为一行,添加分号
  ```
* `func main` 程序入口

### 2. 变量

可以使用多种方式声名变量,可以使用`var`关键字,采用`var variable_name type` 的语法进行变量声明,也可以在声明变量时赋予初始值;在赋予初始值的同时可以省去变量的类型,根据变量值来推测其类型;还可以使用更为优雅的方式,同时声明多个变量;在代码块内部,可以使用短声明的方式声明一个变量`a := 12.0` ,但是不可以用在函数外部

```go
package main

import (
    "fmt"
    "math"
)

var (
    var1 int
    var2 string
    name = "kobe"
    age = 39
)

func main() {
    var test string = "It's just a test"
    var2 = test
    var1 = age
    a, b := 12.6, 22.0
    a, b = b, a

    fmt.Println(var1, var2, test, name, age, math.Sqrt(a))
}
```

**注意:使用()进行多个变量声明或者包引入时,每个item间可以用`;`隔开也可以不用,不可以用`,`**&#x20;

go为静态语言不可以将不同类型的数据赋值给不同类型的变量

### 3. 基本类型

* bool
* Numeric Types
  * int8, int16, int32, int64, int
  * uint8, uint16, uint32, uint64, uint
  * float32, float64

    complex64, complex128
  * byte
  * rune
* string

#### Signed integers

| Type  | size                | range                   |
| ----- | ------------------- | ----------------------- |
| int8  | 8 bits              | -128-127                |
| int16 | 16 bits             | -32768-32767            |
| int32 | 32 bits             | -2147483648- 2147483647 |
| int64 | 64 bits             |                         |
| int   | depends on platform | 64 bits                 |

可以使用`fmt.Printf` 中的格式化输出`%T`获取一个变量或者数据的类型，也可以使用`unsafe`包中的`Sizeof（）` 函数获得变量的大小(Bytes)

```go
package main

import (
  "fmt"
  "unsafe"
)

func main() {
  a, b := 'a', 12
  c, d, e := 123, "Test", "go"

  fmt.Println(a, b, c, d, e)
  fmt.Println("Type test:")
  fmt.Printf("a: %T, size: %d, b: %T, size: %d", a, unsafe.Sizeof(a), b, unsafe.Sizeof(b))

}


97 12 123 Test go
Type test:
a: int32, size: 4, b: int, size: 8%
```

#### `MAX_INT and MIN_INT`

可以借助位操作符，得到相应的int最大值以及最小值：

```go
const NAX_INT = int(^uint(0) >> 1)
const MIN_INT = ^MAX_INT
```

#### Unsigned int

与有符号数类似 byte类型是uint8的一个别名，rune是int32的一个别名，所以使用byte表示string时只有8位可用，所以只可以表示基本的ASCII码，而unicode需要使用2个字节进行表示，所以需要使用rune，utf-8一般使用三个字节表示

#### Floating point types

浮点数，包含32位以及64位的类型

#### Complex types

* complex64: 实部和虚部都是32为浮点数
* complex128：实部和虚部都是64位浮点数

可以使用内置函数初始化一个复数,传入的参数类型需要相同，得到的复数的类型根据传入的参数决定

```go
func complex(r, i FloatType) ComplexType
```

或者可以直接使用变量声明的方式：

```go
c := 1 + 2i
```

其他两种数据类型byte 对应于uint8, rune对应于int32,字符串类型必须使用`“”` 括起来，使用单引号对应于byte

### 4. 类型转换

go语言不支持不同类型的number的直接运算，需要进行显式的类型转换

```go
i := 2
j := 2.2
sum := i + int(j)   // 4

var j float64 = float64(12)  // 必须加后面的强制类型转换
```

### 5.常量使用

使用const关键字定义常量，注意常量的处理是在编译时进行的，不可以在运行时给常量赋值，类似于C中的`#define` 。所以不可以给一个常量赋值为一个函数的返回值

在go语言中任意包含在双括号中的string，都是字符串常量，该常量没有类型`untyped`

```go
const hello = "The string is untyped"
```

这似乎和之前的变量声明有所矛盾，如果"string" 没有类型，**对于`var a = "cds"`那又是如何根据常量“cds”向a进行类型传递呢？**

这是因为无类型的常量有一个默认的类型与之关联，只有代码真正需要使用该默认类型时才会使用，

对于以上声明操作，由于变量a需要一个类型所以使用了默认类型`string`

```go
const typed string = "cdsvds"   // 声明有类型的常量

func main() {  
        var defaultName = "Sam" //allowed
        type myString string
        var customName myString = "Sam" //allowed
        customName = defaultName //not allowed

}
```

**Golang是强类型语言，所以以上代码会报错**。虽然`myString` 是`string` 的一个别名，但是强类型不允许不同类型的变量相互赋值，所以该操作不被允许

与之相似bool常量操作与string常量相同，别名类型变量赋值仍会报错。

**数字常量**

数字常量包含所有int, float, complex.对于数字常量而言，也是没有类型的，所以同一个常量可以赋给各种类型的变量，并且根据需求进行相应的类型转换。对于单一的`const a = 5` 而言其默认类型是int, float64, complex128(根据平台判断)

## 3. 函数

golang 函数的基本语法如下：

```go
func functionname(param type) returnType {

}

func rectProg(length, width float64)（float64, float64）{
    var area = length * width
    var perimeter = (length + width) * 2
    return area, perimeter
}
```

其中参数可以具有多个参数，不同类型，如果所有参数都是同一类型，只需要在最后一个参数后添加类型即可。对于多返回值的函数，可以使用括号将返回值类型包含其中。

golang需要指定返回值的类型，也可以指定返回值的名称，此时`return` 语句就不用显式指明返回值的名称

```go
resfunc rectProps(length, width float64) (area, per, float64) {
    area = length * width
    per = (length + width) / 2
    return
}
```

**空白操作符：**

对于多个返回值，如果不需要使用其中的某些返回值，可以使用`_` 空白操作符进行变量指示，注意空白符与动态语言中用法不同，不可以使用其值,同时一个赋值语句中可以使用多个`_` 代表多个不使用的空位值，注意go中对于声明但是没有使用的变量会报错，而对于空白操作符不可以使用其值。`__` 不是空白操作符

```go
func rectTest(length, width float64) (area, per, div float64) {
  area = length * width
  per = (length + width) / 2
  div = math.Sqrt(length*length + width*width)
  return
}

fmt.Println("\nTest for func:")
area, _, __ := rectTest(3, 4)
fmt.Println("Area:", area, "div:", __, )
// Test for func:
//Area: 12 div: 5
```

#### 可变参数列表的函数

golang中支持使用可变参数列表，可以再参数列表的最后一项使用变长参数，`args ...T` ,最常见的可变参数列表的函数是`append()`函数用于进行slice对象的扩展，第二个参数可以接受一个相当于Slice的多个数值

注意使用Slice或者数组作为可变参数传入时，可以使用语法糖`slice...` ,对于可变参数而言，期待是一个类型为的slice。使用该语法糖后，将切片或者数组中的元素全部迭代。但是需要注意使用slice作为参数的情况下，虽然go是按值传递，但是由于切片的头指针是一个指向目标数组的首指针，所以在函数内部改变切片内容，也会映射到函数外部xcat

```go
func change(s ...string) {  
    s[0] = "Go"
}

func main() {  
    welcome := []string{"hello", "world"}
    change(welcome...)
    fmt.Println(welcome)
}
// Go world


func change_(s ...string) {
  s[0] = "Rust"
  c := make([]string, cap(s)*2)
  copy(c, s)
  c[2] = "test"
  s = c
  fmt.Println(s)
}
// [Rust world test ] [Rust world]  注意使用slice时使用nake进行切片扩展时，只有在0-len-1之间的ｉｎｄｅｘ可以赋值
```

## 4. Go的分号插入

go默认采用`；`作为每条语句的结束标志，可以省去分号，按照一定的代码规则，go在编译时会自动插入分号

* 当源码被分为多行的标记时，go会在一行的末尾添加分号，如果最后的标记是

  > 标识符（程序中的元素，变量，类型）
  >
  > integer, floating-point, imaginary, string
  >
  > break, continue, fallthrough, return关键字之一
  >
  > 操作符： ++ ，--，）， ]， }
* 为了进行跨越多行的复杂声明，可以使用()

所以对于go中的条件控制语句, else 或者if else必须写在}的同一行，否则会被默认加上分号，导致编译错误。所以在go中一个条件控制被作为一个单独的语句，对于{}， （）， \[]中的内容都被作为单独的语句

```go
if condition {

} else {}
```

## 5. Go中的控制流

go使用基本的`if..else`, `if .. else if`进行流控制，使用`for` 同时进行静态语言中循环遍历，也可以作为`while` 使用，同时可以进行类似动态语言的数组遍历操作

```go
var list []int = []int{1321, 435, 453}
for i, v := range list {
    fmt.Printf("list[%d] = %d", i, v)
}

  for i, no := 10, 1; i <= 20 && no <= 10; i, no = i + 1, no + 1 {
    fmt.Printf("%d * %d = %d\n", i, no, i * no)
  }

  // Use for as while, all the ; can bo omitted
  i := 2
  for i < 10 {
    fmt.Printf("The %d out\n", i)    // Format output is only for Printf
    i += 2
  }
```

**Switch 使用：**

在go中`switch`用法与c/c++不同，默认是每一个`case` 后都跟一个break, 如果需要多个case都可以执行，可以使用`fallthrough` 关键字，使之在第一个满足条件的case与`fallthrough` 关键字最后连接的一个case上的代码都可以执行.`fallthrough` 关键字只允许作为一个case的最后的语句，case的表达式可以不是常量，可以在运行时赋予其值

```go
switch finger := 2; finger {//finger is declared in switch
    case 1:
        fmt.Println("Thumb")
    fallthrough
    case 2:
        fmt.Println("Index")
        fallthrough
    case 3:
        fmt.Println("Middle")
        fallthrough
    case 4:
        fmt.Println("Ring")
        fallthrough
    case 5:
        fmt.Println("Pinky")
        fallthrough
    default: //default case
        fmt.Println("incorrect finger number")
    }
// output
Index
Middle
Ring
Pinky
incorrect finger number
```

同时，在switch语句前可以允许执行一个语句，使用分号分开，但是该语句中变量的作用域仅限于该switch scope

除此之外，switch可以不加条件，此时可以作为if逻辑语句使用

```go
num := 75
  switch { // expression is omitted
  case num >= 0 && num <= 50:
    fmt.Println("num is greater than 0 and less than 50")
  case num >= 51 && num <= 100:
    fmt.Println("num is greater than 51 and less than 100")
  case num >= 101:
    fmt.Println("num is greater than 100")
  }

letter := "i"
    switch letter {
    case "a", "e", "i", "o", "u": //multiple expressions in case
        fmt.Println("vowel")
    default:
        fmt.Println("not a vowel")
    }
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://books.innohub.top/goinfo/info/bases.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
