Golang快速入门

跟風遠走 提交于 2020-10-29 05:51:09

变量

// 变量声明与赋值
var i1 int 
var i2 int = 2
// Outside a function, every statement begins with a keyword (var, func, and so on) and so the := construct is not available.
i3 := 3		   // 变量声明赋值的简写
i4, i5 := 4, 5 // 多个变量平行赋值

// 常量
//Constants are declared like variables, but with the const keyword.
//Constants can be character, string, boolean, or numeric values.
//Constants cannot be declared using the := syntax.
const (
	a = iota       // iota遇到const初始值为0,同const中使用一次'+1'
	b = iota
	c              // 常用中如果变量赋值表达式一样,后面的可以省略
	b string = 0   // 可以明确指定类型
)

// 字符串

// Go中字符串是UTF-8的,并且不可修改
s1 := "Hello world"
// 多行字符串
s2 := `Starting part
		Ending part`
s3 := "Starting part" + 
		"Ending part"
// 如下会错误,内部会有分号置入
// s2 := "Starting part"
// 	+ "Ending part"
// ->
// s2 := "Starting part";
// 	+ "Ending part";


// Some numeric conversions:
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
// Or, put more simply:
i := 42
f := float64(i)
u := uint(f)

Basic Type

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // alias for uint8

rune // alias for int32
     // represents a Unicode code point

float32 float64

complex64 complex128

零值 zero value

When storage is allocated for a variable, either through a declaration or a call of new, or when a new value is created, either through a composite literal or a call of make, and no explicit initialization is provided, the variable or value is given a default value. Each element of such a variable or value is set to the zero value for its type:

  • false for booleans,
  • 0 for integers,
  • 0.0 for floats,
  • ""or strings,
  • nil for pointers, functions, interfaces, slices, channels, and maps.

This initialization is done recursively, so for instance each element of an array of structs will have its fields zeroed if no value is specified.

// 1.
type Money float64

m1 := new(Money)	// type *Money
m2 := Money(1)		// type Money

fmt.Printf("%T, %T\n", m1, m2)	// *main.Money, main.Money
fmt.Println(m1, *m1, m2)		// 0xc820078220 0 1

// 2.
type Person struct {
	ID int64
	Name string
}

func (this *Person) run() {
	fmt.Println("I'm running...")
}

// 对象类型,
var p1 Person
p3 := Person{}
// 指针类型,p2形式与p4相同
p2 := new(Person)
p4 := &Person{}
var p5 *Person = nil // 指针可以初始化为nil,
// 对于nil的对象可以调用其方法,但是不可以访问其字段,如果方法内访问字段也会报错

fmt.Printf("%T, %T, %T, %T, %T\n", p1, p2, p3, p4, p5) 
// main.Person, *main.Person, main.Person, *main.Person, *main.Person

p1.run()	// I'm running...
p5.run()	// I'm running...

数据结构

数组(array)

定义,创建

arr1 := [3]byte{}
arr2 := [3]float64{}
// array、slice 和 map 的复合声明变得更加简单。
// 使用复合声明的array、slice和map,元素复合声明的类型与外部一致,则可以省略。
arr3 := [3][2]int{
			{1, 2}, // 复合的此处简写,而不用写成[]int{1, 2}
			{2, 3},
		}
arr4 := [...]int{1, 2, 3} // Go会自动统计个数
// 复杂类型数组(test中常用)
data := []struct{
	input string
	output string
} {
	{"ab", "ba"},
	{"cd", "dc"},
}

访问

for i := 0; i < len(arr1); i++ {
fmt.Print(i, arr1[i])
}

for i, v := range arr1 {
	fmt.Print(i, v)
}

值传递

Go语言中数组是一个值类型(value type)。 所有值类型变量在**赋值和作为参数传递**的时候都会产生一次复制动作。

package main

import "fmt"

func modify(array [5]int) {
	array[0] = 10
	// out: 10, 2, 3, 4, 5
	fmt.Println("In modify(), array values:", array)
}

func main() {
	array := [5]int{1,2,3,4,5}
	modify(array)
	// out: 1, 2, 3, 4, 5
	fmt.Println("In main(), array values:", array)
}

切片(Slice)

数组切片的数据结构: 一个指向原数组的指针(当切片增加超过数组容量,数组会扩容,新建数组,复制数据) 数组切片中的元素个数 数组切片已分配的存储空间

创建

// 基于数组创建
// 此时切片内部引用数组,修改会影响到数组,
// 但是如果切片append后进行扩容 那便会引用新的数组,再进行修改不会影响原数组了
a1 := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 
// a1[from:to] to <= len(a1)
s1 := a1[:]
s2 := a1[:5]

// 直接创建
// len:5 cap:5 value:[1,2,3,4,5]
s3 := []int{1,2,3,4,5}
// len:5 cap:5 value:[0,0,0,0,0]
s4 := make([]int, 5)
// len:0 cap:5 value:[]
s5 := make([]int, 0, 5)

// 基于切片创建 s1[from:to] to <= cap(s1)
s6 := s5[:3] 

遍历

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

for i,v := range s1 {
	fmt.Println(i, v)
}

动态增减元素

切片内部指向一个**数组,但其lencap是两个值,并且可以随着元素增加动态扩容**(从新指向一个新的数组),这也就需要内部重新分配空间并且进行数据复制。 函数 appendslice s 追加零或N个值,并且返回追加后的新的、与 s 有相同类型的 slice. 如果 s 没有足够的容量存储追加的值,append 分配一 个足够大的、新的 slice 来存放原有 slice 的元素和追加的值。因此,返回 的 slice 可能指向不同的底层 array。

s1 := make([]int, 5, 10)
// out: len:5 cap:10 // s1中的内容[0, 0, 0, 0, 0]
fmt.Println("len:", len(s1), " cap:", cap(s1))

// 往切片新增元素
append(s1, 1, 2, 3)
// 往切片新增另一个切片,注意'...'
append(s1, s2...)

内容复制

copy(s1, s2):将s2内容复制到s1中。 如果两个数组切片**len不一样,就会安其中较小的len**进行复制操作

s1 := []int{1, 2, 3, 4, 5}
s2 := []int{5, 4, 3}

// 将s2的3个元素复制到s1的前三位
copy(s1, s2)
// 只会复制s1的前三个元素到s2的前三位
copy(s2, s1)

Map

申明创建

type Person struct {
	ID int
	Name string
	Addr string
}

m0 := map[string] int {
	"a": 1,
	"b": 2,   // 最后一个元素后面逗号是必须的
}

m1 := map[string] Person {
	"Tom" : Person{
		ID: 1,
		Name: "Tom",
		Addr: "beijing",
	},
	"Jack" : Person{
		ID: 2,
		Name: "Jack",
		Addr: "shanghai",
	},
}

var m2 map[string] Person = make(map[string] Person)
// var m2 map[string] Person = make(map[string] Person, 20)

// value:可以设置为任意type
m3 := make(map[string] interface{})

赋值

m1["name"] = "Tom"
m1["jack"] = Person {
	ID: 1
}

删除

如果“name”这个键不存在,那么操作将不会有什么作用 但是如果传入的**key值是nil**,该调用将导致程序抛出异常(panic)。

delete(m1, "name")

查找

if person, ok := m1["Tom"]; ok {
	fmt.Println(person)
}

流程控制

条件语句

// if语句使用,条件语句可以不适用括号,花括号是必须的 
if a < 5 {
	// do something...
} else {
	// do something...
}
// if语句中添加初始化语句,变量作用域为if内
if obj, ok := m["name"]; ok {
	// do something...
}

选择语句

条表达式不限制为常量或者整数; 与C语言等规则相反,Go语言不需要用break来明确退出一个case;

// case 1
switch s {
	case "a", "A":         // 单个case中,可以出现多个结果选项;逗号隔开
		fmt.Printf("a")
	case "b": fallthrough  // 只有在case中明确加fallthrough关键字,才会继续执行紧跟的下一个case;
	case "B":
		fmt.Println("b")
	default:
		fmt.Println("default")
}

// case 2:switch可以不加条件表达式,此时等同于多个if...else...
switch {
	case core > 90 :
		fmt.Println("Great")
	case core > 60 :
		fmt.Println("Good")
	default :
		fmt.Println("No good")
}

循环语句

Go中的循环三种用法 for init; condition; post { } for condition { } for { }

// case 1:普通
sum := 0
for i := 0; i < 10; i++ {
	sum += i
}

// case 2:无限循环
for {
	if sum > 100 {
		break;
	}
}

// case 3:多重赋值
a := []int{1, 2, 3, 4, 5, 6}
for i, j := 0, len(a) – 1; i < j; i, j = i + 1, j – 1 {
    a[i], a[j] = a[j], a[i]
}

// case 4:break加控制参数
for j := 0; j < 5; j++ {
	for i := 0; i < 10; i++ {
		if i > 5 { 
			break JLoop
		}
		fmt.Println(i)
	} 
}
JLoop:

J: for j := 0; j < 5; j++ {
		for i := 0; i < 5; i++ {
			fmt.Println(j, i)
			if(i == 2) {
				break J
			}
		}
	}

跳转语句

func myfunc() {
	i := 0
	HERE:
	fmt.Println(i)
	i++
	if i<10 {
		go HERE
	}
}

函数

func (p mytype) funcname (q int) (r,s int) { return 0,0 }

  1. 关键字 func 用于定义一个函数;
  2. 函数可以绑定到特定的类型上。这叫做接收者。有接收者的函数被称作 method
  3. funcname 是你函数的名字;
  4. **int类型的变量q作为输入参数。参数用pass-by-value**方式传递,意味着它们会被复制;
  5. 变量 r 和 s 是这个函数的 命名返回值。在 Go 的函数中可以返回多个值。如果不想对返回的参数命名,只需要提供类型:(int,int)。 如果只有一个返回值,可以省略圆括号。如果函数是一个子过程,并且没有任何 返回值,也可以省略这些内容;
  6. 这是函数体。注意 return 是一个语句,所以包裹参数的括号是可选的。

函数定义

func Add(a int, b int) (ret int, err error) {
	if a < 0 || b < 0 {
		err = erroros.New("Should be non-negative numbers!")
		return
	}
	return a + b, nil
}

// 参数,返回值类型相同可以合并,返回值只有一个可以简写
func Add2(a, b int) int {
	// ...
}

约定

小写字母开头的函数只在本包内可见,大写字母开头的函数才能被其他包使用。 这个规则也适用于类型和变量的可见性。

不定参数

// ...type格式为不定参数,只能作为最后一个参数
func myfunc(args ...int) {
	for i,v := range args {
		fmt.Println(i, v)
	}
}

// 不定参数函数的调用
myfunc(2, 3, 4)
myfunc(s1[:]...)

func myfunc2(args ...int) {
	myfunc(args...)
	myfunc(args[:]...)
}

// 任意类型不定参数
func myPrint(args ...interface{}) {
	for _, arg := range args {
		switch arg.(type) {
			case int:
				fmt.Println(arg, " is an int value.")
			case string:
				fmt.Println(arg, " is a string value.")
			case int64:
				fmt.Println(arg, " is an int64 value.")
			default:
				fmt.Println(arg, " is an unknow type.")
		}
	}
}

异常使用

// 自定义异常对象
type MyIllegalError struct {
	Code int
	Msg string
	Info string
}
// 实现error接口的方法
func (this *MyIllegalError) Error() string {
	return this.Msg
}
// 添加构造函数
func NewMyIllegalError(code int, msg, info string) *MyIllegalError {
	return &MyIllegalError{
		Code: code,
		Msg: msg,
		Info: info,
	}
}

func add(a, b int) (ret int, err error) {
	if a<0 || b<0 {
		err = NewMyIllegalError(1, "参数错误", "id=1")
		return
	}
	return a+b, nil
}

defer

func main() {
	// defer按先进后出顺序执行
	func1()
}

func func1() {
	i := 1
	// 参数在执行此句时传入,形成闭包,闭包内参数在执行的时候调用,
	// 而后对i的修改回影响内部的直接引用,而影响不到参数
	defer func(a int) {
		fmt.Println("First", a, i)
	}(i)
	i = 2

	defer func() {
		fmt.Println("Second")
	}()
}

//output:
//Second
//First 1 2

panic&recover

面向对象

接口赋值

go会为struct的**func (this Obj) XXX()** 生成 func (this *Obj) XXX()方法 因为两种方法内部操作完全一样,反之则不会 并且对于对象的指针成员操作,内部会自动使用*

type Money int

// go内部会生成 func (this *Money) Less(money Money) bool方法
func (this Money) Less(money Money) bool {
	return this < money
}
// go无法生成func (this Money) Add(money Money)方法,
// 因为引用传递会改变外部的值,改为值传递无法达到相同的效果。反之可以,如上
func (this *Money) Add(money Money)  {
	*this += money
}

type LessAdder interface {
	Less(money Money) bool
	Add(money Money)
}

func main() {
	m := Money(2)

	//Go automatically handles conversion between values and pointers for method calls. You may want to use a pointer receiver type to avoid copying on method calls or to allow the method to mutate the receiving struct.
	// 调用对象方法时候,go会在value跟pointer之间自动转换
	m.Add(Money(10))
	
	// cannot use m (type Money) as type LessAdder in assignment:
	// Money does not implement LessAdder (Add method has pointer receiver)
	// var la LessAdder = m // 此处会报错,因为m对象没有Add方法,m指针才有Add方法
	var la LessAdder = &m
	
	fmt.Println(la)

	var lesser Lesser = &m
	fmt.Println(lesser)
}

接口组合

type ReadWriter interface {
	Reader
	Writer 
}
// 这个接口组合了Reader和Writer两个接口,它完全等同于如下写法:
type ReadWriter interface {
	Read(p []byte) (n int, err error) 
	Write(p []byte) (n int, err error)
}
type MyError struct {
	Msg string
}

func (this MyError) Error() string {
	return this.Msg
}

// 返回值error是interface所有可以使用&MyError返回,因为指针实现了接口方法
// 如果是struct则必须同时对象或者指针
func NewError(msg string) error {
	return &MyError{
		Msg: msg,
	}
}

var e1 MyError  // value : MyError{Msg:""}
var e2 *MyError // value : nil
// new(MyError) 等同于 &MyError{},所有字段零值
var e3 *MyError = new(MyError) 

// 类型转换
// switch 中特有写法
func reflect(obj interface{}) {
	switch obj.(type) {
	case int:
		fmt.Println("Int value")
	case float64:
		fmt.Println("Float64 value")
	case string:
		fmt.Println("string value")
	default:
		fmt.Println("Other value")
	}
}

// 普通
if i, ok := obj.(I); ok {
	fmt.Println(i.Get())
}

// 如果可以确定一个变量实现了某接口,可以使用
i := obj.(I) // 运行时可能会报错

并发

基础

Goroutine

channel

ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})

cb := make(chan string, 4)	// 创建带缓冲区的通道

// 发送接收数据:<-
ci <- 1		// 发送整数1到ci	// 如果通道被关闭,会立即返回通道类型的0值
<- ci		// 从channel接收数据
i := <- ci	// 从channel接收数据,保存到i

obj,ok := <- ch // ok,false:通道被关闭,true:通道可以读取数据
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!