GO语言快速入门

简介

特性

强类型、编译型、并发型,并具有垃圾回收功能的编程语言。

运行方式

直接运行:
go run helloworld.go

编译后再运行:

$ go build helloworld.go

$ ./helloworld
Hello,world

语法

  • “{”必须和func同一行
  • 首字母大写的是可以由package外部访问的;首字母小写的只能在package内部访问

数据类型

类型名称长度零值说明
bool布尔类型1false其值不为真即为假,不可以用数字代表true或false
byte字节型10uint8别名
rune字符类型40专用于存储unicode编码,等价于uint32
int, uint整型4或8032位或64位
int8, uint8整型10-128 ~ 127, 0 ~ 255
int16, uint16整型20-32768 ~ 32767, 0 ~ 65535
int32, uint32整型40-21亿 ~ 21 亿, 0 ~ 42 亿
int64, uint64整型80
float32浮点型40 小数位精确到7位
float64浮点型80小数位精确到15位
complex64复数类型8
complex128复数类型16
uintptr整型4或8⾜以存储指针的uint32或uint64整数
string字符串“”utf-8字符串

变量声明

第一种,指定变量类型,声明后若不赋值,使用默认值。

var v_name v_type
v_name = value

第二种,根据值自行判定变量类型。

var v_name = value

第三种,省略var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误。

v_name := value

// 例如
var a int = 10
var b = 10
c := 10
多变量声明
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3

a, b, c := 5, 7, "abc"

如果你在定义变量 a 之前使用它,则会得到编译错误 undefined: a。
如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误

如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a,两个变量的类型必须是相同。
空白标识符 _ 也被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。

常量

const identifier [type] = value
iota

iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

const (
    a = iota // 0
    b			// 1
    c			// 2
)
const (
    i=1<<iota // 1
    j=3<<iota // 3 * 2 =6
    k			 // 3 * 4 = 12
    l			 // 3 * 8 = 24
)

运算符

  • 只有后自增和后自减 a++

流程控制

if

// if 后不用括号
if a < 20 {
   fmt.Printf("a 小于 20\n" )
}

switch

// case后不用break。自动break 如果要继续下一个case则需要fallthrough
switch marks {
  case 90: grade = "A"
  case 80: grade = "B"
  case 50,60,70 : grade = "C"
  default: grade = "D"  
}

switch {
  case grade == "A" :
     fmt.Printf("优秀!\n" )     
  case grade == "B", grade == "C" :
     fmt.Printf("良好\n" )      
  case grade == "D" :
     fmt.Printf("及格\n" )      
  case grade == "F":
     fmt.Printf("不及格\n" )
  default:
     fmt.Printf("差\n" );
}

//支持多条件匹配
switch a {
    case 1,2,3,4:
    default:
}

循环

// 类似c的for
for init; condition; post { }
// 类似c的while
for condition { }
// range 对slice map array
for key, value := range oldMap {
    newMap[key] = value
}

函数

func function_name( [parameter list] ) [return_types] {
   函数体
}

函数可以作为值来传递

func (name MyStruct) imp() string{
    print("这是实现方法的写法")
}

func sum(x int,y int) int{
    print("这是正常写法")
}
传递方式

和其他语言不同的是,go语言在将数组名作为函数参数的时候,参数传递即是对数组的复制。在形参中对数组元素的修改都不会影响到数组元素原来的值。

在使用slice作为函数参数时,本质也是传值,但由于slice由指针、长度、容量组成,因此也可以看成传递一个地址拷贝,即将底层数组的内存地址复制给参数slice。这时,对slice元素的操作就是对底层数组元素的操作,但slice本身不改变,若在函数内append,底层数组改变,但传入的参数的外部的slice长度不变。

切片传递的时候,等效于从原始切片中再切了一次。原始切片slice和参数s切片的底层数组是一样的。因此修改函数内的切片,也就修改了数组。如果在函数内,append操作超过了原始切片的容量,将会有一个新建底层数组的过程,那么此时再修改函数返回切片,应该不会再影响原始切片。

slice或者array作为函数参数传递的时候,本质是传值而不是传引用。传值的过程复制一个新的切片,这个切片也指向原始变量的底层数组。(个人感觉称之为传切片可能比传值的表述更准确)。函数中无论是直接修改切片,还是append创建新的切片,都是基于共享切片底层数组的情况作为基础。也就是最外面的原始切片是否改变,取决于函数内的操作和切片本身容量。

因此要传引用的话还是需要用指针

func main() {
    slice := make([]int, 2, 2)
    for i := 0; i < len(slice); i++ {
        slice[i] = i
    }

    fmt.Printf("slice %v %p \n", slice, &slice)

    ret := changeSlice(slice)
    fmt.Printf("slice %v %p, ret %v \n", slice, &slice, ret)

    ret[1] = -1111

    fmt.Printf("slice %v %p, ret %v \n", slice, &slice, ret)
}

func changeSlice(s []int) []int {
    fmt.Printf("func s %v %p \n", s, &s)
    s[0] = -1
    s = append(s, 3)
    s[1] =  1111
    return s
}

//输出
slice [0 1] 0xc42000a1a0 
func s [0 1] 0xc42000a200 
slice [-1 1] 0xc42000a1a0, ret [-1 1111 3] 
slice [-1 1] 0xc42000a1a0, ret [-1 -1111 3]

append之前操纵的是同一个数组

匿名函数

Go 语言支持匿名函数,可作为闭包。匿名函数是一个"内联"语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。

数组

数组是值类型

在 Go 中数组是值类型而不是引用类型。这意味着当数组变量被赋值时,将会获得原数组(译者注:也就是等号右面的数组)的拷贝。新数组中元素的改变不会影响原数组中元素的值。

var variable_name [SIZE] variable_type
var balance [10] float32


// 初始化数组中 {} 中的元素个数不能大于 [] 中的数字。
// 如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

// 多维数组
var threedim [5][10][4]int

指针

// 当一个指针被定义后没有分配到任何变量时,它的值为 nil。
var ip *int  

结构体

type Books struct {
   title string
   author string
   subject string
   book_id int
}

func main() {

    // 创建一个新的结构体
    fmt.Println(Books{"Go 语言", "www.runoob.com", "Go 语言教程", 6495407})

    // 也可以使用 key => value 格式
    fmt.Println(Books{title: "Go 语言", author: "www.runoob.com", subject: "Go 语言教程", book_id: 6495407})

    // 忽略的字段为 0 或 空
   fmt.Println(Books{title: "Go 语言", author: "www.runoob.com"})
}

使用结构体指针访问结构体成员,也使用 “.” 操作符

Slice

切片本身不包含任何数据。它仅仅是底层数组的一个上层表示。对切片进行的任何修改都将反映在底层数组中。

当若干个切片共享同一个底层数组时,对每一个切片的修改都会反映在底层数组中。

// 可以声明一个未指定大小的数组来定义切片
var identifier []type
// 或使用make()函数来创建切片 capacity是可选参数
make([]T, length, capacity)

初始化

s :=[] int {1,2,3 } // cap=len=3 创建了一个长度为 3 的 int 数组,并返回一个切片给 c。
s := arr[startIndex:endIndex]  // s是arr的引用 缺省startIndex时将表示从arr的第一个元素开始,缺省endIndex时将表示一直到arr的最后一个元素

len() cap()

func main() {
   var numbers = make([]int,3,5)

   printSlice(numbers)
}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

append() 和 copy()

numbers = append(numbers, 2,3,4)
/* 拷贝 numbers 的内容到 numbers1 */
copy(numbers1,numbers)

当新元素通过调用 append 函数追加到切片末尾时,如果超出了容量,append 内部会创建一个新的数组(没超的话直接改原来的)。并将原有数组的元素被拷贝给这个新的数组,最后返回建立在这个新数组上的切片。这个新切片的容量是旧切片的二倍(译者注:当超出切片的容量时,append 将会在其内部创建新的数组,该数组的大小是原切片容量的 2 倍。最后 append 返回这个数组的全切片,即从 0 到 length - 1 的切片)

切片保留对底层数组的引用。只要切片存在于内存中,数组就不能被垃圾回收。这在内存管理方便可能是值得关注的。假设我们有一个非常大的数组,而我们只需要处理它的一小部分,为此我们创建这个数组的一个切片,并处理这个切片。这里要注意的事情是,数组仍然存在于内存中,因为切片正在引用它。

解决该问题的一个方法是使用 copy 函数 func copy(dst, src []T) int 来创建该切片的一个拷贝。这样我们就可以使用这个新的切片,原来的数组可以被垃圾回收。

range

用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素

nums := []int {2, 3, 4}
for i, num := range nums {
    if num == 3 {
        fmt.Println("index:", i)
    }
}
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
    fmt.Printf("%s -> %s\n", k, v)
}
for i, c := range "go" {
    fmt.Println(i, c)
}

map

var countryCapitalMap map[string]string /*创建集合 */
countryCapitalMap = make(map[string]string)
captial, ok := countryCapitalMap [ "美国" ]
delete(countryCapitalMap, "France") // 参数为map和key

类型转换

var sum int = 17
var count int = 5
var mean float32
   
mean = float32(sum)/float32(count)
fmt.Printf("mean 的值为: %f\n",mean) // 3.400000

接口

  • 接⼝命名习惯以 er 结尾
  • 接口只有方法声明,没有实现,没有数据字段
  • 如果一个类型实现了一个接口要求的所有方法,那么该类型实现了这个接口,无须声明实现哪个接口。
  • 接口的赋值规则:仅当一个表达式实现了一个接口时,这个表达式才可以赋值给该接口
  • 空接口(interface{})不包含任何的方法,正因为如此,所有的类型都实现了空接口
type Phone interface {
    call()
}

type NokiaPhone struct {
}

// 方法
func (nokiaPhone NokiaPhone) call() {
    fmt.Println("I am Nokia, I can call you!")
}

type IPhone struct {
}

func (iPhone IPhone) call() {
    fmt.Println("I am iPhone, I can call you!")
}

func main() {
    var phone Phone

    phone = new(NokiaPhone)
    phone.call()

    phone = new(IPhone)
    phone.call()

}

类型断言

把对象赋给接口之后,若要得到它原来的类型,可以用类型断言。类型断言是一个作用于接口对象上的操作,写出来类似 x.(T),其中x是接口对象,而T是一个类型。

var v1 interface{} = 1     
value, ok := v1.(int) // 1 true

错误处理

使用errors.New 可返回一个错误信息

func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return 0, errors.New("math: square root of negative number")
    }
    // 实现
}

result, err:= Sqrt(-1)

if err != nil {
   fmt.Println(err)
}

并发

原理

Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。

func say(s string) {
    for i := 0; i < 5; i++ {
            time.Sleep(100 * time.Millisecond)
            fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
}
等待

sync.waitGroup :等待所有goroutine执行完成,并且阻塞主线程的执行,直到所有的goroutine执行完成

GOMAXPROCS

调用 runtime.GOMAXPROCS() 用来设置可以并行计算的CPU核数的最大值,并返回之前的值。

通道(channel)

是用来传递数据的一个数据结构。从设计上确保,在同一时刻只有一个 goroutine 能从中接收或放入数据。发送和接收都是原子操作,不会中断。

通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。

用途:

  • 使用通道来同步goroutine
  • 使用异步管道来保护临界资源(令牌 谁拿到了才能用,使用完退回管道)
  • 用管道来发送事件
同步通道

缓冲长度为0的channel称为同步管道,可以用来同步两个routine

  • 发送操作被阻塞,直到接收端准备好接收
  • 接收操作被阻塞,直到发送端准备好发送
异步通道

缓冲长度大于0的channel称为异步管道。
异步 channel,就是给 channel 设定个 buffer 值

  • 在 buffer 未填满的情况下 ,不阻塞发送操作。
  • 在buffer 未读完前,不阻塞接收操作。
// 声明通道
ch := make(chan int)

var ch1 chan int       // ch1是一个正常的channel,不是单向的
var ch2 chan<- float64 // ch2是单向channel,只用于写float64数据
var ch3 <-chan int     // ch3是单向channel,只用于读取int数据


// 通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小

ch := make(chan int, 2)

// 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
// 而不用立刻需要去同步读取数据
ch <- 1
ch <- 2

// 获取这两个数据
fmt.Println(<-ch)
fmt.Println(<-ch)

func fibonacci(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
            c <- x
            x, y = y, x+y
    }
    close(c)
}

func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    // range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
    // 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
    // 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
    // 会结束,从而在接收第 11 个数据的时候就阻塞了。
    for i := range c {
            fmt.Println(i)
    }
}
select

每个case语句里必须是一个IO操作

 select {    
	case <-chan1:        
		// 如果chan1成功读到数据,则进行该case处理语句    
	case chan2 <- 1:        
		// 如果成功向chan2写入数据,则进行该case处理语句    
	default:        
		// 如果上面都没有成功,则进入default处理流程    
	}

defer

defer后面的函数在defer语句所在的函数执行结束的时候会被调用;

defer语句:一个普通的函数或方法调用,在调用前加上关键字 defer
函数和参数表达式会在语句执行时求值,但是无论在正确情况(如执行return语句等)还是非正常情况(如宕机),实际的调用推迟到包含defer语句的函数结束后才执行。
defer语句经常使用于成对的操作,比如打开和关闭、连接和断开、加锁和解锁,即使是再复杂的控制流、资源在任何情况下都能正确释放
如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行。

标准库

文档

  • 输入输出。这个分类包括二进制以及文本格式在屏幕、键盘、文件以及其他设备上的输
    入输出等,比如二进制文件的读写。对应于此分类的包有bufio、 fmt、 io、 log和flag
    等,其中 flag 用于处理命令行参数。
  • 文本处理。这个分类包括字符串和文本内容的处理,比如字符编码转换等。对应于此分
    类的包有encoding、 bytes、 strings、 strconv、 text、 mime、 unicode、 regexp、
    index和path等。其中path用于处理路径字符串。
  • 网络。这个分类包括开发网络程序所需要的包,比如Socket编程和网站开发等。对应于此
    分类的包有: net、 http和expvar等。
  • 系统。这个分类包含对系统功能的封装,比如对操作系统的交互以及原子性操作等。对
    应于此分类的包有os、 syscall、 sync、 time和unsafe等。
  • 数据结构与算法。对应于此分类的包有math、 sort、 container、 crypto、 hash、
    archive、 compress和image等。因为image包里提供的图像编解码都是算法,所以也
    归入此类。
  • 运行时。对应于此分类的包有: runtime、 reflect和go等。

面向对象

方法

func后面加一个参数表示方法
Go可以将方法绑定到任何类型上,可以很方便的为简单的类型(如 int等)定义附加的方法
与普通函数声明类似,只是在函数名字前面多了一个参数,该参数把方法绑定到这个参数对应的类型上

type Point struct { X, Y float64}
func Distance (p, q Point) float64 { /* 普通函数 */
	return math.Hypot(q.X-p.X,q.Y-p.Y)
}
func (p Point) Distance (q Point) float64 { /*Point 类型的方法*/
	return math.Hypot(q.X-p.X,q.Y-p.Y)
}

p称为方法的接收者(可以是类型的值或是指针),接收者不使用特殊名(如this),而可以选择接收者名字

用指针接收者

func (p *Point) ScaleBy (factor float64) {
	p.X *= factor
	p.Y *= factor
}

习惯上遵循如果Point的任何一个方法使用指针接收者,那么所有的Point方法都应该使用指针接收者,用指针的调用方法:

p := Point{1, 2}
(&p).ScaleBy(2)
p.ScaleBy(2)  //编译器会对变量进行 &p 的隐式转换,只有变量才允许这么做
// 如下面调用是错误的 
Point{1, 2}.ScaleBy(2)

不允许本身是指针的类型进行方法声明
没有指向指针的用法

方法变量

一个函数,把方法绑定到一个接收者上,函数只需要提供实参而不需要提供接收者就能调用

p := Point{1, 2}
q := Point{2, 4}
distanceFromP := p.Distance //distanceFromoP 就叫做方法变量
distanceFromP(q)
方法表达式

T.f 或者(*T).f,其中T是类型
是一种函数变量
把原来方法的接收者替换为函数的第一个形参
distance := Point.Distance //方法表达式
distance(p, q)

只支持封装,不支持继承和多态,go中用面向接口来做继承和多态的任务
go中没有class ,只有struct

不支持重载
构造函数用工厂方法

反射

在编译时不知道类型的情况下,更新变量、在运行时查看值、调用方法以及直接对它们的布局进行操作的机制,叫做反射

反射功能由reflect包提供,定义了两个重要的类型

  • Type

    • 有很多方法的接口,这些方法可以用来识别类型以及透视类型的组成部分。
    • 接口只有一个实现,即类型描述符,接口值中的动态类型也是类型描述符。
    • reflect.TypeOf函数接收任何的interface{}参数,并把接口中的动态类型以reflect.Type形式返回。
    t := reflect.TypeOf(3)
    fmt.Println(t)  // “int”
    
  • Value

    包含一个任意类型的值
    ValueOf函数接受任意的interface{}参数,并把接口的动态值以reflect.Value的形式返回。

stu := student{Name:"zhangsan", Age:25} 
t := reflect.TypeOf(stu) //获取对象的类型名称fmt.Println("class name:", t.Name()) 

//获取stu对象的成员名称和值 
v := reflect.ValueOf(stu)
for i := 0; i < t.NumField();i++{ 
    field := t.Field(i)
    fmt.Println(field.Name, "type:", field.Type) 
    fmt.Println(field.Name, "value:", v.Field(i)) 
} 
//获取stu的方法
for i := 0; i < t.NumMethod(); i++ {
    f := t.Method(i) 
    fmt.Println(f.Name, f.Type) 
} 

//设置stu的age值为100
v := reflect.ValueOf(&stu)
v = v.Elem()
field := v.FieldByName("Age") 
field.SetUint(100) 

//调用stu的setName方法
v = reflect.ValueOf(&stu)
m := v.MethodByName("SetName")
args := []reflect.Value{reflect.ValueOf("liSi")}
m.Call(args)
fmt.Println(stu) 

其他

sync.WaitGroup

简单使用就是在创建一个任务的时候wg.Add(1), 任务完成的时候使用wg.Done()来将任务减一。使用wg.Wait()来阻塞等待所有任务完成。

注意点:传递给函数时要用地址传

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值