go基础语法学习,对比python

因为以前用的语言是pyhton,所以笔记中会和python做对比,和大家一起探讨,希望多多评论,互相学习提高。

go的语言组成

1.每个 Go 应用程序都包含一个名为 main 的包
2.main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)
3.开头字母大小写标识可引用的范围:
当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);
标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )

package main
import "fmt"
func main() {
   /* 这是我的第一个简单的程序 */
   fmt.Println("Hello, World!")
}

运行go文件可以直接“go run hello.go",也可以先“go build hello.go",会生成hello.exe,再执行hello.exe。


image.png
image.png

go语法

1.和python一样,一行结尾无需以分号结束,换行即代表结束.但是非要一行写多条语句,可用分号隔开(不提倡)
2.注释
多行用/*xxxxx*/
单行用//
3.标识符命名方式和python一样
4.和python一样,可以用+连接字符串
5.支持格式化字符串,类似python

package main

import "fmt"

func main(){
    fmt.Println("hello world~")
    var date = "2022.03.30"
    var old = 20
    var format = "Birthday %s,%d years old"
    var msg = fmt.Sprintf(format,date,old)  
    fmt.Println(msg)
}

数据类型:

整型数据长短分的比较细,比如有int8,int16,int32,int64,uint8,uint16,uint32,uint64,
还有指针类型:uintptr
其他语言有的go也都有,而且感觉更全面。
与python不同的是字符串是string,且用双引号才是字符串,单引号是int32,反引号类似python的a=r"abc\n",是元字符,是什么就输出什么

package main

import (
   "fmt"
   "unsafe"
   "reflect"
)

func main(){
    a := 'a'
    b := a+1
    fmt.Printf("单引号%c,类型%s,大小%d\n",a,reflect.TypeOf(a),unsafe.Sizeof(a))
    fmt.Println(b)
    
    c := "ab\n"
    fmt.Printf("双引号%s,类型%s,大小%d\n",c,reflect.TypeOf(c),unsafe.Sizeof(c))
    
    d := `ab\n`
    fmt.Printf("反引号%s,类型%s,大小%d\n",d,reflect.TypeOf(d),unsafe.Sizeof(d))

}

定义变量:

var a,b int8 = 1,2

可以一次定义多个变量
如果只定义,不赋值,则为默认零值(数字类型为0,字符类型为"",布尔类型为false,其他情况为nil。)

var a,b int8

也可以定义的时候不写类型,直接根据赋值来决定

var a,b = 1,2

也可以不写var,用简单符号:=

g := 1
fmt.Println(g)

注意:
1.局部变量不能够声明后不使用,编译会报错。全局变量不受这个限制。
2.只写变量“”,当你从一个函数获取多个返回值,但是部分返回结果不会使用时,可以赋值给.如下,Func返回两个值,只使用了第二个

_,a = Func()
fmt.Println(a)

定义常量:固定不变的量,值不能被改变

const ca int =9
fmt.Println(ca)

特殊常量:iota 用法

    const(
      fa  = iota
      fb 
      fc
      fd = "haha"
      fe
      ff = iota
      fg
    )
    fmt.Println(fa,fb,fc,fd,fe,ff,fg)

运行结果:
image.png

iota相当于常量的索引,如果中间被打断就停止加1,再继续iota就继续加1

const (
    i=1<

结果:
image.png

第一次<

运算符:

该有的都有,而且还有一些其他语言没有的。
算数运算:+-/
逻辑运算:&& || !
位运算:&,|,^,<<,>>
赋值:=,+=,-+,
=,/=,%=,&=,|=,<<=,>>=,^=
比较:>,=,<,>=,<=,==,!=
其他:获取变量地址使用&,例如&a;表示一个变量是指针变量使用,例如a

条件语句:

if

package main
import "fmt"
func main(){
    fmt.Println("hello world~")
    var flag bool = false
    if !flag{
        fmt.Println("flag is false")
    }
}

if...else:else必须要和上一个大括号在同一行

package main
import "fmt"
func main(){
    fmt.Println("hello world~")
    var flag bool = true
    if !flag{
        fmt.Println("flag is false")
    }else{
        fmt.Println("flag is true") 
    }
}

switch语句:

package main
import "fmt"
func main(){
    fmt.Println("hello world~")
    var flag bool = true
    /*从前往后,有一个符合则停止Switch语句*/
    switch flag {
        case flag==true:
            fmt.Println("flag is true")
        case flag==false:
            fmt.Println("flag is false")
        default:
            fmt.Println("error")
    }
    a := 80
    /*case后面值的类型应该一致,case后可以有多可匹配的值*/
    switch a{
        case 90,80:fmt.Println("优秀")
        case 70:fmt.Println("优良")
        case 60:fmt.Println("及格")
        default:fmt.Println("不合格")
    }
    /*可以判断变量类型*/
    var name interface{}
    switch i:= name.(type){
        case int:fmt.Println("i的类型为int")
        case nil:fmt.Println("i类型为%T",i)
    }
    /*fallthrough语句可以是的不判断下一个case的真假直接执行下一个case的内容*/
    var num int =1
    switch num{
        case 1:fmt.Printf("num is %d\n",num)
        fallthrough
        case 2:fmt.Printf("num is %d\n",num)
        fallthrough
        case 10:fmt.Printf("num is %d\n",num)
        case 20:fmt.Printf("num is %d\n",num)
    
    }
}

image.png

select语句:
了解select前需要先知道go的一个类:chan,即channel,水渠,频道的意思。
chan是一个环形队列:

type hchan struct {
  qcount   uint  // 队列中的实际总元素个数
  dataqsiz uint  // 环形队列大小,即可存放元素的个数
  buf      unsafe.Pointer // 环形队列指针(头)
  elemsize uint16  //每个元素的大小
  closed   uint32  //标识关闭状态
  elemtype *_type // 队列中元素类型
  sendx    uint   // 发送索引,元素写入时存放到队列中的位置
  recvx    uint   // 接收索引,元素从队列的该位置读出
  recvq    waitq  // 等待读消息的goroutine队列
  sendq    waitq  // 等待写消息的goroutine队列
  lock mutex  //互斥锁,chan不允许并发读写
}

go中提倡用channel来进行多协程之间的通信。以下是包含创建和使用chan的一个select的例子

package main

import (
  "fmt"
)

func main() {
/*创建一个chan,元素类型是int,大小为1*/
  ch := make(chan int, 1)
  for i := 0; i < 10; i++ {
    select {
    /*从chan中读数据*/
    case x := <-ch:
      fmt.Println(x)
    /*往chan中写数据*/
    case ch <- i:
      fmt.Println("--", i)
    }
  }
}

每个 case 必须是一个通信操作,要么是发送要么是接收。
select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。


image.png

循环:

package main
import ("fmt" )
func main() {
    for i:=1;i<5;i++ {
        fmt.Printf("i=%d\n",i)
    }
    nums := [6]string{"a","b","c"}
    for index,num := range nums{
        fmt.Printf("第%d个字母是%s\n",index,num)
    }
    
    j:=0
    sum:=0
    for j<=4 {
        sum+=j
        j++
    }
    fmt.Printf("sum=%d",sum)
}

break语句和python含义一样。另外还有goto语句,但是尽量不要用

函数:

package main

import (
   "fmt"   )
/*值传递*/
func myfunc(a,b int) (int,int){
    var tmp int=a
    a=b
    b=tmp
    return a,b
    
}
/*地址传递*/
func myfunc1(a,b *int){
    var tmp int= *a
    *a=*b
    *b=tmp   
}

func main() {
    var a int=1
    var b int=2
    var c,d=myfunc(a,b)
    fmt.Println(c,d)
    fmt.Println(a,b)
    myfunc1(&a,&b)
    fmt.Println(a,b)
    
}

结果


image.png

支持把函数作为参数

package main

import (
   "fmt"
   "math"
)

func main(){
   /* 声明函数变量 */
   getSquareRoot := func(x float64) float64 {
      return math.Sqrt(x)
   }

   /* 使用函数 */
   fmt.Println(getSquareRoot(9))

}

支持闭包:

支持给类额外加方法:

package main

import (
   "fmt"  
)

/* 定义结构体 */
type Circle struct {
  radius float64
}

func main() {
  var c1 Circle
  c1.radius = 10.00
  fmt.Println("圆的面积 = ", c1.getArea())
}

//该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
  //c.radius 即为 Circle 类型对象中的属性
  return 3.14 * c.radius * c.radius
}

作用域

go中有局部变量和全局变量,函数内定义的是局部参数,函数外包内定义的是全局变量。
可以有相同变量名的局部和全局变量,函数内优先使用局部变量。
和python差不多。

数组:

声明长度的值放到类型前用[],例如 var n [4]int, 也可以用...来定义未知长度的数组var n [...]int,但是一经赋值,长度就固定了。以下实例中体现了声明、初始化等操作

package main

import (
   "fmt"
)

func main(){
   /* 声明数组并初始化 并输出*/
    var n =[5]int{1,2,4,3,5}   
    for i:=0;i<5;i++ {
        fmt.Printf("第%d个数是%d",i,n[i])
    }

   /* 声明数组,再初始化,再输出 */
   var m [4]float32
   for i:=0;i<4;i++{
       m[i]=float32(i)+1.0
   }
   for i:=0;i<4;i++ {
       fmt.Printf("第%d个数是%f\n",i,m[i])
    }
    /* 声明数组并初始化部分索引值 */
    var p =[3]int{1:2}
    for i:=0;i<3;i++ {
       fmt.Printf("第%d个数是%d\n",i,p[i])
    }
    /* 声明不确定长度数组并初始化部分索引值 */
    var o =[...]int{1,2}

    for i:=0;i<2;i++ {
       fmt.Printf("第%d个数是%d\n",i,o[i])
    }

}

二维数组:

append方法与python不同,append(a,b)的意思是把b作为一个元素加入到a里面,并且需要把返回值赋值给a才能改变a的值。

package main

import (
   "fmt"
)

func main(){
   /* 声明数组并初始化 并输出*/
    var n = [2][3] int{{1,2,3},{4,5,6}}
    for i := range n{
        fmt.Printf("ROW %d:",i)
        fmt.Println(n[i])
    }
   /* 声明数组,再初始化,再输出 */
   var m [4][3]float32
   for i:=0;i<4;i++{
        for j:=0;j<3;j++{
            m[i][j]=float32(i)+1.0+float32(j)
        }
   }
   for i:= range m {
       fmt.Printf("第%d行数是%f\n",i,m[i])
    }
    
    /* 声明等长度二维数组并初始化,未赋值部分置初始值 */
    var o =[3][3]int{{1,2,3},{4},{5,6}}

    for i:= range o {
       fmt.Printf("第%d个数是%d\n",i,o[i])
    }
   
    /* 声明不等长度二维数组并初始化*/
    var oo =[][]int{{1,2,3},{4},{5,6}}

    for i:= range oo {
       fmt.Printf("第%d个数是%d\n",i,oo[i])
    }
    /* 用一维数组给二维数组赋值*/
    var a = []string{"1a","2b"}
    var b = []string{"3c","4d"}
    var c = [][]string{}
    c=append(c,a)
    c=append(c,b)
    for i:=0;i<2;i++{
        fmt.Printf("第%d个数是%v\n",i,c[i])
   }

}
image.png

指针

我们都知道,变量是一种使用方便的占位符,用于引用计算机内存地址。
Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。

package main
import (
   "fmt"
)
func main(){
    /**ptr代表取指针保存的地址所存储的值,&a表示取变量a的地址*/
    var a int = 100
    var ptr *int 
    /*nil相当于其他语言的null*/
    if ptr == nil{
        fmt.Println("ptr是空指针")
    }
    ptr = &a
    if ptr != nil{
        fmt.Printf("ptr不是空指针:%d\n",ptr)
    }
    fmt.Printf("ptr保存的地址是a的地址,a的值是%d\n",*ptr)
    /*指针数组*/
    var ptr_arr [3]*string
    var arr = [3]string{"ab","cd","df"}
    for i:=0;i<3;i++{
        ptr_arr[i]=&arr[i]
        fmt.Printf("ptr_arr[%d]的值是%d,这是一个地址,该地址内保存了变量值是%s\n",i,ptr_arr[i],*ptr_arr[i])
    }
    /*指向指针的指针*/
    var ptrptr **int
    ptrptr = &ptr
    fmt.Printf("ptr保存的地址是a的地址,a的值是%d\n",*ptr)
    fmt.Printf("ptrptr保存的地址是ptr的地址,ptr的值是%d,ptr保存的是a的地址,a的值是%d\n",*ptrptr,**ptrptr)
    
    /*指针作为参数传递*/
    var b int = 200
    fmt.Printf("a的值是%d\n",a)
    fmt.Printf("b的值是%d\n",b)
    swap(&a,&b)
    fmt.Printf("a的值是%d\n",a)
    fmt.Printf("b的值是%d\n",b)
}
func swap(x *int,y *int){
    var tmp int
    tmp = *x
        /*此次赋值改变了该地址中的值*/
    *x = *y
    *y = tmp
}

结构体:

类似于类,但是比类轻量级,类似于数组,但是里面的元素又可以是不同类型的。介于两者之间的,可定义新类型。
使用type来标识结构体的名字,struct标识这是一个结构体

import (
   "fmt"
)

type book struct{
    name string
    price float32
    athor string
}
func print_book(book1 book){
    fmt.Printf("书名:%s\n价格:%f\n作者:%s\n",book1.name,book1.price,book1.athor)
}
func print_book_ptr(book1 *book){
    book1.name = "go如何学习"
    fmt.Printf("书名:%s\n价格:%f\n作者:%s\n",book1.name,book1.price,book1.athor)
}

func main(){
    /*给结构体赋值和引用结构体内部变量*/
    var book1 book
    book1.name="go学习笔记"
    book1.price=10.0
    book1.athor="yaqingirl"
    fmt.Printf("书名:%s,价格:%f,作者:%s\n",book1.name,book1.price,book1.athor)
    /*结构体作为变量*/
    print_book(book1)
    /*结构体地址作为变量*/
    print_book_ptr(&book1)
    
}

切片:

类似python的切片,可以看做可变长度的数组,可使用索引截取值

package main

import (
   "fmt"
   "reflect"
)

func main(){
    /*定义切片的两种方式*/
    var number []int
    fmt.Printf("number:%t,类型:%s\n",number,reflect.TypeOf(number))
    /*长度为3,最长可扩展到6*/
    var number1=make([]int,3,6)
    fmt.Printf("number1:%s,类型:%s\n",number1,reflect.TypeOf(number1))
    /*初始化切片*/
    var a =[]int{1,2,3}
    fmt.Printf("a=%s\n",a)
    b:=a[:]
    fmt.Printf("b=%s\n",b)
    /*len函数可以取切片长度,cap可取切片的容量*/
    fmt.Printf("len(a)=%d,cap(a)=%d\n",len(a),cap(a))
    /*如果切片未赋值,则为空切片,是nil*/
    var c []int
    if nil == c{
        fmt.Printf("c=%s",c)
    }
    /*切片截取*/
    var log=[]int{0,1,2,3,4,5,6,7,8}
    /*不写start和end默认是全部,如果有开始和结束则左边是开区间,右边闭区间*/
    printSlice(log[:])
    printSlice(log[1:7])
    printSlice(log[2:])
    printSlice(log[:4])
    /*用copy拷贝一个切片,用append扩展一个切片*/
    var log2=make([]int,len(log),cap(log)*2)
    copy(log2,log)
    printSlice(log2)
    log3:=append(log,9,10)
    printSlice(log3)

}
func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
image.png

范围range

Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对

package main

import (
   "fmt"
)

func main(){
    /*使用range遍历切片*/
    var number=[]int{1,2,3}
    sum := 0
    for i,num := range number{
        fmt.Printf("第%d个数是%d\n",i,num)
        sum+=num
    }
    fmt.Printf("sum=%d\n",sum)
    /*使用range遍历map*/
    mymap := map[string]string{"a":"b","c":"d"}
    for key ,value := range mymap{
        fmt.Printf("key=%s,value=%s\n",key,value)
    }
}

集合map

go的map类似于python的dict。

package main

import (
   "fmt"
)

func main(){
    /*两种方式创建map*/
    mymap := map[string]string{"a":"b","c":"d"}
    for key ,value := range mymap{
        fmt.Printf("key=%s,value=%s\n",key,value)
    }
    var my_map2=make(map[string]string)
    my_map2["123"]="6"
    my_map2["234"]="9"
    for key ,value := range my_map2{
        fmt.Printf("key=%s,value=%s\n",key,value)
    }
    /*删除元素*/
    delete(my_map2,"123")
    for key ,value := range my_map2{
        fmt.Printf("key=%s,value=%s\n",key,value)
    }
}

递归:go支持递归,和其他语言一样。递归要有出口条件,否则会一直循环调用

类型转换:不支持隐式转换,需要明确转换

package main
import (
   "fmt"
)
func main(){
    /*类型转换:会报错*/
    a := 1
    b :=1.0
    c := a+b
    fmt.Printf("a+b=%f",c)
}
image.png
package main
import (
   "fmt"
)
func main(){
    /*类型转换*/
    a := 1
    b :=1.0
    c := a+int(b)
    fmt.Printf("a+b=%d",c)
}
image.png

接口:

类似于上面的结构体struct,也是用户可以自定义的类型。用作类或者结构体共同的动作抽象

package main

import (
   "fmt"
)
type Phone interface{
    call()
}
type NokiaPhone struct{
    name string
}
type iPhone struct{
    name string
}
func (iphone iPhone)call(){
    fmt.Printf("I AM CALLING USE IPHONE\n")
}
func (nokia NokiaPhone)call(){
   fmt.Printf("I AM CALLING USE NOKIA\n")
}
func main(){
    /*结构体的接口实现*/
    iphone := new(iPhone)
    iphone.call()
    nokia := new(NokiaPhone)
    nokia.call()
    /*还可以这样*/
    var phone Phone
    phone = new(iPhone)
    phone.call()
    phone = new(NokiaPhone)
    phone.call()
}

错误处理:实现Error()接口

系统中该接口定义如下:

type error interface {
    Error() string
}

示例如下:

package main
import (
   "fmt"
)
type DivideError struct{
    divider int
    dividee int
}
func (de DivideError)Error(){
    fmt.Printf("Error msg:除数是%d,被除数是%d,除数不能为0\n",de.divider,de.dividee)
}
func divide(a,b int) (result int,error_str string){
    
    if b==0{
        derror := DivideError{divider:a,dividee:b}
        derror.Error()
        return 
    }else{
        return a/b,""
    }   
}
func main(){
    var a ,b int = 100,0
    divide(a,b)
    var c ,d int = 100,10
    result,_ := divide(c,d)
    fmt.Printf("result =%d",result)
}

并发:

go使用goroutine开启轻量级多线程进行并发。语法格式:go f(),意思是启动一个goroutine,运行f函数。
Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。
可以使用通道channel来实现多个goroutine之间的数据同步,通道定义:ch := make(chan int ).

package main

import (
   "fmt"
)
func sum(s []int,c chan int){
    sum :=0
    for _,v :=range s{
        sum+=v
    }
        //输入通道
    c <- sum
    
}
func main(){
    var nums = []int{1,3,5,-9,4,6,7}
    //5是缓冲区
    c := make(chan int,5)
    go sum(nums[:len(nums)/2],c)
    go sum(nums[len(nums)/2:],c)
        //从通道输出
    x,y := <-c,<-c
    fmt.Printf("x,y,x+y=%d,%d,%d",x,y,x+y)

}

带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。

关闭通道:通道如果不主动关闭,那么如果没有进程被杀,通道一直存在。下面通过利用关闭通道实现主线程等待子线程执行完才关闭主线程。

package main

import (
    "fmt"
    "time"
)

func mysleep() {
    time.Sleep(150 * time.Millisecond)
    fmt.Printf("hello")
    
}
func mysleep1() {
    time.Sleep(100 * time.Millisecond)
    fmt.Printf("world")
}
func main() {
    go mysleep()
    mysleep1()
}

上面的代码运行结果:


image.png

很显然,子线程还没执行完就结束了主线程,子线程也随之结束。现在我们想让主等子。

package main

import (
    "fmt"
    "time"
)

func mysleep(c chan int) {
    time.Sleep(150 * time.Millisecond)
    fmt.Printf("hello")
    c <- 0
    close(c)

}
func mysleep1() {
    time.Sleep(100 * time.Millisecond)
    fmt.Printf("world")
}

func main() {
    c := make(chan int)
    go mysleep(c)
    mysleep1()
    num := <-c
    fmt.Printf("num:%d", num)
}

运行结果


image.png

你可能感兴趣的:(go基础语法学习,对比python)