Hello Golang|Go主题月

399 阅读9分钟

Hello Golang

作为一个不误正业的前端程序员,在这个寒假也追了一波 Golang 的风,作为 Hello World 程序员,就一起来看一下,Golang 是如何 Hello world 的吧。

package main
import "fmt"
func main() {
   fmt.Println("Hello, World!")
}

有那味了吧,好戏才刚刚开始。

都是成熟的程序员了,装个环境不难吧,最好的学习资料就是官网了,一起来看一下 Go 语言之旅

tour.studygolang.com/welcome/1

简单看过之后,我们一起做做练习吧

Golang 都要学点啥

如何最快的从一门语言过渡到另一门语言,最好的办法就是去寻找,语法层面的不同,以及新语言的独特之处。

那让我们通过一些练习一起看一下吧。

1. 没有初始化的变量,默认值是什么?

package main

import "fmt"

// 1. 没有初始化的变量,默认值是什么?

func main() {
	// 声明一个变量并初始化
	var sampleInitStr = "wdxtub.com"
	fmt.Println(sampleInitStr)
	// wdxtub.com

	// 字符串
	var sampleStr string
	fmt.Println(sampleStr)
	// ""

	// 整型、浮点型初始化
	var sampleInt int
	fmt.Println(sampleInt)
	//0 

	var sampleFloat float32
	fmt.Println(sampleFloat)
	// 0

	// 布尔值初始化
	var sampleBool bool
	fmt.Println(sampleBool)
	// 0

	var t1 *int
	fmt.Println(t1)
	// <nil>

	var t2 []int
	fmt.Println(t2)
	// []

	var t3 map[string]int
	fmt.Println(t3)
	// map[]

	var t4 chan int
	fmt.Println(t4)
	// <nil> 管道类型

	var t5 func(string) int
	fmt.Println(t5)
	// <nil> 函数类型

	var t6 error // error 是接口
	fmt.Println(t6)
	// <nil> 接口类型 error

}

2. 能否准确给出包含 iota 的代码的执行结果?

package main

import "fmt"

// 2. 能否准确给出包含 iota 的代码的执行结果?

// 在 golang 中,一个方便的习惯就是使用 iota 标示符,它简化了常量用于增长数字的定义,给以上相同的值以准确的分类。
// 使用 - 跳过 使用赋值打断 


func main() {
	const (
		a = iota
		b
		c
		d = "wdxtub"
		e
		f
		g = 10086
		h
		i
		j = iota
		k
		l
	)
	fmt.Println(a, b, c, d, e, f, g, h, i, j, k, l)
	// 0 1 2 wdxtub wdxtub wdxtub 10086 10086 10086 9 10 11

	const (
		z = 1 << iota
		y
		x
		w = 2 << iota
		v
		u
	)
	fmt.Println(z, y, x, w, v, u)
	// 1 2 4 16 32 64 分别对应 1 << 0, 1 << 1, 1 << 2, 2 << 3, 2 << 4, 2 << 5
}

3. 如何通过 switch 语句来判断变量类型?

package main

import "fmt"

// 如何通过 switch 语句来判断变量类型?

func checkType(v interface{}) {
	switch i := v.(type) {
	case nil:
		fmt.Printf("v 的类型 :%T", i)
	case int:
		fmt.Printf("v 是 int 型")
	case float64:
		fmt.Printf("v 是 float64 型")
	case func(int) float64:
		fmt.Printf("v 是 func(int) 型")
	case bool:
		fmt.Printf("v 是 bool 型")
	case string:
		fmt.Printf("v 是 string 型")
	default:
		fmt.Printf("未知型")
	}
	fmt.Println()
}

func main() {
	v1 := 123
	v2 := "123"
	var v3 interface{}

	checkType(v1)
	// int
	checkType(v2)
	// string
	checkType(v3)
	// <nil>
}

4. 如果 switch 后没有表达式,具体执行哪个 case?

package main

import "fmt"

// 如果 switch 后没有表达式,具体执行哪个 case?
// golang 中 switch case 语句自带 break 使用 fallthrough 强制执行下面语句  
// fallthrough 不能用在 switch 的最后一个分支。
func main() {
	switch {
	case false:
		fmt.Println("1 - false")
		fallthrough
	case true:
		fmt.Println("2 - true")
		fallthrough
	case false:
		fmt.Println("3 - false")
		fallthrough
	case true:
		fmt.Println("4 - true")
	case false:
		fmt.Println("5 - false")
		fallthrough
	default:
		fmt.Println("6 - default")
	}

}

5. 你能准确给出包含 select 语句的代码执行结果吗?

package main

import (
	"fmt"
	"time"
)

// 5. 你能准确给出包含 select 语句的代码执行结果吗?

// Sender 用来发送信号
// ch <- v   将 v 发送至信道 ch
// v := <-ch 从 ch 接收值并赋予 v
func Sender(ch chan int, stopCh chan bool) {
	i := 220
	for j := 0; j < 10; j++ {
		ch <- i
		time.Sleep(time.Second)
	}
	stopCh <- true
}


// 对应代码为 5.go,从结果中可以看到,在所有满足条件的 case 中会随机选一个执行
// select 语句主要用于控制通讯,要么是发送要么是接收,如果没有条件满足,则会一直阻塞(特定情况可以利用这样的特性,但一定要小心),具体语法为:
// 每个 case 都必须是一个通信
// 所有 channel 表达式都会被求值
// 所有被发送的表达式都会被求值
// 如果任意一个通信可以进行,就会执行并忽略其他
// 如果多个 case 都可以运行,select 会随机选出一个执行

func main() {
	// 创建一个发送单个 int 的 channel
	ch := make(chan int)
	var c int
	// 创建一个发送单个 bool 的 channel,用于停止程序
	stopCh := make(chan bool)

	go Sender(ch, stopCh)

	for {
		select {
		case c = <-ch: // 从 ch 中读取
			fmt.Println("Receiver 1", c)
		case s := <-ch: // 从 ch 中读取
			fmt.Println("Receiver 2", s)
		case t := <-ch: // 从 ch 中读取
			fmt.Println("Receiver 3", t)
		case _ = <-stopCh:
			// 代码域
			goto end
		}
	}
end:
}

6. 你能准确给出包含 select 语句的代码执行结果吗?

package main

import (
	"fmt"
)
// 6. 你能准确给出包含 select 语句的代码执行结果吗?(powered by jiasen)
func main() {
	// 创建一个发送单个 int 的 channel
	// ch <- v   将 v 发送至信道 ch
	// v := <-ch 从 ch 接收值并赋予 v
	ch := make(chan int, 1)
	for i := 0; i < 10; i++ {
		select {
		case x := <-ch:
			fmt.Println(x)
		case ch <- i:
		}
	}
}

7. 传引用和引用类型一样吗?你能说出下面代码的执行结果吗?

package main

import (
	"fmt"
)

// 7. 传引用和引用类型一样吗?你能说出下面代码的执行结果吗?

// 第一种交换方式
func swap1(x, y int) {
	x, y = y, x
	fmt.Println("swap1 函数交换", x, y)
}

// 第二种交换方式
func swap2(x, y *int) {
	*x, *y = *y, *x
	fmt.Println("swap1 函数交换", *x, *y)
}

// 修改 map
func modifyMap(item map[string]int) {
	item["a"] = 314
	fmt.Println("modifyMap 函数修改", item)
}

// 修改 切片
func modifySlice(item []int) {
	item[0] = 314
	fmt.Println("modifySlice 函数修改", item)
}

// 修改 数组
func modifyArray(item [3]int) {
	item[0] = 314
	fmt.Println("modifyArray 函数修改", item)
}

// 修改数组指针
func modifyArrayPtr(item *[3]int) {
	item[0] = 314
	fmt.Println("modifyArrayPtr 函数修改", *item)
}

// 切片与字典为指针
func main() {
	x := 314
	y := 220
	fmt.Println("x y 交换前", x, y)
	// 314 220
	swap1(x, y)
	fmt.Println("x y swap1 交换后", x, y)
	// 314 220
	swap2(&x, &y)
	fmt.Println("x y swap2 交换后", x, y)
	// 220 314 

	arraySample := [3]int{1, 2, 3}
	fmt.Println("arraySample 修改前", arraySample)
	// 1 2 3
	modifyArray(arraySample)
	fmt.Println("arraySample 修改后", arraySample)
	// 1 2 3
	modifyArrayPtr(&arraySample)
	fmt.Println("modifyArrayPtr 修改后", arraySample)
	// 314 2 3
	sliceSample := []int{1, 2, 3}
	fmt.Println("sliceSample 修改前", sliceSample)
	// 1 2 3
	modifySlice(sliceSample)
	fmt.Println("sliceSample 修改后", sliceSample)
	// 314 2 3
	mapSample := map[string]int{"a": 1, "b": 2, "c": 3}
	fmt.Println("mapSample 修改前", mapSample)
	// "a":1 "b":2 "c":3
	modifyMap(mapSample)
	fmt.Println("mapSample 修改后", mapSample)
	// "a":314 "b":2 "c":3
}

8. 如何通过传入函数实现回调?

package main

import "fmt"

// 8. 声明一个函数类型,用做回调,这里用于输出统一的日志信息
type callback func(string) string

// 如何通过传入函数实现回调?

func main() {
	// 传入函数作为回调
	testCallback("hello", callbackSample)
	// 使用匿名函数作为回调
	testCallback("world", func(str string) string {
		fmt.Println("[CALLBACK]", str)
		return "[CALLBACK]" + str
	})
}

// 外层函数内部调用传入函数
func testCallback(str string, f callback) {
	f(str)
}

// 这个函数需要和之前声明的一样
func callbackSample(str string) string {
	fmt.Println("[CALLBACK]", str)
	return "[CALLBACK]" + str
}

9. 你能准确说出以下包含闭包的函数执行结果吗?

package main

import (
	"fmt"
	"runtime"
	"time"
)


// 9. 你能准确说出以下包含闭包的函数执行结果吗?

// 简易计数器
func simpleCounter() func() int {
	i := 0
	return func() int {
		i++
		return i
	}
}


// 第一题
func question1() {
	counter := simpleCounter()
	fmt.Println(counter())
	// 0
	fmt.Println(counter())
	// 1
	fmt.Println(counter())
	// 2

	counter = simpleCounter()
	fmt.Println(counter())
	// 1
	fmt.Println(counter())
	// 2
	fmt.Println(counter())
	// 3
}

func calc(base int) (func(int) int, func(int) int) {
	add := func(i int) int {
		fmt.Printf("%p\n", &base) // 这里可以看到都是同一个地址
		base += i
		return base
	}

	sub := func(i int) int {
		fmt.Printf("%p\n", &base) // 这里可以看到都是同一个地址
		base -= i
		return base
	}
	return add, sub
}

// 第二题
func question2() {
	f1, f2 := calc(100)

	fmt.Println(f1(1), f2(2))
	// 101 99
	fmt.Println(f1(3), f2(4))
	// 102 98
	fmt.Println(f1(5), f2(6))
	// 103 97
	fmt.Println(f1(7), f2(8))
	// 104 6

	// 实际上是 100+1-2+3-4+5-6
}

// 第三题
func question3() {
	runtime.GOMAXPROCS(1) // 设置最大可同时使用的 CPU 核数为 1

	for i := 0; i < 10; i++ {
		go func() {
			fmt.Println("3.1:", i)
		}()
	}
	time.Sleep(3 * time.Second)

	for i := 0; i < 10; i++ {
		go func(i int) {
			fmt.Println("3.2:", i)
		}(i)
	}
	time.Sleep(3 * time.Second)
}

func main() {
	// 总共有多道题目,每个题目封装到一个函数中
	question1()
	question2()
	question3()
}
No information from extensions available. Find extensions in the Sourcegraph extension registry

10. 你能说出切片的扩容逻辑吗?

package main

import "fmt"

// 10. 你能说出切片的扩容逻辑吗?
// 通过 append 扩容的时候,指针会发生变化,原切片不受影响。在 1024 大小之前每次扩容会翻倍,之后会按照 1.25 倍扩容。
func main() {
	fmt.Println("深浅拷贝")
	sliceA := []int{1, 2}
	sliceB := make([]int, 2) // 空的切片
	sliceC := sliceA
	fmt.Printf("slice A %p\n", sliceA)
	fmt.Printf("slice B %p\n", sliceB)
	fmt.Printf("slice C %p\n", sliceC)

	fmt.Println("切片扩容地址测试")
	slice1 := []int{2}
	fmt.Printf("[slice1] ptr:%p, content:%o, len:%d, cap:%d\n", &slice1, slice1, len(slice1), cap(slice1))
	slice2 := append(slice1, 3)
	fmt.Printf("[slice2] ptr:%p, content:%o, len:%d, cap:%d\n", &slice2, slice2, len(slice2), cap(slice2))
	fmt.Printf("[slice1] ptr:%p, content:%o, len:%d, cap:%d\n", &slice1, slice1, len(slice1), cap(slice1))

	fmt.Println("切片扩容容量变化测试")
	sliceZero := []int{1}
	length, capicity := len(sliceZero), cap(sliceZero)
	fmt.Printf("[sliceZero] ptr:%p, content:%o, len:%d, cap:%d\n", *&sliceZero, sliceZero, length, capicity)
	for i := 0; i < 4098; i++ {
		sliceZero = append(sliceZero, i)
		if capicity != cap(sliceZero) {
			length = len(sliceZero)
			capicity = cap(sliceZero)
			// 这里打印的是 sliceZero 指针所指向的地址,应该在不断变化
			fmt.Printf("[sliceZero] ptr:%p, len:%d, cap:%d\n", *&sliceZero, length, capicity)
		}
	}

}

11. 你能说出上面类型转换的最终结果吗?

package main

import "fmt"

// 11. 你能说出上面类型转换的最终结果吗?
// 答案是 17 17 17,并没有四舍五入
func main() {
	oneInt := 17
	oneFloat := 17.2

	int2Float := float32(oneInt)
	// 17
	float2Int := int(oneFloat)
	// 17

	fmt.Println(int2Float)
	fmt.Println(float2Int)
	oneFloat = 17.6
	float2Int = int(oneFloat)
	// 17
	fmt.Println(float2Int)
}

12. 你能给出下面并发代码的执行结果吗?

package main

import (
	"fmt"
	// "time"
)
// 12. 你能给出下面并发代码的执行结果吗?
// 答案是什么都不输出,因为需主进程在 goroutine 执行之前就结束了,所以我们一般会通过 channel 来控制并发!
func gossip(s string) {
	fmt.Println("[Gossip]", s)
}

func main() {
	go gossip("wdxtub")

	// 如果想要看到结果,需要让主进程等待
	// time.Sleep(100 * time.Millisecond)
}

13. 给你一个数组,你能通过 channel 用 2 个 goroutine 完成求和计算吗?

package main

import "fmt"

// 13. 给你一个数组,你能通过 channel 用 2 个 goroutine 完成求和计算吗?
// 我们可以利用两个 goroutine 分别计算前半和后半部分,并最终加起来
func splitSum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	fmt.Println("split sum", sum)
	c <- sum
}

func main() {
	numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	c := make(chan int)
	gap := len(numbers) / 2

	go splitSum(numbers[gap:], c)
	go splitSum(numbers[:gap], c)
	part1, part2 := <-c, <-c // 从同道中接收

	fmt.Println("Total Sum", part1+part2)

}

14. 你能通过 range 和 channel 打印出斐波那契数列吗?

package main

import "fmt"
// 14. 你能通过 range 和 channel 打印出斐波那契数列吗?
func channelFibo(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, 15)
	go channelFibo(cap(c), c)

	for result := range c {
		fmt.Println(result)
	}
}

总结

相信这十四道题已经让你成为一个合格的 Helloworld 工程师了,golang 后面的世界更精彩,一起来学吧