基础入门
1.学习方式
第一种,本地项目学习 如何使用 go 命令来获取、构建并安装包、命令及运行测试
第二种,在线学习工具使用。 第一部分只是简单欢迎引导。
2.欢迎使用Go指南
欢迎来到Go编程语言指南。本指南涵盖了该语言的大部分重要特性,主要包括:
欢迎!
学习如何使用本指南:包括如何在不同的课程之间切换,以及如何运行代码。
复制代码
基础
一开始,将学习关于语言的所有基础内容。
定义变量,调用函数,以及在您学习下一课之前所需要了解的全部内容。
包,变量和函数。
学习Go程序的基本组件。
复制代码
流程控制语句:for,if,else和switch
学习如何用条件,循环和开关语句控制代码的流程。
复制代码
复杂类型:struct,slice和map。
学习如何基于现有类型定义新的类型:本课涵盖了结构体,分布,切片和地图。
复制代码
方法和接口
学习如何为类型定义方法;如何定义接口;以及如何将所有内容互换起来。
方法和接口
本课包含了方法和接口,可以用它们来定义对象和其行为。
复制代码
并发
作为语言的核心部分,转到提供了并发的特性。
这一部分概述了goroutein和通道,以及如何使用它们来实现不同的并发模式。
并发
Go将并发为语言的核心构成。本课将进行进行介绍,并提供一些示例来展示如何使用它们。
复制代码
一.开始学习- 包、变量、函数
/*fmt标准库是我们在学习Go语言过程中接触最早最多使用的库,
#fmt包实现了类似C语言printf和scanf的格式化I/O。主要分为向外输出内容和获取输入内容两大部分
*/
import (
"fmt"
"math/rand" # math/rand 额外导入的包
)
#
/*fmt向外输出相关函数
Print系列函数会将内容输出到系统的标准输出
*/
fmt.Print //Print函数直接输出内容
fmt.Fprint //Printf函数支持格式化输出字符串
fmt.Println //Println函数会在输出内容的结尾添加一个换行符
fmt.Sprint
fmt.Errorf
fmt.Scan
fmt.Scanf
fmt.Scanln
// 这里比较全:https://www.cnblogs.com/wanghui-garcia/p/10326395.html
复制代码
1.导出名
用首字母大小写来区分公开,私有函数?
2.函数
- 参考 这篇关于 Go 语法定义的文章了解类型以这种形式出现的原因。
- 当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他都可以省略。
3.多值返回
- 函数可以返回任意数量的返回值。 有点意思,这些不是难点
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}
复制代码
4.命名返回值
?
5.变量
var 语句定义了一个变量的列表;跟函数的参数列表一样,类型在后面。
就像在这个例子中看到的一样,var
语句可以定义在包或函数级别。
6.初始化变量
变量定义可以包含初始值,每个变量对应一个。
如果初始化是使用表达式,则可以省略类型;变量从初始值中获得类型。
7.短声明变量
在函数中,:=
简洁赋值语句在明确类型的地方,可以用于替代 var 定义。
函数外的每个语句都必须以关键字开始(var
、func
、等等),:=
结构不能使用在函数外。
func main() {
var i, j int = 1, 2
k := 3
c, python, java := true, false, "no!"
fmt.Println(i, j, k, c, python, java)
}
复制代码
8.基本类型
Go 的基本类型有Basic types
bool
string
int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的别名
rune // int32 的别名 // 代表一个Unicode码
float32 float64
complex64 complex128 这个例子演示了具有不同类型的变量。 同时与导入语句一样,变量的定义“打包”在一个语法块中。
9.零值
变量在定义时没有明确的初始化时会赋值为_零值_。
零值是:
数值类型为 0
,
布尔类型为 false
,
字符串为 ""
(空字符串)。
func main() {
var i int
var f float64
var b bool
var s string
fmt.Printf("%v %v %v %q\n", i, f, b, s)
}
复制代码
10.类型转换
表达式 T(v) 将值 v 转换为类型 T
。
一些关于数值的转换:
var i int = 42 var f float64 = float64(i) var u uint = uint(f) 或者,更加简单的形式:
i := 42 f := float64(i) u := uint(f) 与 C 不同的是 Go 的在不同类型之间的项目赋值时需要显式转换。 试着移除例子中 float64 或 int 的转换看看会发生什么。
func main() {
var x, y int = 3, 4
var f float64 = math.Sqrt(float64(x*x + y*y))
var z int = int(f)
fmt.Println(x, y, z)
}
复制代码
11.类型推导
在定义一个变量但不指定其类型时(使用没有类型的 var 或 := 语句), 变量的类型由右值推导得出。
当右值定义了类型时,新变量的类型与其相同:
var i int
j := i // j 也是一个 int
但是当右边包含了未指名类型的数字常量时,新的变量就可能是 int 、 float64 或 complex128
。 这取决于常量的精度:
i := 42 // int f := 3.142 // float64 g := 0.867 + 0.5i // complex128 尝试修改演示代码中 v 的初始值,并观察这是如何影响其类型的。
func main() {
v := 42 // change me!
fmt.Printf("v is of type %T\n", v)
}
复制代码
12.常量
常量的定义与变量类似,只不过使用 const 关键字。
常量可以是字符、字符串、布尔或数字类型的值。
常量不能使用 := 语法定义。
const Pi = 3.14
func main() {
const World = "世界"
fmt.Println("Hello", World)
fmt.Println("Happy", Pi, "Day")
const Truth = true
fmt.Println("Go rules?", Truth)
}
复制代码
13.数值常量
数值常量是高精度的 值。
一个未指定类型的常量由上下文来决定其类型。
也尝试一下输出 needInt(Big) 吧。
package main
import "fmt"
const (
Big = 1 << 100
Small = Big >> 99
)
func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
return x * 0.1
}
func main() {
fmt.Println(needInt(Small))
fmt.Println(needFloat(Small))
fmt.Println(needFloat(Big))
}
复制代码
二.渐入佳境-流控语句
1.for
Go 只有一种循环结构——for
循环。
基本的 for 循环除了没有了 ( )
之外(甚至强制不能使用它们),看起来跟 C 或者 Java 中做的一样,而 { }
是必须的。
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
}
复制代码
跟 C 或者 Java 中一样,可以让前置、后置语句为空。
func main() {
sum := 1
for ; sum < 1000; {
sum += sum
}
fmt.Println(sum)
}
复制代码
2.for 是 Go 的 “while”
基于此可以省略分号:C 的 while 在 Go 中叫做 for
。
func main() {
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}
复制代码
3.死循环
如果省略了循环条件,循环就不会结束,因此可以用更简洁地形式表达死循环。
func main() {
for {
}
}
复制代码
4.if
if 语句除了没有了 ( )
之外(甚至强制不能使用它们),看起来跟 C 或者 Java 中的一样,而 { }
是必须的。
(耳熟吗?)
func sqrt(x float64) string {
if x < 0 {
return sqrt(-x) + "i"
}
return fmt.Sprint(math.Sqrt(x))
}
func main() {
fmt.Println(sqrt(2), sqrt(-4))
}
复制代码
5.if 的便捷语句
跟 for 一样,if
语句可以在条件之前执行一个简单的语句。
由这个语句定义的变量的作用域仅在 if 范围之内。
(在最后的 return 语句处使用 v 看看。)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
复制代码
6.if 和 else
在 if 的便捷语句定义的变量同样可以在任何对应的 else 块中使用。
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
// 这里开始就不能使用 v 了
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
复制代码
练习:循环和函数
func Sqrt(x float64) float64 {
}
func main() {
fmt.Println(Sqrt(2))
}
复制代码
7.switch
一个结构体(struct
)就是一个字段的集合。
除非以 fallthrough 语句结束,否则分支会自动终止。
复制代码
func main() { fmt.Print("Go runs on ") switch os := runtime.GOOS; os { case "darwin": fmt.Println("OS X.") case "linux": fmt.Println("Linux.") default: // freebsd, openbsd, // plan9, windows... fmt.Printf("%s.", os) } }
switch 的执行顺序
switch 的条件从上到下的执行,当匹配成功的时候停止。
(例如,
switch i {
case 0:
case f():
}
复制代码
当 i==0 时不会调用 f
。)
注意:Go playground 中的时间总是从 2009-11-10 23:00:00 UTC 开始, 如何校验这个值作为一个练习留给读者完成。
func main() {
fmt.Println("When's Saturday?")
today := time.Now().Weekday()
switch time.Saturday {
case today + 0:
fmt.Println("Today.")
case today + 1:
fmt.Println("Tomorrow.")
case today + 2:
fmt.Println("In two days.")
default:
fmt.Println("Too far away.")
}
}
复制代码
没有条件的 switch
没有条件的 switch 同 switch true
一样。
这一构造使得可以用更清晰的形式来编写长的 if-then-else 链。
func main() {
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
}
复制代码
8.defer
defer 语句会延迟函数的执行直到上层函数返回。 延迟调用的参数会立刻生成,但是在上层函数返回前函数都不会被调用。
func main() {
defer fmt.Println("world") // 上层函数也就是main函数了,这和js的defer执行顺序很相似
fmt.Println("hello")
}
# 执行结果
hello
world
复制代码
defer 栈
延迟的函数调用被压入一个栈中。当函数返回时, 会按照后进先出的顺序调用被延迟的函数调用。
阅读博文了解更多关于 defer 语句的信息。
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
// 这个settimeout的做法基本类似
复制代码
9.指针
为什么要有指针? 指针可以提高性能,指针可以节省复制的开销,但同时要考虑解引用和垃圾回收带来的影响。 www.jianshu.com/p/7e8ea1b83…
指针的好处:
1.函数的值传递,无法通过调用函数,来修改函数的实参。
2.被调用函数需要提供更多的“返回值”,给调用函数。
3.指针能极大的提高效率(共享内存数据)。 blog.csdn.net/zengzisuzi/…
Go 具有指针。 指针保存了变量的内存地址。
类型 *T 是指向类型 T 的值的指针。其零值是 `nil`。
var p *int
& 符号会生成一个指向其作用对象的指针。
i := 42
p = &i
* 符号表示指针指向的底层的值。
fmt.Println(*p) // 通过指针 p 读取 i
*p = 21 // 通过指针 p 设置 i
这也就是通常所说的“间接引用”或“非直接引用”。
复制代码
与 C 不同,Go 没有指针运算。“Go语言里面的指针和C++指针一样,都是指向某块内存的地址值,可以解引用,不同只是在于C++里可以直接对指针做算术运算而Go里面不行。”
而 C#、java这些高级语言是没有指针的。
10.结构体
一个结构体(struct
)就是一个字段的集合。
(而 type 的含义跟其字面意思相符。)
结构体和类非常相似。
Go里面它确实没有类,要表示对象,它是用struct的(和C很像);Go语言,是一门没有对象的OOP。
Go的面向对象 blog.csdn.net/u011606307/…
- 结构体字段使用点号来访问。
- 结构体字段可以通过结构体指针来访问。通过指针间接的访问是透明的。
- 结构体文法表示通过结构体字段的值作为列表来新分配一个结构体。
使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。) 特殊的前缀 & 返回一个指向结构体的指针。
12.数组
类型 [n]T 是一个有 n 个类型为 T 的值的数组。
表达式 var a [10]int 定义变量 a 是一个有十个整数的数组。
数组的长度是其类型的一部分,因此数组不能改变大小。 这看起来是一个制约,但是请不要担心; Go 提供了更加便利的方式来使用数组。
slice
一个 slice 会指向一个序列的值,并且包含了长度信息。
[]T 是一个元素类型为 T 的 slice。
对 slice 切片
slice 可以重新切片,创建一个新的 slice 值指向相同的数组。
表达式
s[lo:hi] 表示从 lo 到 hi-1 的 slice 元素,含两端。因此
s[lo:lo] 是空的,而
s[lo:lo+1] 有一个元素。
构造 slice
slice 由函数 make 创建。这会分配一个零长度的数组并且返回一个 slice 指向这个数组:
a := make([]int, 5) // len(a)=5
为了指定容量,可传递第三个参数到 make
:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5 b = b[1:] // len(b)=4, cap(b)=4
nil slice
slice 的零值是 nil
。
一个 nil 的 slice 的长度和容量是 0。
向 slice 添加元素
向 slice 添加元素是一种常见的操作,因此 Go 提供了一个内建函数 append
。 内建函数的文档对 append 有详细介绍。
func append(s []T, vs ...T) []T append 的第一个参数 s 是一个类型为 T 的数组,其余类型为 T 的值将会添加到 slice。
append 的结果是一个包含原 slice 所有元素加上新添加的元素的 slice。
如果 s 的底层数组太小,而不能容纳所有值时,会分配一个更大的数组。 返回的 slice 会指向这个新分配的数组。
(了解更多关于 slice 的内容,参阅文章slice:使用和内幕。)
13.range
for 循环的 range 格式可以对 slice 或者 map 进行迭代循环 range(续) 可以通过赋值给 _ 来忽略序号和值。
如果只需要索引值,去掉“, value”的部分即可。
练习:slice
实现 Pic
。它返回一个 slice 的长度 dy
,和 slice 中每个元素的长度的 8 位无符号整数 dx
。当执行这个程序,它会将整数转换为灰度(好吧,蓝度)图片进行展示。
图片的实现已经完成。可能用到的函数包括 (x+y)/2 、 x*y 和 x^y
(使用 math.Pow 计算最后的函数)。
(需要使用循环来分配 [][]uint8 中的每个 []uint8
。)
(使用 uint8(intValue) 在类型之间进行转换。)
14.map
map 映射键到值。
map 在使用之前必须用 make 而不是 new 来创建;
map 的文法 map 的文法跟结构体文法相似,不过必须有键名。
map 的文法(续) 如果顶级的类型只有类型名的话,可以在文法的元素中省略键名。
修改 map 在 map m 中插入或修改一个元素:
m[key] = elem 获得元素:
elem = m[key] 删除元素:
delete(m, key) 通过双赋值检测某个键存在:
elem, ok = m[key]
如果 key 在 m 中,ok
为 true 。否则, ok 为 false
,并且 elem 是 map 的元素类型的零值。
同样的,当从 map 中读取某个不存在的键时,结果是 map 的元素类型的零值。
练习:map
实现 WordCount
。它应当返回一个含有 s 中每个 “词” 个数的 map。函数 wc.Test 针对这个函数执行一个测试用例,并输出成功还是失败。
你会发现 strings.Fields 很有帮助。
15.函数值
函数也是值。
16.函数的闭包
Go 函数可以是闭包的。闭包是一个函数值,它来自函数体的外部的变量引用。 函数可以对这个引用值进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。
例如,函数 adder 返回一个闭包。每个闭包都被绑定到其各自的 sum 变量上。
练习:斐波纳契闭包 现在来通过函数做些有趣的事情。
实现一个 fibonacci 函数,返回一个函数(一个闭包)可以返回连续的斐波纳契数。
有疑问加站长微信联系(非本文作者)