0 前言
一周之内要完成的任务。
本文目前不做过多的知识介绍,一是因为赶时间完成任务,二是对于有编程基础的人来说,不用事无巨细的都记下来,只要学学该语言的设计思想,总结一下其特别的地方和用法,差不多就入门了。
但是这篇文章开在这里,留给以后在学习使用Go语言时,记录一些不会的基础的知识。
1. 数据类型
2. 函数
1. 函数结构
func func_name(s string, a int) (int, int) { # 这一行是函数签名
}
2. 不定参函数
// 计算任意多个整数的和并返回
func sumNumbers(nums...int) int {
total := 0
for _, x := range nums {
total += x
}
return total
]
...
3. 具名返回值
4. 将函数作为值传递
本质上,Go将函数视为一种类型,因此可将函数赋给变量
func main() {
fn := func() {
fmt.Println("xxx")
}
fn()
}
既然函数是一种类型,可以赋给变量,那么,也可以作为参数,再传给其它函数
func anoFunc(t func() string) string {
return t()
}
func main() {
fn := func() string {
return "xxx"
}
fmt.Println(anoFunc(fn))
}
有一点要注意,就是参数中,函数的返回类型,要与接收函数的参数类型一致,这很好理解,就像MR中,Map的输出格式肯定是和Reduce的输入格式一样的,不然怎么对接的上。
5. 其它地方
-
函数声明后,编译器就记录了其签名,并根据签名,检查调用时该函数的参数数量和类型是否正确;
-
不允许函数重载(overload);
3. 控制结构
3.1 for range用法
for each
for pos, char := range nums {
// pos = [0, 1, 2,...]
// char = [values1, values2,...]
}
3.2 defer用法
用于在外部函数都运行结束后,再运行的语句。一些应用场景如:在读取文件后将其关闭,需要在某项工作完成后,执行特定的函数时使用。如果有多条defer语句,则按照出现顺序相反的来执行。
4. 数组、切片(Slice)与映射(Map)
var nums [n]int
var sb = make([]string, 2)append
// 1. 添加
// 2. 删除
// 3. 复制
var age = make(map [string] int)delete(age,"key")
注意,delete方法不能用户切片和数组,没有专门用于从切片中删除元素的函数,用内置函数append来完成。
5. 结构体(Struct)与方法(Method)
5.1 结构体创建方式
type Movie sruct {
Name string
Rating float32
}
// 1. 变量声明方式
var m Movie
m.Name = "xxx"
m.Rating = 0.99
// 2.关键字 new
m := new(Movie)
m.Name = "xx"
m.Rating = 0.99
// 3. 简短变量赋值方式和
m := Movie{
Name: "xxx",
Rating: 0.99,
}
简短赋值方式中的逗号别忘了写
5.2 方法
定义方式
func (recv receive_type) methodName(parameter_list) return_value_list {
// your code
}
Go中的方法集可以与任何类型相关联(切片等),能够在数据类型和方法之间建立关系。
函数和方法的区别
函数将变量作为参数:Function1(recv)
方法在变量上被调用:recv.Method1()
在接收者是指针时,方法可以改变接收者的值(或状态),这点函数也可以做到(当参数作为指针传递,即通过引用调用时,函数也可以改变参数的状态)。
接收者必须有一个显式的名字,这个名字必须在方法中被使用。
receiver_type 叫做 (接收者)基本类型,这个类型必须在和方法同样的包中被声明。
在 Go 中,(接收者)类型关联的方法不写在类型结构里面,就像类那样;耦合更加宽松;类型和方法之间的关联由接收者来建立。
个人理解,结构体本质上是一种模型,数据模型,虽然书上说,结构体并非创建面向对象代码的方式,而是一种数据结构创建方式,旨在满足数据建模需求。但对于我这个初学者来说,就是会把其理解为面向对象中的类,然后对其进行不同的实例化。
在Java中,基本数据类型是按值传递,引用数据类型(数组,接口,类)按值传递的时候都是传递对象的地址(可以参考我的另一篇博客 Java基础知识补充,不定时更新)。
Go 默认使用按值传递来传递参数,也就是传递参数的副本。函数接收参数副本之后,在使用变量的过程中可能对副本的值进行更改,但不会影响到原来的变量,比如 Function(arg1)。
如果你希望函数可以直接修改参数的值,而不是对参数的副本进行操作,你需要将参数的地址(变量名前面添加&符号,比如 &variable)传递给函数,这就是按引用传递,比如 Function(&arg1),此时传递给函数的是一个指针。
6. 协程(Goroutine)和通道(Channel)
这应该才是Go最具特色的地方吧
Concurrency is not parallelism
我就先不在这说了, 怕误导了大家,先放几个参考博文
7. 命令行参数
稍微具体一点的在我的周报中写了
在这补充一些。
7.1 os.Args[]
7.2 flag包
关于Nflag函数
上面可以看到,args的内容一直是空的,我倒要来看看这个咋出来的。
non-flag
--1-12--
x. 一些小地方
首字母大写
PublicProtected
目前看来,函数名、结构体及其字段名都是这样。
_
_
可以用在几个地方
- 代码中作为占位符或者丢弃返回值
举个例子吧。
// 1. 正常操作
func main() {
f, err := ioutil.ReadFile("readme.txt")
// code...
}
但是如果我不关心文件内容,只关心这个文件有没有读取成功呢?也就是说,我不要 f 变量,只要对 err 进行判断一下,在Go语言中,如果有变量未被使用,会提示,讲究的就是每个代码都有用,没有多余的代码,那么这时候,就可以用下划线了。
// 2. 丢弃返回的 f
func main() {
_, err := ioutil.ReadFile("readme.txt")
// code...
}
这样,就可以了,依然是返回两个变量,但是将上面的 f 赋给了下划线,就会被忽略,也不会引起程序报错。
但是我有个疑问,这样会开辟内存空间么?不会造成浪费么?
- 导包时
在导包时,有时候不需要导入整个包,只要调用一下其 init() 函数就行了,这时候也是用下划线实现,它会调用该包下所有的 init() 函数,但不导入整个包。
import "database/sql"
import _ "github.com/go-sql-driver/mysql"
这样就会执行mysql包中的初始化函数,将mysql驱动注册到sql中,但不会导入mysql包,并且后面就可以在sql包中操作mysql数据库。
几种变量声明方式
Go语言提供了多种变量声明方式
1. 指定类型并赋值
var s string = "hello,world!"
2. 省略类型并赋值
var s = "hello,world!"
3. 先声明类型,后赋值
var t string
t = "hello,world!"
4. 简短变量声明
u := "hello,world!"
是不是有点懵,所以,我们到底应该用哪种呢?(以下摘自《Go语言入门经典》P23)
Go语言设计者在标准库中遵循的约定如下:在函数内使用简短变量声明,在函数外省略类型。且Go源码中,简短变量声明是最常用的。