百木园-与人分享,
就是让自己快乐。

Golang笔记

一、基本介绍

1、开发环境安装-windows安装

打开Golang官网,选择对应版本,进行安装。

2、环境变量配置

1)步骤

(1)首先在环境变量中添加 GOPATH,值为 go 的安装目录:

Golang笔记

(2)然后在环境变量 PATH 中添加 go 安装目录下的 bin 文件夹。

Golang笔记

(3)接着添加一个环境变量 GOPATH,值为你自己希望的工作目录。

Golang笔记

(4)最后重启一下 命令行工具,输入 go version命令即可查看版本信息

2)GOROOT

$GOROOT,便是 Go 的安装路径,存放 Go 的内置程序库。通常你安装完后,你电脑的环境变量就会设好 GOROOT 路径。当你开发 Go 程序的时候,当你 import 内置程序库的时候,并不需要额外安装,而当程序运行后, 默认也会先去 GOROOT 路径下寻找相对应的库来运行。

3)GOPATH与Go工作区

GOPATH 是我们定义的自己的工作空间。

一个 GOPATH 工作区,一般这样:

./
├── bin
├── pkg
└── src
    ├── hello_github
    └── hello_router.go

(1)bin:保存编译后生成的可执行文件。我们的操作系统使用$PATH环境变量来查找无需完整路径即可执行的二进制应用程序,建议将此目录:$GOPATH/bin添加到我们的全局 $PATH 变量中。

(2)pkg:它保存已安装的包对象(比如:.a)。每个目标操作系统和体系结构对都有自己的 pkg 子目录。 Go 编译包时生成的中间文件,用来缓存提高编译效率。

(3)src:包含源代码(比如:.go .c .h .s等)。 该路径决定 import 包时的导入路径或可执行文件名称。

import包的搜索顺序:
GOROOT/src:该目录保存了Go标准库代码。
GOPATH/src:该目录保存了应用自身的代码和第三方依赖的代码。

3、Go程序开发注意事项

1)Go源文件以“go”为扩展名

2)Go应用程序的执行入口是main()方法

3)Go语言严格区分大小写

4)Go方法由一条条语句构成,每个语句后不需要分号(Go语言会在每行后自动加分号)

5)Go编译器是一行行进行编译的,因此我们一行就写一条语句,不能把多余语句写在同一行,否则会报错。

6)Go语言定义的变量或者import的包如果没有使用到,代码不能编译通过。

7)大括号都是成对出现的,缺一不可。

4、常用的转义字符(escape char)

1)\\t:一个制表位
2)\\n:换行符
3)\\\\:一个\\
4)\\\":一个”
5)\\r:一个回车

5、Go变量

1)使用基本步骤

(1)声明变量(定义变量)

(2)赋值

(3)使用

2)Golang变量声明和赋值的三种方式

(1)指定变量类型,声明后若不赋值,使用默认值

//声明同时赋值
var 变量名字 类型 = 表达式
//声明时不赋值
var 变量名字 类型 

(2)根据值自行判定变量类型(类型推导)

var 变量名字 = 表达式

(3)省略var,注意:=左侧的变量不应该是一级声明过的,否则会导致编译错误

变量名字 := 表达式

3)多变量声明

//可以声明时赋值,也可以不赋值
var 变量名字1,变量名字2 类型
////声明时需赋值
var 变量名字1,变量名字2 =值1,值2
//类型推导,需要赋值
变量名字1,变量名字2 :=值1,值2

6、数据类型

 1)基本数据类型:变量存的就是值,也叫值类型。

(1)数值型

整数类型、浮点类型

(2)字符型

使用byte来保存单个字符字符

(3)布尔型(bool)

(4)字符串(string):官方将string归属到基本数据类型

2)派生数据类型

(1)指针(Pointer)-引用类型

指针类型:变量存的是一个地址,这个地址指向的空间存的才是值。获取指针类型所指向的值,使用:*。

package main

import \"fmt\"

func main() {
	i := 10
	var p *int = &i
	fmt.Println(p, *p)
}

(2)数组-值类型

见数组章节

(3)结构体(struct)-值类型

见结构体章节

(4)管道(Channel)-引用类型

(5)函数

见函数章节

(6)切片(slice)-引用类型

见切片章节

(7)接口(interface)-引用类型

见接口章节

(8)map-引用类型

见nmap章节

 3)值类型与引用类型

(1)值类型:变量直接存储值,内存通常在栈中分配。都有对应的指针类型,形式未*数据类型,比如int的对应的指针就是*int,依次类推。

(2)引用类型:变量存储的是一个地址,这个地址对应的空间才是真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收。

7、函数

1)函数声明

函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。

func name(parameter-list) (result-list) {
    body
}

 形式参数列表描述了函数的参数名以及参数类型。这些参数作为局部变量,其值由参数调用者提供。返回值列表描述了函数返回值的变量名以及类型。如果函数返回一个无名变量或者没有返回值,返回值列表的括号是可以省略的。如果一个函数声明不包括返回值列表,那么函数体执行完毕后,不会返回任何值。

函数的类型被称为函数的签名。如果两个函数形式参数列表和返回值列表中的变量类型一一对应,那么这两个函数被认为有相同的类型或签名。形参和返回值的变量名不影响函数签名,也不影响它们是否可以以省略参数类型的形式表示。
每一次函数调用都必须按照声明顺序为所有参数提供实参(参数值)。在函数调用时,Go语言没有默认参数值,也没有任何方法可以通过参数名指定形参,因此形参和返回值的变量名对于函数调用者而言没有意义。
在函数体中,函数的形参作为局部变量,被初始化为调用者提供的值。函数的形参和有名返回值作为函数最外层的局部变量,被存储在相同的词法块中。
实参通过值的方式传递,因此函数的形参是实参的拷贝。对形参进行修改不会影响实参。但是,如果实参包括引用类型,如指针,slice(切片)、map、function、channel等类型,实参可能会由于函数的间接引用被修改。

你可能会偶尔遇到没有函数体的函数声明,这表示该函数不是以Go实现的。这样的声明定义了函数签名。

2)init函数

 略

3)Deferred函数

 见异常处理章节

4)匿名函数

 拥有函数名的函数只能在包级语法块中被声明,通过函数字面量(function literal),我们可绕过这一限制,在任何表达式中表示一个函数值。函数字面量的语法和函数声明相似,区别在于func关键字后没有函数名。函数值字面量是一种表达式,它的值被称为匿名函数(anonymous function)。

(1)定义

func (参数列表) (返回值列表) {
	函数体
}

(2)在定义时调用匿名函数

package main

import \"fmt\"

func main() {
	f := func(x int) {
		fmt.Println(x)
	}
	f(10)
}

5)闭包

 函数值不仅仅是一串代码,还记录了状态。在squares中定义的匿名内部函数可以访问和更新squares中的局部变量,这意味着匿名函数和squares中,存在变量引用。这就是函数值属于引用类型和函数值不可比较的原因。Go使用闭包(closures)技术实现函数值,Go程序员也把函数值叫做闭包。

6)函数值

在Go中,函数被看作第一类值(first-class values):函数像其他值一样,拥有类型,可以被赋值给其他变量,传递给函数,从函数返回。对函数值(function value)的调用类似函数调用。

8、包

 包的本质就是创建不同的文件夹来存放程序文件。每个包一般都定义了一个不同的名字空间用于它内部的每个标识符的访问。每个名字空间关联到一个特定的包,让我们给类型、函数等选择简短明了的名字,这样可以在使用它们的时候减少和其它部分名字的冲突。每个包还通过控制包内名字的可见性和是否导出来实现封装特性。通过限制包成员的可见性并隐藏包API的具体实现,将允许包的维护者在不影响外部包用户的前提下调整包的内部实现。通过限制包内变量的可见性,还可以强制用户通过某些特定函数来访问和更新内部变量,这样可以保证内部变量的一致性和并发时的互斥约束。当我们修改了一个源文件,我们必须重新编译该源文件对应的包和所有依赖该包的其他包。

1)包的三大作用

(1)区分相同名字的函数、变量等标识符

(2)当程序文件很多时,可以很好的管理项目

(3)控制函数、变量等访问范围,即作用域

 2)包的相关说明

(1)打包基本语法/声明基本语法

package 包名

在每个Go语言源文件的开头都必须有包声明语句。包声明语句的主要目的是确定当前包被其它包导入时默认的标识符(也称为包名)。

(2)引入包的基本语法

import \"包的路径\"

每个包是由一个全局唯一的字符串所标识的导入路径定位。出现在import语句中的导入路径也是字符串。(在import包时,路径从$GOPATH的src下开始,不用带src。

 3)包的匿名导入
如果只是导入一个包而并不使用导入的包将会导致一个编译错误。但是有时候我们只是想利用导入包而产生的副作用:它会计算包级变量的初始化表达式和执行导入包的init初始化函数。这时候我们需要抑制“unused import”编译错误,我们可以用下划线_来重命名导入的包。像往常一样,下划线_为空白标识符,并不能被访问。

import _ \"image/png\" // register PNG decoder

这个被称为包的匿名导入。它通常是用来实现一个编译时机制,然后通过在main主程序入口选择性地导入附加的包。

9、数组

数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的,不能动态变化。

1)基本介绍(定义和初始化)

定义:var  数组名 [数组大小]数据类型

以下是四种数组初始化方式:

默认情况下,数组的每个元素都被初始化为元素类型对应的零值,对于数字类型来说就是0。我们也可以使用数组字面值语法用一组值来初始化数组

var q [3]int = [3]int{1, 2, 3}

var r  = [3]int{1, 2}
p  := [3]int{1, 2}
fmt.Println(r[2]) // \"0\"

在数组字面值中,如果在数组的长度位置出现的是“...”省略号,则表示数组的长度是根据初始化值的个数来计算。因此,上面q数组的定义可以简化为:

b := [...]int{1, 2, 3}
var c = [...]int{1, 2, 3}
fmt.Println(b[2])
fmt.Println(c[2])

 可以指定元素值对应的下标:

var names=[3]string{1:\"tom\",0:\"jack\",2:\"marry\"}

数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定。

2)数组遍历

 (1)常规遍历

package main

import \"fmt\"

func main() {
	var score [5]float64 = [5]float64{1.0, 2.0, 3.0, 4.0, 5.0}
	for i := 0; i < len(score); i++ {
		fmt.Println(score[i])
	}
}

 (2)for-range结构遍历

 这个是go语言一种独有的结构,可以用来遍历访问数组的元素,

基本语法:for index,value:=range array01{}

第一个返回值index是数组的下标,第二个value是在该下标位置的值,它们都是仅在for循环内部可见的局部变量,遍历数组元素的时候如果不想使用下标index,可以直接把下标index标记为下划线_,index和value的名称是不固定的,也可以自行指定。

package main

import \"fmt\"

func main() {
	var score [5]float64 = [5]float64{1.0, 2.0, 3.0, 4.0, 5.0}
	for index, value := range score {
		fmt.Println(index, value)
	}
}

10、切片

1)基本介绍

Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。

数组和slice之间有着紧密的联系。一个slice是一个轻量级的数据结构,提供了访问数组子序列(或者全部)元素的功能,而且slice的底层确实引用一个数组对象。一个slice由三个部分构成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。

2)切片定义的基本语法:

var 变量名 []类型

例如:

package main

func main() {
	var intArr [5]int = [...]int{1, 2, 3, 4, 5}
	myslice:=intArr[1:4]
        var myslice2 []int = intArr[0:3]
}

 3)切片的创建

(1)方式一:定义一个切片,然后让切片去引用一个已经创建好的数组。

直接引用数组,这个数组是事先存在的,程序员是可见的。

(2)方式二:通过make来创建切片。

基本语法:var 切片名 []type=make([]type,len,[capacity])
参数说明:type:切片的类型,len:长度,capacity:容量(可选的)。

package main

import \"fmt\"

func main() {
	var myslice []int = make([]int, 4)
	myslice[0] = 100
	fmt.Println(myslice)
}

通过make来创建切片,make也会创建一个数组,是由切片在底层进行维护,程序员是看不见的。

(3)方式三:定义一个切片,直接就指定具体数组,使用原理类似make的方式

package main

import \"fmt\"

func main() {
	var myslice []int = []int{1, 2, 3, 4, 5}
	fmt.Println(myslice)
}

 4)切片遍历

(1)常规遍历

 同数组

for i := 0; i < len(myslice); i++ {
	fmt.Println(myslice[i])
}

(2)for-range结构遍历

 同数组

for index, value := range myslice2 {
	fmt.Println(index, value)
}

11、map

1)基本语法(声明)

var map变量名 map[keytype]valuetype

key的类型通常为:int、string。

声明是不会分配内存的,初始化需要make,分配内存后才能赋值和使用(map在使用前一定要make)。

var a map[string]string
a=make(map[string]string,10)
a[\"u01\"]=\"jack\"

2)map的三种使用方式

 在Go语言中,一个map就是一个哈希表的引用,map类型可以写为map[K]V,其中K和V分别对应key和value。map中所有的key都有相同的类型,所有的value也有着相同的类型,但是key和value之间可以是不同的数据类型。其中K对应的key必须是支持==比较运算符的数据类型,所以map可以通过测试key是否相等来判断是否已经存在。虽然浮点数类型也是支持相等运算符比较的,但是将浮点数用做key类型则是一个坏的想法,最坏的情况是可能出现的NaN和任何浮点数都不相等。对于V对应的value数据类型则没有任何的限制。

(1)先声明,再make

//声明,这时map=nil
var ages map[string]string
//make(map[string],string,10)分配一个map
ages=make(map[string]string,10)

(2)声明时直接make,内置的make函数可以创建一个map:

ages := make(map[string]int) // mapping from strings to ints

 或者

var ages map[string]int=make(map[string]int)

(3)声明时直接赋值,我们也可以用map字面值的语法创建map,同时还可以指定一些最初的key/value:

var ages map[string]int=map[string]int{
\"alice\":31
}

ages := map[string]int{
    \"alice\":   31,
    \"charlie\": 34,
}

 这相当于:

ages := make(map[string]int)
ages[\"alice\"] = 31
ages[\"charlie\"] = 34

 因此,另一种创建空的map的表达式是map[string]int{}

3)map的crud操作

使用内置的delete函数可以删除元素:

delete(ages, \"alice\") // remove element ages[\"alice\"]

 4)map遍历

 

5)map排序

 

12、结构体

 结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。用结构体的经典案例是处理公司的员工信息,每个员工信息包含一个唯一的员工编号、员工的名字、家庭住址、出生日期、工作岗位、薪资、上级领导等等。所有的这些信息都需要绑定到一个实体中,可以作为一个整体单元被复制,作为函数的参数或返回值,或者是被存储到数组中,等等。

下面两个语句声明了一个叫Employee的命名的结构体类型,并且(直接声明)声明了一个Employee类型的变量dilbert:

type Employee struct {
    ID        int
    Name      string
    Address   string
    DoB       time.Time
    Position  string
    Salary    int
    ManagerID int
}

var dilbert Employee

赋值:

type Point struct{ X, Y int }

p := Point{1, 2}
var q Point = Point{12, 12}

 1)成员变量

如果一个结构体的成员变量名称是首字母大写的,那么这个变量是可导出的。(即在其它包中可以访问),一个结构体可以同时包含可导出和不可导出的成员变量。

type Person struct {
    Name string  //不可导出
    age int      // 可导出
}

 命名结构体类型s不可以定义一个拥有相同结构体类型s的成员变量,也就是一个聚合类型不可以包含它自己。但是s中可以定义一个s的指针类型,即*s。

type Person struct {
    Name string
    p1 Person    //错误
    p2 *person   //正确
}

结构体的成员变量如果是引用类型,如指针、切片、map,需要make再赋值。

 2)创建结构体变量和访问结构体字段的四种方法

(1)直接声明

var dilbert Employee

(2){}

type Point struct{ X, Y int }
p := Point{1, 2}

(3)&-返回的是结构体指针

var person *Person=new(Person)

(4){}-返回的是结构体指针

var person *Person=&Person{}

结构体指针访问字段的标准方式应该是:(*结构体指针).字段名,go做了一个简化,也支持结构体指针.字段名

3)结构体的每个字段上,可以写一个tag,该tag可以通过反射机制获取,常见的场景就是序列化和反序列化。

13、方法

 在函数声明时,在其名字之前放上一个变量,即是一个方法。这个附加的参数会将该函数附加到这种类型上,即相当于为这种类型定义了一个独占的方法。

package main

import (
	\"fmt\"
	\"math\"
)

type Point struct{ x, y float64 }

//func
func Distance(p, q Point) float64 {
	return math.Hypot(q.x-p.x, q.y-p.y)
}

//method
func (p Point) Distance(q Point) float64 {
	return math.Hypot((q.x - p.x), q.y-p.y)
}

func main() {
	var p Point = Point{12, 12}
	var q Point = Point{8, 8}
	x := Distance(p, q)
	//function call
	fmt.Println(x)
	//method call
	y := p.Distance(q)
	fmt.Println(y)
}

 上面的代码里那个附加的参数p,叫做方法的接收器(receiver),早期的面向对象语言留下的遗产将调用一个方法称为“向一个对象发送消息”。在Go语言中,我们并不会像其它语言那样用this或者self作为接收器;我们可以任意的选择接收器的名字。由于接收器的名字经常会被使用到,所以保持其在方法间传递时的一致性和简短性是不错的主意。这里的建议是可以使用其类型的第一个字母,比如这里使用了Point的首字母p。

在方法调用过程中,接收器参数一般会在方法名之前出现。这和方法声明是一样的,都是接收器参数在方法名字之前。

 

14、接口

 接口类型。接口类型是一种抽象的类型。它不会暴露出它所代表的对象的内部值的结构和这个对象支持的基础操作的集合;它们只会表现出它们自己的方法。也就是说当你有看到一个接口类型的值时,你不知道它是什么,唯一知道的就是可以通过它的方法来做什么。接口类型具体描述了一系列方法的集合,一个实现了这些方法的具体类型是这个接口类型的实例。

15、错误处理机制

go中引入的处理方式为:defer、panic、recover
GO中可以先抛出一个panic的异常,然后再defer中通过recover捕获这个异常,然后正常处理。
1)基本介绍
(1)defer
你只需要在调用普通函数或方法前加上关键字defer,就完成了defer所需要的语法。当执行到该条语句时,函数和参数表达式得到计算,但直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。
需要注意一点:不要忘记defer语句后的圆括号,否则本该在进入时执行的操作会在退出时执行,而本该在退出时执行的,永远不会被执行。
(2)panic
一般而言,当panic异常发生时,程序会中断运行,并立即执行在该go routine(可以先理解成线程)中被延迟的函数(defer 机制)。随后,程序崩溃并输出日志信息。日志信息包括panic value和函数调用的堆栈跟踪信息。panic value通常是某种错误信息。对于每个goroutine,日志信息中都会有与之相对的,发生panic时的函数调用堆栈跟踪信息。
不是所有的panic异常都来自运行时,直接调用内置的panic函数也会引发panic异常;panic函数接受任何值作为参数。当某些不应该发生的场景发生时,我们就应该调用panic。
(3)recover
内置函数,可以捕获到异常。
如果在deferred函数中调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recover,recover会返回nil。
2)错误处理的好处
进行错误处理后,程序不会轻易挂掉。如果加入预警代码,程序会更加的健壮。
3)示例

package main

import \"fmt\"

func test() {
	//使用defer +recover来捕获和处理异常
	defer func() {
		err := recover()
		if err != nil { //说明捕获到异常
			fmt.Println(\"err:\", err)
		}
	}()

	num1 := 10
	num2 := 0
	res := num1 / num2
	fmt.Println(\"rest=\", res)
}
func main() {
	test()
	fmt.Println(\"main()...\")
}

4)自定义错误

使用errors.New和panic内置函数

(1)errors.New(\"错误说明\"),会返回一个error类型的值,表示一个错误。

(2)panic内置函数,接收一个interface{}类型的值(也就是任何值)作为参数。可以接收error类型的变量,输出错误信息,并退出程序。

(3)示例

package main

import (
	\"errors\"
	\"fmt\"
)

func readConf(name string) (err error) {
	if name == \"config.ini\" {
		return nil
	} else {
		return errors.New(\"读取文件错误...\")
	}
}

func test() {
	err := readConf(\"config.in1i\")
	if err != nil {
		panic(err)
	}
	fmt.Println(\"test()...\")
}
func main() {
	test()
}

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 

出处:http://www.cnblogs.com/hoaprox

如果你真心觉得文章写得不错,而且对你有所帮助,那就不妨小小打赏一下吧,如果囊中羞涩,不妨帮忙“推荐\"一下,您的“推荐”和”打赏“将是我最大的写作动力!

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接.

qq群
微信


来源:https://www.cnblogs.com/hoaprox/p/15967088.html
本站部分图文来源于网络,如有侵权请联系删除。

未经允许不得转载:百木园 » Golang笔记

相关推荐

  • 暂无文章