Go语言基础

Go是一门`支持并发`、`垃圾回收`的编译型系统编程语言,诣在创造一门
具有在静态编译语言的`高性能`和动态语言的高效开发之间拥有良好平衡点的一门编程语言

Go语言特点

  • 类型安全和内存安全
  • 以非常直观和极低代价的方案实现高并发
  • 高效的垃圾回收机制
  • 快速编译(同时解决C语言中头文件太多问题)
  • 为多核计算机提供性能提升的方案
  • UTF-8编码支持

安装Go语言

由于无法访问Google,可以通过国内链接下载
Mac用户可以通过`brew install go`进行安装。
安装后需要配置环境变量
brew方式安装在/usr/local/opt/go/libexec目录下
export GOPATH=$HOME/go  # 需要在用户家目录创建一个名为go的目录,到时候你可以在该目录下编写Go程序,可以自定义路径,然后该变量指定为自定义路径
export GOROOT=/usr/local/opt/go/libexec  # 指定go软件包目录
export PATH=$GOROOT/bin:$GOPATH/bin:$PATH  # 输出go命令为全局模式,方便调用
可以将上述三行写在`~/.bashrc`文件或全局环境变量文件`/etc/profile`

根据约定GOPATH需要创建3个目录:

  • bin 存放编译后生成的可执行文件
  • pkg 存放编译后生成的包文件
  • src 存放项目源码

Go命令介绍

通过命令行输入后即可查看所有支持的命令参数

  • go get: 获取远程包 (如果是github源则需要提前安装git工具,google项目源则需要安装hg工具)
  • go run: 直接运行程序
  • go build: 测试编译,检查是否有编译错误
  • go fmt: 格式化源码(部分IDE会自动调用)
  • go install: 编译包文件并编译整个程序
  • go test: 运行以*_test.go结尾的测试文件
  • go doc: 查看文档,通过 godoc -http=:8000则可开启本地一个在线文档服务http://127.0.0.1:8000方式即可打开。与官方网站文档一摸一样

Go Hello world!

新建hello.go文件

// 单行注释
/*
多
行
注释
*/
// 当定义的为入口文件时,则该包名为main,否则可自定义取一个有意义的合法标识符名称
// 这样别人在导入你定义的模块时,可以使用该名称,如下面的fmt
package main 

// 导入内置包
import "fmt"
/*
也可以一次性导入多个包,类似python从一个模块导入多个方法
import (
    "fmt"
    "os"
)
注意每个包之间是以换行作为分割,不需要使用逗号

导入时可以定义别名
如把fmt改为类型C头文件stdio.h
import stdio "fmt"

使用时,比如下面的打印方法就变成
stdio.Println("Hello world!")

也可以不使用stdio,直接调用Println,这种方式以这种方式导入
import . "fmt"
然后就可以直接调用fmt里面的方法
Println("Hello world!")

这种方法尽量不要使用,避免方法被覆盖,应该显示的调用

*/
// 程序入口为main函数,必须为main才能执行
func main(){
	fmt.Println("Hello world!")  // Println表示打印后自动换行
}

通过命令行运行go run hello.go,即可输出Hello world!

关于方法输出

  • Go没有显示的private或public关键字用于定义共享或私有方法和属性,它是通过标识符来界定的。
  • 标识符首字母大写,则可以被其它包导入时调用,如果首字母小写,则其它包导入该包时,则不能使用该名称定义的
    变量或方法

关键字

break // 用于循环体内终止循环,如果有标签,则终止标签对应的循环,没有则终止当前循环
default // 与switch case条件语句一起使用,用于条件不匹配时的默认条件
func  // 定义函数的关键字名称
interface  // 定义接口的关键字名称
select
case  // 与switch一起使用
defer
go
map
struct  // 定义结构体数据结构
chan  
else  // 条件语句
goto  // 后跟标签名,指执行一个新的标签代码块
package  // 定义包名称
switch  // 与case一起使用
const  // 定义常量,常量内的变量只能是常量类型或内置类型
fallthrough  // 由于case语句不需要break,匹配则停止,如果需要继续匹配,则需要使用该关键字继续匹配
if  // 条件表达式
range  // 生成一个范围
type  // 定义类型,结构体,接口的关键字
continue  // 跳出当前循环,继续下一次循环,当带有标签,则继续标签内循环
for  // for的功能强大,因为GO语法简洁,没有冗余关键字,所以for兼并了while功能,
import  // 导入包
return  // 函数体内返回
var  // 定义变量

数组及切片操作

package main

import "fmt"

func main() {
	var a = [...]int{9: 1}
	var b = [10]int{9: 1}
	c := [...]int{9: 1}
	fmt.Println(a, b == c)
	s1 := a[1]
	s2 := a[len(a)/2:]
	fmt.Println(s1, s2)
	s3 := make([]int, 3, 10) // 声明切片类型。初始值3个。最大容量10个。超过10个。则需要重新扩容
	fmt.Println(s3, len(s3), cap(s3))
	a1 := []byte{'a', 'b', 'c', 'd', 'e', 'f', 'i', 'j', 'k'}
	s4 := a1[2:5]
	s5 := s4[3:len(a1)-2] // 重新切片时开始位置为上一个切片的后第4个位置。以上一个切片开始位置为0到索引3的位置。结束位置为数组总长度减去上一个切片开始位置个数
	fmt.Println(string(a1[2:5]), s4)
	fmt.Println(string(s5), s5)

	ss1 := make([]int, 3, 6)                                  // 创建一个初始值3个数。最大容量6个元素的切片
	fmt.Printf("%p", ss1)                                     // 打印初始时指针地址
	ss1 = append(ss1, 1, 2, 3)                                //追加三个元素。刚好达到切片容量
	fmt.Printf("%v %p
", ss1, ss1)                           // 发现指针地址依然是之前的地址,说明并没有新开辟内存空间
	ss1 = append(ss1, 1, 2, 3)                                // 再次追加三个元素
	fmt.Printf("%v %p %d %d
", ss1, ss1, len(ss1), cap(ss1)) // 发现指针地址改变,说明切片在内存中重新开辟了空间。原来的切片容量为6,现在切片大小为9,容量变成12了

	aa1 := [10]int{1, 2, 3, 4, 5, 6, 7}
	ss2 := aa1[3:5]
	ss3 := aa1[1:4]
	fmt.Printf("%v %v
", ss2, ss3)
	ss2[0] = 11 // 当改变切片内第1个元素时,发现ss3的重叠部分的元素也一起改变,说明切片是一种浅拷贝操作
	fmt.Printf("%v %v
", ss2, ss3)
	ss2 = append(ss2, 2, 2, 1, 4, 3, 4) //对切片进行扩容到超过原始的数组时,内存地址重新开辟,这个切片在新元素长度加上之前切片结束位置长度大小大于数组长度时才进行开辟内存空间
	ss2[0] = 222                        // 这时再去改变重叠部分的值,发现ss3并未受影响,依旧是之前的11
	fmt.Printf("%v %v
", ss2, ss3)

	sss1 := []int{1, 2, 3, 4, 5, 6}
	sss2 := []int{7, 8, 9}
	//copy(sss1, sss2)  // 将sss2拷贝到sss1中。这时sss2的所有元素会依次覆盖sss1中前面的元素。sss1变成[7 8 9 4 5 6]
	//copy(sss2, sss1) // 将sss1拷贝到sss2中。这时sss1会从前面的元素依次填充sss2对应的位置。如果sss2长度小于sss1,则只拷贝到sss2最大位置结束,这时sss2变成[1 2 3]
	//copy(sss1[2:3], sss2[1:])  // 将sss2指定位置数据拷贝到sss1指定位置。这里指定拷贝8,9。但是目标只有1一个位置。所以最终只拷贝了8。也就是sss1变成了[1 2 8 4 5 6]
	copy(sss1[2:], sss2[1:]) // 将sss2 8,9拷贝到sss1索引2的后面,也就是替换索引2开始,到索引4之间[2:4] 最终sss1变成[1 2 8 9 5 6] 
	fmt.Printf("%v %v
", sss1, sss2)
	sss3 := sss1[:] //完整拷贝sss1的元素
	fmt.Printf("%v %v
", sss1, sss3)

}


map及迭代操作

package main

import "fmt"
import "sort"

func main() {
	var m map[int]string       // [key类型]value类型
	m = map[int]string{}       //初始化空map
	m = make(map[int]string)   //通过make关键字创建map
	m2 := make(map[int]string) // 也可以这样声明并初始化map
	m2[1] = "ok"               // 存储键值对  任何可以进行==或!=比较的数据类型都能作为key,函数,map slice不能作为key,但可以作为value
	delete(m2, 1)              // 删除键
	b := m2[1]                 // 取出值,如果键不存在。则返回空值
	fmt.Println(m, m2, b)

	var m3 map[int]map[int]string     // 声明一个键为int,值为map的map
	m3 = make(map[int]map[int]string) // 先初始化m3
	m3[1] = make(map[int]string)      // 再初始化m3的值。因为它的值是map类型
	m3[1][1] = "ok"                   //这时才能直接对值进行map操作
	a := m3[1][1]
	fmt.Println(m3, a)

	a1 := m3[2][1]     // 对于不知道值有没有初始化,可以先进行获取,如果为空,则说明没有初始化,假如存在的就行空值,可能会判断出错,所以用下面这种方式解决
	a1, ok := m3[2][1] //  可以通过多返回值进行判断。当一个返回值时返回value。当两个返回值时第二个值为 bool类型
	if !ok {
		m3[2] = make(map[int]string)
	}
	m3[2][1] = "good"
	fmt.Println(a1, ok)
	a1, ok = m3[2][1]
	fmt.Println(a1, ok)

	//迭代切片
	var array = [10]int{1}
	slice := array[:]
	for index, value := range slice {
		fmt.Println(index, value)
	}
	//迭代map
	for key, value := range m3 {
		m3[key] = value // 这里可以对map进行迭代操作
		fmt.Println(key, value)
	}

	sm := make([]map[int]string, 5) // 生成一个以map为元素的切片。初始值为5个元素
	for index, value := range sm {
		value = make(map[int]string)     // 对map进行初始化
		value[1] = "10"                  //这里对value的操作不会影响原始切片,因为操作的是拷贝数据
		sm[index] = make(map[int]string) //这样操作才是会改变原始切片
		sm[index][12] = "ok"
		fmt.Println(index, value)
	}
	fmt.Println(sm)

	//map排序

	m5 := map[int]string{1: "a", 2: "b", 3: "c", 4: "d"}
	s5 := make([]int, len(m5))
	i := 0
	for key, _ := range m5 { // 对不需要的变量使用 _
		s5[i] = key // 将map key存入切片,然后对切片排序
		i++
	}
	fmt.Println(s5) // 未排序时。每次切片元素顺序不一样
	sort.Ints(s5)   // 导入sort包,对int型进行排序
	fmt.Println(s5)
	for index, value := range s5 {
		fmt.Println(index, value, m5[value]) // 有序输出map
	}
}

func定义及参数传递

package main

import "fmt"

func main() {
	//除了切片。指针以参数传递时,在函数内可以直接改变该变量,其它类型当参数传递都是按值传递,不会改变原来内存地址数据
	s2 := 121
	f(s2, 2, 3, 3) // s2是按值传递,非内存地址传递,所以函数内改变s2。并没有影响函数外的s2
	fmt.Println(s2)
	s1 := []int{1, 2, 3}
	g(s1)
	fmt.Println(s1)

	// 定义匿名函数
	f2 := func(a *int) {
		*a = 220 //地址传递,可以直接改变内存地址数据
		fmt.Println("hello")
	}
	f2(&s2)
	fmt.Println(s2) // s2通过内存地址方式传递,值被函数内改变了
	x := closure(2) // 调用闭包。得到一个匿名函数
	y := x(3)       // 该匿名函数接收一个int型参数。并返回一个int型数据
	fmt.Println(y)
	fmt.Println(x(5))
	fmt.Println(x(6))
}

// 定义无返回值函数,参数为s1 int型,s2 string型
func a(s1 int, s2 string) {

}

// 定义返回值为一个int型函数,参数为s1 int型,s2 string型
func b(s1 int, s2 string) int {
	return 1
}

// 定义两个返回值函数int和string型,参数为s1 int型,s2 string型
func c(s1 int, s2 string) (int, string) {
	return 1, "a"
}

// 定义返回值为一个int型函数,参数为s1 ,s2 都是int型
func d(s1, s2 int) int {
	return 1
}

// 定义返回值为两个int型函数,对于简写返回值类型时,则必须定义为命名返回值,参数为s1 int型,s2 string型
func e(s1 int, s2 string) (a, b int) {
	return 1, 2
}

// 不定长相同类型参数传参,不定长类型为最后一个声明为不定长类型
func f(s1 int, s2 ...int) int {
	// slice是引用传递,引用类型数据,但是如果调用时传入的非引用传递类型。那么函数内操作不影响实际调用时的参数数据
	// s2是一个值传递。也就是说函数内操作不影响函数传入的实际数据的值
	s1 = 100
	fmt.Println(s1)
	fmt.Println(s2)
	return 1
}

func g(a []int) {
	a[0] = 23
	fmt.Println(a)
}

// 创建闭包,返回为一个参数为int的匿名函数。该匿名函数返回值为int型
func closure(x int) (func(int) int) {
	return func(i int) int {
		fmt.Printf("%p
", &x)
		return i + x
	}
}

defer 的用处

package main

import "fmt"

func main() {

	fmt.Println("a")
	// defer为执行完所有操作后最后调用的的操作。且按倒叙执行,即使出现错误也会执行defer操作
	defer fmt.Println("b")
	defer fmt.Println("c")
	fmt.Println("d")

	for i := 0; i < 3; i++ {
		//defer fmt.Println(i)  // 倒着打印2 1 0
		//defer func() {
		//  这种情况实际上用到了一个闭包,闭包内的i一直是作为地址引用形式存在
		//	fmt.Println(i) // 打印的都是3。因为是先遍历完整个循环。最后才执行defer。此时i的值被加到3。不满足条件,才结束循环
		//}()

		defer func(a int) {
			fmt.Println(a)  // 这种方式定义,最终才会和我们期望的一样执行
		}(i)
	}

	//A()
	//B()  // 此时B内通过Panic抛出异常。如果没有使用recover恢复。则不会进行下面的操作而终止程序
	//C()

	A()

	// 先注册recover。如果后面出现异常。会在此进行捕获到
	defer func() {
		if err := recover(); err != nil{
			fmt.Println(err)
		}
	}()
	B()
	// 当出现异常时,recover在异常出现之后是不会触发的
	defer func() {
		if err := recover(); err != nil{
			fmt.Println(err)
		}
	}()
	C()

}

func A()  {
	fmt.Println("A")
}

func B()  {
	fmt.Println("B")
	// 先注册捕获。后面发生的异常才会在此终止并恢复
	defer func() {
		if err := recover(); err != nil{
			fmt.Println(err)
		}
	}()
	panic("panic in b")
	// 如果只是放在这。异常已经发生了。是没法恢复的
	defer func() {
		if err := recover(); err != nil{
			fmt.Println(err)
		}
	}()
}

func C()  {
	fmt.Println("C")
}

判断输出结果

package main

import "fmt"

func main() {
	/*
	该代码执行顺序。原则,先执行正常代码,最后倒序执行defer
	closure i = 4
	closure i = 4
	closure i = 4
	closure i = 4
	defer_closure i=4
	defer i=3
	defer_closure i=4
	defer i=2
	defer_closure i=4
	defer i=1
	defer_closure i=4
	defer i=0
	*/
	var fs = [4]func(){}

	for i := 0; i < 4; i++ {
		defer fmt.Println("defer i=", i)
		defer func() { fmt.Println("defer_closure i=", i) }()
		fs[i] = func() {
			fmt.Println("closure i = ", i)
		}
	}

	for _, f := range fs {
		f()
	}
}

struct与匿名struct

在go语言中。struct 也是一种类型。所以也是使用type定义

package main

import "fmt"

//go  一切皆类型
//struct 也是按值传递的类型
type pearson struct {
	Name string
	Age  int
}

type man struct {
	Name string
	Age  int
	//匿名结构
	Concat struct {
		Phone, City string
	}
}

//匿名字段
type woman struct {
	string
	int
}

func main() {
	a := pearson{}
	fmt.Println(a) // 打印为0,因为int是一个零值
	a.Name = "John"
	a.Age = 12
	fmt.Println(a)

	b := pearson{
		Name: "Peter",
		Age:  18,
	}

	fmt.Println(b)
	E(b)
	fmt.Println(b)
	F(&b)
	fmt.Println(b)
	// 建议初始化结构时以取地址形式初始化
	c := &pearson{
		Name: "hi",
		Age:  22,
	}

	F(c)
	//对地址结构类型。可以直接对字段赋值
	c.Name = "地址"
	fmt.Println(c)
	//定义匿名结构类型
	d := struct {
		Name string
		Age  int
	}{Name: "F",
		Age: 200,}
	fmt.Println(d)

	e := &struct {
		Name string
		Age  int
	}{Name: "E",
		Age: 12,}
	fmt.Println(e)

	f := man{Name: "hello", Age: 299}
	//对匿名结构初始化
	f.Concat.Phone = "10086"
	f.Concat.City = "China"
	fmt.Println(f)

	g := woman{}
	h := woman{"Year", 18} // 匿名字段必须严格按照定义字段类型顺序初始化

	var i woman //定义一个woman类型变量
	i = h       // 变量赋值,最终h和i一样
	fmt.Println(g, h, i)
	fmt.Println(i == h) // 同类型可以比较,不同类型不能进行比较
}

//按值传递
func E(per pearson) {
	per.Age = 16
	fmt.Println(per)
}

//明确接受指针类型
func F(per *pearson) {
	per.Age = 19
	fmt.Println(per)
}

struct 嵌入

package main

import "fmt"

type human struct {
	Sex int
}

type teacher struct {
	human
	Name string
	Age  int
}

type student struct {
	human // 这种字段是一种匿名字段,Go会默认将嵌入结构作为匿名字段名称,
	Name string
	Age  int
}

func main() {
	a := teacher{Name: "David", Age: 32}
	a.Sex = 1
	a.human.Sex = 0 //也可以这样赋值,这种可以避免与需要嵌入对结构内字段名称冲突赋值。
	// 这里初始化匿名结构。需要以匿名字段类型名称为key
	b := student{Name: "tim", Age: 12, human: human{Sex: 0}}
	fmt.Println(a, b)
}

method

package main

import "fmt"

type A1 struct {
	B1
	Name string
}

type B1 struct {
	Name string
}

type B2 struct{
	//定义私有字段
	name string
}

type MyInt int //定义一个新的类型,与原始的int类型是不能作比较操作的。因为这个是两个不同类型

func (myInt MyInt) Print() {
	fmt.Println("hello myInt")
}

func (myInt *MyInt) Print2() {
	fmt.Println("hello myInt2")
}

func (myInt *MyInt) Increase(){
	*myInt = 100 // 对类型本身赋值
}

func (myInt *MyInt) Increase2(number int){
	*myInt += MyInt(number) // 进行类型强转
}

func main() {
	a := A1{Name: "HELLO", B1: B1{Name: "B1"}}
	fmt.Println(a)
	a.Print()
	fmt.Println(a.Name, a.B1.Name)
	a.B1.Print()
	fmt.Println(a.Name)
	a.Print2()
	fmt.Println(a.Name)

	var int1 MyInt
	int1.Print()
	//不带指针传递的方式可以以下面两种方式调用
	(MyInt).Print(int1)
	(*MyInt).Print(&int1)
	//以指针形式传递定义的方法必须将实例以内存地址形式传递调用
	(*MyInt).Print2(&int1)
	//不能对m2和int1进行比较操作。因为她们类型不一样
	//	var m2 int
	//fmt.Println(m2 == int1)

	fmt.Println(int1)
	int1.Increase()
	fmt.Println(int1)

	int1.Increase2(100)
	fmt.Println(int1)

	b2 := B2{name:"b2?"}
	fmt.Println(b2.name)
}

//按值传递。不修改原始值
func (a1 A1) Print() {
	a1.Name = "a2"
	fmt.Println("a1")
}

//按指针地址传递值,会修改原始值
func (a1 *A1) Print2() {
	a1.Name = "a2"
	fmt.Println("a1")
}

func (b1 B1) Print() {
	fmt.Print("b1")
}


interface

package main

import "fmt"

//type USB interface {
//	Name() string
//	Connect()
//}

type USB interface {
	Name() string
	Connection // 嵌入接口,与上面这种实现方式一样
}

type Connection interface {
	Connect()
}

type PhoneConnect struct {
	name string
}

func (pc PhoneConnect) Name() string {
	return pc.name
}

func (pc PhoneConnect) Connect() {
	fmt.Println("Connect:", pc.name)
}

func main() {
	var a USB
	a = PhoneConnect{name: "PhoneConnect"}
	a.Connect()
	var b USB = PhoneConnect{name: "PhoneConnect2"}
	b.Connect()
	DisConnect(a)

}

func DisConnect(usb USB) {
	// 如果要调用实现接口的结构字段,需要先进行类型判断。
	// 如果ok 条件成立,则执行ok内的语句
	if pc, ok := usb.(PhoneConnect); ok {
		fmt.Println("DisConnect: ", pc.name)
		return
	}
	fmt.Println("Unknow device")
}

原文地址:https://www.cnblogs.com/zengchunyun/p/9131308.html