Go基础知识

语言结构

  • Go语言基础组成:包声明,引入包,函数,变量,语句&表达式,注释
package main  /* 定义包名,必须在源文件非注释第一行指明属于哪个包 */
import "fmt"  /* 引入包 */
func main() {  /* 函数,如果有init()函数会先执行该函数,注意{不能单独一行 */
    fmt.Printf("hello, world")
}
  • 运行程序
    • go run xx.go
    • go build xx.go # 生成二进制文件
      • ./xx

基础语法

关键字

break   default     func    interface   select
case    defer       go      map         struct
chan    else        goto    package     switch
const   fallthough  if      range       type
continue for        import  return      var

预定义标识符

append  bool    byte    cap     close   complex     complex64   complex128  uint16
copy    false   imag    int     int8    int16       uint32      float32     float64
int32   int64   iota    len     make    new         nil         panic       uint64
print   println real    recover string  true        nint        uint8       uintptr
  • Go语言中变量的声明必须使用空格隔开
  • 只有 package 名称为 main 的源码文件可以包含 main 函数。
  • 一个可执行程序有且仅有一个 main 包。通过 import 关键字来导入其他非 main 包。
package main
import (
    "fmt"
    "math"
    "test/controllers"  /* 项目名/包名 */
)
/* 方法的调用为:包名.方法名 */
controllers.Test()  本包内方法名可为小写,包外调用方法名首字母必须为大写
  • 通过const关键字定义常量
  • 函数体外使用var关键字定义全局变量和赋值
  • 通过type关键字进行结构(struct)和接口(interface)声明
  • 通过func关键字进行函数声明
  • Go使用大小写决定改常量、变量、类型、接口、结构或函数是否可以被外部包调用
  • 函数名首字母小写为private(私有),大写为public(公开)

数据类型

  • 布尔型
  • 数字类型
  • 字符串类型
  • 派生类型
    • 指针类型(Pointer)
    • 数组类型
    • 结构化类型(struct)
    • Channel类型
    • 函数类型
    • 切片类型
    • 接口类型(interface)
    • Map类型

语言变量

  • 声明变量一般使用 var 关键字
var xx, yy type  /* 可以一次声明多个变量 */
1,变量声明,如果没有初始化,默认为零值(系统默认值)
    var a int
2,根据值自行判断
    var xx = true
3,省略var, :=左侧如果没有声明新的变量,会编译错误
    f := 'xx'  等同于 var f string = 'xx'  该形式只能用在函数体内

值类型和引用类型

  • 像int, float, bool, string这些基本类型都属于值类型,使用这些类型的变量直接指向内存中的值
  • 当使用等号将一个变量的值赋值给另一个变量时,如 j = i 实际上是在内存中将i的值进行了拷贝
  • 可以通过 &i获取变量的内存地址(每次地址可能都不一样),值类型的变量值存储在栈中
  • 更复杂的数据通常会使用多个字,这些数据一般使用引用类型保存
  • 一个引用类型的变量存储的是值对应的内存地址,这个内存地址被称之为指针,这个指针实际上也被存在另外的某一个字中

语言常量

  • 常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
  • 显式类型定义:const b string = ‘abc’
  • 隐式类型定义:const b = ‘abc’
  • 常量可以用作枚举
const (
    Unknown = 0
    Female = 1
    Male = 2
)
  • 常量可以用len(), cap(), unsafe(), Sizeof()函数计算表达式值,常量表达式中,函数必须是内置函数,否则编译不过

iota

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

语言运算符

  • 算术运算符
    • Go 的自增,自减只能作为表达式使用,而不能用于赋值语句
      运算符 | 描述 | 实例(a=10,b=20)
      —|---|—
  • | 相加 | a+b=30
  • | 相减 | a-b=-10
  • | 相乘 | a*b=200
    / | 相除 | b/a=2
    % | 求余 | b%a=0
    ++ | 自增 | a++=21
    – | 自减 | a–=9
  • 关系运算符
运算符描述实例(a=10,b=20)
==检查两个值是否相等,相等返回True(a == b) False
!=检查两个值是否不相等,不等返回True(a != b) True
>检查左边值是否大于右边值(a > b) False
<检查左边值是否小于右边值(a < b) True
>=检查左边值是否大于等于右边值(a >= b) False
<=检查左边值是狗小于等于右边值(a <= b) True
  • 示例
package main

import "fmt"

func main() {
    var a int = 21
    var b int = 10
    
    if ( a == b ) {
        fmt.Printf("1,a==b")
    } else {
        fmt.Printf("2,a!=b")
    }
    if ( a < b ) {
        fmt.Printf("3,a<b")
    } else {
        fmt.Print("4,a!<b")
    }
    if ( a > b ) {
        fmt.Printf("5,a>b")
    } else {
        fmt.Printf("6,a!>b")
    }
    a = 5
    b = 20
    if ( a <= b) {
        fmt.Printf("7,a<=b")
    }
    if ( a >= b) {
        fmt.Printf("8,a>=b")
    }
}
  • 逻辑运算符
运算符描述实例(a=10,b=20)
&&AND,两边都是True,条件为True(a && b) False
OR,两边有一个True,条件为true
!NOT,如果条件为True,则逻辑条件为False!(a && b) True
  • 示例
package main

import "fmt"

func main() {
    var a bool = true
    var b bool = false
    if ( a && b ) {
        fmt.Printf("1,true\n")
    }
    if ( a || b ) {
        fmt.Printf("2,true\n")
    }
    a = false
    b = true
    if ( a && b ) {
        fmt.Printf("3,true\n")
    } else {
        fmt.Printf("3,false\n")
    }
    if ( !(a && b) ) {
        fmt.Printf("4,true\n")
    }
}
  • 位运算符(对整数在内存中的二进制位进行操作)
  • 赋值运算符
  • 其他运算符
    • 指针变量保存的是一个地址值,会分配独立的内存来存储一个整型数字。当变量前面有 * 标识时,才等同于 & 的用法,否则会直接输出一个整型数字
      运算符 | 描述 | 实例(a=10,b=20)
      —|---|—
      & | 返回变量存储地址 | &a, 给出变量实际地址
  • | 指针变量 | *a,是一个指针变量
  • 示例
package main

import "fmt"

func main() {
    var a int = 4
    var b int32
    var c float32
    var ptr *int
    
    fmt.Printf("变量类型为 %T\n", a)
    fmt.Printf("变量类型为 %T\n", b)
    fmt.Printf("变量类型为 %T\n", c)
    
    ptr = &a
    fmt.Printf("a的值 %d\n", a)
    fmt.Printf("*ptr为 %d\n", *ptr)
}
  • 运算符优先级
  • 有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低
优先级运算符
5* / % << >> & &^
4+ -
3== != < <= > >=
2&&
1|

条件语句

  • Go 没有三目运算符,所以不支持 ?: 形式的条件判断
语句描述
if 语句if语句由一个布尔表达式紧跟一个/多个语句组成
if else语句if语句后面可以使用else语句,表达为False时执行
if 嵌套语句可以在if / if else语句中嵌入一个或多个else if语句
switch语句switch语句基于不同条件执行不同动作
select语句select语句类似switch语句,但是select会随机执行一个可运行的case,如果没有将阻塞,直达有case
  • if语句
# 判断数值大小
package main
import "fmt"
func main() {
    var a int = 10
    if a < 20 {
        fmt.Printf("a小于20")
    }
    fmt.Printf("a的值为%d\n", a)
}
# 判断偶数
package main
import "fmt"
func main() {
    var a int
    fmt.Print("输入一个数字")
    fmt.Scan(&a)
    if a%2 == 0 {
        fmt.Printf("a是偶数\n", a)
    } else {
        fmt.Printf("a不是偶数", a)
    }
}

(1) 不需使用括号将条件包含起来
(2) 大括号{}必须存在,即使只有一行语句
(3) 左括号必须在if或else的同一行
(4) 在if之后,条件语句之前,可以添加变量初始化语句,使用;进行分隔
(5) 在有返回值的函数中,最终的return不能在条件语句中
  • if else语句
  • if 嵌套语句
# 判断输入
package main
import "fmt"
func main() {
    var a, b int
    fmt.Printf("请输入密码")
    fmt.Scan(&a)
    if a == 534221 {
        fmt.Printf("请再次输入密码")
        fmt.Scan(&b)
        if b == 534221 {
            fmt.Printf("密码正确")
        } else {
            fmt.Printf("密码错误")
        }
    } else {
        fmt.Printf("密码错误")
    }
}
  • switch语句
  • witch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用 fallthrough
package main
import "fmt"
func main() {
    var grade string = "B"
    var marks int = 90
    /* 可以同时测试多个可能符合条件的值,使用逗号分割它们 case val1, val2, val3 */
    switch marks {
        case 90: grade = "A"
        case 80: grade = "B"
        case 50,60,70: grade = "C"
        default: grade = "D"
    }
    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.Print("不及格\n")
        default:
            fmt.Printf("差\n")
    }
    fmt.Printf("等级为 %s",grade)
}
  • Type Switch
  • switch还可以被用于 type-switch来判断某个interface变量中实际存储的变量类型
package main
import "fmt"
func main() {
    var x interface{}
    switch i := x.(type) {
        case nil:
            fmt.Printf("x类型",i)
        case int:
            fmt.Printf("xint类型")
        case float64:
            fmt.Printf("x是float64")
        case func(int) float64:
            fmt.Printf("是func(int)")
        case bool, string:
            fmt.Printf("是bool或string型")
        default:
            fmt.Printf("未知型")
    }
}
  • 使用fallthrough会强制执行后面的case语句,fallthrough不会判断下一条case表达式是否为True
1,支持多匹配
switch{
    case 1,2,3,4:
    default:
}
2,不同case之间不适用break分隔,默认只会执行一个case
3,如果要执行多个case,需要使用fallthrough,也可以用break终止
switch {
    case false:
            fmt.Println("1、case 条件语句为 false")
            fallthrough  # 会执行上下两条case
    case true:
            fmt.Println("2、case 条件语句为 true")
            fallthrough
    case false:
            fmt.Println("3、case 条件语句为 false")
            fallthrough
    default:
  • select语句
  • 是Go中的一个控制结构,类似用于通信的switch语句,每个case必须是一个通信操作,要么发送要么接收
  • select随机执行一个可运行的case,如果没有case可与西宁,它将阻塞,直到有case运行。一个默认的子句应该总是可运行的
package main
import "fmt"
func main() {
    var c1, c2, c3 chan int
    var i1, i2 int
    select {
        case i1 = <-c1:
            fmt.Printf("received", i1)
        case c2 <- i2:
            fmt.Printf("sent", i2)
        case i3, ok := (<-c3):
            if ok {
                fmt.Printf("received", i3)
            } else {
                fmt.Printf("c3 is close")
            }
        default:
            fmt.Printf("no communication\n")
    }
}
  • select 会循环检测条件,如果有满足则执行并退出,否则一直循环检测。

循环语句

for循环

  • for循环是一个循环控制结构,可以执行指定次数的循环
  • Go的for循环有三种形式,只有其中一种使用分号
for init; condition; post {}
    init 一般为赋值表达式,给控制变量赋初始值
    condition 关系表达式/逻辑表达式
    post 赋值表达式,给控制变量增量减量
# for语句执行过程如下:
    1,先对表达式1赋值
    2,判断赋值表达式init是否满足给定条件,若值为真满足循环条件,执行循环体内语句,然后执行post,进入第二次村换,再判断condition;否则判断condition值为假,终止循环
for condition {}
for {}

# 计算1-10数字之和
package main
import "fmt"
func main() {
    sum := 0
    for i := 0; i <= 10; i++ {
        sum += i
    }
    fmt.Printf(sum)
}

# 无限循环
package main
import "fmt"
func main() {
    sum := 0
    for {
        sum++  //无限循环
    }
    fmt.Printf(dum)
}

# For-each range循环,可以对字符串、数组、切片等进行迭代输出元素
package main
import "fmt"
func main() {
    string := []string{"google", "runoob"}
    for i, s := range string {
        fmt.Printf(i, s)
    }
    numbers := [6]int{1, 2, 3, 5}
    for i,x := range numbers {
        fmt.Printf('第 %d 位值 %d\n', i, x)
    }
}

循环嵌套

  • 循环控制语句
    • break # 用于中断当前for循环或跳出switch语句
用于循环语句中跳出循环,并开始执行循环之后的语句
break在switch(开关语句)中执行一条case后跳出语句作用
在多重循环中,可以用引号label标出想break的循环

# 示例
package main
import "fmt"
func main() {
    var a int = 10
    for a < 20 {
        fmt.Printf("a的值为%d\n", a);
        a++;
        if a > 15 {
            break;
        }
    }
}
# 使用标记
func main() {
    re:
        for i := 1; i <= 3; i++ {
            fmt.Printf("i: %d\n", i)
            for i2 := 11; i2 <= 13; i2++ {
                fmt.Printf("i2: %d\n", i2)
                break re
            }
        }
}
  • continue
# 跳出当前循环的剩余语句,然后继续进行下一轮循环
for循环中,执行continue语句会触发for增量语句执行
多重循环中,可以用标号label标出想continue的循环
  • goto
将控制转移到被标记的语句
可以无条件转移到过程中指定的行
goto语句通常和条件语句配合使用,可用来实现条件转移,构成循环,跳出循环体等功能
但是,结构化程序设计中一啊不能不主张使用goto语句,以免造成程序流程的混乱
func main() {
    var a int = 10
    LOOP: for a < 20 {
        if a == 15 {
            a = a + 1
            goto LOOP  # 当a==15时跳过迭代回到开始LOOP处
        }
        fmt.Printf(a)
        a++
    }
}
  • 示例
(9*9乘法表)
package main
import "fmt"
func main() {
    // i := 1  > var i int = 1
    for i := 1; i <= 9; i++ {
        for j := 1; j <= i; j++ {
            fmt.Printf("%d * %d = %d", j, i, j*i)
        }
        fmt.Printf("")
    }
}
(输出1-100素数)
package main
import "fmt"
func main() {
    var a, b int
    for a = 2; a <= 100; a++ {
        for b = 2; b <= (a / b); b++ {
            if a%b == 0 {
                break
            }
        }
        if b > (a / b) {
            fmt.Printf("%d\t是素数\n", a)
        }
    }
}

语言函数

  • Go最少有一个main()函数,用函数划分不同的功能,逻辑上每个函数执行的是指定的任务
  • 函数声明告诉了编译器函数的名称,返回类型和参数
package main
import "fmt"

func main() {
    var a int = 100
    var b int = 200
    var ret int
    ret = max(a, b)
    fmt.Printf("最大值为 %d\n", ret)
}
func max(num1, num2 int) int {
    var result int
    if (num1 > num2) {
        result = num1
    } else {
        result = num2
    }
    return result
}
  • 函数参数
    • 函数如果使用参数,该变量为函数的形参
    • 形参类似定义在函数体内的局部变量
    • 调用函数可以通过两种方式传递参数:
    • 值传递:调用函数时将实际参数复制一份到函数中,函数对参数修改不影响实际参数(默认go使用是值传递)
    • 引用传递:调用函数时将实际参数的地址传递到函数中,在函数中对参数进行修改,将影响到实际参数(引用传递指针参数传递到函数内)
  • 函数用法
  • 函数作为另外一个函数的实参
package main
import (
    "fmt"
    "math"
)
func main() {
    getSquareRoot := func(x float64) float64 {  # 声明函数变量
        return math.Sqrt(x)
    }
    fmt.Println(getSquareRoot(9))  # 使用函数
}

闭包

闭包是匿名函数,匿名函数的优越性在于可以直接使用函数内变量,不必申明

package main
import "fmt"
func main() {
    add_func := add(1,2)
    fmt.Println(add_func())
    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
    }
}

方法

Go中同时有函数和方法,一个方法就是一个包含了接收者的函数,接收者可以是命名类型或者结构体类型的一个值或者一个指针。所有给定类型的方法属于该方法的方法集
package main
import (
    "fmt"
)
type Circle struct {  # 定义结构体
    radius float64
}
func main() {  # 执行函数
    var c1 Circle
    c1.radius = 10.00
    fmt.Println("圆的面积",c1.getArea())
}
func (c Circle) getArea() float64 {  # 该method属于Circle类型对象中方法
    return 3.14 * c.radius * c.radius  # c.radius为对象属性
}
Go没有面向对象,常见的java中实现类的方法都是编译器隐式给函数加一个this指针,在Go中,这个this指针需要明确申明出来
即:return 3.14 * c.radius * c.radius
c++中:return 3.14 * radius * radius

语言变量作用域

  • 语言变量可以在三个地方声明
    • 函数内定义的变量称为局部变量
    • 函数外定义的变量称为全局变量
    • 函数定义中的变量称为形式参数
package main
import "fmt"

var g int  # 声明全局变量,可以在整个包甚至外部包(导出后)使用
func main() {
    var a, b, c int  # 声明局部变量,作用域只在函数体内,参数和返回值也是局部变量
    a = 10
    b = 20
    c = a + b
    fmt.Printf("结果",c)
}
Go中全局变量和局部变量名称可以相同,但是函数内局部变量会被优先考虑
形式参数会作为函数的局部变量来使用
不同类型的局部和全局变量默认值为
int 0
float32 0
poniter nil

语言数组

  • 数组具有唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型,如整形、字符串或自定义类型
  • 相对于单独声明,使用数组形式更加方便且易扩展
  • 数组可以通过索引来读取(修改),索引从0开始,第一个元素索引为0
  • 声明数组
    • var balance [10] float32
  • 初始化数组
    • var balance = [5]float32{100.0, 2.0, 3.4, 7.0, 50.0}
    • 初始化数组{}中元素个不能大于[]中数字
    • 如果忽略[]中数字不设置数组大小,Go会根据元素个数来设置数组大小
  • 访问数组元素
    • var salary float32 = balance[9] # 格式为数组名加中括号
  • 数组完整操作
package main
import "fmt"
func main() {
    var n [10]int
    var i,j int
    for i = 0; i < 10; i++ {
        n[i] = i + 100
    }
    for j = 0; j < 10; j++ {
        fmt.Printf("Element[%d] = %d\n", j, n[j])
    }
}

多维数组

  • 常用的多维数组声明方式
    • var threedim [5][10][4]int
  • 二维数组,是最简单的多维数组,二维数组本质上是由一维数组组成的
    • 二维数组可认为是一个表格,x为行,y为列
    • a = [3][4]int{{0,1,2,3},{4,5,6,7},{8,9,10,11}} # 3行4列的一个数组
  • 访问二维数组,可以通过指定坐标来访问,如数组中行索引和列索引
    • var value int = a[2][3]
    • 可以使用循环嵌套来输出元素
  • 维数组初始化或赋值时需要注意 Go 语法规范,该写在一行就写在一行,一行一条语句
package main
import "fmt"
func main() {
    var a = [5][2]int{{0,0},{1,2},{2,4},{3,6},{4,8}}
    var i, j int
    for i = 0; i < 5; i++ {
        for j = 0; j < 2; j++ {
            fmt.Printf("a[%d][%d] = %d\n", i, j, a[i][j])
        }
    }
}

向函数传递数组

  • 如果想向函数传递数组参数,需要在函数定义时,声明形参为数组,可以通过两种方式来声明
  • 形参设定数组大小
    • void myfunc(param [10]int)
  • 形参未设定数组大小
    • void myfunc(param []int)
  • 示例
package main
import "fmt"

func main() {
    var balance = [5]int {1000, 2, 3, 17, 50}
    var avg float32
    avg = getAverage( balance, 5 )
    fmt.Printf("平均值 %f", avg)
    /* 转整形设置精度 */
    fmt.Printf(float64(avg))
    
}
# 函数一个参数指定接收整数型数组参数,另一个参数指定了数组元素个数
func getAverage(arr [5]int, size int) float32 {
    /* 使用形参并未设置数组大小 arr []int */
    /* 未定义长度的数组只能传给不限制数组长度的函数 */
    /* 定义长度的数组只能传给限制了相同数组长度的函数 */
    var i, sum int
    var avg float32
    for i = 0; i < size; i++ {
        sum += arr[i]  # 求合
    }
    avg = float32(sum) / float32(size)
    return avg;
}

# 多维数组传参
package main
func prt(arr [][] float32) {
    for i := 0; i < 3; i++ {
        Println(arr[i][0])
    }
}
func main() {
    var arr = [][]float32 {{-1,-2}, {-3, -4}, {-5}}
    prt(arr)
}
  • go数组作为函数参数传递的是副本,函数内修改数组并不改变原来的数组
  • 声明数组:nums := [3]int{1,2,3}
    • 数组是值,其长度是类型的一部分,作为函数参数时,是值传递,函数中修改对调用者不可见
  • 声明切片:nums := []int{1,2,3}
    • 切片是常用的数组处理方式,包含对底层数组内容的引用,作为函数参数时,类似指针传递,函数中修改对调用者可见
# 杨辉三角
package main
import "fmt"
func main() {
    triangle(12)
}
func triangle(rows int) {
    for i := 0; i < rows; i++ {   # 打印行数
        number := 1
        for k := 0; k < rows-i; k++ {  # 打印空白处,随着行数增加空白减少
            fmt.Print(" ") 
        }
        for j := 0; j <= i; j++ {  # 打印数,随着行数增加,数字也增加
            fmt.Printf("%5d", number)
            number = number * (i -j) / (j + 1)
        }
        fmt.Println()
    }
}

语言指针

  • Go语言的取地址符是&,放到一个变量前使用就会返回相应变量的内存地址
package main
import "fmt"
func main() {
    var a int = 10
    fmt.Printf("变量地址 %x\n", &a )
}

什么是指针

  • 一个指针指向了一个值的内存地址
  • 类似于变量和常量,在使用指针前需要声明指针
    • var var_name *var-type
    • var ip int / 指向整形 */
    • var fp float32 / 指向浮点型 */
  • 指针使用流程
    • 定义指针变量
    • 为指针变量赋值
    • 访问指针变量中指向地址的值
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)
}

Go空指针

  • 当一个指针被定义后没有分配任何变量时,它的值为nil,也叫做空指针
  • 一个指针变量通常缩写为 ptr
  • 指针数组
    • 需要保存数组,可以使用指针
    • 整形指针数组:var ptr [MAX]*int;
    • ptr为整形数组,每个元素都指向一个值
# 指针数组中存储三个整数
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])
    }
}
  • 创建指针数组时,不适合用range循环
for i, x := range &number {
    ptrs[i] = &x
}
# 这样循环出来,数组中值一样,内存地址也相同
是因为range循环逻辑造成的,和for不同的是range循环中x变量是临时变量,range循环只是将值拷贝到x变量中,因此内存地址都是一样的
number = [max]int{5,6,7}
var ptrs [max]*int  # 指针数组
for i := 0, i < max, i++ {
    ptrs[i] = &number[i]  # 将number数组的值的地址赋给ptrs
}
  • Go语言指向指针的指针
  • 如果一个指针变量存放的有时另一个指针变量的地址,则称这个指针变量为指向指针的指针变量
  • 当定义一个指向指针的指针变量时,第一个指针存放第二个指针的地址,第二个指针存放变量的地址
  • 指向指针的指针变量声明格式如下
    • var ptr **int;
    • 访问指向指针的指针变量值需要使用两个*号
package main
import "fmt"
func main() {
    var a int
    var ptr *int
    var pptr **int
    a = 3000
    ptr = &a
    pptr = &ptr
    fmt.Printf("变量a= %d\n", a)
    fmt.Printf("指针变量 *ptr= %d\n", *ptr)
    fmt.Printf("指向指针的指针变量 **pptr= %d\n", **pptr)
}
  • 三重指针及其对应关系
    • pt3 - > pto - > ptr - >变量a
  • Go语言指针作为函数参数
    • Go允许向函数传递指针,只需要在函数定义的参数上设置为指针类型即可
package main
import "fmt"
func main() {
    var a int = 100
    var b int = 200
    fmt.Printf("交换前\n", a)
    fmt.Printf("交换前\n", b)
    swap(&a, &b)
    fmt.Printf("交换后\n", a)
    fmt.Printf("交换后\n", b)
}
func swap(x *int, y *int) {
    var temp int
    temp = *x  /* 保存x地址的值 */
    *x = *y
    *y = temp
}
# 交换函数简写
func swap(x *int, y *int) {
    *y, *y = *y, *x
}

语言结构体

  • 数组可以存储同一类型的数据,但是结构体中我们可以为不同定义不同的数据类型
  • 结构体是由一系列具有相同类型或不同类型数据构成的数据集合
  • 定义结构体
    • type variable struct {member definition }
    • 一旦定义了结构体,它就能用于变量的声明
    • variable := variable {value1, value2…}
  • 示例
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", "教程", 6479})
    /* 可以使用key => value格式,忽略的字段为0或者为空 */
    fmt.Println(Books{title: "语言"})
}
  • 访问结构体成员
    • 如果要访问结构体成员,需要使用点号.操作符
    • Books.title
  • 结构体作为函数参数
    • 可以像其他数据类型一样将结构体类型作为参数传递给函数
package main
import "fmt"

type Books struct {
    title string
    author string
    subject string
}
func main() {
    var Book1 Books
    var Book2 Books
    Book1.title = "go语言"
    Book1.author = "mm"
    Book2.subject = "教程"
    printBook(Book1)
}
func printBook(book Books) {
    fmt.Printf("book title %s\n", book.title)
    fmt.Printf("book author %s\n", book.author)
}
  • 结构体指针
    • 可以定义指向结构体的指针类似于其他指针变量
    • var struct_pointer *Books
    • 查看结构体变量地址,可以将&符号放置于结构变量前
    • struct_pointer = &Book1
    • 使用结构体指针访问结构体成员,使用 . 操作符
    • struct_pointer.title
package main
import "fmt"

type Books struct {
    title string
    author string
    subject string
}
func main() {
    var Book1 Books
    var Book2 Books
    Book1.title = "go语言"
    Book1.author = "mm"
    Book2.subject = "教程"
    printBook(&Book1)
}
func printBook(book *Books) {
    fmt.Printf("book title %s\n", book.title)
    fmt.Printf("book author %s\n", book.author)
}
  • 结构体是作为参数的值传递
  • 如果想在函数中改变结构体数据内容,需要传入指针
  • struct类似于java中的类,可以在struct总定义成员变量,要访问成员变量,有两种方式
    • struct 变量.成员
    • struct 指针.成员
    • 不需要通过过getter, setter设置访问权限
type Rect struct {  //定义类
    x, y float64  //类型只包含数属性,并没有方法
    width, height float64
}
func (r *Rect) Area() float64 {  //为类绑定Area方法,*Rect为指针引用可以修改传入参数的值
    return r.width*r.height  //方法归属类型,不归属具体的对象,声明该类型的对象即可调用该类型方法
}

语言切片(Slice)

  • 语言切片是对数组的抽象
  • 数组长度不可以改变,在特定场景中这样的集合不太适用,与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大
  • 定义切片,可以声明一个未指定大小的数组来定义切片
    • var identifier []type
    • 切片不需要说明长度
    • 也可以适用make()函数来创建切片
    • var slice1 []type = make([]type, len)
    • 简写为:slice1 := make([]type, len) # len是数组长度也是切片的初始长度
    • 也可以指定容量:make([]T, length, capacity) #其中capacity为可选参数
    • 切片初始化:s :=[] int {1,2,3} # []表示切片类型,{1,2,3}表示初始化值
  • len()和cap()函数
    • 切片是可索引的,len()方法获取长度
    • cap()方法计算容量
  • 空(nil)切片
    • 一个切片在未初始化之前默认为nil,长度为0
package main
import "fmt"
func main() {
    var numbers []int
    printSlice(numbers)
    if(numbers == nil){
        fmt.Printf("切片为空")
    }
}
func printSlice(x []int){
    fmt.Printf("len %d cap %d slice %v\n", len(x), cap(x), x)
}
  • 切片截取
    • 通过设置下限和上限来设置截取切片
  • append()和copy()函数
    • 如果想要增加切片的容量,必须创建一个新的更大的切片并把原分片的内容都拷贝过来
    • var numbers []int > numbers = append(numbers, 1)
    • 通过append()函数向数组中添加元素,首先cap以2倍的速度增长,如果发现增长2倍以上容量后可以满足扩容后需求,那么cap*2,否则看扩容后数组length为多少 cap=length+1
package main
import "fmt"
func main() {
    var numbers []int
    printSlice(numbers)
    numbers = append(numbers, 0)
    printSlice(numbers)
    numbers = append(numbers, 1)
    printSlice(numbers)
    numbers = append(numbers, 2,3,4)
    printSlice(numbers)
    numbers1 := make([]int, len(numbers), (cap(numbers))*2)
    copy(numbers1, numbers)
    printSlice(numbers1)
}
func printSlice(x []int){
    fmt.Printf("len=%d cap=%d slice=%v\n", len(x),cap(x),x)
}
  • 切片实际获取数组的某一部分,len切片<=cap切片<=len数组,切片由三部分组成:指向底层数组的指针、Len、cap
  • 在做函数调用时,slice按引用传递,array按值传递

语言范围(Range)

  • range关键字用于for循环中迭代数组(array),切片(slice),通道(channel)或集合(map)的元素,在数组嗝切片中它返回元素的索引和索引对应的值,在集合中返回key-value对
package main
import "fmt"
func main() {
    nums := []int{2,3,4}
    sum := 0
    for _, num := range nums {
        sum += num  /* 适用range去求slice和 */
    }
    fmt.Println("sum", sum)
    for i, num := range nums {
        if num == 3 {
            fmt.Println("index", i)  //在数组上适用range传入index和值两个变量,_适用空白符省略序号
        }
    }
    kvs := map[string]string{"a": "apple", "b": "banana"}
    for k, v := range kvs {
        fmt.Printf("%s %s\n", k, v)
    }
}

Map(集合)

  • map是一种无序的键值对集合,map最重要的一点是通过key来快速检索数据,key类似索引,指向数据的值
  • map是一种集合,可以向迭代数组和切片那样迭代它,但是Map是无序的,无法判断返回顺序,这是因为Map适用hash表来实现的
  • 定义Map
    • 可以适用内建函数make,也可以适用map关键字来定义Map
    • var map_variable map[type]type / map_variable := make(map[type]type)
    • 如果不初始化map,就会创建一个nil map,nil map不能用来存放键值对
package main
import "fmt"
func main() {
    var countMap map[string]string  /* 创建集合 */
    countMap = make(map[string]string)
    /* map插入key-value */
    countMap ["France"] = "巴黎"
    countMap ["Japan"] = "东京"
    for country := range countMap {
        fmt.Println(country, "首都为", countMap [country])
    }
    capital, ok := countMap ["American"]
    if (ok) {
        fmt.Println("American首都为", capital)
    } else {
        fmt.Println("American首都不存在")
    }
}
  • delete()函数
    • delete()函数用于删除集合的元素,参数为Map和其对应的key
    • delete(countMap, “France”)

递归函数

  • 递归就是运行过程中调用自己
  • 使用递归时,开发者需要设置退出条件,否则递归将陷入无限循环中
  • 示例
# 阶乘
package main
import "fmt"
func Factorial(n uint64)(result uint64){
    if (n > 0){
        result = n * Factorial(n-1)
        return result
    }
    return 1
}
func main() {
    var i int = 15
    fmt.Printf("%d阶乘是%d\n", i, Factorial(uint64(i)))
}
# 斐波那契数列
package main
import "fmt"
func fib(n int) int {
    if n < 2{
        return n
    }
    return fib(n-2) + fib(n-1)
}
func main() {
    var i int
    for i = 0; i < 10; i++{
        fmt.Printf("%d\t", fib(i))
    }
}

语言类型转换

  • 类型转换用于将一种数据类型的变量转换为另一种类型的变量
    • type_name(expression) # 类型(表达式)
  • 示例
package main
import "fmt"
func main() {
    var sum int = 17
    var count int = 5
    var mean float32
    mean = float32(sum)/float32(count)
    fmt.Printf("mean值为 %f\n", mean)
}

接口

  • 还有另外一种数据类型:接口,把所有具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口
  • 示例
# 定义一个接口Phone,定义一个方法call(),在main函数中定义了一个Phone类型变量,并为其赋值,然后调用call()方法
package main
import "fmt"
type Phone interface {  //定义接口
    call()
}
type Nokia struct {  //定义结构体
}
func (nokia Nokia) call(){  //实现方法
    fmt.Println("i am nokia")
}
type Iphone struct {
}
func (iphone Iphone) call(){
    fmt.Println("i am iphone")
}
func main() {  //调用
    var phone Phone
    phone = new(Nokia)
    phone.call()
    phone = new(Iphone)
    phone.call()
}

# 如果想要通过接口方法修改属性,需要在传入指针的结构体
type fruit interface{ // 定义接口
    getName() string  // 方法 [类型]
    setName(name string)
}
type apple struct{  //结构体
    name string   //定义结构体数据类型
}
func (a *apple) getName() string{  //实现方法,传入结构体的指针
    return a.name
}
func (a *apple) setName(name string){
    a.name = name  // 修改属性
}
func main() {
    a := apple{"红富士"}
    fmt.Println(a.getName())
    a.setName("国光")
    fmt.Println(a.getName())
}

# 将接口作为参数
package main
import (
    "fmt"
)
type Phone interface {
    call() string
}
type Android struct {
    brand string
}
type Iphone struct {
    version string
}
func (android Android) call() string {
    return "i am android" + android.brand
}
func (iphone Iphone) call() string {
    return "i am iphone" + iphone.version
}
func printCall(p Phone) {
    fmt.Println(p.call() + "i call you")
}
func main() {
    var vivo = Android{brand:"Vivo"}
    var hw = Android{"Huawei"}
    i7 := Iphone{"7 plus"}
    printCall(vivo)
    printCall(hw)
    printCall(i7)
}

错误处理

  • 内置的错误接口提供了非常简单的错误处理机制
  • error是一个接口类型
    • type error interface { Error() string }
  • 可以在编码中通过error接口类型生成错误信息,函数通常在最后的返回值中返回错误信息,是哦也能够errors.New可以返回一个错误信息
  • 示例
package main
import (
    "fmt"
)
type DivideError struct {  //定义一个error结构
    dividee int
    divider int
}
func (de *DivideError) Error() string {  //实现error接口
    strFormat := `cannot proceed, dividee: %d divider: 0`
    return fmt.Sprintf(strFormat, de.dividee)
}
//定义除法运算函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
    if varDivider == 0{
        dData := DivideError{
            dividee: varDividee,
            divider: varDivider,
        }
        errorMsg = dData.Error()
        return
    } else {
        return varDividee / varDivider, ""
    }
}
func main() {
    //正常情况
    if result, errorMsg := Divide(100, 10); errorMsg == "" {
        fmt.Println("100/10 = ", result)
    }
    //异常情况,会返回错误信息
    if _, errorMsg := Divide(100, 0); errorMsg != "" {
        fmt.Println("errorMsg is:", errorMsg)
    }
}

概念:panic和recover

  • panic用户主动抛出错误;recover用来捕获panic抛出的错误
  • 引发panic有两种情况,一是程序主动调用,二是程序产生运行时错误,由运行时检测并退出
  • 发生panic后,程序会从调用panic的函数位置或发生panic的地方立即返回,逐层向上执行函数的defer语句,然后逐层打印函数调用堆栈,知道被recover捕获或与运行到最外层函数
  • panic不但可以在函数正常流程中抛出,在defer逻辑里也可以再次调用panic或抛出panic,defer里面的panic能够被后续执行的defer捕获
  • recover用来捕获panic,阻止panic继续线上传递。revocer()和defer一起使用,但是defer只有在后面的函数体内直接被调用才能捕获panic来终止异常,否则返回nil,异常继续向外传递
  • 多个panic只会捕捉最后一个
  • 一般有两种情况会用
    • 程序遇到无法执行下去的错误时,抛出错误,主动结束运行
    • 在调试程序时,通过panic来打印堆栈,方便定位错误

并发

  • Go支持并发,下hi需要通过go关键字来开启goroutine即可
  • goroutine是轻量级线程,调度是由Golang运行时进行管理的
  • 语法格式为:
    • go 函数名(参数列表)
  • Go允许使用go语句开启一个新的运行期线程,即goroutine,以一个不同的、新创建的goroutine来执行一个函数。同一个程序中所有的goroutine共享同一个地址空间
  • 示例
package main
import (
    "fmt"
    "time"
)
func say(s string) {
    for i := 0; i < 5; i++{
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}
func main() {
    go say("world")
    say("hello")
}
  • 通道(channel)
  • 通道是用来传递数据的一个数据结构
  • 通道可用于两个goroutine之间 通过传递指定类型的值 来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或者接收。如果未指定方向,则为双向通道
    • ch <- v 把v发送到通道ch
    • v := <-ch 从ch接收数据,并把值赋给v
  • 声明一个通道很简单,使用chan关键字即可,通道使用前必须先创建
    • 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  //定义sum函数,计算完成后,将sum发送到通道c
}
func main() {
    s := []int{7, 2, 8, -9, 4, 0}  //定义数组
    c := make(chan int)  //定义通道c
    go sum(s[:len(s)/2], c)  //s[:3]  => 7,2,8
    go sum(s[len(s)/2:], c) // s[3:] =>-9,4,0
    x, y := <-c, <-c  //从通道c中接收,先进先出
    fmt.Println(x, y, x+y)
}
  • 通道缓冲区
    • 通道可以设置缓冲区,通过make的第二个参数指定缓冲区大小
    • ch := make(chan int, 100)
    • 带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立即需要接收端去获取数据
    • 不过缓冲区大小有限,还是要有接收端来接收数据,否则缓冲区一满,数据发送端就无法再发送数据了
    • 注意:如果通道不带缓冲,发送发会阻塞直到接收方从通道中接收了值,如果通道带缓冲,发送方会阻塞直到发送的值被拷贝到缓冲区内,如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值,接收方在有值可以接收之前会一直阻塞
  • 示例
package main
import "fmt"
func main() {
    ch := make(chan int, 2)  // 定义带缓冲区通道
    ch <- 1
    ch <- 2  //定义缓存区后可以同时发送这两个数据,不需要立刻去同步读取数据
    fmt.Println(<-ch)  //获取数据
    fmt.Println(<-ch)
}
  • Go遍历通道与关闭通道
  • 通过range关键字来实现遍历读取到的数据,类似于数组或切片
    • v, ok := <-ch
    • 如果通道接收不到数据后ok就为false,这时通道就可以使用close()函数来关闭
    • 关闭通道并不会丢失里面的数据,只是让读取通道数据的时候不会读完之后一直阻塞等待新数据写入
  • 示例
package main
import (
    "fmt"
)
func fib(n int, c chan int){
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x  //将x值赋值c
        x, y = y, x+y
    }
    close(c)  //计算完毕关闭通道
}
func main() {
    c := make(chan int, 10)  //定义通道,缓存区为10
    go fib(cap(c), c)
    for i := range c{  //运行10次后通道关闭
        fmt.Println(i)
    }
}
  • channel是可以控制读写权限的
    • go func(c chan int) 读写均可
    • go func(c <- chan int) 只读
    • go func(c chan <- int) 只写
  • 有缓冲和无缓冲的区别
    • 无缓冲是同步的,如make(chan int),消息必须被接收后
    • 有缓冲是异步的,如make(chan int, 2),消息放入缓冲区,除非满了,否则不会阻塞
  • 主进程和子进程
package main
import (
    "fmt"
    "time"
)
func say(s string){
    for i := 0; i < 5; i++{
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s, (i+1)*100)
    }
}
func say2(s string){
    for i := 0; i < 5; i++{
        time.Sleep(150 * time.Millisecond)
        fmt.Println(s, (i+1)*150)
    }
}
func say2(s string, ch chan int){
    for i := 0; i < 5; i++{
        time.Sleep(150 * time.Millisecond)
        fmt.Println(s, (i+1)*150)
    }
    ch <- 0  //引入通道,当程序结束时,发送消息0,然后关闭通道
    chose(ch)
}

func main(){
    go say2("world")  //这里say2只执行3次,而不是5次,原因是子函数没结束,主函数结束后退出了
    ch := make(chan int)
    go say2("world", ch)  //引入一个通道,默认通道的存消息和取消息都是阻塞的,当主函数完成后,会一直等待通道中的值,一旦通道有值,关闭通道后主函数才结束
    say("hello")
    fmt.Println(<-ch)
}

开发工具

  • GoLand
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 变量和常量声明 Go语言中,使用var关键字声明变量,常量使用const关键字声明。 ``` var name string = "Tom" var age int = 18 const pi = 3.14 ``` 2. 数据类型 Go语言中,基本数据类型包括整型、浮点型、布尔型、字符串型等。 ``` var num int = 10 var f float64 = 3.14 var flag bool = true var str string = "hello world" ``` 3. 控制语句 Go语言中,支持if、for、switch等控制语句。 ``` if num > 0 { fmt.Println("positive") } else { fmt.Println("negative") } for i := 0; i < 10; i++ { fmt.Println(i) } switch num { case 1: fmt.Println("one") case 2: fmt.Println("two") default: fmt.Println("others") } ``` 4. 函数 Go语言中,使用func关键字定义函数。 ``` func add(a, b int) int { return a + b } result := add(1, 2) fmt.Println(result) ``` 5. 包和导入 Go语言中,使用package关键字定义包,使用import关键字导入包。 ``` package main import "fmt" func main() { fmt.Println("hello world") } ``` 6. 结构体和方法 Go语言中,使用type关键字定义自定义结构体,可以给结构体定义方法。 ``` type Person struct { name string age int } func (p Person) sayHello() { fmt.Println("hello, my name is", p.name) } p1 := Person{name: "Tom", age: 18} p1.sayHello() ``` 7. 指针 Go语言中,使用&符号获取变量的地址,使用*符号获取指针指向的值。 ``` num := 10 ptr := &num fmt.Println(*ptr) ``` 8. 数组和切片 Go语言中,使用[]定义数组和切片,数组长度固定,切片长度可变。 ``` arr := [3]int{1, 2, 3} slice := []int{1, 2, 3} ``` 9. map Go语言中,使用map关键字定义map。 ``` m := make(map[string]int) m["one"] = 1 m["two"] = 2 fmt.Println(m["one"]) ``` 10. 接口 Go语言中,使用interface关键字定义接口,一个类型只要实现了接口中定义的所有方法,就可以被认为是实现了该接口。 ``` type Shape interface { area() float64 } type Rectangle struct { width float64 height float64 } func (r Rectangle) area() float64 { return r.width * r.height } r := Rectangle{width: 10, height: 5} var s Shape = r fmt.Println(s.area()) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值