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 后面的世界更精彩,一起来学吧