GO基础知识分享

兵长:哟,最近在干啥呢

胖sir:在看我之前的go基础学习资料呢,回顾一下

兵长:那给我分享一下呗,我也想回顾回顾

胖sir:用你的小手指点开你的手机,我来传给你

兵长:你信不信我的小手指可以带你飞整个峡谷 . . .

go语言的基本事项

  1. go run hello.go直接运行,输出结果(原理也是编译后执行)
  2. go build hello.go生成可执行程序,运行可执行程序,输出结果
  3. 注意go语言中花括号不能单独占一行,否则会报错
package main

import "fmt"

func main(){ //go语言中此处的花括号不能单独占一行,否则会报错
    fmt.Println("hello world")
}
  1. go语言一条语句占一行,如果一行需要执行多个语句 使用 分号 隔开

  2. go语言的输出语句有3种方式

    1. import "fmt" 后适用fmt.Println(x) --输出
    2. println(x)--输出
    3. fmt.Printf("%d",x)--格式化输出

关键字

下面列举了 Go 代码中会使用到的 25 个关键字或保留字:

除了以上介绍的这些关键字,Go 语言还有 36 个预定义标识符:

字符串的拼接和变量的定义方式

定义变量的三种方式

  1. 正常使用var定义变量
  2. 使用var定义变量,但是不定义类型,通过赋初值的方式,go编译器自动识别
  3. 使用:=的方式来进行 新变量的定义,仅限于新变量 -- 适用于定义在函数内部
//字符串 可以使用+ 进行拼接
	fmt.Println("this is my func")
	fmt.Println("hello ,wolrd" + "xiaozhuzhu")

//定义变量
	var  name string="xiaomotong"
	var age,tail int=24,170
	fmt.Println(name, age , tail)
	fmt.Println(name)
	fmt.Println(age)
	fmt.Println(tail)

//定义变量的三种方式
//1
	var a int = 1
	fmt.Println(a)

//2 使用var定义变量,但是不定义类型,通过赋初值的方式,go编译器自动识别
	var b = "hello"
	fmt.Println(b)

//3 使用:=的方式来进行 新变量的定义,仅限于新变量
//:= 左侧如果没有声明新的变量,就产生编译错误
	c := 20
	fmt.Println(c)
	//c:=30 //报错,因为c已经不是新变量的
	c=30	//正确,是一个正常的赋值操作
	fmt.Println(c)

	c,d:=40,90 //这样是合法的
	fmt.Println(c,d)

因式分解的方式,仅仅适用于定义全局变量

//因式分解的方式,仅仅适用于定义全局变量
var
(
	g_a int = 1
	g_b,g_c int=1,2
)

空白符

空白标识符 _ 也被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。

_ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。

//空白符
	_,e := 2,3
	fmt.Println(e)

const常量

  • 定义const常量
//定义const常量
	const width,height = 10,5
	var area int=width*height
	fmt.Println("面积为", area)  //50
  • const常量用作枚举
const(
	unknow = 0
	man = 1
	woman =	2
)

println(unknow,man,woman)  //0 1 2
  • 常量可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过:
const(
	a = "hello"
	b = len(a)
	c = unsafe.Sizeof(a)
)
println(a,b,c)  //hello 5 16

iota的用法

iota,特殊常量,可以认为是一个可以被编译器修改的常量。

iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

iota 可以被用作枚举值:

//itoa的用法
const(
	g_a = iota
	g_b
	g_c
	g_d
)

const(
	g_e = iota
	g_f = "hello"
	g_g
	g_h = iota
	g_i
)

const(
	g_j = 1<<iota
	g_k
	g_l
	g_m
)
println(g_a,g_b,g_c,g_d)
println(g_e,g_f,g_g,g_h,g_i)
println(g_j,g_k,g_l,g_m)
//0 1 2 3
//0 hello hello 3 4
//1 2 4 8

运算符

go语言的运算符和C语言的运算符基本一致

Go 没有三目运算符,不能适用?:

算术运算符

关系运算符

逻辑运算符

位运算符

赋值运算符

其他运算符

语言条件语句

  • if xxx
if xxx {
    ...
}
  • if xxx {...} else{...}
if xxx{
    ...
}else{
    ...
}
  • if xxx{ ... if xxx { ...}}
if xxx{
    if xxx {
        ...
    }
    ...
}
  • switch
package main

import "fmt"

func main(){

	grade:= 90
	if grade >= 90{
		println("优秀")
	}else if grade >=70 && grade <90{
		println("良好")
	}else{
		println("差")
	}

	var x interface{} //计算类型

	switch i := x.(type){
		case nil:
		fmt.Printf(" x 的类型 :%T\n",i)
		case int:
		fmt.Printf("x 是 int 型")
		default:
		println("未知")
	}
}
  • select

    类似于C语言中的select,用于多路IO复用

for循环的方式

  • 三种方式
  1. 类似C语言中的for
  2. 类似C语言中的while
  3. 死循环
package main

import "fmt"

func main(){

//类似C语言中的for
	var sum int
	for i:=1;i<=10;i++{
		sum +=i
	}

	fmt.Println(sum)


//类似于while
	for sum >30{
		sum -= 10
		fmt.Println(sum)
	}

//死循环

for {
    ...
}

  • For-each range 循环
//for-each  range 循环的方式
    name := []string{"qqq","yyy"}
	for i,str:= range name{
	fmt.Printf("%d -- %s\n",i,str)
	}

//0 -- qqq
//1 -- yyy
------------------------------------------------------------------------
    str := []string{"北京", "天津", "山东"}

    //可以默认丢掉第二个返回值
    for i := range str {
        fmt.Printf("%d -- %s\n", i, str[i])
    }

函数

go语言的函数,可以有多个返回值,其余和C语言没有什么区别

作用域

与C语言一致

  • 局部变量
  • 全局变量
  • 函数形参

数组&切片

思想和C语言一致,数组是固定长度的

切片是动态扩容的,类似于C++的vector

切片写法如下:

name := []string{"xiaomotong","pangsir"}

nums :=[]int{1,2,3,4,5,6}

指针

var ptr1 *int

二级指针

var  a int
var ptr *int
var pptr **int

ptr = &a
pptr = &ptr

指针数组

var ptr [5]*int

结构体

go语言中的结构体变量,和结构体指针,访问结构体成员的时候,都是使用 点(.)来进行访问,如下:

//定义一个结构体
type info struct{
    name string
    age int
    height int
}

//使用
var stu info
stu.name = "xiaomotong"
stu.age = 24
stu.height = 170

fmt.Println(stu.name,stu.age,stu.height)

var stu2 *info = &stu
stu2.name = "pangsir"
stu2.age = 24
stu2.height = 160

fmt.Println(stu2.name,stu2.age,stu2.height)

切片slice

Go 语言切片是对数组的抽象。

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

  • 使用var定义
  • 定义空slice
  • 使用:=定义
  • 使用make来定义 make([]type,len,cap)
  • apend 和 copy的使用
package main

/*
    author:xiaomotong
    file:slice
    function:study slice for golang
*/
import "fmt"


func main(){
//定义切片的方式
//1、使用var定义
    var s1 = []int{1,2,3};
    printInfo(s1);
//2、定义空slice
    var s2 []int
    printInfo(s2);
//3、使用:=定义
    ss := []int{3,4,5,6}
    printInfo(ss);

//4、使用make来定义 make([]type,len,cap)
    s3 := make([]int,2,3)
    printInfo(s3);
//复制操作
    s3[0] = 3
    printInfo(s3);
//覆盖整个slice
    s1 = s3
    printInfo(s1);

//apend 和 copy的使用
    s3 = append(s3,6,7,8,9)
    printInfo(s3);
//扩容
    s4 := make([]int,len(s3),cap(s3) * 3)
    copy(s4,s3)
    printInfo(s4);
//s[2:]
    println(s4[1:])
    println(s4[:4])
    println(s4[1:3])
    fmt.Printf("s4[1:] = %v \n",s4[1:])
    fmt.Printf("s4[:4] = %v \n",s4[:4])
    fmt.Printf("s4[1:3] = %v \n",s4[1:3])
}

func printInfo(s[]int){
    fmt.Printf("len = %d, cap = %d, slic = %v\n",len(s),cap(s),s);

}

范围Range

Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。

在数组和切片中它返回元素的索引和索引对应的值在集合中返回 key-value 对

  • range 对于 数组、切片
  • 对于字符串
  • range对于map集合
  • 占位符_
package main

/*
    author:xiaomotong
    file:range
    function:study range for golang
*/
import "fmt"


func main(){

//1、range 对于 数组、切片

    s := []string{"apple","pen"}
    for i,value := range s{
        fmt.Println(i,value)
    }

//2、对于字符串
    for i,value := range "hello"{
        fmt.Println(i,value)
    }

//3、range对于map集合
    m := map[string]string{"name":"xiaopang","age":"25"}
    for i,value := range m{
        fmt.Println(i,value)
    }

//4、占位符_
    sum := 0
    nums := []int{1,2,3,4,5}
    for _,value := range nums{
        sum += value
    }
    fmt.Println(sum)
}

MAP集合

Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。

Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。

//类似于key-value的形式
map[string]string

m := map[string]string{"name":"xiaozhu","age":"15"}

mm := make(map[string]string)

countryCapitalMap [ "France" ] = "巴黎"
countryCapitalMap [ "Italy" ] = "罗马"
countryCapitalMap [ "Japan" ] = "东京"
countryCapitalMap [ "India " ] = "新德里"

delete() 函数

delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key

delete(countryCapitalMap,"France")

递归函数

Go 语言支持递归。但我们在使用递归时,开发者需要设置退出条件,否则递归将陷入无限循环中。

递归函数对于解决数学上的问题是非常有用的,就像计算阶乘,生成斐波那契数列等。

递归算阶乘

package main

import "fmt"

func fabulaxiaomotong(n uint 64) (result uint64){
    if n>0 {
        return fabulaxiaomotong(n-1)*n
    }
    return 1
}

func main(){
    fmt.Println("result : ",fabulaxiaomotong(15))
}

菲波拉契数列

func fabolaxiaomotong(n uint64)(result utin64){
    if n<2{
        return n
    }else{
        return fabolaxiaomotong(n-2)+fabolaxiaomotong(n-1)
    }
}

接口

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

package main

import "fmt"

//接口
type phone interface {
    call()
    show()
}

type xiaomi struct {
    name string
    ads  string
}

type huawei struct {
    name string
    ads  string
}

//接口实现
func (x xiaomi) call() {
    fmt.Println("phoneName :", x.name)
}

func (x xiaomi) show() {
    fmt.Println("advertisement :", x.ads)
}

func (h huawei) call() {
    fmt.Println("phoneName :", h.name)
}

func (h huawei) show() {
    fmt.Println("advertisement :", h.ads)
}

func main() {
    x := xiaomi{"mi note2", "for fire"}
    x.call()
    x.show()

    h := huawei{"hw p40", "your better phone"}
    h.call()
    h.show()
}

错误

Go 语言通过内置的错误接口提供了非常简单的错误处理机制。error类型是一个接口类型,这是它的定义:

package main

import "fmt"

//定义数据结构
type DivideError struct {
    devidee int
    devider int
}

//错误处理实现Error()接口
func (de *DivideError) Error() string {
    strdata := `
        error,divide is zero
        dividee is %d
        divider is zero
    `

    return fmt.Sprintf(strdata, de.devidee)
}

//实现功能接口
func Divide(dividee int, divider int) (result int, errMsg string) {
    if divider == 0 {
        data := DivideError{dividee, divider}
        errMsg = data.Error()
        return
    } else {
        return dividee / divider, ""
    }
}

func main() {
    a := 10
    b := 0
    result, err := Divide(a, b)
    if err != "" {
        fmt.Println(err)
        return
    }
    fmt.Printf("%d / %d == %d \n", a, b, result)

}

go语言的并发

Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。goroutine 语法格式:

  • go的并发也是线程不安全的,需要加锁才安全
package main

import (
    "fmt"
    "time"
)

func say(s string) {
    var i int
    for i = 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

var num int = 0

//goroutine 是线程不安全的
func countNum() {
    var i int
    for i = 0; i < 10; i++ {
        time.Sleep(5 * time.Millisecond)
        num++
    }
}
func main() {
    //go say("hello")
    //say("world")

    go countNum()
    countNum()
    fmt.Println(num)
}

通道(channel)

  • 通道(channel)是用来传递数据的一个数据结构。通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。

    • 注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。以下实例通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和:
  • 通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小

  • Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片

package main

import "fmt"

//不带缓冲的 通道
func getSum(s []int, c chan int) {
    sum := 0
    for _, value := range s {
        sum += value
    }
    c <- sum
}

func getSum2(c chan int, n int) {
    x, y := 0, 1
    var i int
    for i = 0; i < n; i++ {
        c <- x
        x, y = y, x+y
    }
    close(c) //关闭通道
}

func main() {
    //不带缓冲的 通道
    // s := []int{3, 5, -2, 3, 4, 7, 1, 1, 1}
    // c := make(chan int)
    // go getSum(s[:3], c)
    // go getSum(s[3:6], c)
    // go getSum(s[6:], c)
    // x, y, z := <-c, <-c, <-c
    // fmt.Println(x, y, z, x+y+z)

//带缓冲的通道
    c := make(chan int, 10)
    go getSum2(c, cap(c))

    for value := range c {
        fmt.Println(value)
    }

}

自己调用别的包/自己的包

GO基础知识分享-LMLPHP

自己调用别人的包或者自己的包,如上目录结构

  • 自己写的包名,要和目录名一样
  • 使用go mod 模块 ,执行 go mod init mystudy

mylib.go

package mylib

func Add(a, b int) int {
    return a + b
}

main.go

package main

import (
    "fmt"
    "mystudy/mylib"
)

func main() {
    fmt.Println(mylib.Add(2, 3))
}

以上为本期全部内容,如有疑问可以在评论区或后台提出你的疑问,我们一起交流,一起成长。

好家伙要是文章对你还有点作用的话,请帮忙点个关注,分享到你的朋友圈,分享技术,分享快乐

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

作者:小魔童哪吒

04-01 05:06