Go

环境

环境配置

1,Go开启代理:go env -w GOPROXY=https://goproxy.cn,direct,查看环境参数:go env,系统变量path: GoInstallPath\bin ,GOROOT: GoInstallPath

2,GOPATH: 工作目录,下有:src pkg bin子目录,

3,在控制面板->用户环境变量中修改 GOPATH,GOPATH可存在多个,优先级依次降低,GOPATHbin 子目录添加到系统变量中

4,找包顺序:gomod =off : goroot/src->gopath0/src->gopath1/src->...

gomod =on : goroot/pkg/mod->gopath0/pkg/mod->gopath1/pkg/mod->...

5,自动初始化目录:sh ./create_go_proj.sh porject_name

IDE

1,Goland:将当前工程路径加入project gopath,proj gopath优先于glob gopath

2,vscode : setting.json

1,包的构建:go build packagePath,若在该包的源码目录中:go build,如果构建的是库源码文件(库源码文件是不能被直接运行的源码文件,它仅用于存放程序实体,这些程序实体可以被其他代码使用),那么操作的结果文件只会存在于临时目录中。这里的构建的主要意义在于检查和验证;如果构建的是命令源码文件(即包含 main() 入口函数的源码文件”),在命令行执行目录内生成可执行文件,文件一般会与命令源码文件的直接父目录同名。

2,如果被当前包依赖的代码包的归档文件不存在,或者源码文件有了变化,那它会自动编译依赖包,否则默认不会编译目标代码包所依赖的那些代码包。

3,如果要强制编译依赖包:加入标记-a;如果不但要编译依赖的代码包,还要安装它们的归档文件:加入标记-i;查看go build命令具体都执行了哪些操作: 加入标记-x;只查看go build具体操作而不执行它们: 加入标记-n;查看go build命令编译的代码包的名称: 加入标记-v,(常与-a标记搭配使用)

4,包的构建并安装:go install packagePath,在包文件夹内简化为:go install,安装某个代码包而产生的归档文件是与这个代码包同名的。安装操作会先执行构建,然后还会进行链接操作,并且把结果文件搬运到指定目录。如果安装的是库源码文件,安装该包及他所用到的其他包,生成的静态链接库文件会被搬运到它所在工作区的 pkg 目录下的某个子目录中;如果安装的是命令源码文件,命令会构建产生一个以main函数为入口的可执行的二进制文件,文件一般会与命令源码文件的直接父目录同名,结果文件会被搬运到它所在工作区的 bin 目录中,或者环境变量GOBIN指向的目录中。并会自动编译所依赖的任何包。

5,构建和安装代码包的时候都会执行编译、打包等操作

6,包的获取:go get 就会自动地获取、 构建并安装远程包,在src,pkg下保存相应文件:

7,包的根路径为:src目录。

8,代码用圆括号组合了导入,这是“分组”形式的导入语句。 包名与导入路径的最后一个元素一致。如"math/rand" 包中的源码均以 package rand 语句开始。

9,链接成单个二进制文件的所有包,其包名无需是唯一的,只有导入路径(它们的完整文件名) 才是唯一的。

10,可执行命令必须使用 package main,并包含一个无参数、无返回值的main函数,作为程序入口。

11,包应当以小写的单个单词来命名,且不应使用下划线或驼峰记法.

12,使用包结构可以帮助选择好的名称,通过文件名来判定使用的包,不会产生混淆的。如:io.Reader,bufio.Reader

14,代码包的名称可以和源码文件所在的目录不同名。如果报名与文件夹名不一致,要保证该文件夹下文件都为自定义的包名导包时以文件夹名导入,但以包名来调用包内容。构建与安装时使用文件夹名,生成归档文件也以文件夹命名。

15,如果导入的多个包的最末路径一致,且包名与文件夹名相同会产生冲突

16,内部包:代码包命名为internal的包,该代码包的直接父包及其子包中的代码可以引用内部包的大写可导出内容。其它包内不能访问内部包的内容。

编写

注释

1,注释:/* */ , //

2,每个包都应包含一段包注释,即放置在包子句前的一个块注释。对于包含多个文件的包, 包注释只需出现在其中的任一文件中即可。包注释应在整体上对该包进行介绍,并提供包的相关信息。在包中,任何顶级声明前面的注释都将作为该声明的文档注释。在程序中,每个可导出(首字母大写)的名称都应该有文档注释。在package, const, type, func关键字上面并且紧邻关键字

3,type, const, func以名称为注释的开头, packagePackage name为注释的开头

空白标识符

1,空白标识符可被赋予或声明为任何类型的任何值,而其值会被无害地丢弃。

2,有时导入某个包只是为了其副作用, 而没有任何明确的使用。让编译器停止关于未使用导入的提示,需要空白标识符来引用已导入包中的符号。

3,将未使用的变量 赋予空白标识符也能关闭未使用变量错误。

格式

1,无论如何,不应将一个控制结构(ifelseforswitchselect)的左大括号放在下一行。由于编译器会在代码中自动加入分号,如果这样做,就会在大括号前面插入一个分号,这可能引起不需要的效果。

数据类型

基本类型
序号类型和描述
1布尔型: 布尔型的值只可以是常量 true 或者 false。
2数字类型:整型 int 和浮点型 float32、float64.
3字符串类型:Go 的字符串是由单个字节连接起来的。字符串的字节使用 UTF-8 编码标识 Unicode 文本。
4派生类型: (a) 指针类型(Pointer)(b) 数组类型 (c) 结构化类型(struct) (d) Channel 类型 (e) 函数类型 (f) 切片类型 (g) 接口类型(interface) (h) Map 类型
序号类型和描述
1uint8 无符号 8 位整型 (0 到 255)
2uint16 无符号 16 位整型 (0 到 65535)
3uint32 无符号 32 位整型 (0 到 4294967295)
4uint64 无符号 64 位整型 (0 到 18446744073709551615)
5int8 有符号 8 位整型 (-128 到 127)
6int16 有符号 16 位整型 (-32768 到 32767)
7int32 有符号 32 位整型 (-2147483648 到 2147483647)
8int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)
序号类型和描述
1float32 IEEE-754 32位浮点型数
2float64 IEEE-754 64位浮点型数
3complex64 32 位实数和虚数:complex128(1+2i)
4complex128 64 位实数和虚数
序号类型和描述
1byte 类似 uint8
2rune 类似 int32
3uint 32 或 64 位
4int 与 uint 一样大小
5uintptr 无符号整型,用于存放一个指针

2,自定义数据类型

type Seq []int

3,表达式 T(v) 将值 v 转换为类型 T。 Go 在不同类型的项之间赋值时需要显式转换(上转、下转都要显示转换)。转换过程并不会创建新值,它只是值暂让现有的时看起来有个新类型而已。

4,转换会创建新值,如从整数转换为浮点数等。

变量

1,Go中约定使用驼峰记法 MixedCapsmixedCaps。名字以大写字母开头,那么它就是已导出的。任何“未导出”的名字在该包外均无法访问。

math.Pi ✔ math.pi ❌

  
C : 基本类型 含变量名的表达式int *p => *p指向int => p为指向int的指针,即求解以p为变量的表达式来获得p的类型,繁琐。
Go: 变量名 基本类型/复合类型p *int,p为指向int的指针,简单明了。

2,var 语句用于声明一个变量列表:var a,b,c int=1,2,3如果初始化值已存在,则可以省略类型,变量会从初始值中获得类型:var a,b,c=1,2,false当右边包含未指明类型的数值常量时,新变量的类型取决于常量的精度:f := 3.142 // float64,变量声明也可以“分组”成一个语法块。

3,在函数中,简洁赋值语句 := 可在类型明确的地方代替 var 声明。 := 结构一般表示不太重要的变量,如方法的局部变量,所以不能在函数外使用。var x type 表示重要的变量,如全局变量。 i:=0 == var i=0 == var i int =0

4,在满足下列所有条件时,已被声明的变量 v 可被重声明可出现在:= 声明中:

5,使用var a=1,a:=1这种声明时不指明变量类型,而使用类型推断来确定数据类型。Go 语言的类型推断可以明显提升程序的灵活性,可以随意改变右侧表达式返回值的类型,使得代码重构变得更加容易,同时因为Go语言为静态语言,类型检查在编译期间完成类型检查,所以不会给代码的维护带来额外负担,更不会损失程序的运行效率。

6,变量找寻顺序

当前块 -> 函数域 -> 当前文件=当前包=导入的包(使用全导入方式导入)

7,重声明与重名

常量

1,使用 const 关键字。 常量不能用 := 语法声明。它们在编译时创建。

2,常量只能是数字、字符、字符串或布尔值。由于编译时的限制, 定义它们的表达式必须也是可被编译器求值的常量表达式,只能使用字面值。

const a int=1<<3 ✔ const b float=math.Sin(math.Pi/4)❌

流程控制

函数

1,当连续两个或多个函数的形参、已命名返回值类型相同时,除最后一个类型以外,其它都可以省略。f(x int, y int)(a,b string)==f(x ,y int)(a,b string)

2,函数可以返回任意数量的返回值,函数声明时相应的也要指明多个返回值的类型。

3,Go函数的返回值“形参”可被命名,它们会被视作定义在函数顶部的变量,就像传入的形参一样。命名后,一旦该函数开始执行,它们就会被初始化为与其类型相应的零值; 若该函数执行了一条不带实参的 return 语句,则结果形参的当前值将按返回值的声明顺序被返回。

4,函数也是值。它们可以像其它值一样传递,函数值可以用作函数的参数或返回值。

5,Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。 保证了函数内引用变量的生命周期与函数的活动时间相同。可以把闭包简单理解成"定义在一个函数内部的函数",在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。闭包最大用处有两个,一个是前可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会性能问题,甚至可能导致IE6内存泄露(内存泄露是指用不到(访问不到)的变量,依然占居着内存空间,不能被再次利用起来,IE6自身问题,与闭包无关)。解决方法是,在退出函数之前,将不使用的局部变量全部删除。闭包实质上涉及的就是词法作用域和将函数作为值return,以及返回的函数不会立即执行,将函数作为值传递,实际上就是打开了一条访问内部变量的通道。

内部函数可以引用外部函数的参数和局部变量,当外部函数返回内部函数时,相关参数和变量都通过指向变量的指针保存在返回的函数中,返回的函数并没有立刻执行,而是直到调用了f()才执行,所以变量不是在内部函数创建时就创建一个当前数值的快照保存到函数中,而是通过指针引用,直到调用时才通过指针去获取当前的值,这在父函数内部循环创建多个闭包,时如果闭包函数引用了循环变量要特别注意,可以通过在闭包函数外层再添加一层函数,并且立即调用,把变量保存在外部函数中,再在内部闭包内引用。

在没有class机制,只有函数的语言里,借助闭包,把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),返回的对象中,实现了一个闭包,该闭包携带了局部变量x,它的状态可以完全对外隐藏起来,从外部代码根本无法访问到变量x,只能通过闭包函数实现访问,并且多次调用父类方法创建多个闭包,相当于创建多个是实例,各个实例内变量各自独立,类似Java中的Record类型。

闭包会在父函数外部,改变父函数内部变量的值。 JavaScript 代码都是基于事件的 — 定义某种行为,然后将其添加到用户触发的事件之上,代码通常作为回调:为响应事件而执行的函数,将闭包函数绑定在特定行为上,实现触发特定行为。

与OOP编程语言的区别

6,匿名函数:

7,可变参

8,数据传递类型

值类型变量引用类型变量
基本数据类型(bool int float string )值传递类型的指针形式
结构体映射
数组切片
函数接收者信道
 函数

9,值类型的变量在赋值与函数参数传递时使用值传递,使用浅拷贝

10,引用类型的变量在赋值与函数参数传递时使用引用传递

11,函数式编程

defer

1,defer 语句会将函数推迟到外层函数返回之后执行。 推迟的函数调用会被压入一个栈中,位于当前函数之下。当外层函数返回时,被推迟的函数会按照后进先出的顺序FILO调用。

2,推迟调用的函数其参数会立即求值即参数被固定,与闭包不同,但直到外层函数返回前该函数都不会被调用。

3,可以用于资源关闭等操纵,推迟诸如 Close 之类的函数调用:

输出

1,fmt.Printf()实现格式化输出,fmt.Sprintf()返回指定格式的字符串。

占位符含义
%T变量的类型
%v变量的值
%+v当打印结构体时,添加字段名的变量值
%#v当打印结构体时,相应值的Go语法表示
%f浮点数
%s输出字符串表示,解释转义符
%q双引号围绕的字符串,不解释转义符原样输出
循环

1,for 循环由三部分组成,它们用分号隔开:

初始化语句通常为一句短变量声明,该变量声明仅在 for 语句的作用域中可见。

3,要在 for 中使用多个变量,应采用平行赋值的方式

4,for 循环的 range 形式可遍历集合类数据如切片、映射、数组、字符串。 当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。 for i,j := range data可以将下标或值赋予 _ 来忽略它。 若只需要索引,忽略第二个变量即可。 for i,_ := range data

5,字符串使用unicode存储

判断

1,if表达式外无需小括号 ( ) ,而大括号 { } 则是必须的。

3,if 语句可以在条件表达式前执行一个简单的语句。 该语句声明的变量作用域仅在 if 和对应的 else 块中使用之内。

switch

1,只运行选定的 case,而非之后所有的 case,Go 自动提供了在这些语言中每个 case 后面所需的 break 语句,没有穿透效应,除非以 fallthrough 语句结束分支,否则分支会自动终止。 加入 break 语句可以使 switchbreak处提前终止。

2,switch 的 case 无需为常量,且取值不必为整数。

3,没有条件的 switch 同 switch true 一样。 这种形式能将一长串 if-then-else 写得更加清晰。

4, case 可通过逗号分隔来列举相同的处理条件。

指针

1,类型 *T 是指向 T 类型值的指针。其零值为 nil& 操作符会生成一个指向其操作数的指针。 * 操作符表示指针指向的底层值。

2,Go 没有指针运算,即无法对指针加减。

内存分配

1,new()用来分配内存的内建函数,它不会初始化内存,只会将内存置零。 new(T) 会为类型为 T 的新项分配根据T的成员的类型置零的内存空间, 并返回它的地址,也就是一个类型为 *T 的值。它返回一个指针, 该指针指向新分配的,类型为 T 的成员对应类型的零值。

2,内建函数 make(T, *arg) 的目的不同于 new(T)。它只用于创建切片、映射和信道,并返回类型为 T(而非 *T)的一个已初始化(而非置零)的值。出现这种用差异的原因在于,这三种类型本质上为引用数据类型,它们在使用前必须初始化。