从精通JS到golang入门 - 基础入门

CharlesYu01 · · 613 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

Go官网

基础入门

1.学习方式

第一种,本地项目学习 如何使用 go 命令来获取、构建并安装包、命令及运行测试

第二种,在线学习工具使用。 第一部分只是简单欢迎引导

2.欢迎使用Go指南

指南的使用

欢迎来到Go编程语言指南。本指南涵盖了该语言的大部分重要特性,主要包括:

欢迎!
学习如何使用本指南:包括如何在不同的课程之间切换,以及如何运行代码。
复制代码

基础

一开始,将学习关于语言的所有基础内容。

定义变量,调用函数,以及在您学习下一课之前所需要了解的全部内容。

包,变量和函数。
学习Go程序的基本组件。
复制代码
流程控制语句:forifelse和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 定义。

函数外的每个语句都必须以关键字开始(varfunc、等等),:= 结构不能使用在函数外。

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
复制代码

js关于defer和async区别

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/…

  1. 结构体字段使用点号来访问。
  2. 结构体字段可以通过结构体指针来访问。通过指针间接的访问是透明的。
  3. 结构体文法表示通过结构体字段的值作为列表来新分配一个结构体。

使用 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 函数,返回一个函数(一个闭包)可以返回连续的斐波纳契数。


有疑问加站长微信联系(非本文作者)

本文来自:掘金

感谢作者:CharlesYu01

查看原文:从精通JS到golang入门 - 基础入门

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

613 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传