Go语言的常用基础

1、核心特性

Go语言有一些让人影响深刻的核心特性核心特性,比如:以消息传递模式的并发、独特的_符号、defer 、函数和方法、值传递等等,可以查看这篇文章《Go语言-让我印象深刻的13个特性》。首先要记住一些核心特性的用法。

1.1、Goroutine

  • 协程:独立的栈空间,共享堆空间,比线程更轻量。
  • 线程:一个线程上可以跑多个协程。
  • Go语言独有的协程,让程序员非常方便的使用并发编程,从而保留更多的心智去思考业务和创新。笔者认为这一点是Go语言最大的特性。

Goroutine就是这种协程特性的实现。Goroutine 是通过通信来共享内存,而不是共享内存来通信。通过共享内存来控制并发,会使编程变得更复杂,容易引入更多的问题。

Goroutine是由Go的运行时调度和管理。Go程序会智能地将 Goroutine 中的任务合理地分配给每个CPU,它在语言层面已经内置了调度和上下文切换的机制,不需要程序员去操作各种方法实现调度。

在Go语言中,当需要让某个任务并发执行时,只需要把这个任务包装成一个函数,开启一个Goroutine去执行就可以了。如下,只需要在调用函数时,在前面加上go关键字。

scss 复制代码
func hello_go() {
  fmt.Println("hello go!!!")
}

func main() {
    go hello_go()
    fmt.Println("main done!!!")
    time.Sleep(time.Second)
}

1.2、接口

在Go语言中接口interface是一种类型。Go语言的接口比较松散,只要是实现了接口定义的方法,就是实现了这个接口,无需使用implement等关键字去声明。

定义接口

go 复制代码
// 定义接口
type Sayer interface {
  say()
}
// 定义结构体
type dog struct {
}
type cat struct {
}
// 定义方法
func (d dog) say() {
  fmt.Println("狗叫")
}
func (c cat) say() {
  fmt.Println("猫叫")
}

空接口可以存储任意类型

go 复制代码
// 比如定义一个map类型的对象
var obj = map[string]interface{}

使用 **类型断言判断 空接口**中的值

scss 复制代码
// x:表示类型为interface{}的变量
// T:表示断言x可能是的类型。
x.(T)
go 复制代码
func main() {
  var x interface{}
  x = 123
  //v, ok := x.(int)
  v, ok := x.(string)
  if ok {
    fmt.Println(v)
  } else {
    fmt.Println("类型断言失败")
  }
}

接口特性

  • 接口类型变量能够存储所有实现了该接口的实例。 如下,Sayer类型的变量能够存储dog和cat类型的变量。
go 复制代码
// 定义接口
type Sayer interface {
  say()
}
// 定义结构体
type dog struct {
}
type cat struct {
}
// 定义方法
func (d dog) say() {
  fmt.Println("狗叫")
}
func (c cat) say() {
  fmt.Println("猫叫")
}

func main(t *testing.T) {
  var x Sayer // 声明一个接口类型的变量
  c := cat{}  // 实例化cat
  d := dog{}  // 实例化dog
  x = c       // cat赋值给接口类型
  x.say()     // 打印:猫叫
  x = d       // dog赋值给接口类型
  x.say()     // 打印:狗叫
}
  • 一个类型可以同时实现多个接口,接口间彼此独立。
go 复制代码
// 定义接口
type Sayer interface {
  say()
}
type Mover interface {
  move()
}

// 定义结构体
type dog struct {
}

// 定义方法
func (d dog) say() {
  fmt.Println("狗叫")
}
func (d dog) move() {
  fmt.Println("狗移动")
}

func main(t *testing.T) {
  var x Sayer
  var y Mover

  var d = dog{}
  x = d
  y = d

  x.say()
  y.move()
}
  • 使用值接收者实现接口 和 使用指针接收者实现接口 有什么区别?值接受者实现时 可以用 指针类型赋值过去,但 指针接受者实现时 无法用 值类型赋值过去。
go 复制代码
// 定义接口
type Mover interface {
  move()
}
type Sayer interface {
  say()
}

// 定义结构体
type dog struct {
}

// 定义方法
func (d *dog) say() {
  fmt.Println("狗叫")
}
func (d dog) move() {
  fmt.Println("狗移动")
}

func TestProgram(t *testing.T) {
  var x Sayer
  var y Mover

  //var d = dog{}
  var d = &dog{}
  x = d        // x不可以接收 dog类型,因为golang 不会 将值类型 转换 为指针类型
  y = d     // y可以接受  *dog类型,因为golang 会 将指针类型 转换 为值类型

  x.say()
  y.move()
}

1.3、下划线

_是特殊标识符,用来忽略结果。

go 复制代码
buf := make([]byte, 1024)
f, _ := os.Open("/Users/***/Desktop/text.txt")

1.4、Go语言中的指针

& :用于获取变量的地址 ,其实就是所谓的指针类型(地址类型)

:用于获取指针所指向的值*

swift 复制代码
func main() {
  a := 10
  fmt.Printf("type of a: %T\n", a)
  b := &a // 取变量a的地址,将指针保存到b中
  fmt.Printf("type of b: %T\n", b)
  c := *b // 取出 指针b 所指向的值
  fmt.Printf("type of c: %T\n", c)
  fmt.Printf("value of c: %v\n", c)
}

1.5、new和make的区别

  • 二者都是用来做内存分配的。
  • make只用于slice、map、channel的初始化,返回的还是这三个引用类型本身。这里的引用有别于指针,他是对 slice、map、channel 值的间接访问,并不是一个指向 slice、map、channel 的指针。
  • new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针指针是一个变量,存储了值的内存地址。

1.6、defer延迟调用

关键字 defer 用于注册延迟调用。这些调用直到 return 前才被执。可以用来做资源清理,常用来关闭资源。defer 是先进后出。

go 复制代码
func main() {
  arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
  for _, v := range arr {
    defer fmt.Println("循环:", v)
  }
  fmt.Println("主流程跑完")
  time.Sleep(time.Second * 3)
  // 等待3秒后,执行defer,输出时先输出10,最后输出1,因为是先进后出
}

2、常用类型和内置函数

2.1、常用类型

go 复制代码
bool                                // 布尔
int, int8, int16, int32, int64      // 整数
uint, uint8, uint16, uint32, uint64 // 0 和正整数
float32, float64                    //浮点数
string                              // 字符串
complex64, complex128               // 数学里的复数
array     // 固定长度的数组
struct    // 结构体
string    // 字符串
slice     // 序列数组
map       // 映射
chan      // 管道
interface // 接口 或 任意类型
func      // 函数

2.2、常用内置函数

go 复制代码
append          // 追加元素到数组
copy            // 用于复制和连接slice,返回复制的数目
len             // 求长度,比如string、array、slice、map、channel
cap             // capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)
delete          // 从map中删除key对应的value
panic           // 抛出异常(panic和recover:用来做错误处理)
recover         // 接受异常
make            // 分配内存,返回Type本身(只能应用于slice, map, channel)
new             // 分配内存,主要用来分配值类型,比如int、struct。返回指向Type的指针
close           // 关闭channel

3、变量、常量

go 复制代码
// 申明变量
var name string

// 申明常量
const pi = 3.1415
const e = 2.7182
// 或
const (
        pi = 3.1415
        e = 2.7182
    )

// 申明并且初始化
n := 10

4、数据结构

4.1、数组

数组的长度固定

go 复制代码
var arr1 = [5]int{1, 2, 3, 4, 5}
// 或
arr2 := [...]struct {
  name string
  age  int8
}{
  {"yangling", 1},
  {"baily", 2},
}

4.2、切片

切片的长度不固定

go 复制代码
// 1.声明切片
var s1 []int
s2 := []int{}
var s3 = make([]int, 0)

// 向切片中添加元素
s1 = append(s1, 2, 3, 4)

// 从切片中按照索引获取切片
s1[low:high]

// 循环
for index, element := range s1 {
  fmt.Println("索引:", index, ",元素:", element)
}

4.3、Map

go 复制代码
scoreMap := make(map[string]int)
scoreMap["张三"] = 90
scoreMap["李四"] = 100

userInfo := map[string]string{
  "username": "baily",
  "password": "111111",
}

// 如果key存在ok 为true,v为对应的值;
// 如果key不存在ok 为false,v为值类型的零值
v, ok := scoreMap["李四"]
if ok {
  fmt.Println(v)
} else {
  fmt.Println("查无此人")
}

// 循环
for k, v := range scoreMap {
  fmt.Println(k, v)
}

//将王五从map中删除
delete(scoreMap, "王五")

4.4、结构体

不同的使用方式,可能返回指针,也可能返回值。

go 复制代码
// 定义结构体
type Student struct {
  name string
  age  int
}

func main() {
  // 使用结构体

  // 方式1,返回的是值
  var stu1 Student
  stu1.name = "baily"
  stu1.age = 1
  fmt.Println("baily1:", stu1)

  // 方式2,返回的是值
  var stu2 = Student{
    name: "baily",
    age:  1,
  }
  fmt.Println("baily2:", stu2)

  // 方式3,返回的是指针
  stu3 := &Student{
    name: "baily",
    age:  1,
  }
  fmt.Println("baily3指针:", stu3)
  fmt.Println("baily3值:", *stu3)

  // 方式4,返回的是指针
  var stu4 = new(Student)
  stu4.name = "baily"
  stu4.age = 1
  fmt.Println("baily4指针:", stu4)
  fmt.Println("baily4值:", *stu4)

}

5、流程控制

流程控制包括:if、switch、for、range、select、goto、continue、break。主要记下select,其他的跟别的语言类似。主要用于等待资源、阻塞等待等等。

select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。

go 复制代码
func main() {
  var c1 = make(chan int)
  go func() {
    time.Sleep(time.Second * 10)
    c1 <- 1
  }()

  // 此处会一直等到10S到期,通道里有值才会继续往下走。
  // 如果增加了 time.After(time.Second * 3) ,则最多3秒则结束
  // 如果这2个case都不行,会走default,也可以不设置default
  select {
  case i, ok := <-c1:
    if ok {
      fmt.Println("取值", i)
    }
  case <-time.After(time.Second * 3):
    fmt.Println("request time out")
  default:
    fmt.Println("无数据")
  }
}

6、函数和闭包

6.1、函数

go 复制代码
// 正常函数
func test(x int, y int, s string) (int, string) {
    n := x + y          
    return n, fmt.Sprintf(s, n)
}

// 匿名函数
func main() {
    getSqrt := func(a float64) float64 {
        return math.Sqrt(a)
    }
    fmt.Println(getSqrt(4))
}

6.2、闭包

在Go语言中,闭包是一种函数值,它引用了其函数体外部的变量。闭包允许函数访问并处理其外部范围内的变量,即使函数已经返回了,这些外部变量也会被保留在闭包内。

所以说,一个闭包由两部分组成:函数体 和 与其相关的引用外部变量的环境。

当一个函数被定义在另一个函数内部时,并且引用了外部函数的变量,就会创建一个闭包。这个闭包函数可以随时访问和修改外部函数中的变量,即使外部函数已经执行完毕。

go 复制代码
func main() {
  // 外部函数定义并返回内部函数
  add := adder()
  
  // 通过闭包调用内部函数,increment是闭包函数
  fmt.Println(add(1)) // 输出:1
  fmt.Println(add(2)) // 输出:3
  fmt.Println(add(3)) // 输出:6
}

// 外部函数,返回一个闭包函数
func adder() func(int) int {
  sum := 0 // 外部函数中的变量

  // 闭包函数
  return func(x int) int {
    sum += x // 闭包函数使用了外部函数中的变量
    return sum
  }
}

7、异常

7.1、内置接口error

vbnet 复制代码
type error interface { //只要实现了Error()函数,返回值为string的都实现了err接口
   Error()    string
}

7.2、异常处理

使用 panic 抛出错误,然后在defer中通过recover捕获异常。

go 复制代码
func main() {
    testPanic()
}

func testPanic() {
  defer func() {
    if err := recover(); err != nil {
      fmt.Println(err.(string))
    }
  }()

  panic("抛出异常")
}

7.3、返回异常

go 复制代码
// 隐式地返回2个值
func getCircleArea(radius float32) (area float32, err error) {
  if radius < 0 {
    // 构建个异常对象
    err = errors.New("半径不能为负")
    return
  }
  area = 3.14 * radius * radius
  return
}

func main() {
  area, err := getCircleArea(-5)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(area)
  }
}

8、面向对象和方法

8.1、面向对象

可以使用匿名字段

go 复制代码
type Person struct {
  name string
  age  int
}
type Student struct {
  Person
  id   int
  addr string
}

func main() {
    s1 := Student{
      Person{"baily", 20},
      1,
      "南京市雨花台区南京南站",
    }
    fmt.Println(s1)
}

如果对象内部嵌套的对象有同名字段的情况,只取对象自己的字段

go 复制代码
type Person struct {
  name string
  age  int
}
type Student struct {
  Person
  id   int
  addr string
  name string
}

func main() {
  var s Student
  s.name = "baily"
  s.Person.name = "baily-parent"
  fmt.Println(s) // 打印出 baily   
}

8.2、方法

一个方法就是一个包含了接受者的函数,接受者可以是 类型或者结构体 的值或者指针。

go 复制代码
type Test struct{}

// 多参数、多返回值
func (t Test) method1(x, y int) (z int, err error) {
  return
}

// 多参数、多返回值
func (t *Test) method2(x, y int) (z int, err error) {
  return
}

8.3、指针接受者 和 值接受者的区别

当方法作用于值接收者时,Go语言会在代码运行时将接收者的值复制一份。在值接收者的方法中可以获取接收者的成员值,但修改操作只是针对复制出来的副本,无法修改接收者本身。

而指针接受者,在修改成员时,会修改接受者本身。

go 复制代码
// SetAge 设置p的年龄
// 使用指针接收者
func (p *Person) SetAge(newAge int) {
  p.age = newAge
}

// SetAge2 设置p的年龄
// 使用值接收者
func (p Person) SetAge2(newAge int) {
  p.age = newAge
}

func main() {
  p := new(Person)
  p.age = 11
  p.SetAge(22)   // 对象p的age会被改变
  fmt.Println(p.age)
  p.SetAge2(33)  // 对象p的age不会被改变
  fmt.Println(p.age)
}

什么时候应该使用指针接受者?

  1. 需要修改接收者中的值
  2. 接收者是拷贝代价比较大的大对象
  3. 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。

9、网络编程

9.1、TCP编程

go 复制代码
// 处理函数
func process(conn net.Conn) {
  defer conn.Close() // 关闭连接
  for {
    reader := bufio.NewReader(conn)
    var buf [128]byte
    n, err := reader.Read(buf[:]) // 读取数据
    if err != nil {
      fmt.Println("读取客户端数据失败:", err)
      break
    }
    recvStr := string(buf[:n])
    fmt.Println("收到client端发来的数据:", recvStr)
    conn.Write([]byte("回复客户端:" + recvStr)) // 发送数据
  }
}

func main() {
  listen, err := net.Listen("tcp", "127.0.0.1:9587")
  if err != nil {
    fmt.Println("启动监听异常:", err)
    return
  }
  for {
    conn, err := listen.Accept() // 建立连接
    if err != nil {
      fmt.Println("没有连接:", err)
      continue
    }
    go process(conn) // 启动一个goroutine处理连接
  }
}

10、并发编程

10.1、使用sync.WaitGroup

go 复制代码
var wg sync.WaitGroup

func hello_wg(i int) {
  defer wg.Done() // goroutine结束就登记-1
  fmt.Println("hello_wg!", i)
}

func main() {
  for i := 0; i < 10; i++ {
    wg.Add(1) // 启动一个goroutine就登记+1
    go hello_wg(i)
    time.Sleep(time.Second)
  }
  wg.Wait() // 等待所有登记的goroutine都结束
}

10.2、使用channel解决并发

Go语言的并发模型是CSP(Communicating Sequential Processes),通过通信共享内存,而不是通过共享内存而实现通信。

go 复制代码
func recv(c chan int) {
  ret := <-c
  fmt.Println("接收成功", ret)
}
func main() {
  c := make(chan int)
  go recv(c) // 启用goroutine从通道接收值
  c <- 10
  fmt.Println("发送成功")   
}

10.3、select

go 复制代码
func main() {
  var c1 = make(chan int)
  go func() {
    time.Sleep(time.Second * 10)
    c1 <- 1
  }()

  // 此处会一直等到10S到期,通道里有值才会继续往下走。
  // 如果增加了 time.After(time.Second * 3) ,则最多3秒则结束
  // 如果这2个case都不行,会走default,也可以不设置default
  select {
  case i, ok := <-c1:
    if ok {
      fmt.Println("取值", i)
    }
  case <-time.After(time.Second * 3):
    fmt.Println("request time out")
  default:
    fmt.Println("无数据")
  }
}

10.4、互斥锁

多个go协程操作同一个资源时,会发生并发问题,需要加锁解决。有互斥锁,还有读写锁。

scss 复制代码
func add() {
  for i := 0; i < 5000; i++ {
    // 如果不加锁,此处会有并发问题
    lock.Lock() // 加锁
    x = x + 1
    lock.Unlock() // 解锁
  }
  wg.Done()
}

func main() {
  wg.Add(2)
  go add()
  go add()
  wg.Wait()
  fmt.Println(x)   
}

11、单元测试

文件以_test.go结尾,方法以Test开头,方法入参t *testing.T

go 复制代码
func TestProgram(t *testing.T) {
  split := strings.Split("a,b,c", ",")

  defer func() {
    if err := recover(); err != nil {
      fmt.Println("异常:", err)
    }
  }()

  findElement(split, "a")
}

// 查找元素
func findElement(split []string, target string) {
  flag := false
  for _, e := range split {
    if e == target {
      flag = true
      break
    }
  }

  if flag {
    fmt.Println("已经找到")
  } else {
    panic("没找到")
  }
}

12、常用命令

  1. go env:用于打印Go语言的环境信息。
  2. go build:用于编译Go程序。例如,go build filename.go 会将 filename.go 编译成可执行文件。
  3. go run:用于直接运行Go程序。例如,go run filename.go 会编译并运行 filename.go 文件中的程序。
  4. go test:用于运行测试文件或者测试包。例如,go test 会运行当前目录下所有的测试文件。
  5. go get:用于下载并安装包。例如,go get github.com/example/package 会下载 github.com/example/package 包并将其安装在 $GOPATH/src 下。
  6. go mod:用于管理依赖和模块。例如,go mod init 用于初始化一个新的模块,并生成 go.mod 文件。
  7. go vet:用于静态检查Go代码中的错误。例如,go vet filename.go 会检查 filename.go 文件中的错误。
  8. go install 命令用于编译并安装Go程序,它会编译指定的包或源文件,并将生成的可执行文件安装到 $GOPATH/bin 目录下。

本篇完结!感谢你的阅读,欢迎点赞 关注 收藏 私信!!!

原文链接: mp.weixin.qq.com/s/h-xvKS_X5...

相关推荐
Amagi.1 小时前
Spring中Bean的作用域
java·后端·spring
2402_857589361 小时前
Spring Boot新闻推荐系统设计与实现
java·spring boot·后端
J老熊1 小时前
Spring Cloud Netflix Eureka 注册中心讲解和案例示范
java·后端·spring·spring cloud·面试·eureka·系统架构
Benaso1 小时前
Rust 快速入门(一)
开发语言·后端·rust
sco52821 小时前
SpringBoot 集成 Ehcache 实现本地缓存
java·spring boot·后端
原机小子2 小时前
在线教育的未来:SpringBoot技术实现
java·spring boot·后端
吾日三省吾码2 小时前
详解JVM类加载机制
后端
努力的布布2 小时前
SpringMVC源码-AbstractHandlerMethodMapping处理器映射器将@Controller修饰类方法存储到处理器映射器
java·后端·spring
PacosonSWJTU2 小时前
spring揭秘25-springmvc03-其他组件(文件上传+拦截器+处理器适配器+异常统一处理)
java·后端·springmvc
记得开心一点嘛3 小时前
在Java项目中如何使用Scala实现尾递归优化来解决爆栈问题
开发语言·后端·scala