【Go语言】基本的语法

  昨天花了几个小时的时间把Go的语法过了一遍,发现Go语言的语法核心和大部分编程语言的规则还是挺相近的,差别的就是不同的书写规范。还有就是前天安装VScode编译器那个插件把人弄得恶心了,总是安装不成功,找了各种办法(包括FQ)还是没能解决。最后也没有过于执着的继续弄了。大概记录以下基础语法部分。

程序结构


  最简单的例子: 

package main           # 包申明

import "fmt"           # 导入的包,和py挺像的,只不过go需要用”“括起来

func main() {          # 函数入口,就像c语言中的main函数一样, 另外{不能单独另起一行,会报错。
   fmt.Println("Hello, World!")         # 打印字符, 一行代表一个语句的结束,不用向c一样需要符号,虽然Go也可以多条语句一行(加;号),但是并不推荐这种用法。
} 

  package main 定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。

  import "fmt" 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。

  func main() 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。

  fmt.Println(...) 可以将字符串输出到控制台,并在最后自动增加换行字符 \n。

  注释规则第一种是 // , 第二种是 /* */

数据类型


【Go语言】基本的语法

  类型转换:用于将一种数据类型的变量转换为另外一种类型的变量, 使用方式 转换的类型(待转换的变量名) 。 例如 int(a) ( a为 float)

变量声明


  在Go语言中声明的变量必须得到使用,否则会报错。

  第一种声明变量的方式

var  变量名 类型      // 如果没有给定初始化的值的话,系统会设置为0
变量名 =  值
package main
import "fmt"
func main() {
    var i int
    var f float64
    var b bool
    var s string
    fmt.Printf("%v %v %v %q\n", i, f, b, s)
}
//result
0 0 false ""

  第二种根据赋的值自动判断类型

var 变量名 = 值

  第三种方法是省略 var 使用 变量名 := 值 来定义新的变量。但是如果变量名已经声明过了会报错。

var  变量名 := 值
var intVal int 

intVal :=1 // 这时候会产生编译错误

intVal,intVal1 := 1,2 // 此时不会产生编译错误,因为有声明新的变量,因为 := 是一个声明语句, 前面的intval相当于一个赋值操作。

  多个变量申明方式

//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3

var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断
vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误 // 这种因式分解关键字的写法一般用于声明全局变量 var ( vname1 v_type1 vname2 v_type2 )

常量


  常量是一个简单值的标识符,在程序运行时,不会被修改的量。常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。这个和c语言中的常量是一个意思,当被定义之后就不能被修改,不然会报错。

const  变量名  类型(可选) = 值      # 单个常量
const 变量1, 变量2 = 值1,值2 # 多个常量赋值

  iota是一个特殊的常量,也可以认为是一个可以被编译器修改的常量。iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

package main
import "fmt"
func main() {
    const (
            a = iota    //0            # 遇到iota开始记为0,下边自动赋值
            b             //1
            c              //2
            d = "ha"    //独立值,iota += 1         # 指定自定义的值, 下面如果未指定其他值的话,会和该行值一样。 此时iota不会中断计数。
            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

 运算符


  和c语言的运算符一样, +、-、*、/、%、++、--、==、!=、>、<、>=、<=、&&、||、!、>>、<<另外还有赋值运算符 +=、-=、/=等等, 在Go语言中没有前++和前--语法。

条件选择语句


  一、if ...else if .... else 语句格式

package main

import (
        "fmt"
)
func main()  { 
        a := -1
        if a > 0{
                fmt.Println("11111")
        }else if a == 0{
                fmt.Println("2222")
        }else{
                fmt.Println("333")
        }
}

  二、switch语句:用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止。语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加 break。 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用 fallthrough 。

package main
import "fmt"
func main() {
   var grade string = "B"

   switch {
      case grade == "A" :
         fmt.Printf("优秀!\n" )     
      case grade == "B", grade == "C" :
         fmt.Printf("良好\n" )      
      case grade == "D" :
         fmt.Printf("及格\n" )      
      case grade == "F":
         fmt.Printf("不及格\n" )
      default:
         fmt.Printf("差\n" );
   }
   fmt.Printf("你的等级是 %s\n", grade );      
}

  当使用fallthrough时会强制执行后面的 case 语句,不会判断下一条 case 的表达式结果是否为 true。

package main
import "fmt"
func main() {
   var grade string = "B"

   switch {
      case grade == "A" :
         fmt.Printf("优秀!\n" )     
      case grade == "B", grade == "C" :
         fmt.Printf("良好\n" )
         fallthrough       case grade == "D" :
         fmt.Printf("及格\n" ) 
         fallthrough      case grade == "F":
         fmt.Printf("不及格\n" )
      default:
         fmt.Printf("差\n" );
   }
 fmt.Printf("你的等级是 %s\n", grade );      
}
// 运行结果
良好
及格
不及格
你的等级是 B

  三、select语句是在Go封装好了的一个类似于消费者生产者模型的语法,在Go中有一种数据类型为chan,他相当于产生了一个和管道类似的东西,可以接收数据和发送数据。而select的工作就是检查chan有没有可读数据,默认为阻塞状态,case语句可以有很多个。相当于多个消费和生产者的模型。下面实现一个斐波那契数列

package main
import "fmt"
func fibonacci(c, quit chan int) {
    x, y := 1, 1     
    for {
        select {
        case c <- x:        # 将结果发送给管道中
        x, y = y, x + y
        case <-quit:          # 当收到0时退出
        fmt.Println("quit")
        return
    }
    }
} 
func main() {
        c := make(chan int)          # 定义两个通道
        quit := make(chan int)
        go func() {                  # 匿名函数并执行, 执行十次
        for i := 0; i < 10; i++ {
        fmt.Println(<-c)
      }
      quit <- 0              # 退出
      }()
    fibonacci(c, quit)
}                    

循环语句 


 一、for 循环。 for循环的形式一共有三种。在后面还可以和rang()及进行组合对数组、字典及进行遍历。

// 第一种循环形式和c语言一样
for i:=0(初始变量); i < n(条件变量); i++{}

// 第二种循环形式, 相当于while 条件
for condition{}

//第三种, 相当于while True 
for {}
// 例子
package main import "fmt" func main(){   sum := 0;   for index:=0; index < 10 ; index++ {   sum += index   }
fmt.Println("sum is equal to ", sum)   }
// 输出:sum is equal to 45

函数


  函数声明告诉了编译器函数的名称,返回类型,和参数用于执行一个任务。Go 语言最少有个 main() 函数。

func 函数名( 输入参数列表 ) (返回类型,多个返回类型时用括号) {
   函数体
}
函数名:函数名称,函数名和参数列表一起构成了函数签名。
输入参数列表:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
返回类型:返回类型,函数返回一列值。返回类型是该列值的数据类型。有些功能不需要返回值,这种情况下 返回类型 不是必须的。  
/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
   var result int

   if (num1 > num2) {
      result = num1
   } else {
      result = num2
   }
   return result 
}

  这里调用函数在传递参数时,会涉及到值传递和引用传递两种类型,这个c语言中一样,值传递就是调用函数时传递的参数,相当于复制了一份参数,在被调用函数中对参数进行修改不会影响原函数的值。 而引用传递引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。。

// 值传递
package main
import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 100
   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, y int) int {
   var temp int

   temp = x /* 保存 x 的值 */
   x = y    /* 将 y 值赋给 x */
   y = temp /* 将 temp 值赋给 y*/

   return temp;
}
// 引用传递
package main
import "fmt"

func main() {
   var a int = 100
   var b int= 200

   fmt.Printf("交换前,a 的值 : %d\n", a )
   fmt.Printf("交换前,b 的值 : %d\n", b )
   /* 调用 swap() 函数   &a 指向 a 指针,a 变量的地址     &b 指向 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中也支持匿名函数,这样就可以直接使用函数内申明变量并使用外层函数的的变量。

package main

import "fmt"

func getSequence() func() int {
   i:=0
   return func() int {   # 匿名函数, 同时也形成了闭包
      i+=1
     return i  
   }
}

func main(){
   nextNumber := getSequence()   # 使用一个变量来接收返回的函数
fmt.Println(nextNumber()) # 调用函数 fmt.Println(nextNumber()) }

  另外带参数的闭包形式为 

package main

import "fmt"
func main() {
    add_func := add(1,2)
    fmt.Println(add_func())
    fmt.Println(add_func())
}

func add(x1, x2 int) func()(int,int)  {
    i := 0
    return func() (int,int){
        i++
        return i,x1+x2
    }
}

数组


数组和c语言中的数组一样,只不过命名方式不同, 访问方式还是一样的,可以通过下标来访问。

var 变量名 [容量大小] 变量类型

var nums[10] int      # 一维数组


var 变量名 [容量大小][容量大小]...[容量大小] 变量类型    # 多维数组
var martix [5][10][4]int

a = [3][4]int{ {0, 1, 2, 3} ,   /*  第一行索引为 0 */
 {4, 5, 6, 7} ,   /*  第二行索引为 1 */
 {8, 9, 10, 11},   /* 第三行索引为 2 */
}                                                       初始化二维数组#

  初始化一维数组  

var balance = [5]int{1,2,3,4,5}      # 注意是{}zhi\'zhen  

指针


  Go语言的指针完全和c语言一样,都是通过&取变量的地址, * 取指针执行的值。而当一个指针被定义后没有分配到任何变量时,它的值为 nil,也称为空指针。nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。

// 命名规则
var 变量名 *变量类型
var ip  *int        /* 指向整型*/

package main
import "fmt"
// 例子
func main() {
   var a int= 20   /* 声明实际变量 */
   var ip *int        /* 声明指针变量 */

   ip = &a  /* 指针变量的存储地址 */

   fmt.Printf("a 变量的地址是: %x\n", &a  )

   fmt.Printf("ip 变量储存的指针地址: %x\n", ip )

   fmt.Printf("*ip 变量的值: %d\n", *ip )
}

// result
   a 变量的地址是: 20818a220
   ip 变量储存的指针地址: 20818a220
   *ip 变量的值: 20

  二级指针:一个指针变量存放的又是另一个指针变量的地址,访问指向指针的指针变量值需要使用两个 * 号

package main
import "fmt"

func main() {
   var a int
   var ptr *int
   var pptr **int
   a = 3000
   ptr = &a

   /* 指向指针 ptr 地址 */
   pptr = &ptr

   fmt.Printf("变量 a = %d\n", a )
   fmt.Printf("指针变量 *ptr = %d\n", *ptr )
   fmt.Printf("指向指针的指针变量 **pptr = %d\n", **pptr)

  指针作为函数参数

package main
import "fmt"

func main() {
   var a int = 100
   var b int= 200
   swap(&a, &b);

   fmt.Printf("交换后 a 的值 : %d\n", a )
   fmt.Printf("交换后 b 的值 : %d\n", b )
}

/* 交换函数这样写更加简洁,也是 go 语言的特性,可以用下,c++ 和 c# 是不能这么干的 */ 
func swap(x *int, y *int){
    *x, *y = *y, *x
}

结构体


 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合 

// 结构体的定义如下
type 结构体名  struct {
   member 类型;
   member 类型;
   ...
   member 类型;
}
使用结构名.成员名进行访问

// 例子
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"})
}

  结构体作为函数参数、指针

package main
import "fmt"

type Books struct {
   title string
   author string
   subject string
   book_id int
}

func main() {
   var Book1 Books              
  // 赋值
   Book1.title = "Go 语言"
   Book1.author = "www.runoob.com"
   Book1.subject = "Go 语言教程"
   Book1.book_id = 6495407

   printBook(Book1)    // 结构体作为参数
   printBook(&Book1)  // 结构体作为指针
}

func printBook( book Books ) {
   fmt.Printf( "Book title : %s\n", book.title);
   fmt.Printf( "Book author : %s\n", book.author);
   fmt.Printf( "Book subject : %s\n", book.subject);
   fmt.Printf( "Book book_id : %d\n", book.book_id);
}

func printBook1( book *Books ) {
   fmt.Printf( "Book title : %s\n", book.title);
   fmt.Printf( "Book author : %s\n", book.author);
   fmt.Printf( "Book subject : %s\n", book.subject);
   fmt.Printf( "Book book_id : %d\n", book.book_id);
}

切片


  在Go中 数组的长度固定,而这在特定的场景就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大,定义和数组的定义方法一样,或者可以用make([]类型,长度)来定义切片。这个有点类似python中的切片。

//切片的定义
var 变量名[]type

slice := make([]数据类型, len)  // len为长度

make([]T, len, capacity) // capacity为最大容量
// 例子 package main import "fmt" func main() { var numbers = make([]int, 3, 5) printSlice(numbers) } func printSlice(x []int){ fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x) } //切片使用, 和python中使用方法一样。 s := arr[startIndex:endIndex] package main import "fmt" func main() { numbers := []int{0,1,2,3,4,5,6,7,8} /* 打印子切片从索引1(包含) 到索引4(不包含)*/ fmt.Println( numbers[1:4]) /* 默认下限为 0*/ fmt.Println(numbers[:3]) } // 添加和扩容 //如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。 package main import "fmt" func main() { var numbers []int /* 允许追加空切片 */ numbers = append(numbers, 0) /* 向切片添加一个元素 */ numbers = append(numbers, 1) numbers = append(numbers, 2,3,4) printSlice(numbers) }

Range


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

package main
import "fmt"
func main() { nums := []int{2, 3, 4} // 定义一个数组 sum := 0
//在数组上使用range将传入index和值两个变量。 for i, num := range nums { // i为数组下标, num为对应的值 if num == 3 { fmt.Println("index:", i) } }
//range也可以用在map的键值对上。 dicts:= map[string]string{"a": "apple", "b": "banana"} for k, v := range dicts{ fmt.Printf("%s -> %s\n", k, v) } //range也可以用来枚举Unicode字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。 for i, c := range "go" { fmt.Println(i, c) } }

Map(字典)


  Map 是一种无序的键值对的集合,通过 key-value 来快速检索数据。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。

// Map定义
/* 声明变量,默认 map 是 nil */
var 变量名 map[键类型]值类型

/* 使用 make 函数 */
变量名:= make(map[键类型]值类型)

// 例子
package main
import "fmt"

func main() {
    countryCapitalMap := make(map[string]string)

    /* map插入key - value对,各个国家对应的首都 */
    countryCapitalMap [ "France" ] = "Paris"
    countryCapitalMap [ "Italy" ] = "罗马"

    /*使用键输出地图值 */ 
    for country := range countryCapitalMap {
        fmt.Println(country, "首都是", countryCapitalMap [country])
    }

    /*查看元素在集合中是否存在 */
    capital, ok := countryCapitalMap [ "美国" ]      /*如果确定是真实的,则存在,否则不存在 */
 
    if (ok) {
        fmt.Println("美国的首都是", capital)
    } else {
        fmt.Println("美国的首都不存在")
    }

    // 删除键值
    delete(countryCapitalMap, "France")
        fmt.Println("法国条目被删除")
}

接口


  Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

/* 定义接口 */
type interface_name interface {
   method_name1 [返回类型]
   method_name2 [返回类型]
}

/* 定义结构体 */
type struct_name struct {
   /* variables */
}

/* 实现接口方法 */
func (struct_name_variable struct_name)method_name1() [return_type] {
   /* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
   /* 方法实现*/
}

// 例子
package main                                                                           
import  "fmt"

type Man interface {
    name() string;
    age() int;
}

type Woman struct {
}

func (woman Woman)name() string {
   return "Jin Yawei"
}

func (woman Woman) age() int {
   return 23;
}

type Men struct {
}

func ( men Men) name() string {
   return "liweibin";
}

func ( men Men) age() int {
    return 27;
}

func main(){
    var man Man;

    man = new(Woman);
    fmt.Println( man.name());
    fmt.Println( man.age());
    man = new(Men);
    fmt.Println( man.name());
    fmt.Println( man.age());
}