let’s go
https://studygolang.com/articles/13958
https://www.jianshu.com/p/aeb27a6aa42d
https://www.runoob.com/go/go-variables.html
基础的不想写,一太多二重复率太高,陆陆续续学习go到今天,应该总结了,fighting
开发工具
工欲善其事必先利其器,GoLand 30天免费试用期
LiteIDE开源、跨平台轻量级https://www.runoob.com/go/go-ide.html
注意
相同代码块不可对相同名称变量做再次初始化声明:=
定义变量之前不能使用,声明了局部变量没有使用都将编译错误
省略var 使用":=" ,且“:=”左测要声明新的变量!出现在函数体中
var vname1, vname2, vname3 = v1, v2, v3和python是不是很像,自动推断类型,并行赋值
val,err = Func1(var1)
全局变量声明:
var (
a int
b bool
)
基础
值类型
int、float、bool、string这些基本数据类型属于值类型(具体请百度),使用该类型的变量直接指向存在内存中的值
使用=将变量的值赋给另一个变量时,j=i ,内存中的i值进行了拷贝
通过&i获取变量i内存地址,变量的值存在栈中
引用类型
变量r1
存储的是r1值所在的内存地址(指针),或内存地址中第一个字所在的位置
r2=r1引用地址的肤质,r1值改变了,值的all引用都会指向被修改后的内容
类型转换
type_name(expression)//类型_表达式
var sum int = 17
var count int = 5
var mean float32
mean = float32(sum)/float32(count)
常量
可用作枚举:
const (
Unknown = 0
Female = 1
Male = 2
)
常量表达式中函数必须是内置函数,否则编译不过
const (
a = "abc"
b = len(a)
c = unsafe.Sizeof(a)
)
iota
可变常量,在const关键字出现后的第一行重置为0,每新增一行常量声明将iota计数一次+1
import "fmt"
func main() {
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i)//0 1 2 ha ha 100 100 7 8
}
const (
i=1<<iota //1,<< 表示左移0位不变
j=3<<iota //6,<<左移1位,二进制110,6
k //12 <<左移2位,变为二进制 1100,12
l //24 <<左移3位,变为二进制 11000,24
)
是不是发现了其中的密码,告诉你吧:<<n==*(2^n)
变量
package main
import "fmt"
/** 函数体外声明全局变量,可在整个包、外部包使用;
** 全局变量和局部变量可重名,优先使用局部变量
**/
var a int = 20;
func main() {
/* main 函数中声明局部变量,这个和java同不说了*/
var a int = 10
var b int = 20
var c int = 0
fmt.Printf("main()函数中 a = %d\n", a);
c = sum( a, b);
fmt.Printf("main()函数中 c = %d\n", c);
}
/* 函数定义-形式参数作为函数局部变量使用 */
func sum(a, b int) int {
return a + b;
}
数组
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
package main
import "fmt"
func main() {
var n [10]int /* 数组长度不可变;长度为10数组,索引从0开始 */
var i,j int
for i = 0; i < 10; i++ {
n[i] = i + 100 /* 设置元素为 i + 100 */
}
for j = 0; j < 10; j++ {
fmt.Printf("Element[%d] = %d\n", j, n[j] )
}
}
多维数组,var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
a = [3][4]int{
{0, 1, 2, 3} , /* 第一行索引为 0 */
{4, 5, 6, 7} , /* 第二行索引为 1 */
{8, 9, 10, 11}, /* 第三行索引为 2 */
}//也可以最后这一行和倒数第二行,合并为{8, 9, 10, 11}}
切片
是不是新的概念?万变不离其宗,总有你熟悉的影子
先提及一下: make是初始化并返回引用T,new是初始化一下指向类型的指针*T
切片slice是对数组的抽象、动态数组,所以 明白为什么在这里写了吧,我可是很用心的在抄、呸~写文字呐
var identifier [] type//长度不固定,可追加元素
var slice1 []type = make([]type, len)//make()函数创建切片
package main
import "fmt"
func main() {
numbers := []int{0,1,2,3,4,5,6,7,8} //创建切片
printSlice(numbers)
fmt.Println("numbers ==", numbers)//打印原始切片
fmt.Println("numbers[1:4] ==", numbers[1:4])//打印子切片从索引1(包含) 到索引4(不包含)
fmt.Println("numbers[:3] ==", numbers[:3])//默认下限为 0
fmt.Println("numbers[4:] ==", numbers[4:])//默认上限为 len(s)
numbers1 := make([]int,0,5)//s :=make([]int,len,cap),len数组长度也是切片初始长度,cap容量不等于长度,可选参数
printSlice(numbers1)
number2 := numbers[:2]//number2以numbers值的底层数组为前提,so we can get more element from origin slice,unsafe
printSlice(number2)
var number3 = numbers[1:4:4]//[]第三个值4限制访问原切片底层数组
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)//%v这里指切片
}
//像什么,list
func printSliceNew(){
var numbers []int
numbers = append(numbers, 2,3,4)//同时添加多个元素
numbers1 := make([]int, len(numbers), (cap(numbers))*2)
copy(numbers1,numbers)//拷贝 numbers 的内容到 numbers1
}
指针
指针变量指向值的内存地址:var var_name *var-type
var ip *int /* 指向整型*/
*ip获取指针所指向的内容;想当初学c++的时候这个地址莫名其妙绕住了宝宝,哈哈~汗颜
空指针:未赋值的指针,值为nil 代指0或空
package main
import "fmt"
const MAX int = 3
func main() {
a := []int{10,100,200}
var i int
var ptr [MAX]*int;
for i = 0; i < MAX; i++ {
ptr[i] = &a[i] /* 整数地址赋值给指针数组 */
}
for i = 0; i < MAX; i++ {
fmt.Printf("a[%d] = %d\n", i,*ptr[i] )
}
/* 定义局部变量 */
var a int = 100
var b int= 200
fmt.Printf("交换前 a 的值 : %d\n", a )
fmt.Printf("交换前 b 的值 : %d\n", b )
/* 调用函数用于交换值
* &a 指向 a 变量的地址,注意这里
* &b 指向 b 变量的地址,注意这里
*/
swap(&a, &b);
fmt.Printf("交换后 a 的值 : %d\n", a )
fmt.Printf("交换后 b 的值 : %d\n", b )
}
func swap(x *int, y *int) {
var temp int
temp = *x /* 保存 x 地址的值 */
*x = *y /* 将 y 赋值给 x */
*y = temp /* 将 temp 赋值给 y */
}
结构体
是不是很陌生,实体 知道吧?到这里是不是很高兴,哇~Go
type struct_variable_type struct {
member definition
...
member definition
}
使用:
variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen
还是很抽象???
package main
import "fmt"
type Books struct {
title string
author string//属性的开头大写则可被其他包访问,否只能本包
subject string
book_id int
}
func main() {
// 创建一个新的结构体
fmt.Println(Books{"Go 语言", "www.runoob.com", "Go 语言教程", 6495407})
// 也可以使用 key => value 格式
fmt.Println(Books{title: "Go 语言", author: "www.runoob.com", subject: "Go 语言教程", book_id: 6495407})
// 忽略的字段为 0 或 空
fmt.Println(Books{title: "Go 语言", author: "www.runoob.com"})
var Book1 Books /* 声明 Book1 为 Books 类型 */
/* book 1 描述 */
Book1.title = "Go 语言"
Book1.author = "www.runoob.com"
Book1.subject = "Go 语言教程"
Book1.book_id = 6495407
fmt.Printf( "Book 1 title : %s\n", Book1.title)//%d是数字
struct_pointer = &Book1//存储结构体变量的地址
printBook(&Book1)
}
func printBook( book *Books ) {
fmt.Printf( "Book title : %s\n", book.title)
fmt.Printf( "Book book_id : %d\n", book.book_id)
}
map
map其实还好,整的不太过,当然我也是开个玩笑,完美没有标准
定义和之前差不多
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
package main
import "fmt"
func main() {
var countryCapitalMap map[string]string /*创建集合 */
countryCapitalMap = make(map[string]string)
/* map插入key - value对,各个国家对应的首都 */
countryCapitalMap [ "France" ] = "巴黎"
countryCapitalMap [ "Italy" ] = "罗马"
delete(countryCapitalMap, "France")
/*使用键输出地图值 */
for country := range countryCapitalMap {
fmt.Println(country, "首都是", countryCapitalMap [country])
}
/*查看元素在集合中是否存在 */
capital, ok := countryCapitalMap [ "American" ] /*如果确定是真实的,则存在,否则不存在 */
/*fmt.Println(capital) */
/*fmt.Println(ok) */
if (ok) {
fmt.Println("American 的首都是", capital)
} else {
fmt.Println("American 的首都不存在")
}
}
func test(){
countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"}
}
运算符
位运算符
其他的运算符比较简单,当然这个也简单,但是平常用的比较少,思维需要
说这个之前,1和0是不是有什么特殊的意义,如果知道了会好理解些哦~简单百度下吧
&按位与,简单的来说 参与运算的二进制位都是1才得1,其他为0
I按位或,二进制位有一个为1就为1
^按位异或,二进制位相同为0不同为1
<<左移,上面多少提到了,左移n位=乘以2的n次方,二进制位全部左移n位,高位丢弃、地位补0
>> 右移,除以2的n次方
其他运算符
第一个多少有些迷惑,& 上面也提到过,返回变量储存地址,&a;返回变量a实际地址
* 指针变量,想起了c++的谁?,*a;指针变量
运算符优先级
由高到低 * / % << >> & &^ + - | ^ == != < <= > >= && || 这里cue一下括号
语句
条件语句
常用的if else,switch不说了,注意:没有三目运算符哦!
select语句类似switch,随机执行一个可运行的case,如果无将阻塞,知道有case
函数
func function_name( [parameter list] ) [return_types] {
//函数体
}
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("Google", "Runoob")
fmt.Println(a, b)
}
cap()
获取数组、切片、通道类型的容量
append()
上面切片的时候说道了访问限制,append可以突破这种限制,对切片扩展并返回新的切片值
一旦扩展操作超出了被操作的切片值的容量,切片底层数组将被替换,调用copy函数
copy
copy(var1,var2)第二个元素复制到第一个参数中:最小复制原则,被复制的元素个数=长度较短的那个的长度
递归
说道递归,自己调自己玩,简单的for循环也可以很强大,所以记得设置退出条件,死循环不是一个好事情
package main
import "fmt"
func fibonacci(n int) int {
if n < 2 {
return n
}
return fibonacci(n-2) + fibonacci(n-1)
}
func main() {
var i int
for i = 0; i < 10; i++ {
fmt.Printf("%d\t", fibonacci(i))
}
}
范围range
for循环中迭代数组array、切片slice、通道channel、集合map的元素;
数组和切片返回元素索引及其值,集合返key/value对
package main
import "fmt"
func main() {
//这是我们使用range去求一个slice的和。使用数组跟这个很类似
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
sum += num
}
fmt.Println("sum:", sum)//sum:9
//在数组上使用range将传入index和值两个变量。上面那个例子我们不需要使用该元素的序号,所以我们使用空白符"_"省略了。有时侯我们确实需要知道它的索引。
for i, num := range nums {
if num == 3 {
fmt.Println("index:", i)//index:1
}
}
//range也可以用在map的键值对上。
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)//a -> apple b -> banana
}
//range也可以用来枚举Unicode字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。
for i, c := range "go" {
fmt.Println(i, c)//0 103 1 111
}
}
接口
一个项目的骨架是不是出来了,受java的影响吧这个接口多少有些接收不了,用的多就好
package main
import (
"fmt"
)
//phone接口
type Phone interface {
call()//call方法
}
type NokiaPhone struct {
}
func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}
type IPhone struct {
}
func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}
func main() {
var phone Phone//定义了应该phone类型变量
phone = new(NokiaPhone)//赋值为NokiaPhone
phone.call()//调NokiaPhone的call
phone = new(IPhone)//赋值为IPhone
phone.call()//调IPhone的call
}
组合:伪集成
func (e *Poem) ShowTitle() {
fmt.Printf(e.Title)
}
type Poem struct {
Title string
Author string
intro string
}
type ProsePoem struct {
Poem//组合了Poem方法和属性
Author string
}
prosePoem := &ProsePoem{
Poem: Poem{
Title: "Jack",
Author: "slow",
intro: "simple",
},
Author: "test",
}
属性有冲突,外围为主:我是没有听懂,🌰
访问Author默认为ProsePoem的Author,需要访问Poem中的使用ProsePoem.Poem.Author
prosePoem.Poem.Author = "Heine"
并发、协程
M:内核线程
G:go routine并发最小逻辑单元
P:处理器,执行G上下文环境,每个P会维护一个本地的go routine队列,还存一个全局的go routine队列
P的数量在初始化时由gomaxprocs决定
G的数量超过M的处理能力且还有空余P,runtime自动创建新的M
M拿到P才能干活、G的顺序:本地队列>全局队列>其他P队列,all队列无可用G,M归还P并休眠
go f(x,y,z)开启新的goroutine运行期线程,同一个程序的all的go routine共享同一个地址空间
package main
import (
"fmt"
"time"
"runtime"
)
func say(s string) {
for i := 0; i < 2; i++ {
time.Sleep(10 * time.Millisecond)
//runtime.Gosched()主动放弃,将G扔进全局队列
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")//执行完主线程结束,上面go*无机会
}
runtime.GOMAXPROCS(n)//指定多核,goroutine都是在一个线程里,通过不停让出时间片轮流运行
通道channel
默认是同步的,<-指定通道的方向,未指定双向通道
默认不带缓冲区,发送端发送必须有接收端接收,否则阻塞
传递数据的数据结构,可用于goroutine间通过传递指定类型的值同步运行和通讯
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x//把x发送到通道ch;value,ok:= <- ch1 ok表通道状态,true有效false你猜
x, y = y, x+y
}
close(c)//重复关闭会引发运行异常
}
func main() {
c := make(chan int, 10)//创建缓冲区大小为10的通道
go fibonacci(cap(c), c)
// range遍历每个从通道接收到的数据,因为 c 在发送完 10 个
// 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
// 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
// 会结束,从而在接收第 11 个数据的时候就阻塞了。
for i := range c {
fmt.Println(i)
}
}
异常
无异常机制,使用panic/recover模式处理
panic一般导致程序挂掉,打印调用栈:panic了函数将到defer处并将其处理完在向上传递,defer是不是类似于finally。
package main//https://blog.csdn.net/qq_27682041/article/details/78786689
import (
"fmt"
)
func main() {
GO()
PHP()
PYTHON()
}
//Go语言追求简洁优雅,所以,Go语言不支持传统的 try…catch…finally,设计者们认为,将异常与控制结构混在一起易使得代码混乱。不要用异常代替错误,更不要用来控制流程。遇到真正的异常的情况下(比如除数为0了)才使用Go中引入的Exception处理:defer, panic, recover
//Panic可以在任何地方引发,但recover只有在defer调用的函数中有效
func GO() {
fmt.Println("我是GO,现在没有发生异常,我是正常执行的。")
}
func PHP() {
// 必须要先声明defer,否则不能捕获到panic异常,也就是说要先注册函数,后面有异常了,才可以调用
defer func() {
if err := recover(); err != nil {
fmt.Println("终于捕获到了panic产生的异常:", err) // 这里的err其实就是panic传入的内容
fmt.Println("我是defer里的匿名函数,我捕获到panic的异常了,我要recover,恢复过来了。")
}
}() //注意这个()就是调用该匿名函数的,不写会报expression in defer must be function call
panic("我是PHP,我要抛出一个异常了,等下defer会通过recover捕获这个异常,捕获到我时,在PHP里是不会输出的,会在defer里被捕获输出,然后正常处理,使后续程序正常运行。但是注意的是,在PHP函数里,排在panic后面的代码也不会执行的。")
fmt.Println("我是PHP里panic后面要打印出的内容。但是我是永远也打印不出来了。因为逻辑并不会恢复到panic那个点去,函数还是会在defer之后返回,也就是说执行到defer后,程序直接返回到main()里,接下来开始执行PYTHON()")
}
func PYTHON() {
fmt.Println("我是PYTHON,没有defer来recover捕获panic的异常,我是不会被正常执行的。")
}
回收
也是利用defer代码块,在函数正常返回、即return后添加一个函数调用,且先进后出
包裹defer函数返回时、执行到末尾时、所在goroutine发生panic时 :defer执行的时机就到了,呀呼~~
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close()//关闭清理src中使用变量
dst, err := os.Create(dstName)
if err != nil {
return
}
defer dst.Close()
return io.Copy(dst, src)
}
匿名返回值、命名返回值
/**
**defer执行过程:创建retValue并intValue=result,是否有defer,有执行,返回刚才创建的返回值retvalue
** defer中的修改是对result执行的,不是retValue返回是是retValue
**/
func returnValues() int {
var result int
defer func() {
result++
fmt.Println("defer")
}()
return result
}
/**
**返回值在方法定义时被定义,无创建retValue过程,result即retValue,defer对result的修改也会被直接返回
**/
func namedReturnValues() (result int) {
defer func() {
result++
fmt.Println("defer")
}()
return result
}
获取资源可能返回err参数,可选择忽略返回的err参数但使用defer延迟释放需要判断是否存在err
resp, err := http.Get(url)
// 先判断操作是否成功
if err != nil {
return err
}
defer resp.Body.Close()// 如果操作成功,再进行Close操作
调用os.Exit时defer不会被执行
https://studygolang.com/articles/10167
https://www.jianshu.com/p/79c029c0bd58
包导入
import(
. "fmt" //可省略前缀的包名,fmt.Println( "hello go" )写成Println( "hello go" )
f "fmt"//别名,f.Println( "hello go" )
_ “github.com/xiyanxiyan10/misakago" //不想引用该包中的方法,导入包时自动执行其中的Init()函数来做需要的初始化工作
)
写了这些,看到的资料也越来越多,想说一句:啥也不是,皮毛都不算
命运呐~虽然你不是土拨鼠但是不影响别人把你当成土拨鼠
来源:oschina
链接:https://my.oschina.net/u/4373992/blog/4263850