Go基础之基础

拈花ヽ惹草 提交于 2020-05-04 10:11:23

let’s go

https://golang.org/

https://play.golang.org/

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()函数来做需要的初始化工作
) 

这里写图片描述

 

写了这些,看到的资料也越来越多,想说一句:啥也不是,皮毛都不算

命运呐~虽然你不是土拨鼠但是不影响别人把你当成土拨鼠

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!