前段时间准备面试,学院里面考试以及家里面有些事情,还有我懒,耽搁了。
现在一切就绪,可以开新坑了~嘿嘿嘿
大部分资料都源自于互联网 但是大部分都是自己手打(侵删)
首先确保各位的GOPATH的版本大于等于1.8 保证环境的一致性
本人也是初学go语言,如果有错误希望各位指正。
基本程序
首先我们先写一个最最最简单的hello word吧
package main //包,表明代码所在的模块(包)
import "fmt" //引⼊代码依赖
//功能实现
func main() {
fmt.Println("Hello World!")
}
以上,我们很容易看出(如果你有其他语言的基础的话)程序的入口
1.一定是在main包下:也就是package main //包,表明代码所在的模块(包)
2.必须要是在main方法:func main()
- 其实文件名不一定是main.go
此时肯定会有人问,既然是函数肯定会有返回值啦~
比如c语言默认会追加一个return
对,有道理但是
Go 中 main 函数不⽀持任何返回值
如果你需要有返回状态,你可以通过
通过 os.Exit 来返回状态
常量和变量
变量
接触常量和变量的时候我们先规定以后编写代码的时候用test程序
也就是
源码⽂件以 _test 结尾:xxx_test.go
测试⽅法名以 Test 开头:func TestXXX(t *testing.T) {…}
这样就不用重复开很多项目了(真棒)
和java的junit单元测试一样好用,还能打印日志
先写一个斐波那契数列吧
func TestFibList(t *testing.T) {
// var a int = 1
// var b int = 1
// var (
// a int = 1
// b = 1
// )
a := 1
// a := 1
b := 1
t.Log(a)
for i := 0; i < 5; i++ {
t.Log(" ", b)
tmp := a
a = b
b = tmp + a
}
}
变量有多种赋值方式(在上面)
• 赋值可以进⾏⾃动类型推断
• 在⼀个赋值语句中可以对多个变量进⾏同时赋值
注意 a := 1 只能用于局部变量,不能作用于全局变量
常量
const: 关键字 使得被起修饰的变量可读不可写 保证其状态
同时:iota (偷懒的写法 如果不愿意一直+1 这个可以替代) 我暂时还没用它写过复杂的程序
基本数据类型
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr(正数)
byte // alias for uint8
rune // alias for int32,represents a Unicode code point
float32 float64
complex64 complex128
类型转化
- Go 语⾔不允许隐式类型转换
- 别名和原有类型也不能进⾏隐式类型转换
func TestImplicit(t *testing.T) {
var a int32 = 1
var b int64
b = int64(a)
var c MyInt
c = MyInt(b)
t.Log(a, b, c)
}
以上是显示类型转换 如果直接b=a就会报错
所以编程的过程中需要小心
指针类型
- 不⽀持指针运算(所以别想着指针操作下一个数组单位)
- string 是值类型,其默认的初始化值为空字符串,⽽不是 nil
func TestPoint(t *testing.T) {
a := 1
aPtr := &a
//aPtr = aPtr + 1
t.Log(a, aPtr)
t.Logf("%T %T", a, aPtr)
}
运算符
就不多提 有编程经验的直接跳过
不过这块着重一点就是 Go 语⾔没有前置的 ++,- -
(作用的话,我猜应该是增加代码的可读性吧)
循环
也不多提
Go 语⾔仅⽀持循环关键字 for
n := 0
for n < 5 {
n++
fmt.Println(n)
}
怎么感觉好像更难读了1?不过没事,习惯就好了
switch条件
- 条件表达式不限制为常量或者整数;
- 单个 case 中,可以出现多个结果选项, 使⽤逗号分隔;
- 与 C 语⾔等规则相反,Go 语⾔不需要⽤break来明确退出⼀个 case;如果需要执行下一个case的话 可以加fallthrough关键字
- 可以不设定 switch 之后的条件表达式,在此种情况下,整个 switch 结
构与多个 if…else… 的逻辑作⽤等同
数组和切片
这个很重要 以后有机会开个新坑形容一下底层原理
数组的声明
var a [3]int //声明并初始化为默认零值
a[0] = 1
b := [3]int{1, 2, 3} //声明同时初始化
元素的遍历
func TestTravelArray(t *testing.T) {
a := [...]int{1, 2, 3, 4, 5} //不指定元素个数
for idx/*索引*/, elem/*元素*/ := range a {
fmt.Println(idx, elem)
}
//当然 你要是不想要ind(索引的话)可以变为_ 不使用它就行
}
数组的截取
a[开始索引(包含), 结束索引(不包含)]
(简单来记就是左闭右开)
a := [...]int{1, 2, 3, 4, 5}
a[1:2] //2
a[1:3] //2,3
a[1:len(a)] //2,3,4,5
a[1:] //2,3,4,5
a[:3] //1,2,3
![X~VMAEVUTITZ3TFJ_O%2)6.png
这个挺复杂的后面详细讲,初学者会使用就行
切片的声明
var s0 []int
s0 = append(s0, 1)
s := []int{}
s1 := []int{1, 2, 3}
s2 := make([]int, 2, 4)
/*[]type, len, cap
其中len个元素会被初始化为默认零值,未初始化元素不可以访问
*/
切⽚共享存储结构
数组和切片的区别
- 容量是否可伸缩
- 是否可以进⾏⽐较
Map基础
本节也很重要 不过作为初学者只要掌握使用即可
Map 声明
m := map[string]int{"one": 1, "two": 2, "three": 3}
m1 := map[string]int{}
m1["one"] = 1
m2 := make(map[string]int, 10 /*Initial Capacity*/)
Map还有一个很有意思的点,就是假如我设置一个key但是并未设置value 那么这个value设置为0值
此时 好处当然就是防止空指针异常呀
但是坏处也有,就是我这么知道这个key的value是真的为空还是就为0?
以下代码中 ok 为true存在 否则不存在
func TestAccessNotExistingKey(t *testing.T) {
m1 := map[int]int{}
t.Log(m1[1])
m1[2] = 1
t.Log(m1[2])
m1[1] = 2
if v, ok := m1[3]; ok {
t.Logf("Key 3's value is %d", v)
} else {
t.Log("key 3 is not existing.")
}
}
遍历map
func TestTravelMap(t *testing.T) {
m1 := map[int]int{1: 1, 4: 4, 3: 9}
for k, v := range m1 {
t.Log(k, v)
}
}
Map的扩展
Map 与⼯⼚模式
• Map 的 value 可以是⼀个⽅法
• 与 Go 的 Dock type 接⼝⽅式⼀起,可以⽅便的实现单⼀⽅法对象的⼯⼚模式
实现 Set
Go 的内置集合中没有 Set 实现, 可以 map[type]bool
- 元素的唯⼀性
- 基本操作
- 添加元素
- 判断元素是否存在
- 删除元素
- 元素个数
func TestMapWithFunValue(t *testing.T) {
m := map[int]func(op int) int{}
m[1] = func(op int) int { return op }
m[2] = func(op int) int { return op * op }
m[3] = func(op int) int { return op * op * op }
t.Log(m[1](2), m[2](2), m[3](2))
}
用Map实现set
func TestMapForSet(t *testing.T) {
mySet := map[int]bool{}
mySet[1] = true
n := 3
if mySet[n] {
t.Logf("%d is existing", n)
} else {
t.Logf("%d is not existing", n)
}
mySet[3] = true
t.Log(len(mySet))
delete(mySet, 1)
n = 1
if mySet[n] {
t.Logf("%d is existing", n)
} else {
t.Logf("%d is not existing", n)
}
}
string
- string 是数据类型,不是引⽤或指针类型
- string 是只读的 byte slice,len 函数可以它所包含的 byte 数
- string 的 byte 数组可以存放任何数据
函数
- 可以有多个返回值
- 所有参数都是值传递:slice,map,channel 会有传引⽤的错觉
- 函数可以作为变量的值
- 函数可以作为参数和返回值
func returnMultiValues() (int, int) {
return rand.Intn(10), rand.Intn(20)
}
func timeSpent(inner func(op int) int) func(op int) int {
return func(n int) int {
start := time.Now()
ret := inner(n)
fmt.Println("time spent:", time.Since(start).Seconds())
return ret
}
}
func slowFun(op int) int {
time.Sleep(time.Second * 1)
return op
}
func TestFn(t *testing.T) {
a, _ := returnMultiValues()
t.Log(a)
tsSF := timeSpent(slowFun)
t.Log(tsSF(10))
}
可变参数
func sum(ops ...int) int {
s := 0
for _, op := range ops {
s += op
}
return s
}
最终拿到的就是所有参数之和
defer 函数
func TestDefer(t *testing.T) {
defer func() {
t.Log("Clear resources")
}()
t.Log("Started")
panic("Fatal error”) //defer仍会执⾏
}
⾯向对象编程
结构体定义
type Employee struct {
Id string
Name string
Age int
}
实例创建及初始化
e := Employee{"0", "Bob", 20}
e1 := Employee{Name: "Mike", Age: 30}
e2 := new(Employee) //注意这⾥返回的引⽤/指针,相当于 e := &Employee{}
e2.Id = “2" //与其他主要编程语⾔的差异:通过实例的指针访问成员不需要使⽤->
e2.Age = 22
e2.Name = “Rose"
//第⼀种定义⽅式在实例对应⽅法被调⽤时,实例的成员会进⾏值复制
func (e Employee) String() string {
return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age)
}
//通常情况下为了避免内存拷⻉我们使⽤第⼆种定义⽅式
func (e *Employee) String() string {
return fmt.Sprintf("ID:%s/Name:%s/Age:%d", e.Id, e.Name, e.Age)
}
定义交互协议
接⼝与依赖
用java实现
Programmer.java
public interface Programmer {
String WriteCodes() ;
}
GoProgrammer.java
public class GoProgrammer implements Programmer {
@Override
public String WriteCodes() {
return "fmt.Println(\"Hello World\")";
} }
Task.java
public class Task{
public static void main(String[] args) {
Programmer prog = new GoProgrammer();
String codes = prog.WriteCodes();
System.out.println(codes);
} }
定义 实现 调用接口的过程
但是在go语言中,我们可以使用鸭子类型
接⼝定义
type Programmer interface {
WriteHelloWorld()
}
接⼝实现
type GoProgrammer struct {
}
func (p *GoProgrammer) WriteHelloWorld() Code {
return "fmt.Println(\"Hello World!\")"
}
我们发现这个是通过组合的方式完成接口的实现的
Go 接⼝
与其他主要编程语⾔的差异
- 接⼝为⾮⼊侵性,实现不依赖于借⼝定义
- 所以接⼝的定义可以包含在接⼝使⽤者包内
接⼝变量
自定义类型
就自定义的 比如struct 然后就能当做一个特殊类型使用(这个有c语言基础能理解)
扩展与复⽤
复合
与其他主要编程语⾔的差异
Go 不⽀持继承,但可以通过复合的⽅式来复⽤
type Pet struct {
}
func (p *Pet) Speak() {
fmt.Println("...PET")
}
func (p *Pet) SpeakTo(host string) {
p.Speak()
fmt.Println(" ", host)
}
type Dog struct {
Pet
}
func (d *Dog) Speak() {
fmt.Println("Wang!")
}
func TestDog(t *testing.T) {
dog := new(Dog)
dog.SpeakTo("Chao")
}
可以通过组合对程序进行继承
匿名类型嵌⼊
与其他主要编程语⾔的差异
它不是继承,如果我们把“内部 struct ”看作⽗类,把“外部 struct” 看作⼦类,
会发现如下问题:
- 不⽀持⼦类替换
- ⼦类并不是真正继承了⽗类的⽅法
• ⽗类的定义的⽅法⽆法访问⼦类的数据和⽅法
多态
空接⼝与断⾔
- 空接⼝可以表示任何类型
- 通过断⾔来将空接⼝转换为制定类型
v, ok := p.(int) //ok=true 时为转换成功
Go 接⼝最佳实践
倾向于使⽤⼩的接⼝定义,很多接
⼝只包含⼀个⽅法
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
较⼤的接⼝定义,可以由多个⼩接
⼝定义组合⽽成
type ReadWriter interface {
Reader
Writer
}
只依赖于必要功能的最⼩接⼝
func StoreData(reader Reader) error {
…
}
Go 的错误机制
- 没有异常机制
- error 类型实现了 error 接⼝
type error interface {
Error() string
}
- 可以通过 errors.New 来快速创建错误实例
errors.New("n must be in the range [0,100]")
panic
• panic ⽤于不可以恢复的错误
• panic 退出前会执⾏ defer 指定的内容
panic vs. os.Exit
• os.Exit 退出时不会调⽤ defer 指定的函数
• os.Exit 退出时不输出当前调⽤栈信息
recover
Java
try{
…
}catch(Throwable t){
}
C++
try{
…
}catch(…){
}
recover
defer func() {
if err := recover(); err != nil {
//恢复错误
}
}()
init ⽅法
• 在 main 被执⾏前,所有依赖的 package 的 init ⽅法都会被执⾏
• 不同包的 init 函数按照包导⼊的依赖关系决定执⾏顺序
• 每个包可以有多个 init 函数
• 包的每个源⽂件也可以有多个 init 函数,这点⽐较特殊
Thead vs. Groutine
- 创建时默认的 stack 的⼤⼩
• JDK5 以后 Java Thread stack 默认为1M
• Groutine 的 Stack 初始化⼤⼩为2K - 和 KSE (Kernel Space Entity) 的对应关系
• Java Thread 是 1:1
• Groutine 是 M:N
![UXJQWHW]2OSZG67]IKT~W00.png](https://upload-images.jianshu.io/upload_images/24046024-5f98801ae75eeb49.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)